1
0
mirror of https://github.com/hashcat/hashcat.git synced 2024-11-21 23:58:07 +00:00

m25400 can now recover both a owner-password and user-password (when set) based on a pdf's o-value

- added option to add the user-password to the pdf hash as an extra colum
- added pdf test files, both with and without a owner- and user-password

The main todo is to add a recovered user-password to back the hash that's written to the potfile.
Currently I'm printing a recovered password as "(user password=...) after the recovered owner-password.
Similair as the VC PIM is printed. However, this isn't most elegant.

A secondary todo is to verify a recovered user-password based on the u-value,
this could possibly simplify the check whether the recoverd password is a user-password or owner-password.
This commit is contained in:
Your Name 2021-08-20 10:16:14 +02:00
parent 012c5b16cd
commit db2e7d1391
16 changed files with 301 additions and 123 deletions

View File

@ -3,8 +3,7 @@
* License.....: MIT * License.....: MIT
*/ */
// TODO use user password as input for md5 of o_digest if no owner password is set // https://www.adobe.com/content/dam/acom/en/devnet/pdf/pdfs/pdf_reference_archives/PDFReference.pdf
// TODO dynamically add user password including padding to the RC4 input for the computation of the pdf o-value
#ifdef KERNEL_STATIC #ifdef KERNEL_STATIC
#include "inc_vendor.h" #include "inc_vendor.h"
@ -20,22 +19,24 @@
typedef struct pdf typedef struct pdf
{ {
int V; int V;
int R; int R;
int P; int P;
int enc_md; int enc_md;
u32 id_buf[8]; u32 id_buf[8];
u32 u_buf[32]; u32 u_buf[32];
u32 o_buf[32]; u32 o_buf[32];
u32 u_pass_buf[8];
int id_len; int id_len;
int o_len; int o_len;
int u_len; int u_len;
int u_pass_len;
u32 rc4key[2]; u32 rc4key[2];
u32 rc4data[2]; u32 rc4data[2];
} pdf_t; } pdf_t;
@ -89,9 +90,9 @@ KERNEL_FQ void m25400_init (KERN_ATTR_TMPS_ESALT (pdf14_tmp_t, pdf_t))
* shared * 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[ 0] = esalt_bufs[DIGESTS_OFFSET].id_buf[0];
id_buf[ 1] = esalt_bufs[DIGESTS_OFFSET].id_buf[1]; 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]; u32 digest[4];
digest[0] = tmps[gid].digest[0]; digest[0] = tmps[gid].digest[0];
digest[1] = tmps[gid].digest[1]; digest[1] = tmps[gid].digest[1];
digest[2] = tmps[gid].digest[2]; digest[2] = tmps[gid].digest[2];
digest[3] = tmps[gid].digest[3]; digest[3] = tmps[gid].digest[3];
u32 out[4]; u32 out[4];
out[0] = tmps[gid].out[0]; out[0] = tmps[gid].out[0];
out[1] = tmps[gid].out[1]; out[1] = tmps[gid].out[1];
out[2] = tmps[gid].out[2]; 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) if (j < 50)
{ {
// the owner-key is generated by iterating a md5 hash 50 times
// see: "Algorithm 3.3 Computing the encryption dictionarys O (owner password) value"
u32 w0_t[4]; u32 w0_t[4];
u32 w1_t[4]; u32 w1_t[4];
u32 w2_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); 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 dictionarys 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[0] = digest[0];
tmps[gid].digest[1] = digest[1]; tmps[gid].digest[1] = digest[1];
tmps[gid].digest[2] = digest[2]; 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)) 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 * modifier
*/ */
const u64 gid = get_global_id (0); const u64 gid = get_global_id (0);
if (gid >= gid_max) return; if (gid >= gid_max) return;
const u64 lid = get_local_id (0); 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 #define il_pos 0
#ifdef KERNEL_STATIC
#include COMPARE_M const u32 out[4] =
#endif {
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);
}
}
}
} }

View File

