mirror of
https://github.com/Momoko-Ayase/Senbei.git
synced 2026-11-03 10:31:28 -05:00
fix(windows): support legacy PE32 payload layouts
Handle fully encrypted PE32 payloads and compact shell, stage, and metadata layouts. Trial-decrypt each table candidate before selecting it, restoring the header and stage ciphertext between attempts. Add synthetic regressions for full and prefix encryption, false table candidates, rollback, metadata placement, and malformed ranges. Preserve existing corpus outputs.
This commit is contained in:
1 parent
6e233bbf18
commit
ea92d26d28
4 files changed
+491
-153
No files matched your search
@@ -104,6 +104,45 @@ impl<'a> Unpacker<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn load_payload(&mut self) -> Option<u32> {
|
||||||
|
self.decrypt_size = self.info[6].wrapping_sub(self.info[3]).wrapping_add(8192);
|
||||||
|
self.decrypt_data2();
|
||||||
|
let src_start = self.info[4]
|
||||||
|
.wrapping_add(4096)
|
||||||
|
.wrapping_add(self.decrypt_size) as usize;
|
||||||
|
let dst_start = self.info[3].wrapping_add(self.decrypt_size) as usize;
|
||||||
|
let len = self.info[5].wrapping_sub(self.decrypt_size) as usize;
|
||||||
|
self.decompressed[dst_start..dst_start + len]
|
||||||
|
.copy_from_slice(&self.file_data[src_start..src_start + len]);
|
||||||
|
|
||||||
|
write_u32(&mut self.decompressed, self.info[3], 4096);
|
||||||
|
self.decompressed[..4096].copy_from_slice(&self.file_data[..4096]);
|
||||||
|
let pe = get_u32(self.file_data, 60);
|
||||||
|
let is_pe32 = get_u16(self.file_data, pe.wrapping_add(24)) == 0x10B;
|
||||||
|
let mut table = if is_pe32 {
|
||||||
|
self.find_pe32_table()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
// Older PE32 loaders encrypt the entire payload; the length also seeds
|
||||||
|
// the cipher, so the prefix-size key cannot decode even their shell.
|
||||||
|
if is_pe32 && self.info[5] > self.decrypt_size && table.is_none() {
|
||||||
|
let prefix_size = self.decrypt_size;
|
||||||
|
let start = self.info[3] as usize;
|
||||||
|
let end = start + self.info[5] as usize;
|
||||||
|
let backup = self.decompressed[start..end].to_vec();
|
||||||
|
self.decrypt_size = self.info[5];
|
||||||
|
self.decrypt_data2();
|
||||||
|
write_u32(&mut self.decompressed, self.info[3], 4096);
|
||||||
|
table = self.find_pe32_table();
|
||||||
|
if table.is_none() {
|
||||||
|
self.decompressed[start..end].copy_from_slice(&backup);
|
||||||
|
self.decrypt_size = prefix_size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
table
|
||||||
|
}
|
||||||
|
|
||||||
// Strategy (a): delegate to primitives::decrypt_data3
|
// Strategy (a): delegate to primitives::decrypt_data3
|
||||||
fn decrypt_data3(&mut self, pos: u32, key: u32, shift: u32) {
|
fn decrypt_data3(&mut self, pos: u32, key: u32, shift: u32) {
|
||||||
primitives::decrypt_data3(&mut self.decompressed, pos, key, shift);
|
primitives::decrypt_data3(&mut self.decompressed, pos, key, shift);
|
||||||
@@ -351,28 +390,18 @@ impl<'a> Unpacker<'a> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
u.decompressed = vec![0u8; size_of_image as usize];
|
u.decompressed = vec![0u8; size_of_image as usize];
|
||||||
u.decrypt_size = u.info[6].wrapping_sub(u.info[3]).wrapping_add(8192);
|
|
||||||
if verbose {
|
if verbose {
|
||||||
println!("[2/9] Decrypting payload...");
|
println!("[2/9] Decrypting payload...");
|
||||||
}
|
}
|
||||||
u.decrypt_data2();
|
let pe32_table = u.load_payload();
|
||||||
|
|
||||||
{
|
|
||||||
let src_start = u.info[4].wrapping_add(4096).wrapping_add(u.decrypt_size) as usize;
|
|
||||||
let dst_start = u.info[3].wrapping_add(u.decrypt_size) as usize;
|
|
||||||
let len = u.info[5].wrapping_sub(u.decrypt_size) as usize;
|
|
||||||
u.decompressed[dst_start..dst_start + len]
|
|
||||||
.copy_from_slice(&u.file_data[src_start..src_start + len]);
|
|
||||||
}
|
|
||||||
write_u32(&mut u.decompressed, u.info[3], 4096);
|
|
||||||
u.decompressed[..4096].copy_from_slice(&u.file_data[..4096]);
|
|
||||||
|
|
||||||
// PE32 (32-bit) images use an entirely different config layout and
|
// PE32 (32-bit) images use an entirely different config layout and
|
||||||
// final-output transform than PE32+ (64-bit). Dispatch here, after the
|
// final-output transform than PE32+ (64-bit). Dispatch here, after the
|
||||||
// shared header/payload setup (the PE32 branch follows the common
|
// shared header/payload setup (the PE32 branch follows the common
|
||||||
// Stage 1/2 work). pe_magic at pe_off+24: 0x10B = PE32, 0x20B = PE32+.
|
// Stage 1/2 work). pe_magic at pe_off+24: 0x10B = PE32, 0x20B = PE32+.
|
||||||
if get_u16(u.file_data, pe_off.wrapping_add(24)) == 0x10B {
|
if get_u16(u.file_data, pe_off.wrapping_add(24)) == 0x10B {
|
||||||
return u.run_pe32(pe_off, verbose);
|
let table = pe32_table.ok_or(UnpackError::Pe32TblNotFound)?;
|
||||||
|
return u.run_pe32(pe_off, table, verbose);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The config block layout in the decrypted region varies between Crackproof
|
// The config block layout in the decrypted region varies between Crackproof
|
||||||
@@ -1992,6 +2021,146 @@ impl<'a> Unpacker<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod payload_tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn check_payload(full: bool, valid_table: bool) {
|
||||||
|
let info = [
|
||||||
|
0x1234_5678,
|
||||||
|
0x4E4E_4F4B,
|
||||||
|
0,
|
||||||
|
0x2000,
|
||||||
|
0x200,
|
||||||
|
0x7000,
|
||||||
|
0x2400,
|
||||||
|
0,
|
||||||
|
];
|
||||||
|
let mut plain = vec![0u8; 0x10000];
|
||||||
|
let table = 0x2800;
|
||||||
|
let (shell_field, header_field) = if full { (0x78, 0x50) } else { (0x88, 0x58) };
|
||||||
|
write_u32(&mut plain, table + shell_field, info[6]);
|
||||||
|
write_u32(&mut plain, table + shell_field + 4, 0x1800);
|
||||||
|
write_u32(&mut plain, table + header_field, 0x80);
|
||||||
|
write_u32(
|
||||||
|
&mut plain,
|
||||||
|
table + header_field + 4,
|
||||||
|
if valid_table { 0x58 } else { 0 },
|
||||||
|
);
|
||||||
|
let ss_size = if full { 0xAEC } else { 0xBC0 };
|
||||||
|
write_u32(&mut plain, table + shell_field + 0x10, 0x6000);
|
||||||
|
write_u32(&mut plain, table + shell_field + 0x14, ss_size);
|
||||||
|
write_u32(&mut plain, table + shell_field + 0x20, info[6]);
|
||||||
|
write_u32(&mut plain, table + shell_field + 0x24, 0x1800);
|
||||||
|
write_u32(&mut plain, table + 0x40, 0x9ABC_DEF0);
|
||||||
|
let third_pair = 0x6000 + if full { 0xAB4 } else { 0xB8C };
|
||||||
|
write_u32(&mut plain, third_pair, 0x8000);
|
||||||
|
write_u32(&mut plain, third_pair + 4, 0x20);
|
||||||
|
if full {
|
||||||
|
// This earlier table has plausible descriptors but a wrong stage key.
|
||||||
|
for (field, value) in [
|
||||||
|
(0x40, 0x1122_3344),
|
||||||
|
(0x50, 0x80),
|
||||||
|
(0x54, 0x58),
|
||||||
|
(0x78, info[6]),
|
||||||
|
(0x7C, 0x1800),
|
||||||
|
(0x88, 0x7000),
|
||||||
|
(0x8C, 0xAEC),
|
||||||
|
(0x98, info[6]),
|
||||||
|
(0x9C, 0x1800),
|
||||||
|
] {
|
||||||
|
write_u32(&mut plain, 0x2500 + field, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
write_u32(&mut plain, 0x8100, 0xAABB_CCDD);
|
||||||
|
let encrypted_size = if full {
|
||||||
|
info[5]
|
||||||
|
} else {
|
||||||
|
info[6] - info[3] + 8192
|
||||||
|
};
|
||||||
|
let src = 4096 + info[4];
|
||||||
|
let mut input = vec![0u8; (src + info[5]) as usize];
|
||||||
|
write_u32(&mut input, 0x3C, 0x80);
|
||||||
|
write_u16(&mut input, 0x98, 0x10B);
|
||||||
|
plain[..4096].copy_from_slice(&input[..4096]);
|
||||||
|
let mut key = primitives::calculate_checksum(&plain, table + header_field)
|
||||||
|
^ primitives::calculate_checksum(&plain, table + shell_field + 0x20)
|
||||||
|
^ get_u32(&plain, table + 0x40);
|
||||||
|
for i in 0..ss_size / 4 {
|
||||||
|
let word = get_u32(&plain, 0x6000 + i * 4);
|
||||||
|
write_u32(
|
||||||
|
&mut plain,
|
||||||
|
0x6000 + i * 4,
|
||||||
|
word.wrapping_add(i).rotate_left(21) ^ key,
|
||||||
|
);
|
||||||
|
key = key.wrapping_add(i);
|
||||||
|
}
|
||||||
|
let mut key = info[0].wrapping_add(!encrypted_size);
|
||||||
|
for i in 0..info[5] / 4 {
|
||||||
|
let word = get_u32(&plain, info[3] + i * 4);
|
||||||
|
let cell = if i * 4 < encrypted_size {
|
||||||
|
word ^ key
|
||||||
|
} else {
|
||||||
|
word
|
||||||
|
};
|
||||||
|
write_u32(&mut input, src + i * 4, cell);
|
||||||
|
key = i.wrapping_mul(i) ^ key.wrapping_add(cell).wrapping_add(i);
|
||||||
|
}
|
||||||
|
let mut unpacker = Unpacker {
|
||||||
|
file_data: &input,
|
||||||
|
decompressed: vec![0u8; plain.len()],
|
||||||
|
info,
|
||||||
|
key_offsets: [0; 4],
|
||||||
|
decrypt_size: 0,
|
||||||
|
};
|
||||||
|
let selected = unpacker.load_payload();
|
||||||
|
assert_eq!(selected, valid_table.then_some(table));
|
||||||
|
if !valid_table {
|
||||||
|
let prefix_size = info[6] - info[3] + 8192;
|
||||||
|
assert_eq!(unpacker.decrypt_size, prefix_size);
|
||||||
|
assert_eq!(
|
||||||
|
get_u32(&unpacker.decompressed, info[3] + prefix_size - 4),
|
||||||
|
{
|
||||||
|
let mut key = info[0].wrapping_add(!prefix_size);
|
||||||
|
let mut word = 0;
|
||||||
|
for i in 0..prefix_size / 4 {
|
||||||
|
let cell = get_u32(&input, src + i * 4);
|
||||||
|
word = key ^ cell;
|
||||||
|
key = i.wrapping_mul(i) ^ key.wrapping_add(cell).wrapping_add(i);
|
||||||
|
}
|
||||||
|
word
|
||||||
|
}
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
&unpacker.decompressed
|
||||||
|
[(info[3] + prefix_size) as usize..(info[3] + info[5]) as usize],
|
||||||
|
&input[(src + prefix_size) as usize..(src + info[5]) as usize]
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
assert_eq!(unpacker.decrypt_size, encrypted_size);
|
||||||
|
assert_eq!(
|
||||||
|
&unpacker.decompressed[info[3] as usize + 4..(info[3] + info[5]) as usize],
|
||||||
|
&plain[info[3] as usize + 4..(info[3] + info[5]) as usize]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pe32_full_payload_encryption_is_trial_validated() {
|
||||||
|
check_payload(true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pe32_prefix_payload_encryption_preserves_plaintext_tail() {
|
||||||
|
check_payload(false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pe32_invalid_full_payload_trial_restores_prefix() {
|
||||||
|
check_payload(true, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod error_tests {
|
mod error_tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
@@ -23,10 +23,153 @@ fn stage_key_rounds(data: &[u8], table: u32, slots: usize) -> Result<u32, Unpack
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Unpacker<'a> {
|
impl<'a> Unpacker<'a> {
|
||||||
|
pub(super) fn find_pe32_table(&mut self) -> Option<u32> {
|
||||||
|
let pe_off = get_u32(self.file_data, 60);
|
||||||
|
let header = self.decompressed[..4096].to_vec();
|
||||||
|
for tbl in layout::find_tbl_pe32_candidates(&self.decompressed, &self.info) {
|
||||||
|
let compact = get_u32(&self.decompressed, tbl + 0x78) == self.info[6];
|
||||||
|
let pair = tbl + if compact { 0x88 } else { 0x98 };
|
||||||
|
let start = get_u32(&self.decompressed, pair) as usize;
|
||||||
|
let size = get_u32(&self.decompressed, pair + 4) as usize;
|
||||||
|
let Some(end) = start
|
||||||
|
.checked_add(size)
|
||||||
|
.filter(|&end| end <= self.decompressed.len())
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let ciphertext = self.decompressed[start..end].to_vec();
|
||||||
|
let valid = self.decrypt_pe32_second_stage(tbl, pe_off).is_ok();
|
||||||
|
self.decompressed[start..end].copy_from_slice(&ciphertext);
|
||||||
|
self.decompressed[..4096].copy_from_slice(&header);
|
||||||
|
if valid {
|
||||||
|
return Some(tbl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decrypt_pe32_second_stage(&mut self, tbl: u32, pe_off: u32) -> Result<u32, UnpackError> {
|
||||||
|
let compact = get_u32(&self.decompressed, tbl.wrapping_add(0x78)) == self.info[6];
|
||||||
|
let header_cs_field = if compact { 0x50 } else { 0x58 };
|
||||||
|
let stage_field = if compact { 0x88 } else { 0x98 };
|
||||||
|
let resource_field = if compact { 0xB0 } else { 0xC8 };
|
||||||
|
if (pe_off as usize)
|
||||||
|
.checked_add(0xB8)
|
||||||
|
.is_none_or(|end| end > 0x1000)
|
||||||
|
{
|
||||||
|
return Err(UnpackError::Pe32RelocationDataNotFound);
|
||||||
|
}
|
||||||
|
let import_rva = get_u32(&self.decompressed, tbl + stage_field + 0x24);
|
||||||
|
let resource_rva = get_u32(&self.decompressed, tbl + resource_field);
|
||||||
|
let resource_size = get_u32(&self.decompressed, tbl + resource_field + 4);
|
||||||
|
write_u32(&mut self.decompressed, pe_off + 0x80, import_rva);
|
||||||
|
write_u32(&mut self.decompressed, pe_off + 0x88, resource_rva);
|
||||||
|
write_u32(&mut self.decompressed, pe_off + 0x8C, resource_size);
|
||||||
|
write_u32(&mut self.decompressed, pe_off + 0xB0, 0);
|
||||||
|
write_u32(&mut self.decompressed, pe_off + 0xB4, 0);
|
||||||
|
|
||||||
|
let cs = tbl + stage_field + 0x10;
|
||||||
|
let cs_start = get_u32(&self.decompressed, cs) as usize;
|
||||||
|
let cs_size = get_u32(&self.decompressed, cs + 4) as usize;
|
||||||
|
if cs_size == 0
|
||||||
|
|| cs_start
|
||||||
|
.checked_add(cs_size)
|
||||||
|
.is_none_or(|end| end > self.decompressed.len())
|
||||||
|
{
|
||||||
|
return Err(UnpackError::Pe32RelocationDataNotFound);
|
||||||
|
}
|
||||||
|
let first_stage_cs = self.calculate_checksum(cs);
|
||||||
|
let second_stage_key = get_u32(&self.decompressed, tbl + 0x40);
|
||||||
|
let ss_pair = tbl + stage_field;
|
||||||
|
let ss = get_u32(&self.decompressed, ss_pair);
|
||||||
|
let ss_size = get_u32(&self.decompressed, ss_pair + 4);
|
||||||
|
let ss_shift = ss_size.wrapping_sub(if compact { 0xAEC } else { 0xBC0 });
|
||||||
|
let third_pair_off = (if compact { 0xAB4u32 } else { 0xB8C }).wrapping_add(ss_shift);
|
||||||
|
let ss_lo = ss as usize;
|
||||||
|
let ss_hi = ss_lo.saturating_add(ss_size as usize);
|
||||||
|
if ss_hi > self.decompressed.len() || third_pair_off.saturating_add(8) > ss_size {
|
||||||
|
return Err(UnpackError::Pe32SecondStageRangeInvalid {
|
||||||
|
offset: ss,
|
||||||
|
size: ss_size,
|
||||||
|
image_len: self.decompressed.len(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let ss_ct = self.decompressed[ss_lo..ss_hi].to_vec();
|
||||||
|
// Header checksums refer to the original PE header. Compact EXEs and
|
||||||
|
// native DLLs can carry a packer-added BaseReloc entry: trial both keys.
|
||||||
|
for zero_reloc in [false, true] {
|
||||||
|
if zero_reloc {
|
||||||
|
write_u32(&mut self.decompressed, pe_off + 0xA0, 0);
|
||||||
|
write_u32(&mut self.decompressed, pe_off + 0xA4, 0);
|
||||||
|
}
|
||||||
|
let mut hcs_addr = tbl + header_cs_field;
|
||||||
|
let mut header_checksum = 0;
|
||||||
|
loop {
|
||||||
|
if hcs_addr + 8 > tbl + stage_field - 0x10 {
|
||||||
|
return Err(UnpackError::Pe32RelocationDataNotFound);
|
||||||
|
}
|
||||||
|
let size = get_u32(&self.decompressed, hcs_addr + 4) as usize;
|
||||||
|
if size == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let start = get_u32(&self.decompressed, hcs_addr) as usize;
|
||||||
|
if start.checked_add(size).is_none_or(|end| end > 0x1000) {
|
||||||
|
return Err(UnpackError::Pe32RelocationDataNotFound);
|
||||||
|
}
|
||||||
|
header_checksum ^= self.calculate_checksum(hcs_addr);
|
||||||
|
hcs_addr += 8;
|
||||||
|
}
|
||||||
|
self.decompressed[ss_lo..ss_hi].copy_from_slice(&ss_ct);
|
||||||
|
self.decrypt_data3(
|
||||||
|
ss_pair,
|
||||||
|
header_checksum ^ first_stage_cs ^ second_stage_key,
|
||||||
|
21,
|
||||||
|
);
|
||||||
|
let pair = ss + third_pair_off;
|
||||||
|
let off = get_u32(&self.decompressed, pair) as usize;
|
||||||
|
let size = get_u32(&self.decompressed, pair + 4) as usize;
|
||||||
|
if off > 0x1000
|
||||||
|
&& size >= 4
|
||||||
|
&& off
|
||||||
|
.checked_add(size)
|
||||||
|
.is_some_and(|end| end <= self.decompressed.len())
|
||||||
|
{
|
||||||
|
return Ok(header_checksum);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(UnpackError::Pe32RelocationDataNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_pe32_metadata(&mut self, compact: bool) -> (u32, [u8; 128]) {
|
||||||
|
let base = self.info[3];
|
||||||
|
let layout_b = get_u32(&self.decompressed, base.wrapping_add(0x10)) > 0x10000;
|
||||||
|
let (offset, size, ep_offset, dirs_offset) = if layout_b {
|
||||||
|
// Compact metadata has an extra 16-byte prefix before the EP/dirs.
|
||||||
|
let shift = if compact { 0x10 } else { 0 };
|
||||||
|
(0x10, 0x290, 0x20 + shift, 0x30 + shift)
|
||||||
|
} else {
|
||||||
|
(0x40, 144, 0x40, 0x50)
|
||||||
|
};
|
||||||
|
let start = base.wrapping_add(offset) as usize;
|
||||||
|
let backup = self.decompressed[start..start + size].to_vec();
|
||||||
|
self.decrypt_data5(base.wrapping_add(offset), size as u32);
|
||||||
|
let ep = get_u32(&self.decompressed, base.wrapping_add(ep_offset));
|
||||||
|
let mut dirs = [0u8; 128];
|
||||||
|
let dirs_start = base.wrapping_add(dirs_offset) as usize;
|
||||||
|
dirs.copy_from_slice(&self.decompressed[dirs_start..dirs_start + 128]);
|
||||||
|
self.decompressed[start..start + size].copy_from_slice(&backup);
|
||||||
|
(ep, dirs)
|
||||||
|
}
|
||||||
|
|
||||||
/// PE32 (32-bit) unpack pipeline. The shared Stage 1/2 setup (info decrypt,
|
/// PE32 (32-bit) unpack pipeline. The shared Stage 1/2 setup (info decrypt,
|
||||||
/// payload decrypt, raw copy, header restore) has already run in `run()`
|
/// payload decrypt, raw copy, header restore) has already run in `run()`
|
||||||
/// before dispatch; this takes over from "Locating shell offsets".
|
/// before dispatch; this takes over from "Locating shell offsets".
|
||||||
pub(super) fn run_pe32(&mut self, pe_off: u32, verbose: bool) -> Result<Vec<u8>, UnpackError> {
|
pub(super) fn run_pe32(
|
||||||
|
&mut self,
|
||||||
|
pe_off: u32,
|
||||||
|
tbl: u32,
|
||||||
|
verbose: bool,
|
||||||
|
) -> Result<Vec<u8>, UnpackError> {
|
||||||
let info = self.info;
|
let info = self.info;
|
||||||
let info3 = info[3];
|
let info3 = info[3];
|
||||||
|
|
||||||
@@ -43,94 +186,21 @@ impl<'a> Unpacker<'a> {
|
|||||||
key
|
key
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- Locate tbl in shell ----
|
// ---- Validated shell table ----
|
||||||
let tbl =
|
let compact = get_u32(&self.decompressed, tbl.wrapping_add(0x78)) == info[6];
|
||||||
layout::find_tbl_pe32(&self.decompressed, &info).ok_or(UnpackError::Pe32TblNotFound)?;
|
let stage_field = if compact { 0x88 } else { 0x98 };
|
||||||
if verbose {
|
if verbose {
|
||||||
println!("[3/9] Locating config layout (PE32)...");
|
println!("[3/9] Locating config layout (PE32)...");
|
||||||
println!(" tbl = 0x{:X}", tbl);
|
println!(" tbl = 0x{:X}, compact = {}", tbl, compact);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- PE header restore ----
|
|
||||||
let val_bc = get_u32(&self.decompressed, tbl.wrapping_add(0xBC));
|
|
||||||
let val_c8 = get_u32(&self.decompressed, tbl.wrapping_add(0xC8));
|
|
||||||
let val_cc = get_u32(&self.decompressed, tbl.wrapping_add(0xCC));
|
|
||||||
write_u32(&mut self.decompressed, pe_off.wrapping_add(0x80), val_bc);
|
|
||||||
write_u32(&mut self.decompressed, pe_off.wrapping_add(0x88), val_c8);
|
|
||||||
write_u32(&mut self.decompressed, pe_off.wrapping_add(0x8C), val_cc);
|
|
||||||
write_u32(&mut self.decompressed, pe_off.wrapping_add(0xB0), 0);
|
|
||||||
write_u32(&mut self.decompressed, pe_off.wrapping_add(0xB4), 0);
|
|
||||||
|
|
||||||
// ---- Header-independent checksum inputs ----
|
|
||||||
let first_stage_cs = self.calculate_checksum(tbl.wrapping_add(0xA8));
|
|
||||||
let second_stage_key = get_u32(&self.decompressed, tbl.wrapping_add(0x40));
|
|
||||||
|
|
||||||
// ---- Stage 3: SecondStage ----
|
// ---- Stage 3: SecondStage ----
|
||||||
//
|
let ss_pair = tbl.wrapping_add(stage_field);
|
||||||
// ss_key = headerChecksum ^ firstStageCS ^ secondStageKey, where the
|
|
||||||
// header checksum (a XOR of crc32(region)^size over the sub-regions at
|
|
||||||
// tbl+0x58) is taken over the *original* pre-pack PE header. For EXEs the
|
|
||||||
// import/resource restore above reconstructs that header exactly. Native
|
|
||||||
// DLLs additionally carry a packer-added BaseReloc data-directory entry
|
|
||||||
// (dir 5) that was absent from the checksummed original, so the header
|
|
||||||
// checksum only matches once that entry is treated as zero. EXEs have no
|
|
||||||
// dir-5 entry, so zeroing it is a no-op for them.
|
|
||||||
//
|
|
||||||
// Rather than branch on EXE-vs-DLL, try the header as-is and, on failure,
|
|
||||||
// with the BaseReloc entry zeroed; keep whichever ss_key decrypts a
|
|
||||||
// SecondStage whose ThirdStage (off,size) pair lands inside the image.
|
|
||||||
// This uses the same shift/key trial-and-validate the later stages
|
|
||||||
// already use, and keeps EXE output byte-identical (the as-is variant
|
|
||||||
// wins first).
|
|
||||||
let ss_pair = tbl.wrapping_add(0x98);
|
|
||||||
let ss = get_u32(&self.decompressed, ss_pair);
|
let ss = get_u32(&self.decompressed, ss_pair);
|
||||||
let ss_size = get_u32(&self.decompressed, ss_pair.wrapping_add(4));
|
let ss_size = get_u32(&self.decompressed, ss_pair.wrapping_add(4));
|
||||||
let ss_shift = ss_size.wrapping_sub(0xBC0);
|
let ss_shift = ss_size.wrapping_sub(if compact { 0xAEC } else { 0xBC0 });
|
||||||
// Back up the SecondStage ciphertext so a failed trial can be retried.
|
let third_pair_off = (if compact { 0xAB4u32 } else { 0xB8C }).wrapping_add(ss_shift);
|
||||||
let ss_lo = ss as usize;
|
let header_checksum = self.decrypt_pe32_second_stage(tbl, pe_off)?;
|
||||||
let ss_hi = ss_lo.wrapping_add(ss_size as usize);
|
|
||||||
if ss_hi < ss_lo || ss_hi > self.decompressed.len() {
|
|
||||||
return Err(UnpackError::Pe32SecondStageRangeInvalid {
|
|
||||||
offset: ss,
|
|
||||||
size: ss_size,
|
|
||||||
image_len: self.decompressed.len(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
let ss_ct: Vec<u8> = self.decompressed[ss_lo..ss_hi].to_vec();
|
|
||||||
// PE32 data dir 5 (BaseReloc) = optional_header(pe+24) + 0x60 + 5*8 = pe+0xA0.
|
|
||||||
let reloc_dir = pe_off.wrapping_add(0xA0);
|
|
||||||
let len = self.decompressed.len() as u64;
|
|
||||||
let pair_off = 0xB8Cu32.wrapping_add(ss_shift);
|
|
||||||
let mut found = false;
|
|
||||||
// Holds the winning variant's header checksum; the later stages
|
|
||||||
// (Forth/Fifth/Seven/Eighth) reuse it as a key component.
|
|
||||||
let mut header_checksum: u32 = 0;
|
|
||||||
for zero_reloc in [false, true] {
|
|
||||||
if zero_reloc {
|
|
||||||
write_u32(&mut self.decompressed, reloc_dir, 0);
|
|
||||||
write_u32(&mut self.decompressed, reloc_dir.wrapping_add(4), 0);
|
|
||||||
}
|
|
||||||
let mut hcs_addr = tbl.wrapping_add(0x58);
|
|
||||||
header_checksum = 0;
|
|
||||||
while get_u32(&self.decompressed, hcs_addr.wrapping_add(4)) != 0 {
|
|
||||||
header_checksum ^= self.calculate_checksum(hcs_addr);
|
|
||||||
hcs_addr = hcs_addr.wrapping_add(8);
|
|
||||||
}
|
|
||||||
let ss_key = header_checksum ^ first_stage_cs ^ second_stage_key;
|
|
||||||
self.decompressed[ss_lo..ss_hi].copy_from_slice(&ss_ct);
|
|
||||||
self.decrypt_data3(ss_pair, ss_key, 21);
|
|
||||||
// Validate: the ThirdStage (off,size) pair must reference the image.
|
|
||||||
let pair = ss.wrapping_add(pair_off);
|
|
||||||
let off = get_u32(&self.decompressed, pair) as u64;
|
|
||||||
let sz = get_u32(&self.decompressed, pair.wrapping_add(4)) as u64;
|
|
||||||
if off > 0x1000 && off < len && sz >= 4 && off.saturating_add(sz) <= len {
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
return Err(UnpackError::Pe32RelocationDataNotFound);
|
|
||||||
}
|
|
||||||
if verbose {
|
if verbose {
|
||||||
println!(
|
println!(
|
||||||
" ss = 0x{:08X}, size = 0x{:X}, shift = 0x{:X}",
|
" ss = 0x{:08X}, size = 0x{:X}, shift = 0x{:X}",
|
||||||
@@ -139,13 +209,12 @@ impl<'a> Unpacker<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ---- PE32 fixed offsets ----
|
// ---- PE32 fixed offsets ----
|
||||||
let third_key_off = 0x968u32.wrapping_add(ss_shift);
|
let third_key_off = (if compact { 0x950u32 } else { 0x968 }).wrapping_add(ss_shift);
|
||||||
let forth_key_off = 0x964u32.wrapping_add(ss_shift);
|
let forth_key_off = (if compact { 0x94Cu32 } else { 0x964 }).wrapping_add(ss_shift);
|
||||||
let cs_base_off = 0x96Cu32.wrapping_add(ss_shift);
|
let cs_base_off = (if compact { 0x954u32 } else { 0x96C }).wrapping_add(ss_shift);
|
||||||
let dp_base_off = 0xA9Cu32.wrapping_add(ss_shift);
|
let dp_base_off = (if compact { 0x9C4u32 } else { 0xA9C }).wrapping_add(ss_shift);
|
||||||
|
|
||||||
// ---- Stage 4: ThirdStage (brute-force the rotate shift) ----
|
// ---- Stage 4: ThirdStage (brute-force the rotate shift) ----
|
||||||
let third_pair_off = 0xB8Cu32.wrapping_add(ss_shift);
|
|
||||||
let key = get_u32(&self.decompressed, ss.wrapping_add(third_key_off));
|
let key = get_u32(&self.decompressed, ss.wrapping_add(third_key_off));
|
||||||
let pair_addr = ss.wrapping_add(third_pair_off);
|
let pair_addr = ss.wrapping_add(third_pair_off);
|
||||||
let ts_addr = get_u32(&self.decompressed, pair_addr);
|
let ts_addr = get_u32(&self.decompressed, pair_addr);
|
||||||
@@ -238,7 +307,7 @@ impl<'a> Unpacker<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ---- Checksum addresses ----
|
// ---- Checksum addresses ----
|
||||||
let second_stage_cs_addr = tbl.wrapping_add(0xB0);
|
let second_stage_cs_addr = tbl.wrapping_add(stage_field + 0x18);
|
||||||
let forth_stage_cs_addr = ss.wrapping_add(cs_base_off);
|
let forth_stage_cs_addr = ss.wrapping_add(cs_base_off);
|
||||||
let fifth_stage_cs_addr = ss.wrapping_add(cs_base_off).wrapping_add(0x08);
|
let fifth_stage_cs_addr = ss.wrapping_add(cs_base_off).wrapping_add(0x08);
|
||||||
let seven_stage_cs_addr = ss.wrapping_add(cs_base_off).wrapping_add(0x10);
|
let seven_stage_cs_addr = ss.wrapping_add(cs_base_off).wrapping_add(0x10);
|
||||||
@@ -298,7 +367,9 @@ impl<'a> Unpacker<'a> {
|
|||||||
);
|
);
|
||||||
let seven_key = !get_u32(
|
let seven_key = !get_u32(
|
||||||
&self.decompressed,
|
&self.decompressed,
|
||||||
cs1_addr.wrapping_add(cs1_size).wrapping_sub(0x10),
|
cs1_addr
|
||||||
|
.wrapping_add(cs1_size)
|
||||||
|
.wrapping_sub(if compact { 0xC } else { 0x10 }),
|
||||||
);
|
);
|
||||||
let fk7 = header_checksum ^ fifth_cs ^ seven_key;
|
let fk7 = header_checksum ^ fifth_cs ^ seven_key;
|
||||||
if let Err(reason) = self.decrypt_and_decompress_data(seven_addr, fk7, None) {
|
if let Err(reason) = self.decrypt_and_decompress_data(seven_addr, fk7, None) {
|
||||||
@@ -640,28 +711,7 @@ impl<'a> Unpacker<'a> {
|
|||||||
)?;
|
)?;
|
||||||
|
|
||||||
// ---- PE32 metadata: EP and data dirs from info[3] ----
|
// ---- PE32 metadata: EP and data dirs from info[3] ----
|
||||||
let test_val = get_u32(&self.decompressed, info3.wrapping_add(0x10));
|
let (metadata_ep, metadata_dirs) = self.read_pe32_metadata(compact);
|
||||||
let metadata_ep: u32;
|
|
||||||
let mut metadata_dirs = [0u8; 128];
|
|
||||||
if test_val > 0x10000 {
|
|
||||||
// Layout B
|
|
||||||
let s = info3.wrapping_add(0x10) as usize;
|
|
||||||
let backup_meta = self.decompressed[s..s + 0x290].to_vec();
|
|
||||||
self.decrypt_data5(info3.wrapping_add(0x10), 0x290);
|
|
||||||
metadata_ep = get_u32(&self.decompressed, info3.wrapping_add(0x20));
|
|
||||||
let d = info3.wrapping_add(0x30) as usize;
|
|
||||||
metadata_dirs.copy_from_slice(&self.decompressed[d..d + 128]);
|
|
||||||
self.decompressed[s..s + 0x290].copy_from_slice(&backup_meta);
|
|
||||||
} else {
|
|
||||||
// Layout A
|
|
||||||
let s = info3.wrapping_add(0x40) as usize;
|
|
||||||
let backup_meta = self.decompressed[s..s + 144].to_vec();
|
|
||||||
self.decrypt_data5(info3.wrapping_add(0x40), 144);
|
|
||||||
metadata_ep = get_u32(&self.decompressed, info3.wrapping_add(0x40));
|
|
||||||
let d = info3.wrapping_add(0x50) as usize;
|
|
||||||
metadata_dirs.copy_from_slice(&self.decompressed[d..d + 128]);
|
|
||||||
self.decompressed[s..s + 144].copy_from_slice(&backup_meta);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- Zero-out list (runs BEFORE decompression) ----
|
// ---- Zero-out list (runs BEFORE decompression) ----
|
||||||
let zero_list_addr = eighth_start.wrapping_add(off_zero_list);
|
let zero_list_addr = eighth_start.wrapping_add(off_zero_list);
|
||||||
@@ -1157,6 +1207,56 @@ impl<'a> Unpacker<'a> {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
fn check_metadata(compact: bool, layout_b: bool) {
|
||||||
|
let base = 0x1000u32;
|
||||||
|
let (offset, size, ep_offset, dirs_offset) = if layout_b {
|
||||||
|
if compact {
|
||||||
|
(0x10, 0x290, 0x30, 0x40)
|
||||||
|
} else {
|
||||||
|
(0x10, 0x290, 0x20, 0x30)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
(0x40, 144, 0x40, 0x50)
|
||||||
|
};
|
||||||
|
let mut data = vec![0u8; 0x2000];
|
||||||
|
let ep = 0x123456;
|
||||||
|
let dirs = std::array::from_fn::<_, 128, _>(|i| i as u8 ^ 0x5A);
|
||||||
|
write_u32(&mut data, base + ep_offset, ep);
|
||||||
|
data[(base + dirs_offset) as usize..(base + dirs_offset + 128) as usize]
|
||||||
|
.copy_from_slice(&dirs);
|
||||||
|
for i in 0..size {
|
||||||
|
let key = (base + offset + i) as u8;
|
||||||
|
let byte = &mut data[(base + offset + i) as usize];
|
||||||
|
let mixed = (byte.rotate_right(2) ^ key).rotate_right(2) ^ key.wrapping_add(1);
|
||||||
|
*byte = mixed.rotate_right(2);
|
||||||
|
}
|
||||||
|
assert_eq!(get_u32(&data, base + 0x10) > 0x10000, layout_b);
|
||||||
|
let backup = data.clone();
|
||||||
|
let mut unpacker = Unpacker {
|
||||||
|
file_data: &[],
|
||||||
|
decompressed: data,
|
||||||
|
info: [0, 0, 0, base, 0, 0, 0, 0],
|
||||||
|
key_offsets: [0; 4],
|
||||||
|
decrypt_size: 0,
|
||||||
|
};
|
||||||
|
assert_eq!(unpacker.read_pe32_metadata(compact), (ep, dirs));
|
||||||
|
assert_eq!(
|
||||||
|
unpacker.decompressed, backup,
|
||||||
|
"metadata reads must be nondestructive"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pe32_compact_metadata_restores_entry_point_and_directories() {
|
||||||
|
check_metadata(true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pe32_standard_metadata_preserves_both_layouts() {
|
||||||
|
check_metadata(false, false);
|
||||||
|
check_metadata(false, true);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn stage_key_rounds_follow_active_descriptor_prefix() {
|
fn stage_key_rounds_follow_active_descriptor_prefix() {
|
||||||
for (sizes, expected) in [
|
for (sizes, expected) in [
|
||||||
|
|||||||
@@ -6,8 +6,9 @@ mod image;
|
|||||||
|
|
||||||
pub(super) use dd8::{select_dd8_formula_pe32, select_dd8_shift};
|
pub(super) use dd8::{select_dd8_formula_pe32, select_dd8_shift};
|
||||||
pub(super) use discovery::{
|
pub(super) use discovery::{
|
||||||
discover_eighth_slots, find_bytecode_offset, find_lfsr_block, find_str_pos, find_tbl_pe32,
|
discover_eighth_slots, find_bytecode_offset, find_lfsr_block, find_str_pos,
|
||||||
find_v_after_pad, find_v4_offset, get_string_to_null, section_name, trial_decrypt5_u32,
|
find_tbl_pe32_candidates, find_v_after_pad, find_v4_offset, get_string_to_null, section_name,
|
||||||
|
trial_decrypt5_u32,
|
||||||
};
|
};
|
||||||
pub(super) use image::{
|
pub(super) use image::{
|
||||||
compact_memory_image_to_pe, move_pe32_imports_to_kmiat, pe32_imports_already_match_idata_layout,
|
compact_memory_image_to_pe, move_pe32_imports_to_kmiat, pe32_imports_already_match_idata_layout,
|
||||||
|
|||||||
@@ -212,37 +212,54 @@ pub fn section_name(data: &[u8], offset: u32) -> String {
|
|||||||
// PE32 (32-bit) helpers
|
// PE32 (32-bit) helpers
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
/// PE32 shell-table locator. Walks the shell region (`info[6]`) for a dword
|
/// PE32 shell-table locator. The compact layout moves the shell descriptor
|
||||||
/// equal to `info[6]` followed by a plausible shell size, returning the table
|
/// from +0x88 to +0x78 and the header-checksum list from +0x58 to +0x50.
|
||||||
/// base (`candidate = off - 0x88`) when `candidate+0x58` holds a valid pointer.
|
/// Return structurally plausible candidates; the pipeline must trial-decrypt
|
||||||
pub fn find_tbl_pe32(data: &[u8], info: &[u32; 8]) -> Option<u32> {
|
/// their second stages before selecting a table.
|
||||||
let shell = info[6];
|
pub fn find_tbl_pe32_candidates(data: &[u8], info: &[u32; 8]) -> Vec<u32> {
|
||||||
if (data.len() as u64) < 0x100 {
|
let mut tables = Vec::new();
|
||||||
return None;
|
let shell = info[6] as usize;
|
||||||
|
let hi = shell.saturating_add(0x3000).min(data.len());
|
||||||
|
for off in (shell..hi.saturating_sub(8)).step_by(4) {
|
||||||
|
if get_u32(data, off as u32) != info[6] {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
let hi = (shell as u64)
|
let shell_size = get_u32(data, off as u32 + 4) as usize;
|
||||||
.saturating_add(0x3000)
|
if !(0x1001..0x100000).contains(&shell_size)
|
||||||
.min(data.len() as u64 - 0x100) as u32;
|
|| shell
|
||||||
let mut off = shell;
|
.checked_add(shell_size)
|
||||||
while off < hi {
|
.is_none_or(|end| end > data.len())
|
||||||
if off as usize + 8 <= data.len() {
|
{
|
||||||
let candidate = off.wrapping_sub(0x88);
|
continue;
|
||||||
if candidate >= shell && get_u32(data, off) == info[6] {
|
}
|
||||||
let shell_size_val = get_u32(data, off.wrapping_add(4));
|
for (shell_field, header_field) in [(0x88, 0x58), (0x78, 0x50)] {
|
||||||
if shell_size_val > 0x1000 && shell_size_val < 0x100000 {
|
let Some(candidate) = off.checked_sub(shell_field).filter(|&base| base >= shell) else {
|
||||||
let v58_off = candidate.wrapping_add(0x58);
|
continue;
|
||||||
if (v58_off as usize + 4) <= data.len() {
|
};
|
||||||
let v58 = get_u32(data, v58_off);
|
let Some(table) = data.get(candidate..candidate + 0xD0) else {
|
||||||
if v58 > 0 && (v58 as usize) < data.len() {
|
continue;
|
||||||
return Some(candidate);
|
};
|
||||||
|
let header = get_u32(table, header_field) as usize;
|
||||||
|
let header_size = get_u32(table, header_field + 4) as usize;
|
||||||
|
let stage = get_u32(table, shell_field as u32 + 0x10) as usize;
|
||||||
|
let stage_size = get_u32(table, shell_field as u32 + 0x14) as usize;
|
||||||
|
if header > 0
|
||||||
|
&& header_size > 0
|
||||||
|
&& header
|
||||||
|
.checked_add(header_size)
|
||||||
|
.is_some_and(|end| end <= 0x1000)
|
||||||
|
&& stage >= info[3] as usize
|
||||||
|
&& stage_size >= 4
|
||||||
|
&& stage
|
||||||
|
.checked_add(stage_size)
|
||||||
|
.is_some_and(|end| end <= data.len())
|
||||||
|
&& !tables.contains(&(candidate as u32))
|
||||||
|
{
|
||||||
|
tables.push(candidate as u32);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
tables
|
||||||
}
|
|
||||||
off = off.wrapping_add(4);
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Locate an LFSR-encrypted bytecode block (decrypt_data6 form) in a region.
|
/// Locate an LFSR-encrypted bytecode block (decrypt_data6 form) in a region.
|
||||||
@@ -449,6 +466,57 @@ pub fn discover_eighth_slots(
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
fn pe32_table_fixture(compact: bool) -> (Vec<u8>, [u32; 8], u32) {
|
||||||
|
let mut data = vec![0u8; 0x10000];
|
||||||
|
let info = [0, 0, 0, 0x2000, 0, 0x7000, 0x2400, 0];
|
||||||
|
let table = 0x2800;
|
||||||
|
let (shell, header) = if compact { (0x78, 0x50) } else { (0x88, 0x58) };
|
||||||
|
for (field, value) in [
|
||||||
|
(header, 0x80),
|
||||||
|
(header + 4, 0x58),
|
||||||
|
(shell, info[6]),
|
||||||
|
(shell + 4, 0x1800),
|
||||||
|
(shell + 0x10, 0x6000),
|
||||||
|
(shell + 0x14, 0xAEC),
|
||||||
|
] {
|
||||||
|
senbei_crypto::primitives::write_u32(&mut data, table + field, value);
|
||||||
|
}
|
||||||
|
(data, info, table)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pe32_table_supports_compact_header_descriptors() {
|
||||||
|
for compact in [false, true] {
|
||||||
|
let (data, info, table) = pe32_table_fixture(compact);
|
||||||
|
assert_eq!(find_tbl_pe32_candidates(&data, &info), vec![table]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pe32_table_rejects_a_shell_reference_without_valid_descriptors() {
|
||||||
|
let (mut data, info, table) = pe32_table_fixture(true);
|
||||||
|
for (field, value) in [(0x58, 0x80), (0x5C, 0x58), (0x88, info[6]), (0x8C, 0x1800)] {
|
||||||
|
senbei_crypto::primitives::write_u32(&mut data, 0x2500 + field, value);
|
||||||
|
}
|
||||||
|
assert_eq!(find_tbl_pe32_candidates(&data, &info), vec![table]);
|
||||||
|
senbei_crypto::primitives::write_u32(&mut data, table + 0x54, 0);
|
||||||
|
assert_eq!(find_tbl_pe32_candidates(&data, &info), Vec::<u32>::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pe32_table_rejects_truncated_and_out_of_range_regions() {
|
||||||
|
let (mut data, mut info, table) = pe32_table_fixture(true);
|
||||||
|
assert_eq!(
|
||||||
|
find_tbl_pe32_candidates(&data[..0x28C0], &info),
|
||||||
|
Vec::<u32>::new()
|
||||||
|
);
|
||||||
|
senbei_crypto::primitives::write_u32(&mut data, table + 0x50, u32::MAX - 15);
|
||||||
|
assert_eq!(find_tbl_pe32_candidates(&data, &info), Vec::<u32>::new());
|
||||||
|
info[6] = u32::MAX;
|
||||||
|
assert_eq!(find_tbl_pe32_candidates(&data, &info), Vec::<u32>::new());
|
||||||
|
assert_eq!(find_tbl_pe32_candidates(&[], &info), Vec::<u32>::new());
|
||||||
|
}
|
||||||
|
|
||||||
/// Task 4.1 regression: build a synthetic buffer whose valid bytecode block
|
/// Task 4.1 regression: build a synthetic buffer whose valid bytecode block
|
||||||
/// sits PAST `len` but within `len*2`. Assert that the smaller window misses
|
/// sits PAST `len` but within `len*2`. Assert that the smaller window misses
|
||||||
/// it and the doubled window finds it.
|
/// it and the doubled window finds it.
|
||||||
|
|||||||
Reference in new issue
Block a user