babyRE
美化程序,查看文件,发现开头有:<project name="re4baby22" app="Snap! 8.2, https://snap.berkeley.edu" version="2">
为Snap!程序,使用相应工具打开:
主要逻辑:整个输入序列后一位异或前一位,与答案比对,复现算法即可。
secret = [102, 10, 13, 6, 28, 74, 3, 1, 3, 7, 85, 0, 4, 75, 20, 92, 92, 8, 28, 25, 81, 83, 7, 28, 76, 88, 9, 0, 29, 73,
0, 86, 4, 87, 87, 82, 84, 85, 4, 85, 87, 30]
for i in range(1,len(secret)):
secret[i] ^= secret[i-1]
for i in range(len(secret)):
print(chr(secret[i]), end='')
ezbyte
从start跟到sub_404C21,发现flag头尾校验,但代码最后跳转的判断部分sub_404BF5有一种脑干缺失的美,除了最后能隐约察觉它是想比对整个函数的返回值是否为0之外一无是处。
然而在看了题解之后,我才发现脑干缺失的是我自己——
通过DWARF Expression藏代码
英文文档太难啃了,而且基本都是把DWARF Expression从机理到实现给你讲了个遍,用来塞题解显然不太现实,到时候再说。不过,看看看雪上的文章,以及这道题的原型的题解,应该还是比较轻松愉快的。
简单来说,跟我们之前在《深入理解》里看到的一样,许多异常处理之后需要进行状态还原,以保证程序运行,在C++里面是这样的——
在类Unix系统中,由g++/clang++编译出的程序,是使用DWARF调试信息完成这个恢复过程的。早期的DWARF标准只支持一些简单的原语以恢复运行状态,在DWARF 3标准中引入了一个DWARF Expression,可以将它理解为一个基于栈的虚拟机,由标准C++库解释执行它,它包含一系列的字节码,提供了读取程序运行内存的Handle,但不支持写入程序运行内存。
DWARF Expression支持的操作可以分为编码(入栈立即数),寄存器寻址(入栈 REG + OFFSET),栈操作(SWAP,POP等操作),算术运算,流程转移。我简单介绍一些常用的操作。
所以,DWARF Expression实际上可以看作一个包含在调试信息内的简单虚拟机代码,可以复现一定范围内功能的源代码并执行,在编译过程之后这些信息在代码上便不可见,所以也很好地保护了代码。而我们要做的,就是将这些内容给提出来,再塞回去。
万幸,此前的出题人也准备好了应对手段。如下是将程序转换为正常C的rust代码:
use std::{collections::HashMap, fs, fmt::Display, io::Write};
use std::process::Command;
use gimli::UnwindSection;
use object::{Object, ObjectSection};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut arg = std::env::args();
if arg.len() != 2 {
panic!("Argument Error!")
}
let bin_data = fs::read(arg.nth(1).unwrap())?;
let obj_file = object::File::parse(&*bin_data)?;
let data = obj_file.section_by_name(".eh_frame").unwrap();
let eh_frame = gimli::read::EhFrame::new(data.data()?, gimli::LittleEndian);
let bases = gimli::BaseAddresses::default().set_eh_frame(data.address());
let mut entries = eh_frame.entries(&bases);
let mut file = fs::OpenOptions::new().append(false).truncate(true).write(true).create(true).open("./output.c")?;
writeln!(file, "#include <stdint.h>")?;
let mut cies = HashMap::new();
while let Some(entry) = entries.next()? {
if let gimli::CieOrFde::Fde(partial) = entry {
let fde = partial.parse(|_, bases, o| {
cies.entry(o)
.or_insert_with(|| eh_frame.cie_from_offset(bases, o))
.clone()
})?;
// 通过长度过滤出我们想要的
if fde.entry_len() < 100 {
continue;
}
let mut instructions = fde.instructions(&eh_frame, &bases);
use gimli::CallFrameInstruction::*;
loop {
match instructions.next() {
Err(e) => {
println!("Failed to decode CFI instruction: {}", e);
break;
}
Ok(Some(ValExpression {
register,
expression,
})) => {
println!(
"DW_CFA_val_expression ({}, ...)",
gimli::X86_64::register_name(register).unwrap_or("{unknown}")
);
display_val_expression(register, expression, &mut file)?;
}
Ok(None) => {
break;
}
_ => {}
}
}
}
}
file.flush()?;
Command::new("gcc")
.arg("-O3")
.arg("./output.c")
.arg("-c")
.spawn()?;
Ok(())
}
#[derive(Clone, Copy)]
struct Val {
id: u64,
}
impl Val {
fn new(id: u64) -> Self {
Val { id }
}
}
struct ValGenerator {
id: u64,
}
impl ValGenerator {
fn new() -> Self {
Self { id: 0 }
}
fn next(&mut self) -> Val {
self.id += 1;
Val::new(self.id - 1)
}
}
impl Display for Val {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "v{}", self.id)
}
}
fn display_val_expression<R>(target_reg: gimli::Register, exp: gimli::Expression<R>, w: &mut dyn Write) -> Result<(), Box<dyn std::error::Error>>
where
R: gimli::Reader,
{
let mut val_generator = ValGenerator::new();
let mut ops = exp.operations(gimli::Encoding { address_size: 8, format: gimli::Format::Dwarf64, version: 5 });
let mut stack: Vec<Val> = Vec::new();
writeln!(w, "uint64_t cal_{}(uint64_t r12, uint64_t r13, uint64_t r14, uint64_t r15){{", gimli::X86_64::register_name(target_reg).unwrap())?;
writeln!(w, " uint64_t rax=0,rbx=0;")?;
loop {
if let Ok(Some(op)) = ops.next() {
match op {
gimli::Operation::Drop => {
stack.pop();
}
gimli::Operation::Pick { index } => {
let val1 = stack.get(stack.len() - 1 - index as usize).unwrap();
let new_val = val_generator.next();
writeln!(w, " uint64_t {}={};", new_val, val1)?;
stack.push(new_val);
}
gimli::Operation::Swap => {
let val1 = stack.pop().unwrap();
let val2 = stack.pop().unwrap();
stack.push(val1);
stack.push(val2);
}
gimli::Operation::Rot => {
let val1 = stack.pop().unwrap();
let val2 = stack.pop().unwrap();
let val3 = stack.pop().unwrap();
stack.push(val1);
stack.push(val3);
stack.push(val2);
}
gimli::Operation::And => {
let val1 = stack.pop().unwrap();
let val2 = stack.pop().unwrap();
let new_val = val_generator.next();
writeln!(w, " uint64_t {}={}&{};", new_val, val2, val1)?;
stack.push(new_val);
}
gimli::Operation::Minus => {
let val1 = stack.pop().unwrap();
let val2 = stack.pop().unwrap();
let new_val = val_generator.next();
writeln!(w, " uint64_t {}={}-{};", new_val, val2, val1)?;
stack.push(new_val);
}
gimli::Operation::Neg => {
let val = stack.get(stack.len() - 1).unwrap();
writeln!(w, " {}=-{};", val, val)?;
}
gimli::Operation::Not => {
let val = stack.get(stack.len() - 1).unwrap();
writeln!(w, " {}=~{};", val, val)?;
}
gimli::Operation::Or => {
let val1 = stack.pop().unwrap();
let val2 = stack.pop().unwrap();
let new_val = val_generator.next();
writeln!(w, " uint64_t {}={}|{};", new_val, val2, val1)?;
stack.push(new_val);
}
gimli::Operation::Plus => {
let val1 = stack.pop().unwrap();
let val2 = stack.pop().unwrap();
let new_val = val_generator.next();
writeln!(w, " uint64_t {}={}+{};", new_val, val2, val1)?;
stack.push(new_val);
}
gimli::Operation::PlusConstant { value } => {
let val = stack.get(stack.len() - 1).unwrap();
writeln!(w, " {}+={}ull;", val, value)?;
}
gimli::Operation::Shl => {
let val1 = stack.pop().unwrap();
let val2 = stack.pop().unwrap();
let new_val = val_generator.next();
writeln!(w, " uint64_t {}={}<<{};", new_val, val2, val1)?; stack.push(new_val); } gimli::operation::shr> {
let val1 = stack.pop().unwrap();
let val2 = stack.pop().unwrap();
let new_val = val_generator.next();
writeln!(w, " uint64_t {}={}>>{};", new_val, val2, val1)?;
stack.push(new_val);
}
gimli::Operation::Shra => {
let val1 = stack.pop().unwrap();
let val2 = stack.pop().unwrap();
let new_val = val_generator.next();
writeln!(w, " uint64_t {}=(uint64_t)((int64_t){}>>(int64_t){});", new_val, val2, val1)?;
stack.push(new_val);
}
gimli::Operation::Xor => {
let val1 = stack.pop().unwrap();
let val2 = stack.pop().unwrap();
let new_val = val_generator.next();
writeln!(w, " uint64_t {}={}^{};", new_val, val2, val1)?;
stack.push(new_val);
}
gimli::Operation::Eq => {
let val1 = stack.pop().unwrap();
let val2 = stack.pop().unwrap();
let new_val = val_generator.next();
writeln!(w, " uint64_t {}= {}=={}?1:0;", new_val, val2, val1)?;
stack.push(new_val);
}
gimli::Operation::Ge => {
let val1 = stack.pop().unwrap();
let val2 = stack.pop().unwrap();
let new_val = val_generator.next();
writeln!(w, " uint64_t {}={}>={}?1:0;", new_val, val2, val1)?;
stack.push(new_val);
}
gimli::Operation::Gt => {
let val1 = stack.pop().unwrap();
let val2 = stack.pop().unwrap();
let new_val = val_generator.next();
writeln!(w, " uint64_t {}={}>{}?1:0;", new_val, val2, val1)?;
stack.push(new_val);
}
gimli::Operation::Le => {
let val1 = stack.pop().unwrap();
let val2 = stack.pop().unwrap();
let new_val = val_generator.next();
writeln!(w, " uint64_t {}={}<={}?1:0;", new_val, val2, val1)?; stack.push(new_val); } gimli::operation::lt> {
let val1 = stack.pop().unwrap();
let val2 = stack.pop().unwrap();
let new_val = val_generator.next();
writeln!(w, " uint64_t {}={}<{}?1:0;", new_val, val2, val1)?; stack.push(new_val); } gimli::operation::ne> {
let val1 = stack.pop().unwrap();
let val2 = stack.pop().unwrap();
let new_val = val_generator.next();
writeln!(w, " uint64_t {}={}!={}?1:0;", new_val, val2, val1)?;
stack.push(new_val);
}
gimli::Operation::UnsignedConstant { value } => {
let new_val = val_generator.next();
writeln!(w, " uint64_t {}={}ull;", new_val, value)?;
stack.push(new_val);
}
gimli::Operation::SignedConstant { value } => {
let new_val = val_generator.next();
writeln!(w, " uint64_t {}=(uint64_t){}ll;", new_val, value)?;
stack.push(new_val);
}
gimli::Operation::Register { register } => {
let new_val = val_generator.next();
writeln!(w, " uint64_t {}={};", new_val, gimli::X86_64::register_name(register).unwrap_or("{error}"))?;
stack.push(new_val);
}
_ => todo!("{:?}", op)
}
} else {
break;
}
}
assert_eq!(stack.len(), 1);
writeln!(w, " return {};", stack.pop().unwrap())?;
writeln!(w, "}}\n")?;
Ok(())
}
{}?1:0;",>={}?1:0;",>{};",>
配置rust的过程有点枯燥,但是菜鸟教程解千愁。记得在Cargo.toml里加上你调用的库的配置(如下是我用的版本):
[dependencies]
gimli = "0.26.1"
object = "0.29.0"
rand = "0.7.3"
然后把你的被DWARF Expression的代码放到项目目录\target\debug
里跑就完事了,但是有点奇怪:
报错显示缺少注册表偏移,按着前面照猫画虎摸一个出来:
gimli::Operation::RegisterOffset{ register, offset, ..} =>{
let new_val = val_generator.next();
writeln!(w, " uint64_t {}=({}+{}ull);", new_val, gimli::X86_64::register_name(register).unwrap_or("{error}"),offset)?;
stack.push(new_val);
}
也可以翻看yulate佬的题解抄一个,这里就不细说了。然后就是跑出来一个中间的.o文件和最后的c文件。由于反汇编自带代码简化,我们直接IDA看.o(代码因人而异):
__int64 __fastcall cal_r12(__int64 a1, __int64 a2, __int64 a3, __int64 a4)
{
return ((a3 + 1512312) ^ 0x2D393663614447B1i64)
+ ((a1 + 1892739) ^ 0x35626665394D17E8i64)
+ ((a2 + 8971237) ^ 0x65342D6530C04912i64)
+ ((a4 + 9123704) ^ 0x6336396431BE9AD9i64);
}
一个数异或自己=0,所以这就是个简单的判断,再加上它非常像某些十六进制字符数据,于是我们放进python逆推:
a = [0, 0, 0, 0]
a[0] = 0x35626665394D17E8 - 1892739
a[1] = 0x65342D6530C04912 - 8971237
a[2] = 0x2D393663614447B1 - 1512312
a[3] = 0x6336396431BE9AD9 - 9123704
print("flag{", end='')
for i in range(4):
while a[i] > 0:
c = chr(int(a[i] % 0x100))
print(c, end='')
a[i] //= 256
print("3861}")
flag{e609efb5-e70e-4e94-ac69-ac31d96c3861}
解多纯粹因为有脚本,我没解出来纯粹因为知识盲区,蔡就多练.jpg