use std::{
io::{self, Read},
marker::PhantomData,
};
use crate::analysis::{ImageView, vm::image::BadRelocsError};
pub trait Decryptor {
type Block: bytemuck::Pod;
fn decrypt(&mut self, block: &mut Self::Block);
}
pub struct DecryptReader<R: Read, D: Decryptor> {
reader: R,
decryptor: D,
block_buffer: D::Block,
consumed: usize,
}
impl<R: Read, D: Decryptor> DecryptReader<R, D> {
const BLOCK_SIZE: usize = size_of::<D::Block>();
pub fn new(reader: R, decryptor: D) -> Self {
Self {
reader,
decryptor,
block_buffer: bytemuck::Zeroable::zeroed(),
consumed: size_of::<D::Block>(),
}
}
}
impl<R: Read, D: Decryptor> Read for DecryptReader<R, D> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
if self.consumed == Self::BLOCK_SIZE {
self.reader.read_exact(bytemuck::bytes_of_mut(&mut self.block_buffer))?;
self.decryptor.decrypt(&mut self.block_buffer);
self.consumed = 0;
}
let to_read = (Self::BLOCK_SIZE - self.consumed).min(buf.len());
buf[..to_read].copy_from_slice(
&bytemuck::bytes_of(&self.block_buffer)[self.consumed..self.consumed + to_read],
);
self.consumed += to_read;
Ok(to_read)
}
}
pub struct FnDecryptor<B: bytemuck::Pod, F: FnMut(&mut B)>(F, PhantomData<fn(&mut B)>);
impl<B: bytemuck::Pod, F: FnMut(&mut B)> FnDecryptor<B, F> {
pub fn new(fun: F) -> Self {
Self(fun, PhantomData)
}
}
impl<B: bytemuck::Pod, F: FnMut(&mut B)> Decryptor for FnDecryptor<B, F> {
type Block = B;
fn decrypt(&mut self, block: &mut Self::Block) {
self.0(block)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ArxanDecryptionKind {
Tea,
Rmx,
Sub,
}
pub fn tea_decryptor(key: &[u8; 16]) -> impl Decryptor<Block = [u32; 2]> {
let key: [u32; 4] = bytemuck::pod_read_unaligned(key);
FnDecryptor::new(move |block: &mut [u32; 2]| {
const NUM_ROUNDS: u32 = 32;
const DELTA: u32 = 0x9E3779B9;
let mut sum = 0xC6EF3720;
fn fiestel_round(b1: u32, b2: &mut u32, k1: u32, k2: u32, sum: u32) {
let k1_term = (b1 << 4).wrapping_add(k1) ^ b1.wrapping_add(sum);
let k2_term = (b1 >> 5).wrapping_add(k2);
*b2 = b2.wrapping_sub(k1_term ^ k2_term);
}
for _ in 0..NUM_ROUNDS {
fiestel_round(block[0], &mut block[1], key[2], key[3], sum);
fiestel_round(block[1], &mut block[0], key[0], key[1], sum);
sum = sum.wrapping_sub(DELTA);
}
})
}
pub fn rmx_decryptor(mut key: u32) -> impl Decryptor<Block = u32> {
let mut key_rot = key & 0x1f;
FnDecryptor::new(move |block: &mut u32| {
key = key.rotate_left(key_rot);
*block = block.wrapping_sub(key.wrapping_mul(key_rot));
key_rot ^= !*block;
})
}
pub fn sub_decryptor(key: u32) -> impl Decryptor<Block = u32> {
FnDecryptor::new(move |block| *block = key.wrapping_sub(*block))
}
pub fn try_read_varint(mut reader: impl io::Read) -> io::Result<u32> {
let mut result = 0u32;
let mut num_read = 0u32;
let mut b = 0u8;
loop {
reader.read_exact(std::slice::from_mut(&mut b))?;
result = (b as u32 & 0x7F)
.checked_shl(7 * num_read)
.and_then(|s| result.checked_add(s))
.ok_or(io::ErrorKind::InvalidData)?;
num_read += 1;
if b < 0x80 {
return Ok(result);
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct EncryptedRegion {
pub stream_offset: usize,
pub size: usize,
pub rva: u32,
}
impl EncryptedRegion {
pub fn decrypted_slice<'a>(&self, list: &'a EncryptedRegionList) -> Option<&'a [u8]> {
list.decrypted_stream.get(self.stream_offset..self.stream_offset + self.size)
}
pub fn try_from_varints(mut reader: impl Read) -> io::Result<Vec<Self>> {
let mut regions = Vec::new();
let mut rva = 0u32;
let mut stream_offset = 0usize;
loop {
let offset = try_read_varint(&mut reader)?;
if offset == 0 {
return Err(io::ErrorKind::InvalidData.into());
}
rva = rva.checked_add(offset).ok_or(io::ErrorKind::InvalidData)?;
if rva == u32::MAX {
return Ok(regions);
}
let size = try_read_varint(&mut reader)?;
if size == 0 {
return Err(io::ErrorKind::InvalidData.into());
}
regions.push(Self {
stream_offset,
size: size as usize,
rva,
});
rva = rva.checked_add(size).ok_or(io::ErrorKind::InvalidData)?;
stream_offset += size as usize;
}
}
pub fn intersects(&self, other: &EncryptedRegion) -> bool {
let end = self.rva as usize + self.size;
let other_end = other.rva as usize + other.size;
end.min(other_end) > self.rva.max(other.rva) as usize
}
}
#[derive(Debug, Clone)]
pub struct EncryptedRegionList {
pub kind: ArxanDecryptionKind,
pub regions: Vec<EncryptedRegion>,
pub decrypted_stream: Vec<u8>,
}
impl EncryptedRegionList {
pub fn len(&self) -> usize {
self.regions.len()
}
pub fn is_empty(&self) -> bool {
self.regions.is_empty()
}
pub fn try_new(
kind: ArxanDecryptionKind,
regions: Vec<EncryptedRegion>,
mut decrypted_stream: impl Read,
) -> io::Result<Self> {
let ctext_len = regions.last().map(|r| r.stream_offset + r.size).unwrap_or(0);
let mut plaintext = vec![0; ctext_len];
decrypted_stream.read_exact(&mut plaintext)?;
Ok(Self {
kind,
regions,
decrypted_stream: plaintext,
})
}
}
pub fn shannon_entropy(bytes: impl IntoIterator<Item = u8>) -> f64 {
let mut byte_dist = [0usize; 256];
let mut len = 0;
for b in bytes {
byte_dist[b as usize] += 1;
len += 1;
}
let len_log2 = (len as f64).log2();
let plogp_sum: f64 = byte_dist
.into_iter()
.filter(|&b| b != 0)
.map(|b: usize| (b as f64) * (len_log2 - (b as f64).log2()))
.sum();
plogp_sum / (len as f64)
}
pub fn apply_relocs_and_resolve_conflicts<
'a,
#[cfg(feature = "rayon")] I: ImageView + Sync,
#[cfg(not(feature = "rayon"))] I: ImageView,
>(
region_lists: impl IntoIterator<Item = &'a EncryptedRegionList>,
image: I,
preferred_base: Option<u64>,
) -> Result<Vec<EncryptedRegionList>, BadRelocsError> {
#[cfg(feature = "rayon")]
use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator};
let base_va = image.base_va();
struct ProcessedRegionList {
rlist: EncryptedRegionList,
entropy: f64,
base_entropy: f64,
eliminated: bool,
}
struct ContiguousRegion {
rlist_index: usize,
region: EncryptedRegion,
}
let sorted_relocs = {
let mut relocs: Vec<_> = image.relocs64()?.collect();
relocs.sort();
relocs
};
let region_lists = region_lists.into_iter();
let mut processed = Vec::with_capacity(region_lists.size_hint().0);
let mut contiguous_regions = Vec::with_capacity(processed.capacity());
for rlist in region_lists.filter(|r| !r.is_empty()) {
let index = processed.len();
processed.push(ProcessedRegionList {
entropy: 0.0,
base_entropy: 0.0,
rlist: rlist.clone(),
eliminated: false,
});
contiguous_regions.extend(rlist.regions.iter().map(|r| ContiguousRegion {
rlist_index: index,
region: r.clone(),
}));
}
contiguous_regions.sort_by_key(|r| (r.region.rva, r.region.size));
let pref_base = preferred_base.unwrap_or(base_va);
let base_diff = base_va.wrapping_sub(pref_base);
let mut crel = sorted_relocs.iter().copied().peekable();
for r in &contiguous_regions {
let parent = &mut processed[r.rlist_index];
if parent.eliminated {
continue;
}
while crel.next_if(|&reloc| reloc < r.region.rva).is_some() {}
if crel.peek().is_none() {
break;
}
let region_end = r.region.rva + r.region.size as u32;
for reloc in crel.clone().take_while(|&r| r + 8 <= region_end) {
let offset = (reloc - r.region.rva) as usize + r.region.stream_offset;
let reloc_area: &mut [u8; 8] =
(&mut parent.rlist.decrypted_stream[offset..offset + 8]).try_into().unwrap();
let relocated = u64::from_le_bytes(*reloc_area).wrapping_add(base_diff);
if image.read(relocated, 1).is_none() {
log::trace!("rlist {} eliminated using relocs", r.rlist_index);
parent.eliminated = true;
break;
}
*reloc_area = relocated.to_le_bytes();
}
}
#[cfg(not(feature = "rayon"))]
let not_eliminated = processed.iter_mut().filter(|p| !p.eliminated);
#[cfg(feature = "rayon")]
let not_eliminated = processed.par_iter_mut().filter(|p| !p.eliminated);
not_eliminated.for_each(|p| {
let base_bytes_iter = p.rlist.regions.iter().flat_map(|r| {
image
.read(base_va + r.rva as u64, r.size)
.map_or(&[] as &[u8], |s| &s[..r.size])
});
p.base_entropy = shannon_entropy(base_bytes_iter.copied());
p.entropy = shannon_entropy(p.rlist.decrypted_stream.iter().copied());
p.eliminated = p.entropy >= p.base_entropy;
if !p.eliminated {
log::trace!(
"kind = {:?} rva = {:08x} base_entropy = {:.03} entropy = {:.03} len = {}",
p.rlist.kind,
p.rlist.regions[0].rva,
p.base_entropy,
p.entropy,
p.rlist.decrypted_stream.len()
);
}
});
if let Some(i) = contiguous_regions.iter().position(|r| !processed[r.rlist_index].eliminated) {
let mut best = &contiguous_regions[i];
for r in contiguous_regions.get(i + 1..).unwrap_or(&[]) {
let Ok([r_rlist, best_rlist]) =
processed.get_disjoint_mut([r.rlist_index, best.rlist_index])
else {
best = r;
continue;
};
if r_rlist.eliminated {
continue;
}
if !best.region.intersects(&r.region) {
best = r;
continue;
}
if best_rlist.entropy > r_rlist.entropy {
best_rlist.eliminated = true;
best = r;
}
else {
r_rlist.eliminated = true;
}
}
};
Ok(processed
.into_iter()
.filter_map(|p| (!p.eliminated).then_some(p.rlist))
.collect())
}