diff --git a/senbei-crypto/src/primitives.rs b/senbei-crypto/src/primitives.rs index 236f137..99b0774 100644 --- a/senbei-crypto/src/primitives.rs +++ b/senbei-crypto/src/primitives.rs @@ -382,11 +382,28 @@ pub fn calculate_checksum(d: &[u8], pos: u32) -> u32 { /// CRC32 chained checksum. The (offset, length) descriptor at `pos` is read /// from `d`; the bytes themselves are read from the separate `clean` buffer -/// (the original file image). `start` is the initial CRC accumulator. -pub fn calculate_checksum2(d: &[u8], clean: &[u8], pos: u32, start: u32) -> u32 { +/// (the original file image). `start` is the initial CRC accumulator. Returns +/// a range error instead of panicking when a descriptor points past `clean`. +pub fn calculate_checksum2( + d: &[u8], + clean: &[u8], + pos: u32, + start: u32, +) -> Result { let offset = get_u32(d, pos); let length = get_u32(d, pos.wrapping_add(4)); - crc32::append(start, &clean[offset as usize..(offset + length) as usize]) + let data_start = offset as usize; + let size = length as usize; + let end = data_start.checked_add(size); + let Some(end) = end.filter(|&end| end <= clean.len()) else { + return Err(crate::Error::BufferRangeOutOfBounds { + operation: crate::BufferOperation::Read, + offset: data_start, + size, + buffer_len: clean.len(), + }); + }; + Ok(crc32::append(start, &clean[data_start..end])) } // --------------------------------------------------------------------------- @@ -1095,4 +1112,22 @@ mod tests { assert!(decompress(&mut d, 0x40, 0x80, 0, 4, 3)); assert_eq!(&d[0x80..0x83], &[0x5A, 0x5A, 0x5A]); } + + #[test] + fn checksum2_rejects_source_range_outside_clean_image() { + let mut descriptor = [0u8; 8]; + descriptor[0..4].copy_from_slice(&448u32.to_le_bytes()); + descriptor[4..8].copy_from_slice(&634_432u32.to_le_bytes()); + let clean = vec![0u8; 590_896]; + let error = calculate_checksum2(&descriptor, &clean, 0, 0).expect_err("range must fail"); + assert!(matches!( + error, + crate::Error::BufferRangeOutOfBounds { + operation: crate::BufferOperation::Read, + offset: 448, + size: 634_432, + buffer_len: 590_896, + } + )); + } } diff --git a/senbei-pe/src/engine/error.rs b/senbei-pe/src/engine/error.rs index 3e7cfa6..11ef75c 100644 --- a/senbei-pe/src/engine/error.rs +++ b/senbei-pe/src/engine/error.rs @@ -149,6 +149,16 @@ pub enum UnpackError { buffer_len: usize, }, + #[error( + "EXE checksum descriptor at 0x{descriptor:08X} points outside input (offset {offset}, size {size}, input length {image_len})" + )] + ExeChecksumRangeOutOfBounds { + descriptor: u32, + offset: usize, + size: usize, + image_len: usize, + }, + #[error( "{table} descriptor out of bounds (offset {offset}, size 16, image length {image_len})" )] diff --git a/senbei-pe/src/engine/exe/pipeline.rs b/senbei-pe/src/engine/exe/pipeline.rs index ffcb61f..fbbf5d5 100644 --- a/senbei-pe/src/engine/exe/pipeline.rs +++ b/senbei-pe/src/engine/exe/pipeline.rs @@ -73,8 +73,22 @@ impl<'a> Unpacker<'a> { } // Strategy (a): delegate to primitives::calculate_checksum2 - fn calculate_checksum2(&self, pos: u32, start: u32) -> u32 { - primitives::calculate_checksum2(&self.decompressed, self.file_data, pos, start) + fn calculate_checksum2(&self, pos: u32, start: u32) -> Result { + primitives::calculate_checksum2(&self.decompressed, self.file_data, pos, start).map_err( + |error| match error { + senbei_crypto::Error::BufferRangeOutOfBounds { + offset, + size, + buffer_len, + .. + } => UnpackError::ExeChecksumRangeOutOfBounds { + descriptor: pos, + offset, + size, + image_len: buffer_len, + }, + }, + ) } // Strategy (a): delegate to primitives::decrypt_data1 @@ -1018,7 +1032,19 @@ impl<'a> Unpacker<'a> { let n = get_u32(&u.decompressed, p.wrapping_add(4)); walk3 = walk3.wrapping_add(16); if n != 0 { - chain_crc = u.calculate_checksum2(walk3.wrapping_sub(16), chain_crc); + match u.calculate_checksum2(walk3.wrapping_sub(16), chain_crc) { + Ok(next) => chain_crc = next, + Err(UnpackError::ExeChecksumRangeOutOfBounds { .. }) => { + if verbose { + println!( + " checksum chain terminates at 0x{:08X}: descriptor payload is outside protected input", + walk3.wrapping_sub(16) + ); + } + break; + } + Err(error) => return Err(error), + } } if get_u32(&u.decompressed, walk3.wrapping_sub(16).wrapping_add(4)) == 0 { break;