@ -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); 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) if (rc_tokenizer == PARSER_OK)
{ {
tmp_len = snprintf (tmp_buf, sizeof (tmp_buf), "WPA*01*%s***", line_buf); tmp_len = snprintf (tmp_buf, sizeof (tmp_buf), "WPA*01*%s***", line_buf);

View File

@ -3,8 +3,7 @@
* License.....: MIT * License.....: MIT
*/ */
// TODO use user password as input for md5 of o_digest if no owner password is set // https://www.adobe.com/content/dam/acom/en/devnet/pdf/pdfs/pdf_reference_archives/PDFReference.pdf
// TODO dynamically add user password including padding to the RC4 input for the computation of the pdf o-value
#include "common.h" #include "common.h"
#include "types.h" #include "types.h"
@ -21,11 +20,11 @@ static const u32 DGST_POS2 = 2;
static const u32 DGST_POS3 = 3; static const u32 DGST_POS3 = 3;
static const u32 DGST_SIZE = DGST_SIZE_4_4; static const u32 DGST_SIZE = DGST_SIZE_4_4;
static const u32 HASH_CATEGORY = HASH_CATEGORY_DOCUMENTS; 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 u64 KERN_TYPE = 25400;
static const u32 OPTI_TYPE = OPTI_TYPE_ZERO_BYTE static const u32 OPTI_TYPE = OPTI_TYPE_ZERO_BYTE
| OPTI_TYPE_NOT_ITERATED; | 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 u32 SALT_TYPE = SALT_TYPE_EMBEDDED;
static const char *ST_PASS = "hashcat"; static const char *ST_PASS = "hashcat";
static const char *ST_HASH = "$pdf$2*3*128*-3904*1*16*631ed33746e50fba5caf56bcc39e09c6*32*5f9d0e4f0b39835dace0d306c40cd6b700000000000000000000000000000000*32*842103b0a0dc886db9223b94afe2d7cd63389079b61986a4fcf70095ad630c24"; 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 typedef struct pdf
{ {
int V; int V;
int R; int R;
int P; int P;
int enc_md; int enc_md;
u32 id_buf[8]; u32 id_buf[8];
u32 u_buf[32]; u32 u_buf[32];
u32 o_buf[32]; u32 o_buf[32];
u8 u_pass_buf[64]; u32 u_pass_buf[8];
int id_len; int id_len;
int o_len; int o_len;
int u_len; int u_len;
int u_pass_len;
u32 rc4key[2]; u32 rc4key[2];
u32 rc4data[2]; u32 rc4data[2];
} pdf_t; } 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) 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; return pw_max;
} }
@ -251,9 +250,6 @@ int module_hash_decode (MAYBE_UNUSED const hashconfig_t *hashconfig, MAYBE_UNUSE
input_len = tmp_len; input_len = tmp_len;
} }
//token_t token; should we first destroy token?
token.token_cnt = 13; token.token_cnt = 13;
token.signatures_cnt = 1; 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[11] = TOKEN_ATTR_VERIFY_LENGTH
| TOKEN_ATTR_VERIFY_HEX; | TOKEN_ATTR_VERIFY_HEX;
token.len_min[12] = 0; 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.sep[12] = '*';
token.attr[12] = TOKEN_ATTR_VERIFY_LENGTH; 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_len_pos = token.buf[10];
const u8 *o_buf_pos = token.buf[11]; // owner hash const u8 *o_buf_pos = token.buf[11]; // owner hash
const u8 *u_pass_buf_pos = token.buf[12]; // user password (optional) 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 // validate data
@ -386,13 +385,12 @@ int module_hash_decode (MAYBE_UNUSED const hashconfig_t *hashconfig, MAYBE_UNUSE
} }
// copy data to esalt // copy data to esalt
pdf->V = V; pdf->V = V;
pdf->R = R; pdf->R = R;
pdf->P = P; 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, 32);
memcpy ( pdf->u_pass_buf , u_pass_buf_pos, 64); pdf->u_pass_len = strlen((char *) pdf->u_pass_buf);
pdf->enc_md = enc_md; 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; pdf->o_len = o_len;
// precompute rc4 data for later use // precompute rc4 data for later use
u32 padding[8] = u32 padding[8] =
{ {
0x5e4ebf28, 0x5e4ebf28,
@ -446,7 +443,6 @@ int module_hash_decode (MAYBE_UNUSED const hashconfig_t *hashconfig, MAYBE_UNUSE
}; };
// md5 // md5
u32 salt_pc_block[32] = { 0 }; u32 salt_pc_block[32] = { 0 };
u8 *salt_pc_ptr = (u8 *) salt_pc_block; 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]; pdf->rc4data[1] = salt_pc_digest[1];
// we use ID for salt, maybe needs to change, we will see... // we use ID for salt, maybe needs to change, we will see...
salt->salt_buf[0] = pdf->id_buf[0]; salt->salt_buf[0] = pdf->id_buf[0];
salt->salt_buf[1] = pdf->id_buf[1]; salt->salt_buf[1] = pdf->id_buf[1];
salt->salt_buf[2] = pdf->id_buf[2]; 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); return (PARSER_OK);
} }
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 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 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)
{ {
const pdf_t *pdf = (const pdf_t *) esalt_buf;
int line_len = 0; int line_len = 0;
pdf_t *pdf = (pdf_t *) esalt_buf;
if (pdf->id_len == 32) 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->V,
pdf->R, pdf->R,
128, 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[4]),
byte_swap_32 (pdf->o_buf[5]), byte_swap_32 (pdf->o_buf[5]),
byte_swap_32 (pdf->o_buf[6]), 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 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->V,
pdf->R, pdf->R,
128, 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[4]),
byte_swap_32 (pdf->o_buf[5]), byte_swap_32 (pdf->o_buf[5]),
byte_swap_32 (pdf->o_buf[6]), 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_hook_salt = MODULE_DEFAULT;
module_ctx->module_benchmark_mask = MODULE_DEFAULT; module_ctx->module_benchmark_mask = MODULE_DEFAULT;
module_ctx->module_benchmark_salt = 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_deep_comp_kernel = MODULE_DEFAULT;
module_ctx->module_dgst_pos0 = module_dgst_pos0; module_ctx->module_dgst_pos0 = module_dgst_pos0;
module_ctx->module_dgst_pos1 = module_dgst_pos1; module_ctx->module_dgst_pos1 = module_dgst_pos1;

