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:
commit
540c0dde81
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
14
tools/pdf_tests/_README.txt
Normal file
14
tools/pdf_tests/_README.txt
Normal 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
|
1
tools/pdf_tests/owner.hash
Normal file
1
tools/pdf_tests/owner.hash
Normal file
@ -0,0 +1 @@
|
||||
$pdf$4*4*128*-3392*1*16*b438e5ce7c355548a28bf1408a36bbdf*32*63a1a7a95566adcf61a8c36002b6bfa900000000000000000000000000000000*32*566fa873ee33c797cd3b904fdadf814afa34df9a38f6ed41b984e2c6da2aa6f5
|
1
tools/pdf_tests/owner.in
Normal file
1
tools/pdf_tests/owner.in
Normal 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
BIN
tools/pdf_tests/owner.pdf
Normal file
Binary file not shown.
1
tools/pdf_tests/user-owner.hash
Normal file
1
tools/pdf_tests/user-owner.hash
Normal file
@ -0,0 +1 @@
|
||||
$pdf$4*4*128*-3392*1*16*f72f279e0db2ee4ca76b0db6884e85cd*32*665d9c5a3c2443ac2aa3a9b855843bf200000000000000000000000000000000*32*0ba3835f88f90388e74e54584125ce142be0de24c6b0d37746e075b891756671
|
BIN
tools/pdf_tests/user-owner.pdf
Normal file
BIN
tools/pdf_tests/user-owner.pdf
Normal file
Binary file not shown.
1
tools/pdf_tests/user-owner_userpw-in-hash.hash
Normal file
1
tools/pdf_tests/user-owner_userpw-in-hash.hash
Normal file
@ -0,0 +1 @@
|
||||
$pdf$4*4*128*-3392*1*16*f72f279e0db2ee4ca76b0db6884e85cd*32*665d9c5a3c2443ac2aa3a9b855843bf200000000000000000000000000000000*32*0ba3835f88f90388e74e54584125ce142be0de24c6b0d37746e075b891756671*user
|
1
tools/pdf_tests/user-owner_userpw-in-hash.in
Normal file
1
tools/pdf_tests/user-owner_userpw-in-hash.in
Normal file
@ -0,0 +1 @@
|
||||
$pdf$4*4*128*-3392*1*16*f72f279e0db2ee4ca76b0db6884e85cd*32*665d9c5a3c2443ac2aa3a9b855843bf200000000000000000000000000000000*32*0ba3835f88f90388e74e54584125ce142be0de24c6b0d37746e075b891756671*user:owner
|
1
tools/pdf_tests/user.hash
Normal file
1
tools/pdf_tests/user.hash
Normal file
@ -0,0 +1 @@
|
||||
$pdf$4*4*128*-1028*1*16*e77b55355f74d54895c93063ddbad299*32*61536f41f6bf35bce77a96eb45cebec500000000000000000000000000000000*32*ce69e44fd4717582076b810914829e577152c3110ee581d65e53c64a4e8f578d
|
BIN
tools/pdf_tests/user.pdf
Normal file
BIN
tools/pdf_tests/user.pdf
Normal file
Binary file not shown.
1
tools/pdf_tests/user_userpw-in-hash.hash
Normal file
1
tools/pdf_tests/user_userpw-in-hash.hash
Normal file
@ -0,0 +1 @@
|
||||
$pdf$4*4*128*-1028*1*16*e77b55355f74d54895c93063ddbad299*32*61536f41f6bf35bce77a96eb45cebec500000000000000000000000000000000*32*ce69e44fd4717582076b810914829e577152c3110ee581d65e53c64a4e8f578d*user
|
1
tools/pdf_tests/user_userpw-in-hash.in
Normal file
1
tools/pdf_tests/user_userpw-in-hash.in
Normal file
@ -0,0 +1 @@
|
||||
$pdf$4*4*128*-1028*1*16*e77b55355f74d54895c93063ddbad299*32*61536f41f6bf35bce77a96eb45cebec500000000000000000000000000000000*32*ce69e44fd4717582076b810914829e577152c3110ee581d65e53c64a4e8f578d*user:user
|
@ -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);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user