1
mirror of https://github.com/hashcat/hashcat synced 2024-11-24 14:27:14 +01:00

Merge pull request #2877 from thatux/25400_salt

improve 25400: recover both owner and user; add user password to hash; better test
This commit is contained in:
Jens Steube 2021-09-11 20:56:18 +02:00 committed by GitHub
commit 540c0dde81
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 439 additions and 117 deletions

View File

@ -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 dictionarys 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 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[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);
}
}
}
}

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);
// 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);

View File

@ -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,22 +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];
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;
@ -134,13 +135,16 @@ 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;
}
int module_hash_decode (MAYBE_UNUSED const hashconfig_t *hashconfig, MAYBE_UNUSED void *digest_buf, MAYBE_UNUSED salt_t *salt, MAYBE_UNUSED void *esalt_buf, MAYBE_UNUSED void *hook_salt_buf, MAYBE_UNUSED hashinfo_t *hash_info, const char *line_buf, MAYBE_UNUSED const int line_len)
{
char *input_buf = (char *) line_buf;
int input_len = line_len;
// based on m22000 module_hash_decode() we detect both the hashformat with and without user-password
u32 *digest = (u32 *) digest_buf;
pdf_t *pdf = (pdf_t *) esalt_buf;
@ -221,8 +225,102 @@ int module_hash_decode (MAYBE_UNUSED const hashconfig_t *hashconfig, MAYBE_UNUSE
token.attr[11] = TOKEN_ATTR_VERIFY_LENGTH
| TOKEN_ATTR_VERIFY_HEX;
const int rc_tokenizer = input_tokenizer ((const u8 *) line_buf, line_len, &token);
int rc_tokenizer = input_tokenizer ((const u8 *) line_buf, line_len, &token); // was a const, now no longer, as we need it again for the new hashformat
//check if hashformat without user-password is detected
if (rc_tokenizer == PARSER_OK)
{
char tmp_buf[1024];
int tmp_len;
tmp_len = snprintf (tmp_buf, sizeof (tmp_buf), "%s*", line_buf); // simply add an extra asterisk to denote a empty user-password
input_buf = tmp_buf;
input_len = tmp_len;
}
token.token_cnt = 13;
token.signatures_cnt = 1;
token.signatures_buf[0] = SIGNATURE_PDF;
token.len[0] = 5;
token.attr[0] = TOKEN_ATTR_FIXED_LENGTH
| TOKEN_ATTR_VERIFY_SIGNATURE;
token.len_min[1] = 1;
token.len_max[1] = 1;
token.sep[1] = '*';
token.attr[1] = TOKEN_ATTR_VERIFY_LENGTH
| TOKEN_ATTR_VERIFY_DIGIT;
token.len_min[2] = 1;
token.len_max[2] = 1;
token.sep[2] = '*';
token.attr[2] = TOKEN_ATTR_VERIFY_LENGTH
| TOKEN_ATTR_VERIFY_DIGIT;
token.len_min[3] = 3;
token.len_max[3] = 3;
token.sep[3] = '*';
token.attr[3] = TOKEN_ATTR_VERIFY_LENGTH
| TOKEN_ATTR_VERIFY_DIGIT;
token.len_min[4] = 1;
token.len_max[4] = 6;
token.sep[4] = '*';
token.attr[4] = TOKEN_ATTR_VERIFY_LENGTH;
token.len_min[5] = 1;
token.len_max[5] = 1;
token.sep[5] = '*';
token.attr[5] = TOKEN_ATTR_VERIFY_LENGTH
| TOKEN_ATTR_VERIFY_DIGIT;
token.len_min[6] = 2;
token.len_max[6] = 2;
token.sep[6] = '*';
token.attr[6] = TOKEN_ATTR_VERIFY_LENGTH
| TOKEN_ATTR_VERIFY_DIGIT;
token.len_min[7] = 32;
token.len_max[7] = 64;
token.sep[7] = '*';
token.attr[7] = TOKEN_ATTR_VERIFY_LENGTH
| TOKEN_ATTR_VERIFY_HEX;
token.len_min[8] = 2;
token.len_max[8] = 2;
token.sep[8] = '*';
token.attr[8] = TOKEN_ATTR_VERIFY_LENGTH
| TOKEN_ATTR_VERIFY_DIGIT;
token.len_min[9] = 64;
token.len_max[9] = 64;
token.sep[9] = '*';
token.attr[9] = TOKEN_ATTR_VERIFY_LENGTH
| TOKEN_ATTR_VERIFY_HEX;
token.len_min[10] = 2;
token.len_max[10] = 2;
token.sep[10] = '*';
token.attr[10] = TOKEN_ATTR_VERIFY_LENGTH
| TOKEN_ATTR_VERIFY_DIGIT;
token.len_min[11] = 64;
token.len_max[11] = 64;
token.sep[11] = '*';
token.attr[11] = TOKEN_ATTR_VERIFY_LENGTH
| TOKEN_ATTR_VERIFY_HEX;
token.len_min[12] = 0;
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;
rc_tokenizer = input_tokenizer ((const u8 *) input_buf, input_len, &token);
// detect hashformat including the user-password
if (rc_tokenizer != PARSER_OK) return (rc_tokenizer);
const u8 *V_pos = token.buf[1];
@ -236,6 +334,11 @@ int module_hash_decode (MAYBE_UNUSED const hashconfig_t *hashconfig, MAYBE_UNUSE
const u8 *u_buf_pos = token.buf[9]; // user hash
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
@ -271,11 +374,13 @@ 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;
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;
pdf->id_buf[0] = hex_to_u32 (id_buf_pos + 0);
@ -314,7 +419,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,
@ -328,7 +432,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;
@ -344,7 +447,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];
@ -365,15 +467,81 @@ int module_hash_decode (MAYBE_UNUSED const hashconfig_t *hashconfig, MAYBE_UNUSE
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)
{
const pdf_t *pdf = (const pdf_t *) esalt_buf;
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,
@ -405,12 +573,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,
@ -438,7 +607,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)
);
}
@ -455,7 +625,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_deprecated_notice = MODULE_DEFAULT;
module_ctx->module_dgst_pos0 = module_dgst_pos0;

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
# 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;
@ -86,7 +83,6 @@ sub pdf_compute_encryption_key_owner
my $R = shift;
my $enc = shift;
# TODO use user password as input for md5 of o_digest if no owner password is set
my $data;
$data .= $word;
$data .= substr ($padding, 0, 32 - length $word);
@ -100,9 +96,6 @@ sub pdf_compute_encryption_key_owner
}
}
#printf("\$o_digest = %s\n", unpack ("H*", $o_digest));
my $o_key;
if ($R == 2)
{
@ -110,9 +103,8 @@ sub pdf_compute_encryption_key_owner
}
else
{
$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;
}
@ -127,6 +119,7 @@ sub module_generate_hash
my $V = shift;
my $R = shift;
my $enc = shift;
my $u_pass = shift;
if (defined $u == 0)
{
@ -162,6 +155,11 @@ sub module_generate_hash
$enc = ($R == 3) ? 1 : random_number (0, 1);
}
if (!defined $u_pass)
{
$u_pass="";
}
my $padding;
for (my $i = 0; $i < 32; $i++)
@ -171,18 +169,33 @@ sub module_generate_hash
################ USER PASSWORD #################
my $res = pdf_compute_encryption_key_user($word, $padding, $id, $u, $o, $P, $V, $R, $enc);
# 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 $digest = md5 ($padding . pack ("H*", $id));
my $m = Crypt::RC4->new ($res);
$u = $m->RC4 ($digest);
my @ress = split "", $res;
#do xor of rc4 19 times
for (my $x = 1; $x <= 19; $x++)
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
{
#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 $m = Crypt::RC4->new ($res);
$u = $m->RC4 ($digest);
my @ress = split "", $res;
#do xor of rc4 19 times
for (my $x = 1; $x <= 19; $x++)
{
my @xor;
for (my $i = 0; $i < 16; $i++)
@ -195,15 +208,27 @@ 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)
}
################ 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);
$o = $n->RC4(substr ($padding, 0, 32 - length "")); # TODO dynamically add user password including padding to the RC4 input for the computation of the pdf o-value
#printf("padding_empty_str = %s\n", unpack ("H*", substr ($padding, 0, 32 - length "")));
my $n = Crypt::RC4->new ($o_key);
if($u_pass eq "")
{
$o = $n->RC4(substr ($padding, 0, 32 - length ""));
}
else
{
#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));
}
my @ress2 = split "", $o_key;
@ -220,22 +245,21 @@ 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));
$u .= substr (pack ("H*", $u_save), 16, 16);
#printf("\$o = %s\n", unpack ("H*", $o));
#printf("\$u = %s\n", unpack ("H*", $u));
my $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));
my $hash;
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));
}
else
{
$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);
}
return $hash;
}
@ -250,7 +274,8 @@ sub module_verify_hash
my @data = split /\*/, $hash_in;
return unless scalar @data == 11;
my $i_data = scalar @data;
return unless ($i_data == 11) || ($i_data == 12); #or 12 if user-password is included
my $V = shift @data; $V = substr ($V, 5, 1);
my $R = shift @data;
@ -264,12 +289,17 @@ sub module_verify_hash
return unless (shift @data eq '32');
my $o = shift @data;
my $u_pass = "";
if($i_data == 12) {
$u_pass = shift @data;
}
return unless defined $id;
return unless defined $word;
$word = pack_if_HEX_notation ($word);
my $new_hash = module_generate_hash ($word, $id, $u, $o, $P, $V, $R, $enc);
my $new_hash = module_generate_hash ($word, $id, $u, $o, $P, $V, $R, $enc, $u_pass);
return ($new_hash, $word);
}