View File

@ -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

View File

@ -0,0 +1 @@
$pdf$4*4*128*-3392*1*16*b438e5ce7c355548a28bf1408a36bbdf*32*63a1a7a95566adcf61a8c36002b6bfa900000000000000000000000000000000*32*566fa873ee33c797cd3b904fdadf814afa34df9a38f6ed41b984e2c6da2aa6f5

1
tools/pdf_tests/owner.in Normal file
View File

@ -0,0 +1 @@
$pdf$4*4*128*-3392*1*16*b438e5ce7c355548a28bf1408a36bbdf*32*63a1a7a95566adcf61a8c36002b6bfa900000000000000000000000000000000*32*566fa873ee33c797cd3b904fdadf814afa34df9a38f6ed41b984e2c6da2aa6f5:owner

BIN
tools/pdf_tests/owner.pdf Normal file

Binary file not shown.

View File

@ -0,0 +1 @@
$pdf$4*4*128*-3392*1*16*f72f279e0db2ee4ca76b0db6884e85cd*32*665d9c5a3c2443ac2aa3a9b855843bf200000000000000000000000000000000*32*0ba3835f88f90388e74e54584125ce142be0de24c6b0d37746e075b891756671

Binary file not shown.

View File

@ -0,0 +1 @@
$pdf$4*4*128*-3392*1*16*f72f279e0db2ee4ca76b0db6884e85cd*32*665d9c5a3c2443ac2aa3a9b855843bf200000000000000000000000000000000*32*0ba3835f88f90388e74e54584125ce142be0de24c6b0d37746e075b891756671*user

View File

@ -0,0 +1 @@
$pdf$4*4*128*-3392*1*16*f72f279e0db2ee4ca76b0db6884e85cd*32*665d9c5a3c2443ac2aa3a9b855843bf200000000000000000000000000000000*32*0ba3835f88f90388e74e54584125ce142be0de24c6b0d37746e075b891756671*user:owner

