diff --git a/OpenCL/m25400-pure.cl b/OpenCL/m25400-pure.cl index d6c15f17a..d782e4eba 100644 --- a/OpenCL/m25400-pure.cl +++ b/OpenCL/m25400-pure.cl @@ -3,8 +3,7 @@ * License.....: MIT */ -// TODO use user password as input for md5 of o_digest if no owner password is set -// TODO dynamically add user password including padding to the RC4 input for the computation of the pdf o-value +// https://www.adobe.com/content/dam/acom/en/devnet/pdf/pdfs/pdf_reference_archives/PDFReference.pdf #ifdef KERNEL_STATIC #include "inc_vendor.h" @@ -20,22 +19,24 @@ typedef struct pdf { - int V; - int R; - int P; + int V; + int R; + int P; - int enc_md; + int enc_md; - u32 id_buf[8]; - u32 u_buf[32]; - u32 o_buf[32]; + u32 id_buf[8]; + u32 u_buf[32]; + u32 o_buf[32]; + u32 u_pass_buf[8]; - int id_len; - int o_len; - int u_len; + int id_len; + int o_len; + int u_len; + int u_pass_len; - u32 rc4key[2]; - u32 rc4data[2]; + u32 rc4key[2]; + u32 rc4data[2]; } pdf_t; @@ -89,9 +90,9 @@ KERNEL_FQ void m25400_init (KERN_ATTR_TMPS_ESALT (pdf14_tmp_t, pdf_t)) * shared */ - u32 P = esalt_bufs[DIGESTS_OFFSET].P; + u32 P = esalt_bufs[DIGESTS_OFFSET].P; // TODO this is never used, but should be according according to "Algorithm 3.2 Computing an encryption key" line 4. - u32 id_buf[12]; + u32 id_buf[12]; // TODO this is never used, but should be according according to "Algorithm 3.2 Computing an encryption key" line 5. id_buf[ 0] = esalt_bufs[DIGESTS_OFFSET].id_buf[0]; id_buf[ 1] = esalt_bufs[DIGESTS_OFFSET].id_buf[1]; @@ -206,14 +207,12 @@ KERNEL_FQ void m25400_loop (KERN_ATTR_TMPS_ESALT (pdf14_tmp_t, pdf_t)) */ u32 digest[4]; - digest[0] = tmps[gid].digest[0]; digest[1] = tmps[gid].digest[1]; digest[2] = tmps[gid].digest[2]; digest[3] = tmps[gid].digest[3]; u32 out[4]; - out[0] = tmps[gid].out[0]; out[1] = tmps[gid].out[1]; out[2] = tmps[gid].out[2]; @@ -223,6 +222,8 @@ KERNEL_FQ void m25400_loop (KERN_ATTR_TMPS_ESALT (pdf14_tmp_t, pdf_t)) { if (j < 50) { + // the owner-key is generated by iterating a md5 hash 50 times + // see: "Algorithm 3.3 Computing the encryption dictionary’s O (owner password) value" u32 w0_t[4]; u32 w1_t[4]; u32 w2_t[4]; @@ -252,28 +253,41 @@ KERNEL_FQ void m25400_loop (KERN_ATTR_TMPS_ESALT (pdf14_tmp_t, pdf_t)) md5_transform (w0_t, w1_t, w2_t, w3_t, digest); } - else - { - const u32 x = j - 50; - - const u32 xv = x << 0 - | x << 8 - | x << 16 - | x << 24; - - u32 tmp[4]; - - tmp[0] = digest[0] ^ xv; - tmp[1] = digest[1] ^ xv; - tmp[2] = digest[2] ^ xv; - tmp[3] = digest[3] ^ xv; - - rc4_init_128 (S, tmp); + } - rc4_next_16 (S, 0, 0, out, out); - } + out[0] = esalt_bufs[DIGESTS_OFFSET].o_buf[0]; // store original o-value in out (scratchpad) + out[1] = esalt_bufs[DIGESTS_OFFSET].o_buf[1]; + out[2] = esalt_bufs[DIGESTS_OFFSET].o_buf[2]; + out[3] = esalt_bufs[DIGESTS_OFFSET].o_buf[3]; + u32 o_rc4_decryption_key[4]; + o_rc4_decryption_key[0] = digest[0]; // store the owner-key + o_rc4_decryption_key[1] = digest[1]; + o_rc4_decryption_key[2] = digest[2]; + o_rc4_decryption_key[3] = digest[3]; + + // we decrypt the o-value to obtain either the owner-password (or user-password if no owner-password is set) + // see: "Algorithm 3.3 Computing the encryption dictionary’s O (owner password) value": "If there is no owner password, use the user password instead". + u32 tmp[4]; + for (u32 i = 19; i>0; i--) + { + // xor the iterator into the rc4 key + const u32 xv = i << 0 + | i << 8 + | i << 16 + | i << 24; + + tmp[0] = o_rc4_decryption_key[0] ^ xv; + tmp[1] = o_rc4_decryption_key[1] ^ xv; + tmp[2] = o_rc4_decryption_key[2] ^ xv; + tmp[3] = o_rc4_decryption_key[3] ^ xv; + + rc4_init_128 (S, tmp); + rc4_next_16 (S, 0, 0, out, out); } + rc4_init_128 (S, o_rc4_decryption_key); + rc4_next_16 (S, 0, 0, out, out); // output of the rc4 decrypt of the o-value should be the padded user-password + tmps[gid].digest[0] = digest[0]; tmps[gid].digest[1] = digest[1]; tmps[gid].digest[2] = digest[2]; @@ -287,28 +301,113 @@ KERNEL_FQ void m25400_loop (KERN_ATTR_TMPS_ESALT (pdf14_tmp_t, pdf_t)) KERNEL_FQ void m25400_comp (KERN_ATTR_TMPS_ESALT (pdf14_tmp_t, pdf_t)) { + const u32 digest[4] = + { + esalt_bufs[DIGESTS_OFFSET].o_buf[0], + esalt_bufs[DIGESTS_OFFSET].o_buf[1], + 0x0,// apparently only the first 16 bytes of the digest are used to look it up? + 0x0 // apparently only the first 16 bytes of the digest are used to look it up? + }; + + const u32 padding[8] = + { + 0x5e4ebf28, + 0x418a754e, + 0x564e0064, + 0x0801faff, + 0xb6002e2e, + 0x803e68d0, + 0xfea90c2f, + 0x7a695364 + }; + /** * modifier */ - const u64 gid = get_global_id (0); if (gid >= gid_max) return; const u64 lid = get_local_id (0); - /** - * digest - */ - - const u32 r0 = tmps[gid].out[0]; - const u32 r1 = tmps[gid].out[1]; - const u32 r2 = 0; - const u32 r3 = 0; #define il_pos 0 - #ifdef KERNEL_STATIC - #include COMPARE_M - #endif + + const u32 out[4] = + { + tmps[gid].out[0], + tmps[gid].out[1], + tmps[gid].out[2], + tmps[gid].out[3] + }; + + + // the best comparison I can think of is checking each byte + // whether it's a padding byte or ASCII, if so we're good, + // if not, decryption was not successful + bool correct = true; + int i_padding=0; + for(int i=0;i<16;i++) + { + // cast out buffer to byte such that we can do a byte per byte comparison + const u32 *u32OutBufPtr = out; + const u8 *u8OutBufPtr; + u8OutBufPtr = (u8*) u32OutBufPtr; + + // cast padding buffer to byte such that we can do a byte per byte comparison + const u32 *u32OutPadPtr = padding; + const u8 *u8OutPadPtr; + u8OutPadPtr = (u8*) u32OutPadPtr; + + // we don't use the user-password in the attack now (as we don't need it), + // however we could use it in the comparison of the decrypted o-value, + // yet it may make this attack a bit more fragile, as now we just check for ASCII + if((u8OutBufPtr[i] >=20 && u8OutBufPtr[i] <= 0x7e) || u8OutBufPtr[i]==u8OutPadPtr[i_padding]) + { + if(u8OutBufPtr[i]==u8OutPadPtr[i_padding]) { + //printf("correct padding byte[%d]=0x%02x\n", i, u8OutBufPtr[i]); + i_padding=i_padding+1; + } + else + { + if(u8OutBufPtr[i] >=20 && u8OutBufPtr[i] <= 0x7e) { + //printf("correct ASCII byte[%d]=0x%02x\n", i, u8OutBufPtr[i]); + } + } + } + else + { + //printf("wrong byte[%d]=0x%02x\n", i, u8OutBufPtr[i]); + // + //printf("u8OutBufPtr=0x"); + //for(int j=0;j<16;j++) { + // printf("%02x", u8OutBufPtr[j]); + //} + //printf("\n"); + // + //printf("u8OutPadPtr=0x"); + //for(int j=0;j<16;j++) { + // printf("%02x", u8OutPadPtr[j]); + //} + //printf("\n"); + + correct = false; + break; + } + } + + if (correct) + { + int digest_pos = find_hash (digest, digests_cnt, &digests_buf[DIGESTS_OFFSET]); + if (digest_pos != -1) + { + const u32 final_hash_pos = DIGESTS_OFFSET + digest_pos; + + if (hc_atomic_inc (&hashes_shown[final_hash_pos]) == 0) + { + mark_hash (plains_buf, d_return_buf, SALT_POS, digests_cnt, digest_pos, final_hash_pos, gid, il_pos, 0, 0); + } + } + } } diff --git a/src/modules/module_22000.c b/src/modules/module_22000.c index 2b5f60cfb..b5543f365 100644 --- a/src/modules/module_22000.c +++ b/src/modules/module_22000.c @@ -734,6 +734,7 @@ int module_hash_decode (MAYBE_UNUSED const hashconfig_t *hashconfig, MAYBE_UNUSE const int rc_tokenizer = input_tokenizer ((const u8 *) line_buf, line_len, &token); + // if the tokenizer reports PARSER_OK, then modify the input line artificially to match the new input line format if (rc_tokenizer == PARSER_OK) { tmp_len = snprintf (tmp_buf, sizeof (tmp_buf), "WPA*01*%s***", line_buf); diff --git a/src/modules/module_25400.c b/src/modules/module_25400.c index 39ef9e50f..ec7675053 100644 --- a/src/modules/module_25400.c +++ b/src/modules/module_25400.c @@ -3,8 +3,7 @@ * License.....: MIT */ -// TODO use user password as input for md5 of o_digest if no owner password is set -// TODO dynamically add user password including padding to the RC4 input for the computation of the pdf o-value +// https://www.adobe.com/content/dam/acom/en/devnet/pdf/pdfs/pdf_reference_archives/PDFReference.pdf #include "common.h" #include "types.h" @@ -21,11 +20,11 @@ static const u32 DGST_POS2 = 2; static const u32 DGST_POS3 = 3; static const u32 DGST_SIZE = DGST_SIZE_4_4; static const u32 HASH_CATEGORY = HASH_CATEGORY_DOCUMENTS; -static const char *HASH_NAME = "PDF 1.4 - 1.6 (Acrobat 5 - 8) - edit password"; +static const char *HASH_NAME = "PDF 1.4 - 1.6 (Acrobat 5 - 8) - user and owner password (o-value)"; static const u64 KERN_TYPE = 25400; static const u32 OPTI_TYPE = OPTI_TYPE_ZERO_BYTE | OPTI_TYPE_NOT_ITERATED; -static const u64 OPTS_TYPE = OPTS_TYPE_PT_GENERATE_LE; +static const u64 OPTS_TYPE = OPTS_TYPE_PT_GENERATE_LE | OPTS_TYPE_COPY_TMPS | OPTS_TYPE_PT_ALWAYS_ASCII; static const u32 SALT_TYPE = SALT_TYPE_EMBEDDED; static const char *ST_PASS = "hashcat"; static const char *ST_HASH = "$pdf$2*3*128*-3904*1*16*631ed33746e50fba5caf56bcc39e09c6*32*5f9d0e4f0b39835dace0d306c40cd6b700000000000000000000000000000000*32*842103b0a0dc886db9223b94afe2d7cd63389079b61986a4fcf70095ad630c24"; @@ -47,23 +46,24 @@ const char *module_st_pass (MAYBE_UNUSED const hashconfig_t *hashconfig, typedef struct pdf { - int V; - int R; - int P; + int V; + int R; + int P; - int enc_md; + int enc_md; - u32 id_buf[8]; - u32 u_buf[32]; - u32 o_buf[32]; - u8 u_pass_buf[64]; + u32 id_buf[8]; + u32 u_buf[32]; + u32 o_buf[32]; + u32 u_pass_buf[8]; - int id_len; - int o_len; - int u_len; + int id_len; + int o_len; + int u_len; + int u_pass_len; - u32 rc4key[2]; - u32 rc4data[2]; + u32 rc4key[2]; + u32 rc4data[2]; } pdf_t; @@ -146,8 +146,7 @@ u64 module_tmp_size (MAYBE_UNUSED const hashconfig_t *hashconfig, MAYBE_UNUSED c u32 module_pw_max (MAYBE_UNUSED const hashconfig_t *hashconfig, MAYBE_UNUSED const user_options_t *user_options, MAYBE_UNUSED const user_options_extra_t *user_options_extra) { - const u32 pw_max = 32; // https://www.pdflib.com/knowledge-base/pdf-password-security/encryption/ - + const u32 pw_max = 32; // "truncate the password string to exactly 32 bytes." see "Algorithm 3.2 computing an encryption key" return pw_max; } @@ -251,9 +250,6 @@ int module_hash_decode (MAYBE_UNUSED const hashconfig_t *hashconfig, MAYBE_UNUSE input_len = tmp_len; } - - //token_t token; should we first destroy token? - token.token_cnt = 13; token.signatures_cnt = 1; @@ -328,9 +324,8 @@ int module_hash_decode (MAYBE_UNUSED const hashconfig_t *hashconfig, MAYBE_UNUSE token.attr[11] = TOKEN_ATTR_VERIFY_LENGTH | TOKEN_ATTR_VERIFY_HEX; - token.len_min[12] = 0; - token.len_max[12] = 64; // arbitrarily limit user-password length to 64 now + token.len_max[12] = 32; // "truncate the password string to exactly 32 bytes." see "Algorithm 3.2 computing an encryption key" token.sep[12] = '*'; token.attr[12] = TOKEN_ATTR_VERIFY_LENGTH; @@ -351,6 +346,10 @@ int module_hash_decode (MAYBE_UNUSED const hashconfig_t *hashconfig, MAYBE_UNUSE const u8 *o_len_pos = token.buf[10]; const u8 *o_buf_pos = token.buf[11]; // owner hash const u8 *u_pass_buf_pos = token.buf[12]; // user password (optional) + // we don't use the user-password in the attack now (as we don't need it), + // however we could use it in the comparison of the decrypted o-value, + // yet it may make this attack a bit more fragile, as now we just check for ASCII + // validate data @@ -386,13 +385,12 @@ int module_hash_decode (MAYBE_UNUSED const hashconfig_t *hashconfig, MAYBE_UNUSE } // copy data to esalt - pdf->V = V; pdf->R = R; pdf->P = P; - //printf("\n\n u_pass_buf_pos: %s\n\n", u_pass_buf_pos); - memcpy ( pdf->u_pass_buf , u_pass_buf_pos, 64); + memcpy ( pdf->u_pass_buf, u_pass_buf_pos, 32); + pdf->u_pass_len = strlen((char *) pdf->u_pass_buf); pdf->enc_md = enc_md; @@ -432,7 +430,6 @@ int module_hash_decode (MAYBE_UNUSED const hashconfig_t *hashconfig, MAYBE_UNUSE pdf->o_len = o_len; // precompute rc4 data for later use - u32 padding[8] = { 0x5e4ebf28, @@ -446,7 +443,6 @@ int module_hash_decode (MAYBE_UNUSED const hashconfig_t *hashconfig, MAYBE_UNUSE }; // md5 - u32 salt_pc_block[32] = { 0 }; u8 *salt_pc_ptr = (u8 *) salt_pc_block; @@ -462,7 +458,6 @@ int module_hash_decode (MAYBE_UNUSED const hashconfig_t *hashconfig, MAYBE_UNUSE pdf->rc4data[1] = salt_pc_digest[1]; // we use ID for salt, maybe needs to change, we will see... - salt->salt_buf[0] = pdf->id_buf[0]; salt->salt_buf[1] = pdf->id_buf[1]; salt->salt_buf[2] = pdf->id_buf[2]; @@ -483,15 +478,81 @@ int module_hash_decode (MAYBE_UNUSED const hashconfig_t *hashconfig, MAYBE_UNUSE return (PARSER_OK); } -int module_hash_encode (MAYBE_UNUSED const hashconfig_t *hashconfig, MAYBE_UNUSED const void *digest_buf, MAYBE_UNUSED const salt_t *salt, MAYBE_UNUSED const void *esalt_buf, MAYBE_UNUSED const void *hook_salt_buf, MAYBE_UNUSED const hashinfo_t *hash_info, char *line_buf, MAYBE_UNUSED const int line_size) +int module_build_plain_postprocess (MAYBE_UNUSED const hashconfig_t *hashconfig, MAYBE_UNUSED const hashes_t *hashes, MAYBE_UNUSED const void *tmps, const u32 *src_buf, MAYBE_UNUSED const size_t src_sz, MAYBE_UNUSED const int src_len, u32 *dst_buf, MAYBE_UNUSED const size_t dst_sz) { - const pdf_t *pdf = (const pdf_t *) esalt_buf; + const u32 padding[8] = + { + 0x5e4ebf28, + 0x418a754e, + 0x564e0064, + 0x0801faff, + 0xb6002e2e, + 0x803e68d0, + 0xfea90c2f, + 0x7a695364 + }; + + pdf14_tmp_t *pdf_tmp = (pdf14_tmp_t *) tmps; + pdf_t *pdf = (pdf_t *) hashes->esalts_buf; + // if the password in tmp->out is equal to the padding, then we recovered just the owner-password + if(pdf_tmp->out[0]==padding[0] && pdf_tmp->out[1]==padding[1] && pdf_tmp->out[2]==padding[2] && pdf_tmp->out[3]==padding[3]) + { + return snprintf ((char *) dst_buf, dst_sz, "%s (user password not set)", (char *) src_buf); + } + + // cast out buffer to byte such that we can do a byte per byte comparison + u32 *u32OutBufPtr = pdf_tmp->out; + u8 *u8OutBufPtr; + u8OutBufPtr = (u8*) u32OutBufPtr; + + // cast padding buffer to byte such that we can do a byte per byte comparison + const u32 *u32OutPadPtr = padding; + const u8 *u8OutPadPtr; + u8OutPadPtr = (u8*) u32OutPadPtr; + + bool remove_padding=false; + int i_padding=0; + for(int i=0;i<16;i++) + { + if(u8OutBufPtr[i]==u8OutPadPtr[i_padding] || remove_padding) + { + u8OutBufPtr[i]=0x0; + remove_padding=true; + } + } + + // if the password in tmp->out is equal to the password tried, then we recovered just the owner-password or just the user-password + // we check whether we already have a user-password in the hash + // TODO would be better to actually also verify the u-value whether we've retrieved the correct user-password, + // however, we'd need to include a lot of code/complexity here to do so (or call into 10500 kernel). + // this seems relevant: run_kernel (hashcat_ctx, device_param, KERN_RUN_3, 0, 1, false, 0) + if(pdf_tmp->out[0]==src_buf[0] && pdf_tmp->out[1]==src_buf[1] && pdf_tmp->out[2]==src_buf[2] && pdf_tmp->out[3]==src_buf[3]) + { + if(pdf->u_pass_len==0) + { + // we seem to only have recovered the user-password as we don't have one yet + return snprintf ((char *) dst_buf, dst_sz, "(user password=%s)", (char *) src_buf); + } + } + // we recovered both the user-password and the owner-password + return snprintf ((char *) dst_buf, dst_sz, "%s (user password=%s)", (char *) src_buf, (char *) pdf_tmp->out); +} + + +// TODO how to add the recovered user-password to the hash? +// module_hash_encode() is called before module_build_plain_postprocess() is +// module_hash_encode() doesn't know the recovered password src_buf or the decrypted o-value pdf_tmp->out +// it seems a bit excessive to add these both to module_hash_encode()'s parameters +// module_build_plain_postprocess() cannot alter the hash nor hash access to the pdf/esalt object +int module_hash_encode (MAYBE_UNUSED const hashconfig_t *hashconfig, MAYBE_UNUSED const void *digest_buf, MAYBE_UNUSED const salt_t *salt, MAYBE_UNUSED const void *esalt_buf, MAYBE_UNUSED const void *hook_salt_buf, MAYBE_UNUSED const hashinfo_t *hash_info, char *line_buf, MAYBE_UNUSED const int line_size) +{ int line_len = 0; + pdf_t *pdf = (pdf_t *) esalt_buf; if (pdf->id_len == 32) { - line_len = snprintf (line_buf, line_size, "$pdf$%d*%d*%d*%d*%d*%d*%08x%08x%08x%08x%08x%08x%08x%08x*%d*%08x%08x%08x%08x%08x%08x%08x%08x*%d*%08x%08x%08x%08x%08x%08x%08x%08x", + line_len = snprintf (line_buf, line_size, "$pdf$%d*%d*%d*%d*%d*%d*%08x%08x%08x%08x%08x%08x%08x%08x*%d*%08x%08x%08x%08x%08x%08x%08x%08x*%d*%08x%08x%08x%08x%08x%08x%08x%08x*%s", pdf->V, pdf->R, 128, @@ -523,12 +584,13 @@ int module_hash_encode (MAYBE_UNUSED const hashconfig_t *hashconfig, MAYBE_UNUSE byte_swap_32 (pdf->o_buf[4]), byte_swap_32 (pdf->o_buf[5]), byte_swap_32 (pdf->o_buf[6]), - byte_swap_32 (pdf->o_buf[7]) + byte_swap_32 (pdf->o_buf[7]), + (char *) pdf->u_pass_buf // TODO just prints the old hash now, we don't edit the hash to add a recovered user-password to it (yet) ); } else { - line_len = snprintf (line_buf, line_size, "$pdf$%d*%d*%d*%d*%d*%d*%08x%08x%08x%08x*%d*%08x%08x%08x%08x%08x%08x%08x%08x*%d*%08x%08x%08x%08x%08x%08x%08x%08x", + line_len = snprintf (line_buf, line_size, "$pdf$%d*%d*%d*%d*%d*%d*%08x%08x%08x%08x*%d*%08x%08x%08x%08x%08x%08x%08x%08x*%d*%08x%08x%08x%08x%08x%08x%08x%08x*%s", pdf->V, pdf->R, 128, @@ -556,7 +618,8 @@ int module_hash_encode (MAYBE_UNUSED const hashconfig_t *hashconfig, MAYBE_UNUSE byte_swap_32 (pdf->o_buf[4]), byte_swap_32 (pdf->o_buf[5]), byte_swap_32 (pdf->o_buf[6]), - byte_swap_32 (pdf->o_buf[7]) + byte_swap_32 (pdf->o_buf[7]), + (char *) pdf->u_pass_buf // TODO just prints the old hash now, we don't edit the hash to add a recovered user-password to it (yet) ); } @@ -573,7 +636,7 @@ void module_init (module_ctx_t *module_ctx) module_ctx->module_benchmark_hook_salt = MODULE_DEFAULT; module_ctx->module_benchmark_mask = MODULE_DEFAULT; module_ctx->module_benchmark_salt = MODULE_DEFAULT; - module_ctx->module_build_plain_postprocess = MODULE_DEFAULT; + module_ctx->module_build_plain_postprocess = module_build_plain_postprocess; module_ctx->module_deep_comp_kernel = MODULE_DEFAULT; module_ctx->module_dgst_pos0 = module_dgst_pos0; module_ctx->module_dgst_pos1 = module_dgst_pos1; diff --git a/tools/pdf_tests/_README.txt b/tools/pdf_tests/_README.txt new file mode 100644 index 000000000..bfc17b7ff --- /dev/null +++ b/tools/pdf_tests/_README.txt @@ -0,0 +1,14 @@ +Test files for pdf kernels + +Didn't use hashcat as password as pdf files/a single pdf hash can +have two passwords, based on the u-value and o-value. +https://www.adobe.com/content/dam/acom/en/devnet/pdf/pdfs/pdf_reference_archives/PDFReference.pdf + +The user-password (to open the file is): user +The owner-password (to e.g. restrict printing is): owner + +We have files with and without the "_userpw-in-hash" prefix, +this is to accommodate tests for 25400, which needs the user-password +to calculate the o-value. The user-password is used when no owner-password is set. + +The pdf files have been made with Adobe Acrobat Pro DC version 2015.007.20003 \ No newline at end of file diff --git a/tools/pdf_tests/owner.hash b/tools/pdf_tests/owner.hash new file mode 100644 index 000000000..8a54c9a91 --- /dev/null +++ b/tools/pdf_tests/owner.hash @@ -0,0 +1 @@ +$pdf$4*4*128*-3392*1*16*b438e5ce7c355548a28bf1408a36bbdf*32*63a1a7a95566adcf61a8c36002b6bfa900000000000000000000000000000000*32*566fa873ee33c797cd3b904fdadf814afa34df9a38f6ed41b984e2c6da2aa6f5 diff --git a/tools/pdf_tests/owner.in b/tools/pdf_tests/owner.in new file mode 100644 index 000000000..b0b50286d --- /dev/null +++ b/tools/pdf_tests/owner.in @@ -0,0 +1 @@ +$pdf$4*4*128*-3392*1*16*b438e5ce7c355548a28bf1408a36bbdf*32*63a1a7a95566adcf61a8c36002b6bfa900000000000000000000000000000000*32*566fa873ee33c797cd3b904fdadf814afa34df9a38f6ed41b984e2c6da2aa6f5:owner diff --git a/tools/pdf_tests/owner.pdf b/tools/pdf_tests/owner.pdf new file mode 100644 index 000000000..fe337fd66 Binary files /dev/null and b/tools/pdf_tests/owner.pdf differ diff --git a/tools/pdf_tests/user-owner.hash b/tools/pdf_tests/user-owner.hash new file mode 100644 index 000000000..4aafd4110 --- /dev/null +++ b/tools/pdf_tests/user-owner.hash @@ -0,0 +1 @@ +$pdf$4*4*128*-3392*1*16*f72f279e0db2ee4ca76b0db6884e85cd*32*665d9c5a3c2443ac2aa3a9b855843bf200000000000000000000000000000000*32*0ba3835f88f90388e74e54584125ce142be0de24c6b0d37746e075b891756671 diff --git a/tools/pdf_tests/user-owner.pdf b/tools/pdf_tests/user-owner.pdf new file mode 100644 index 000000000..20d6a94af Binary files /dev/null and b/tools/pdf_tests/user-owner.pdf differ diff --git a/tools/pdf_tests/user-owner_userpw-in-hash.hash b/tools/pdf_tests/user-owner_userpw-in-hash.hash new file mode 100644 index 000000000..defcde3c7 --- /dev/null +++ b/tools/pdf_tests/user-owner_userpw-in-hash.hash @@ -0,0 +1 @@ +$pdf$4*4*128*-3392*1*16*f72f279e0db2ee4ca76b0db6884e85cd*32*665d9c5a3c2443ac2aa3a9b855843bf200000000000000000000000000000000*32*0ba3835f88f90388e74e54584125ce142be0de24c6b0d37746e075b891756671*user diff --git a/tools/pdf_tests/user-owner_userpw-in-hash.in b/tools/pdf_tests/user-owner_userpw-in-hash.in new file mode 100644 index 000000000..47db180a9 --- /dev/null +++ b/tools/pdf_tests/user-owner_userpw-in-hash.in @@ -0,0 +1 @@ +$pdf$4*4*128*-3392*1*16*f72f279e0db2ee4ca76b0db6884e85cd*32*665d9c5a3c2443ac2aa3a9b855843bf200000000000000000000000000000000*32*0ba3835f88f90388e74e54584125ce142be0de24c6b0d37746e075b891756671*user:owner diff --git a/tools/pdf_tests/user.hash b/tools/pdf_tests/user.hash new file mode 100644 index 000000000..0e34edb06 --- /dev/null +++ b/tools/pdf_tests/user.hash @@ -0,0 +1 @@ +$pdf$4*4*128*-1028*1*16*e77b55355f74d54895c93063ddbad299*32*61536f41f6bf35bce77a96eb45cebec500000000000000000000000000000000*32*ce69e44fd4717582076b810914829e577152c3110ee581d65e53c64a4e8f578d diff --git a/tools/pdf_tests/user.pdf b/tools/pdf_tests/user.pdf new file mode 100644 index 000000000..46db4f2cc Binary files /dev/null and b/tools/pdf_tests/user.pdf differ diff --git a/tools/pdf_tests/user_userpw-in-hash.hash b/tools/pdf_tests/user_userpw-in-hash.hash new file mode 100644 index 000000000..57240c61e --- /dev/null +++ b/tools/pdf_tests/user_userpw-in-hash.hash @@ -0,0 +1 @@ +$pdf$4*4*128*-1028*1*16*e77b55355f74d54895c93063ddbad299*32*61536f41f6bf35bce77a96eb45cebec500000000000000000000000000000000*32*ce69e44fd4717582076b810914829e577152c3110ee581d65e53c64a4e8f578d*user diff --git a/tools/pdf_tests/user_userpw-in-hash.in b/tools/pdf_tests/user_userpw-in-hash.in new file mode 100644 index 000000000..660c2ade4 --- /dev/null +++ b/tools/pdf_tests/user_userpw-in-hash.in @@ -0,0 +1 @@ +$pdf$4*4*128*-1028*1*16*e77b55355f74d54895c93063ddbad299*32*61536f41f6bf35bce77a96eb45cebec500000000000000000000000000000000*32*ce69e44fd4717582076b810914829e577152c3110ee581d65e53c64a4e8f578d*user:user diff --git a/tools/test_modules/m25400.pm b/tools/test_modules/m25400.pm index 7b4b03363..a2d6206da 100644 --- a/tools/test_modules/m25400.pm +++ b/tools/test_modules/m25400.pm @@ -6,12 +6,9 @@ ## # based off m10500 but added the owner password part ($o) to be able to test the edit password -# two TODOs still (now only works if no user password is set): -# 1. TODO use user password as input for md5 of o_digest if no owner password is set -# 2. TODO dynamically add user password including padding to the RC4 input for the computation of the pdf o-value # easy test shortcut for debugging -# a=$(echo 1 | tools/test.pl passthrough 10500 | tail -n1); echo $a; echo 1 | ./hashcat --potfile-disable --runtime 400 --hwmon-disable -O -D 2 --backend-vector-width 4 -a 0 -m 10500 $a +# a=$(echo 1 | tools/test.pl passthrough 25400 | tail -n1); echo $a; echo 1 | ./hashcat --potfile-disable --runtime 400 --hwmon-disable -O -D 2 --backend-vector-width 4 -a 0 -m 25400 $a use strict; use warnings; @@ -99,9 +96,6 @@ sub pdf_compute_encryption_key_owner } } - #printf("\$o_digest = %s\n", unpack ("H*", $o_digest)); - - my $o_key; if ($R == 2) { @@ -111,7 +105,6 @@ sub pdf_compute_encryption_key_owner { $o_key = substr($o_digest, 0, 16); #length is always 128 bits or 16 bytes } - #printf("\$o_key = %s\n", unpack ("H*", $o_key)); return $o_key; } @@ -173,28 +166,31 @@ sub module_generate_hash ################ USER PASSWORD ################# # do not change $u if it exists, keep this the same, as we don't know the user password, # we cannot calculate this part of the hash again - my $res; - if("".$u_pass eq "") + + if ($u eq "0000000000000000000000000000000000000000000000000000000000000000") { + my $res; + if($u_pass eq "") + { + # we don't know the user-password so calculate $u based on the owner-password $res = pdf_compute_encryption_key_user($word, $padding, $id, $u, $o, $P, $V, $R, $enc); - } - else - { - #$u = pack("H*", $u) - #now that we know the user-password we can generate it + } + else + { + #we do know the user-password, so we can generate $u $res = pdf_compute_encryption_key_user($u_pass, $padding, $id, $u, $o, $P, $V, $R, $enc); - } + } - my $digest = md5 ($padding . pack ("H*", $id)); + my $digest = md5 ($padding . pack ("H*", $id)); - my $m = Crypt::RC4->new ($res); - $u = $m->RC4 ($digest); + my $m = Crypt::RC4->new ($res); + $u = $m->RC4 ($digest); - my @ress = split "", $res; + my @ress = split "", $res; - #do xor of rc4 19 times - for (my $x = 1; $x <= 19; $x++) - { + #do xor of rc4 19 times + for (my $x = 1; $x <= 19; $x++) + { my @xor; for (my $i = 0; $i < 16; $i++) @@ -207,13 +203,19 @@ sub module_generate_hash my $m2 = Crypt::RC4->new ($s); $u = $m2->RC4 ($u); + } + $u .= substr (pack ("H*", $u_save), 16, 16); + } + else + { + $u = pack("H*", $u) } - $u .= substr (pack ("H*", $u_save), 16, 16); ################ OWNER PASSWORD ################# my $o_key = pdf_compute_encryption_key_owner($word, $padding, $id, $u, $o, $P, $V, $R, $enc); + my $n = Crypt::RC4->new ($o_key); - if("".$u_pass eq "") + if($u_pass eq "") { $o = $n->RC4(substr ($padding, 0, 32 - length "")); } @@ -222,8 +224,6 @@ sub module_generate_hash #dynamically add user password including padding to the RC4 input for the computation of the pdf o-value $o = $n->RC4($u_pass.substr ($padding, 0, 32 - length $u_pass)); } - - #printf("padding_empty_str = %s\n", unpack ("H*", substr ($padding, 0, 32 - length ""))); my @ress2 = split "", $o_key; @@ -240,19 +240,14 @@ sub module_generate_hash } my $s = join ("", @xor); - - my $n2 = Crypt::RC4->new ($s); + my $n2 = Crypt::RC4->new ($s); $o = $n2->RC4 ($o); } } - #printf("\$u = %s\n", unpack ("H*", $u)); - #printf("\$o = %s\n", unpack ("H*", $o)); - #printf("\$u = %s\n", unpack ("H*", $u)); - my $hash; - if("".$u_pass eq "") + if($u_pass eq "") { $hash = sprintf ('$pdf$%d*%d*128*%d*%d*16*%s*32*%s*32*%s', $V, $R, $P, $enc, $id, unpack ("H*", $u), unpack ("H*", $o)); } @@ -260,7 +255,6 @@ sub module_generate_hash { $hash = sprintf ('$pdf$%d*%d*128*%d*%d*16*%s*32*%s*32*%s*%s', $V, $R, $P, $enc, $id, unpack ("H*", $u), unpack ("H*", $o), $u_pass); } - #print("hash\n".$hash."\n"); return $hash; } @@ -293,7 +287,6 @@ sub module_verify_hash my $u_pass = ""; if($i_data == 12) { $u_pass = shift @data; - #printf("u_pass = %s\n", $u_pass); } return unless defined $id;