use crate::hashes::{AttackModeType, HashModule, HashPattern, ParsedHash};
use aes::Aes128;
use cipher::{BlockCipherEncrypt, KeyInit};
pub struct RawPdf;
#[derive(Debug)]
pub struct PdfParams {
pub v: u32,
pub r: u32,
pub length: u32,
pub p: i32,
pub encrypt_meta: bool,
pub id: Vec<u8>,
pub u: Vec<u8>,
pub o: Vec<u8>,
pub ue: Vec<u8>,
pub oe: Vec<u8>,
}
pub fn parse_pdf_hash(s: &str) -> Result<PdfParams, String> {
let s = s.trim();
let body = s.strip_prefix("$pdf$").ok_or_else(|| "Missing $pdf$ prefix".to_string())?;
let fields: Vec<&str> = body.split('*').collect();
if fields.len() < 11 {
return Err(format!("Expected at least 11 fields, got {}", fields.len()));
}
let v = fields[0].parse::<u32>().map_err(|_| "Invalid V".to_string())?;
let r = fields[1].parse::<u32>().map_err(|_| "Invalid R".to_string())?;
let length = fields[2].parse::<u32>().map_err(|_| "Invalid Length".to_string())?;
let p = fields[3].parse::<i32>().map_err(|_| "Invalid P".to_string())?;
let encrypt_meta = fields[4] != "0";
let id_len = fields[5].parse::<usize>().map_err(|_| "Invalid ID length".to_string())?;
let id = hex::decode(&fields[6]).map_err(|_| "Invalid ID hex".to_string())?;
if id.len() != id_len {
return Err(format!("ID length mismatch: expected {} got {}", id_len, id.len()));
}
let u_len = fields[7].parse::<usize>().map_err(|_| "Invalid U length".to_string())?;
let u = hex::decode(&fields[8]).map_err(|_| "Invalid U hex".to_string())?;
if u.len() != u_len {
return Err(format!("U length mismatch: expected {} got {}", u_len, u.len()));
}
let o_len = fields[9].parse::<usize>().map_err(|_| "Invalid O length".to_string())?;
let o = hex::decode(&fields[10]).map_err(|_| "Invalid O hex".to_string())?;
if o.len() != o_len {
return Err(format!("O length mismatch: expected {} got {}", o_len, o.len()));
}
let (ue, oe) = if fields.len() > 11 {
let _ue_len = fields[11].parse::<usize>().map_err(|_| "Invalid UE length".to_string())?;
let ue = hex::decode(&fields[12]).map_err(|_| "Invalid UE hex".to_string())?;
let _oe_len = fields[13].parse::<usize>().map_err(|_| "Invalid OE length".to_string())?;
let oe = hex::decode(&fields[14]).map_err(|_| "Invalid OE hex".to_string())?;
(ue, oe)
} else {
(Vec::new(), Vec::new())
};
Ok(PdfParams { v, r, length, p, encrypt_meta, id, u, o, ue, oe })
}
fn pdf_compute_key(password: &str, params: &PdfParams) -> Vec<u8> {
use md5::{Digest, Md5};
let pw = password.as_bytes();
let pwlen = pw.len().min(32);
let n = (params.length / 8) as usize;
let mut hasher = Md5::new();
hasher.update(&pw[..pwlen]);
hasher.update(&crate::pdf_extract::PDF_PADDING[..32 - pwlen]);
hasher.update(¶ms.o[..32.min(params.o.len())]);
let p_le = (params.p as u32).to_le_bytes();
hasher.update(&p_le);
if params.r >= 3 {
hasher.update(¶ms.id[..4.min(params.id.len())]);
}
if params.r >= 4 && !params.encrypt_meta {
hasher.update(&[0xFFu8; 4]);
}
let mut key = hasher.finalize().to_vec();
if params.r >= 3 {
for _ in 0..50 {
let mut h = Md5::new();
h.update(&key[..n]);
key = h.finalize().to_vec();
}
}
key.truncate(n);
key
}
fn pdf_verify_rev2_4(password: &str, params: &PdfParams) -> bool {
use md5::{Digest, Md5};
let key = pdf_compute_key(password, params);
let _n = key.len();
if params.r == 2 {
let decrypted = rc4_decrypt(&key, ¶ms.u);
decrypted.len() >= 32 && &decrypted[..32] == crate::pdf_extract::PDF_PADDING
} else {
let mut hasher = Md5::new();
hasher.update(&crate::pdf_extract::PDF_PADDING[..]);
hasher.update(¶ms.id[..4.min(params.id.len())]);
let digest = hasher.finalize();
let mut output = rc4_decrypt(&key, &digest[..16]);
for x in 1..20 {
let xor_key: Vec<u8> = key.iter().map(|&k| k ^ x).collect();
output = rc4_decrypt(&xor_key, &output);
}
params.u.len() >= 16 && output.len() >= 16 && output[..16] == params.u[..16]
}
}
fn pdf_verify_rev5(password: &str, params: &PdfParams) -> bool {
use sha2::{Digest, Sha256};
let pw = password.as_bytes();
let pwlen = pw.len().min(127);
let mut buffer = Vec::new();
buffer.extend_from_slice(&pw[..pwlen]);
if params.o.len() >= 40 {
buffer.extend_from_slice(¶ms.o[32..40]); }
if params.u.len() >= 48 {
buffer.extend_from_slice(¶ms.u[..48]); }
let hash = Sha256::digest(&buffer);
params.u.len() >= 32 && hash[..] == params.u[..32.min(hash.len())]
}
#[allow(dead_code)]
fn aes128_encrypt(key: &[u8; 16], block: &mut [u8; 16]) {
let cipher = Aes128::new_from_slice(key).expect("valid AES-128 key");
let buf = *block;
let mut ga: cipher::Block::<Aes128> = buf.as_slice().try_into().unwrap();
cipher.encrypt_block(&mut ga);
block.copy_from_slice(&ga);
}
#[allow(dead_code)]
fn sub_word(w: u32) -> u32 {
let b = w.to_be_bytes();
u32::from_be_bytes([SBOX[b[0] as usize], SBOX[b[1] as usize], SBOX[b[2] as usize], SBOX[b[3] as usize]])
}
#[allow(dead_code)]
fn rot_word(w: u32) -> u32 {
(w << 8) | (w >> 24)
}
#[allow(dead_code)]
fn mix_column(a: [u8; 4]) -> [u8; 4] {
[
gf_mul(2, a[0]) ^ gf_mul(3, a[1]) ^ a[2] ^ a[3],
a[0] ^ gf_mul(2, a[1]) ^ gf_mul(3, a[2]) ^ a[3],
a[0] ^ a[1] ^ gf_mul(2, a[2]) ^ gf_mul(3, a[3]),
gf_mul(3, a[0]) ^ a[1] ^ a[2] ^ gf_mul(2, a[3]),
]
}
#[allow(dead_code)]
fn gf_mul(a: u8, b: u8) -> u8 {
let mut result: u8 = 0;
let mut x = a;
let mut y = b;
for _ in 0..8 {
if y & 1 != 0 {
result ^= x;
}
let carry = x & 0x80;
x <<= 1;
if carry != 0 {
x ^= 0x1B;
}
y >>= 1;
}
result
}
#[allow(dead_code)]
static SBOX: [u8; 256] = [
0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76,
0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0,
0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,
0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75,
0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84,
0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF,
0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8,
0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2,
0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73,
0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB,
0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79,
0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08,
0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A,
0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E,
0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,
0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16,
];
#[allow(dead_code)]
static RCON: [u32; 10] = [0x01000000, 0x02000000, 0x04000000, 0x08000000, 0x10000000, 0x20000000, 0x40000000, 0x80000000, 0x1B000000, 0x36000000];
fn pdf_compute_hash_rev6(password: &str, salt: &[u8], udata: &[u8]) -> [u8; 32] {
use sha2::{Digest, Sha256, Sha384, Sha512};
let pw = password.as_bytes();
let mut k: Vec<u8> = Sha256::digest(&[pw, salt, udata].concat()).to_vec();
let mut count = 0u32;
loop {
count += 1;
let mut k1 = Vec::new();
k1.extend_from_slice(pw);
k1.extend_from_slice(&k);
k1.extend_from_slice(udata);
let k1_64 = k1.repeat(64);
let key_16: [u8; 16] = k[..16].try_into().unwrap();
let mut iv: [u8; 16] = k[16..32].try_into().unwrap();
let mut encrypted = k1_64.clone();
for chunk in encrypted.chunks_exact_mut(16) {
for i in 0..16 {
chunk[i] ^= iv[i];
}
let block: &mut [u8; 16] = chunk.try_into().unwrap();
aes128_encrypt(&key_16, block);
iv.copy_from_slice(chunk);
}
let sum_first_16: u32 = encrypted[..16].iter().map(|&b| b as u32).sum();
let hash_fn_idx = sum_first_16 % 3;
k = match hash_fn_idx {
0 => Sha256::digest(&encrypted).to_vec(),
1 => Sha384::digest(&encrypted).to_vec(),
_ => Sha512::digest(&encrypted).to_vec(),
};
if count >= 64 && encrypted[encrypted.len() - 1] <= (count - 32) as u8 {
break;
}
}
let mut result = [0u8; 32];
result.copy_from_slice(&k[..32]);
result
}
fn pdf_verify_rev6(password: &str, params: &PdfParams) -> bool {
if params.u.len() < 48 {
return false;
}
let hash = pdf_compute_hash_rev6(password, ¶ms.u[32..40], &[]);
hash == params.u[..32]
}
fn rc4_decrypt(key: &[u8], data: &[u8]) -> Vec<u8> {
let mut s: [u8; 256] = std::array::from_fn(|i| i as u8);
let mut j: u8 = 0;
for i in 0..256 {
j = j.wrapping_add(s[i]).wrapping_add(key[i % key.len()]);
s.swap(i, j as usize);
}
let mut i: u8 = 0;
j = 0;
let mut result = data.to_vec();
for byte in result.iter_mut() {
i = i.wrapping_add(1);
j = j.wrapping_add(s[i as usize]);
s.swap(i as usize, j as usize);
let k = s[(s[i as usize].wrapping_add(s[j as usize])) as usize];
*byte ^= k;
}
result
}
impl HashModule for RawPdf {
fn name(&self) -> &'static str { "pdf" }
fn mode(&self) -> u32 { 10500 }
fn digest_words(&self) -> u32 { 8 }
fn needs_int64(&self) -> bool { false }
fn cpu_verify(&self, password: &str, salt: &[u8], _hash: &[u32]) -> bool {
if salt.len() < 18 {
return false;
}
let v = u32::from_le_bytes(salt[0..4].try_into().unwrap());
let r = u32::from_le_bytes(salt[4..8].try_into().unwrap());
let length = u32::from_le_bytes(salt[8..12].try_into().unwrap());
let p = i32::from_le_bytes(salt[12..16].try_into().unwrap());
let encrypt_meta = salt[16] != 0;
let id_len = salt[17] as usize;
let mut offset = 18;
if offset + id_len > salt.len() {
return false;
}
let id = salt[offset..offset + id_len].to_vec();
offset += id_len;
if offset >= salt.len() {
return false;
}
let u_len = salt[offset] as usize;
offset += 1;
if offset + u_len > salt.len() {
return false;
}
let u = salt[offset..offset + u_len].to_vec();
offset += u_len;
if offset >= salt.len() {
return false;
}
let o_len = salt[offset] as usize;
offset += 1;
if offset + o_len > salt.len() {
return false;
}
let o = salt[offset..offset + o_len].to_vec();
offset += o_len;
let ue = if offset < salt.len() {
let ue_len = salt[offset] as usize;
offset += 1;
if offset + ue_len <= salt.len() {
let v = salt[offset..offset + ue_len].to_vec();
offset += ue_len;
v
} else {
Vec::new()
}
} else {
Vec::new()
};
let oe = if offset < salt.len() {
let oe_len = salt[offset] as usize;
offset += 1;
if offset + oe_len <= salt.len() {
salt[offset..offset + oe_len].to_vec()
} else {
Vec::new()
}
} else {
Vec::new()
};
let params = PdfParams {
v, r, length, p,
encrypt_meta,
id,
u,
o,
ue,
oe,
};
match r {
2 => pdf_verify_rev2_4(password, ¶ms),
3 | 4 => pdf_verify_rev2_4(password, ¶ms),
5 => pdf_verify_rev5(password, ¶ms),
6 => pdf_verify_rev6(password, ¶ms),
_ => false,
}
}
fn shader_source(&self, mode: &AttackModeType) -> &'static str {
match mode {
AttackModeType::BruteForce => include_str!("../pdf_crack.wgsl"),
AttackModeType::Mask => include_str!("../pdf_mask.wgsl"),
AttackModeType::Wordlist => include_str!("../pdf_wordlist.wgsl"),
}
}
fn detect_patterns(&self) -> &[HashPattern] {
&[HashPattern { prefix: Some("$pdf$"), hex_len: None, priority: 90 }]
}
fn parse_hash_string(&self, s: &str) -> Result<ParsedHash, String> {
let params = parse_pdf_hash(s)?;
let mut hash_words = [0u32; 8];
for i in 0..8 {
let start = i * 4;
if start + 4 <= params.u.len() {
let mut buf = [0u8; 4];
buf.copy_from_slice(¶ms.u[start..start + 4]);
hash_words[i] = u32::from_le_bytes(buf);
}
}
let mut salt = Vec::new();
salt.extend_from_slice(¶ms.v.to_le_bytes());
salt.extend_from_slice(¶ms.r.to_le_bytes());
salt.extend_from_slice(¶ms.length.to_le_bytes());
salt.extend_from_slice(&(params.p as u32).to_le_bytes());
salt.push(if params.encrypt_meta { 1u8 } else { 0u8 });
salt.push(params.id.len() as u8);
salt.extend_from_slice(¶ms.id);
salt.push(params.u.len() as u8);
salt.extend_from_slice(¶ms.u);
salt.push(params.o.len() as u8);
salt.extend_from_slice(¶ms.o);
salt.push(params.ue.len() as u8);
salt.extend_from_slice(¶ms.ue);
salt.push(params.oe.len() as u8);
salt.extend_from_slice(¶ms.oe);
Ok(ParsedHash {
hash_words,
extra_words: [0u32; 8],
salt,
digest_words: 8,
})
}
}
#[test]
fn test_pdf_parse_and_verify_rev3() {
let hash_str = "$pdf$2*3*128*-4*1*16*733ab0e911f8aa4c77782aa056996f57*32*0000000000000000000000000000000000000000000000000000000000000000*32*0000000000000000000000000000000000000000000000000000000000000000";
let parsed = parse_pdf_hash(hash_str).unwrap();
assert_eq!(parsed.v, 2);
assert_eq!(parsed.r, 3);
}
#[test]
fn test_pdf_rc4() {
let key = b"Key";
let plaintext = b"Plaintext";
let encrypted = rc4_decrypt(key, plaintext);
let decrypted = rc4_decrypt(key, &encrypted);
assert_eq!(&decrypted, plaintext);
}
#[test]
fn test_aes128_encrypt() {
let key = hex::decode("2b7e151628aed2a6abf7158809cf4f3c").unwrap();
let mut block = hex::decode("6bc1bee22e409f96e93d7e117393172a").unwrap();
let expected = hex::decode("3ad77bb40d7a3660a89ecaf32466ef97").unwrap();
let k: [u8; 16] = key.try_into().unwrap();
let mut b: [u8; 16] = block.try_into().unwrap();
aes128_encrypt(&k, &mut b);
assert_eq!(b.to_vec(), expected);
}
#[test]
fn test_pdf_verify_rev2() {
let params = PdfParams {
v: 1, r: 2, length: 40,
p: -4, encrypt_meta: true,
id: vec![0x73, 0x3a, 0xb0, 0xe9, 0x11, 0xf8, 0xaa, 0x4c, 0x77, 0x78, 0x2a, 0xa0, 0x56, 0x99, 0x6f, 0x57],
u: vec![0; 32],
o: vec![0; 32],
ue: vec![], oe: vec![],
};
let _ = pdf_verify_rev2_4("", ¶ms);
}
#[test]
fn test_pdf_verify_rev6() {
let hash_str = "$pdf$5*6*32*-1028*1*16*3b7d3434edc5b7354b75ae411b387b9c*48*39e8f0bc5f2e2785ce6c955e8022ae700e2dfa280b12ba8c2980ef6fc17414c0a47cd7872bd4079ea77c474f59188c62*48*5d31ed6525e4c22f4165e260445671bf570f37c9ed174821971f442f81c42790e9dbe06ce91369be74fc46270082044f*32*dad365ca3ef8a4f1f188f194298ffce71e12eeaba5f189cd856277cfe5dcb9e9*32*7085b7b2b24b68e6002989887fdb2f62b47f15beca072dee3a29a63d0f5a375f";
let params = parse_pdf_hash(hash_str).unwrap();
assert_eq!(params.v, 5);
assert_eq!(params.r, 6);
assert_eq!(params.u.len(), 48);
assert_eq!(hex::encode(¶ms.u[..32]), "39e8f0bc5f2e2785ce6c955e8022ae700e2dfa280b12ba8c2980ef6fc17414c0");
assert!(pdf_verify_rev6("abcd", ¶ms), "Password 'abcd' should verify");
assert!(!pdf_verify_rev6("wrong_password", ¶ms));
assert!(!pdf_verify_rev6("", ¶ms));
assert!(!pdf_verify_rev6("abcde", ¶ms));
}