View File

@ -0,0 +1 @@
$pdf$4*4*128*-1028*1*16*e77b55355f74d54895c93063ddbad299*32*61536f41f6bf35bce77a96eb45cebec500000000000000000000000000000000*32*ce69e44fd4717582076b810914829e577152c3110ee581d65e53c64a4e8f578d

BIN
tools/pdf_tests/user.pdf Normal file

Binary file not shown.

View File

@ -0,0 +1 @@
$pdf$4*4*128*-1028*1*16*e77b55355f74d54895c93063ddbad299*32*61536f41f6bf35bce77a96eb45cebec500000000000000000000000000000000*32*ce69e44fd4717582076b810914829e577152c3110ee581d65e53c64a4e8f578d*user

View File

@ -0,0 +1 @@
$pdf$4*4*128*-1028*1*16*e77b55355f74d54895c93063ddbad299*32*61536f41f6bf35bce77a96eb45cebec500000000000000000000000000000000*32*ce69e44fd4717582076b810914829e577152c3110ee581d65e53c64a4e8f578d*user:user

View File

@ -6,12 +6,9 @@
## ##
# based off m10500 but added the owner password part ($o) to be able to test the edit password # 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 # 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 strict;
use warnings; use warnings;
@ -99,9 +96,6 @@ sub pdf_compute_encryption_key_owner
} }
} }
#printf("\$o_digest = %s\n", unpack ("H*", $o_digest));
my $o_key; my $o_key;
if ($R == 2) 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 $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; return $o_key;
} }
@ -173,28 +166,31 @@ sub module_generate_hash
################ USER PASSWORD ################# ################ USER PASSWORD #################
# do not change $u if it exists, keep this the same, as we don't know the 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 # 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); $res = pdf_compute_encryption_key_user($word, $padding, $id, $u, $o, $P, $V, $R, $enc);
} }
else else
{ {
#$u = pack("H*", $u) #we do know the user-password, so we can generate $u
#now that we know the user-password we can generate it
$res = pdf_compute_encryption_key_user($u_pass, $padding, $id, $u, $o, $P, $V, $R, $enc); $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); my $m = Crypt::RC4->new ($res);
$u = $m->RC4 ($digest); $u = $m->RC4 ($digest);
my @ress = split "", $res; my @ress = split "", $res;
#do xor of rc4 19 times #do xor of rc4 19 times
for (my $x = 1; $x <= 19; $x++) for (my $x = 1; $x <= 19; $x++)
{ {
my @xor; my @xor;
for (my $i = 0; $i < 16; $i++) for (my $i = 0; $i < 16; $i++)
@ -207,13 +203,19 @@ sub module_generate_hash
my $m2 = Crypt::RC4->new ($s); my $m2 = Crypt::RC4->new ($s);
$u = $m2->RC4 ($u); $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 ################# ################ OWNER PASSWORD #################
my $o_key = pdf_compute_encryption_key_owner($word, $padding, $id, $u, $o, $P, $V, $R, $enc); my $o_key = pdf_compute_encryption_key_owner($word, $padding, $id, $u, $o, $P, $V, $R, $enc);
my $n = Crypt::RC4->new ($o_key); my $n = Crypt::RC4->new ($o_key);
if("".$u_pass eq "") if($u_pass eq "")
{ {
$o = $n->RC4(substr ($padding, 0, 32 - length "")); $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 #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)); $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; my @ress2 = split "", $o_key;
@ -240,19 +240,14 @@ sub module_generate_hash
} }
my $s = join ("", @xor); my $s = join ("", @xor);
my $n2 = Crypt::RC4->new ($s);
my $n2 = Crypt::RC4->new ($s);
$o = $n2->RC4 ($o); $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; 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)); $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); $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; return $hash;
} }
@ -293,7 +287,6 @@ sub module_verify_hash
my $u_pass = ""; my $u_pass = "";
if($i_data == 12) { if($i_data == 12) {
$u_pass = shift @data; $u_pass = shift @data;
#printf("u_pass = %s\n", $u_pass);
} }
return unless defined $id; return unless defined $id;