llvm中ll文件解读
[TOC] ll文件解析
ll文件中常见变量的理解
@ - 全局变量
% - 局部变量
alloca - 在当前执行的函数的堆栈帧中分配内存,当该函数返回到其调用者时,将自动释放内存
i32 - 32位4字节的整数
align - 对齐
load - 读出,store写入
icmp - 两个整数值比较,返回布尔值
br - 选择分支,根据条件来转向label,不根据条件跳转的话类型goto
label - 代码标签
call - 调用函数
例题WMCTF-2024 babysigin
很重要的一些理解
CallInst:函数类型的变量
LoadInst:ll中应对应load指令
StoreInst:ll中应对应store指令
llvm::dyn_cast<llvm::GlobalVariable/LoadInst/StoreInst,llvm::Value>:这种就是判断其是否是GlobalVariable/LoadInst/StoreInst类型
有关LoadInst
发现想要满足LoadInst类型,那么传入的不应该是个直接的值,而是先int cmd=1234,func(cmd)用这种方式传参即可 再详细解释一下LoadInst,比如我的exp.c中是这样的
WMCTF_WRITE(0x8888);
那么ll文件就会是这样,显然不满足load
call void @WMCTF_WRITE(i32 noundef 34952)
但如果先定义一个变量再传参
int cmd = 0x8888;
WMCTF_WRITE(cmd);
那么ll文件就长这样
@cmd = dso_local global i32 34952, align 4
%1 = load i32, i32* @cmd, align 4
call void @WMCTF_WRITE(i32 noundef %1)
显然这样就满足load类型了!!!
有关StoreInst
- 这部分和LoadInst类似,可以在WMCTF_OPEN分析中的最后一关这部分看到如何解决
这里先看简单的 WMCTF_OPEN 和 WMCTF_READ
v77 = llvm::ilist_iterator<llvm::ilist_detail::node_options<llvm::Instruction,false,false,void>,false,false>::operator*(&v79);
//v76变成CallInst类型
v76 = (llvm::CallBase *)llvm::dyn_cast<llvm::CallInst,llvm::Instruction>(v77);
//********************************************WMCTF_READ***************************************************
v19 = (llvm::Value *)llvm::CallBase::getCalledFunction(v76);
//得到函数名称
v55 = llvm::Value::getName(v19);
v56 = v20;
llvm::StringRef::StringRef((llvm::StringRef *)v54, "WMCTF_READ");
if ( (llvm::operator==(v55, v56, v54[0], v54[1]) & 1) != 0 )
{
//得到函数的第0个参数
v21 = llvm::CallBase::getOperand(v76, 0);
//将这个参数转换为ConstantInt类型
v53 = (llvm::ConstantInt *)llvm::dyn_cast<llvm::ConstantInt,llvm::Value>(v21);
if ( v53 && llvm::ConstantInt::getSExtValue(v53) == 0x6666 )
{
v22 = (llvm *)fd;
if ( read(fd, (void *)mmap_addr, 0x40uLL) < 0 )
{
v23 = llvm::errs(v22);
llvm::raw_ostream::operator<<(v23, "WMCTF_READ error\n");
v86 = 0;
return v86 & 1;
}
v24 = llvm::errs(v22);
llvm::raw_ostream::operator<<(v24, "WMCTF_READ success\n");
}
}
//***********************************************WMCTF_MMAP****************************************
//与WMCTF_READ类似不再做分析
v25 = (llvm::Value *)llvm::CallBase::getCalledFunction(v76);
v51 = llvm::Value::getName(v25);
v52 = v26;
llvm::StringRef::StringRef((llvm::StringRef *)v50, "WMCTF_MMAP");
if ( (llvm::operator==(v51, v52, v50[0], v50[1]) & 1) != 0 )
{
v27 = llvm::CallBase::getOperand(v76, 0);
v49 = (llvm::ConstantInt *)llvm::dyn_cast<llvm::ConstantInt,llvm::Value>(v27);
if ( v49 && llvm::ConstantInt::getSExtValue(v49) == 0x7890 )
{
mmap_addr = mmap(0LL, 0x1000uLL, 3, 33, 0, 0LL);
if ( mmap_addr == (const void *)-1LL )
{
v28 = llvm::errs(0LL);
llvm::raw_ostream::operator<<(v28, "WMCTF_MMAP failed\n");
}
else
{
v29 = llvm::errs(0LL);
llvm::raw_ostream::operator<<(v29, "WMCTF_MMAP success\n");
}
}
}
- 分析完后发现WMCTF_OPEN 和 WMCTF_READ只要正常传参就行
再看WMCTF_WRITE
v30 = (llvm::Value *)llvm::CallBase::getCalledFunction(v76);
v47 = llvm::Value::getName(v30);
v48 = v31;
llvm::StringRef::StringRef((llvm::StringRef *)v46, "WMCTF_WRITE");
if ( (llvm::operator==(v47, v48, v46[0], v46[1]) & 1) != 0 )
{
//获取函数的第0个参数
v32 = llvm::CallBase::getOperand(v76, 0);
//判断这个参数是否是LoadInst类型
v45 = (llvm::UnaryInstruction *)llvm::dyn_cast<llvm::LoadInst,llvm::Value>(v32);
if ( !v45 )
{
v86 = 0;
return v86 & 1;
}
//从LoadInst中获取第一个参数,判断其是否是全局变量
v33 = llvm::UnaryInstruction::getOperand(v45, 0);
v44 = (llvm::GlobalVariable *)llvm::dyn_cast<llvm::GlobalVariable,llvm::Value>(v33);
if ( !v44 )
{
v86 = 0;
return v86 & 1;
}
//是全局变量再从GlobalVariable获的第一个参数
v34 = llvm::GlobalVariable::getOperand(v44, 0);
//将获得的参数值转换为ConstantInt类型
v43 = (llvm::ConstantInt *)llvm::dyn_cast<llvm::ConstantInt,llvm::Value>(v34);
if ( v43 )
{
if ( (unsigned int)llvm::ConstantInt::getSExtValue(v43) != 0x8888 )
{
v86 = 0;
return v86 & 1;
}
if ( write((int)&dword_0 + 1, mmap_addr, 0x40uLL) < 0 )
{
v35 = llvm::errs((llvm *)((char *)&dword_0 + 1));
llvm::raw_ostream::operator<<(v35, "WMCTF_WRITE error\n");
v86 = 0;
return v86 & 1;
}
}
}
- 分析完后发现WMCTF_WRITE传入的参数在ll中应当是load指令的结果,同时这个参数得是个全局变量
- ll文件中应当长这样
@cmd = dso_local global i32 34952, align 4
%1 = load i32, i32* @cmd, align 4
call void @WMCTF_WRITE(i32 noundef %1)
最后看WMCTF_OPEN
- 先过第一关
CalledFunction = (llvm::Value *)llvm::CallBase::getCalledFunction(v76);
Name = llvm::Value::getName(CalledFunction);
v75 = v3;
llvm::StringRef::StringRef((llvm::StringRef *)v73, "WMCTF_OPEN");
if ( (llvm::operator==(Name, v75, v73[0], v73[1]) & 1) != 0 )
{
//获取函数的0个参数,判断是否是LoadInst类型
Operand = llvm::CallBase::getOperand(v76, 0);
if ( (llvm::isa<llvm::LoadInst,llvm::Value *>(&Operand) & 1) == 0 )
{
v4 = llvm::errs((llvm *)&Operand);
llvm::raw_ostream::operator<<(v4, "parameter error: first operand is not a LoadInst\n");
v86 = 0;
return v86 & 1;
}
//也是获取函数的第0个参数,但是和上面类似有点不同,笔者这里不懂,但是只要保证WMCTF_OPEN的参数是load类型就行
v5 = (llvm *)llvm::CallBase::getOperand(v76, 0);
v71 = (llvm::UnaryInstruction *)llvm::dyn_cast<llvm::LoadInst,llvm::Value>(v5);
if ( !v71 )
{
v6 = llvm::errs(v5);
llvm::raw_ostream::operator<<(v6, "parameter error: filename is not a LoadInst\n");
v86 = 0;
return v86 & 1;
}
//获取LoadInst的第0个参数
v70 = (llvm::Value *)llvm::UnaryInstruction::getOperand(v71, 0);
//获取参数的名字
/*
这里打个比方
%3 = load i8*, i8** @filename, align 8
v70 = (llvm::Value *)llvm::UnaryInstruction::getOperand(v71, 0);就相当于获取和@filename有关的东西
那么llvm::Value::getName(v70);就是或者上述内容的名字,也就是filename
在exp.c中这就是char *filename = "./flag";但是我们知道变量的命名不能带. 所以这里我们只能自己手动改ll文件
*/
v69[0] = llvm::Value::getName(v70);
v69[1] = v7;
llvm::StringRef::StringRef((llvm::StringRef *)v68, ".addr");
if ( (llvm::StringRef::contains(v69, v68[0], v68[1]) & 1) == 0 )
{
v8 = llvm::errs((llvm *)v69);
llvm::raw_ostream::operator<<(v8, "parameter error: filepath does not contain .addr\n");
v86 = 0;
return v86 & 1;
}
}
- 再过第二关,可以看到这个open实际上就是根据v66这个字符串的值来open,因此这个WMCTF::getFunctionCallValue[abi:cxx11]将是我们分析的重点
anonymous namespace::WMCTF::getFunctionCallValue[abi:cxx11]((llvm *)v66, (__int64)this, Parent, v84, 0);
if ( (std::string::empty(v66) & 1) != 0 )
{
v9 = llvm::errs((llvm *)v66);
llvm::raw_ostream::operator<<(v9, "function error: could not retrieve function call value\n");
v86 = 0;
v65 = 1;
}
else
{
Context = llvm::Module::getContext(Parent);
llvm::StringRef::StringRef(v63, v66);
v10 = v63[0];
String = (llvm::Value *)llvm::ConstantDataArray::getString(Context, v63[0], v63[1], 0LL);
v41 = llvm::GlobalVariable::operator new((llvm::GlobalVariable *)&qword_58, v10);
v38 = Parent;
Type = llvm::Value::getType(String);
v40 = String;
v11 = rand();
std::to_string((std::__cxx11 *)v59, v11);
std::operator+<char>(v60, "string_constant", v59);
llvm::Twine::Twine(v61, v60);
llvm::Optional<unsigned int>::Optional(&v58, 1LL);
llvm::GlobalVariable::GlobalVariable(v41, v38, Type, 1LL, 8LL, v40, v61, 0LL, 0, v58, 0);
std::string::~string(v60);
std::string::~string(v59);
v62 = v41;
v12 = llvm::Module::getContext(Parent);
Int8PtrTy = llvm::Type::getInt8PtrTy(v12, 0LL);
BitCast = (llvm::Value *)llvm::ConstantExpr::getBitCast(v41, Int8PtrTy, 0LL);
llvm::CallBase::setArgOperand(v76, 0, BitCast);
v14 = (char *)std::string::c_str(v66);
fd = open(v14, 0);
if ( (fd & 0x80000000) == 0 )
{
v18 = llvm::errs((llvm *)v14);
llvm::raw_ostream::operator<<(v18, "WMCTF_OPEN success\n");
v65 = 0;
}
else
{
v15 = llvm::errs((llvm *)v14);
v16 = llvm::raw_ostream::operator<<(v15, "open error: could not open file ");
v17 = llvm::raw_ostream::operator<<(v16, v66);
llvm::raw_ostream::operator<<(v17, "\n");
v86 = 0;
v65 = 1;
}
}
std::string::~string(v66);
if ( v65 )
return v86 & 1;
- 最后一关
llvm *__fastcall anonymous namespace::WMCTF::getFunctionCallValue[abi:cxx11](
llvm *a1,
__int64 a2,
llvm::Module *a3,
llvm::Value *a4,
int a5)
{
__int64 v5; // rax
llvm::Value *CalledFunction; // rax
__int64 v7; // rdx
__int64 v8; // rdx
__int64 Operand; // rax
llvm::Value *v10; // rax
__int64 v11; // rdx
__int64 v12; // rax
__int64 v13; // rax
__int64 v14; // rdx
__int64 User; // rax
__int64 v16; // rax
__int64 v17; // rax
__int64 Initializer; // rax
__int64 v19; // rdx
char v21[8]; // [rsp+20h] [rbp-150h] BYREF
__int64 v22[2]; // [rsp+28h] [rbp-148h] BYREF
llvm::ConstantDataSequential *v23; // [rsp+38h] [rbp-138h]
llvm::GlobalVariable *v24; // [rsp+40h] [rbp-130h]
llvm::ConstantExpr *v25; // [rsp+48h] [rbp-128h]
llvm::StoreInst *v26; // [rsp+50h] [rbp-120h]
llvm::Use *v27; // [rsp+58h] [rbp-118h]
__int64 v28; // [rsp+60h] [rbp-110h] BYREF
__int64 v29; // [rsp+68h] [rbp-108h] BYREF
__int64 v30[2]; // [rsp+70h] [rbp-100h] BYREF
__int64 *v31; // [rsp+80h] [rbp-F0h]
llvm::Value *v32; // [rsp+88h] [rbp-E8h]
char v33[8]; // [rsp+90h] [rbp-E0h] BYREF
__int64 v34[2]; // [rsp+98h] [rbp-D8h] BYREF
__int64 v35[2]; // [rsp+A8h] [rbp-C8h] BYREF
llvm::UnaryInstruction *v36; // [rsp+B8h] [rbp-B8h]
__int64 v37; // [rsp+C0h] [rbp-B0h]
__int64 v38; // [rsp+C8h] [rbp-A8h]
__int64 Name; // [rsp+D0h] [rbp-A0h]
__int64 v40; // [rsp+D8h] [rbp-98h]
llvm::CallBase *v41; // [rsp+E0h] [rbp-90h]
__int64 v42; // [rsp+E8h] [rbp-88h]
__int64 v43; // [rsp+F0h] [rbp-80h] BYREF
__int64 v44; // [rsp+F8h] [rbp-78h] BYREF
llvm::BasicBlock *v45; // [rsp+100h] [rbp-70h]
llvm::BasicBlock *v46; // [rsp+108h] [rbp-68h]
__int64 v47; // [rsp+110h] [rbp-60h] BYREF
__int64 v48; // [rsp+118h] [rbp-58h] BYREF
llvm::Function *v49; // [rsp+120h] [rbp-50h]
llvm::Value *v50; // [rsp+128h] [rbp-48h]
__int64 v51; // [rsp+130h] [rbp-40h] BYREF
__int64 v52[2]; // [rsp+138h] [rbp-38h] BYREF
char v53[4]; // [rsp+148h] [rbp-28h] BYREF
unsigned int v54; // [rsp+14Ch] [rbp-24h]
llvm::Value *v55; // [rsp+150h] [rbp-20h]
llvm::Module *v56; // [rsp+158h] [rbp-18h]
__int64 v57; // [rsp+160h] [rbp-10h]
llvm *v58; // [rsp+168h] [rbp-8h]
v58 = a1;
v57 = a2;
v56 = a3;
v55 = a4;
v54 = a5;
if ( a5 <= 5 )
{
v52[1] = (__int64)v56;
v52[0] = llvm::Module::begin(v56);
v51 = llvm::Module::end(v56);
while ( (llvm::operator!=(v52, &v51) & 1) != 0 )
{
v50 = (llvm::Value *)llvm::ilist_iterator<llvm::ilist_detail::node_options<llvm::Function,false,false,void>,false,false>::operator*(v52);
v49 = v50;
v48 = llvm::Function::begin(v50);
v47 = llvm::Function::end(v49);
while ( (llvm::operator!=(&v48, &v47) & 1) != 0 )
{
v46 = (llvm::BasicBlock *)llvm::ilist_iterator<llvm::ilist_detail::node_options<llvm::BasicBlock,false,false,void>,false,false>::operator*(&v48);
v45 = v46;
v44 = llvm::BasicBlock::begin(v46);
v43 = llvm::BasicBlock::end(v45);
//进入遍历list的过程
while ( (llvm::operator!=(&v44, &v43) & 1) != 0 )
{
v42 = llvm::ilist_iterator<llvm::ilist_detail::node_options<llvm::Instruction,false,false,void>,false,false>::operator*(&v44);
v41 = (llvm::CallBase *)llvm::dyn_cast<llvm::CallInst,llvm::Instruction>(v42);
if ( v41 )
{
//获得函数名称
CalledFunction = (llvm::Value *)llvm::CallBase::getCalledFunction(v41);
Name = llvm::Value::getName(CalledFunction);
v40 = v7;
v37 = llvm::Value::getName(v55);
v38 = v8;
if ( (llvm::operator==(Name, v40, v37, v8) & 1) != 0 )
{
//获得函数第一个参数
Operand = llvm::CallBase::getOperand(v41, 0);
//变成LoadInst类型
v36 = (llvm::UnaryInstruction *)llvm::dyn_cast<llvm::LoadInst,llvm::Value>(Operand);
if ( v36 )
{
//获得LoadInst类型的第一个参数
v10 = (llvm::Value *)llvm::UnaryInstruction::getOperand(v36, 0);
v35[0] = llvm::Value::getName(v10);
v35[1] = v11;
//判断LoadInst类型的第一个参数是否包含.addr,这里要手动改
llvm::StringRef::StringRef((llvm::StringRef *)v34, ".addr");
if ( (llvm::StringRef::contains(v35, v34[0], v34[1]) & 1) != 0 )
{
//进行这个函数的递归,v54初值为0
anonymous namespace::WMCTF::getFunctionCallValue[abi:cxx11](a1, a2, v56, v50, v54 + 1);
return a1;
}
//0,1,2,3,第3次函数调用应当不包含.addr(从第0次开始计数),防止进入if语句
if ( v54 != 3 )
{
v12 = llvm::errs((llvm *)v35);
v13 = llvm::raw_ostream::operator<<(v12, v54);
llvm::raw_ostream::operator<<(v13, "\n");
std::allocator<char>::allocator(v33);
std::string::basic_string(a1, "", v33);
std::allocator<char>::~allocator(v33);
return a1;
}
//这下面一大段步骤都是获取LoadInst类型的第0个参数,然后把它复制到a1,也就是刚才的v66中
v32 = (llvm::Value *)llvm::UnaryInstruction::getOperand(v36, 0);
v30[0] = llvm::Value::uses(v32);
v30[1] = v14;
v31 = v30;
v29 = llvm::iterator_range<llvm::Value::use_iterator_impl<llvm::Use>>::begin(v30);
v28 = llvm::iterator_range<llvm::Value::use_iterator_impl<llvm::Use>>::end(v31);
while ( (llvm::Value::use_iterator_impl<llvm::Use>::operator!=(&v29, &v28) & 1) != 0 )
{
v27 = (llvm::Use *)llvm::Value::use_iterator_impl<llvm::Use>::operator*(&v29);
User = llvm::Use::getUser(v27);
//User要是StoreInst类型,一直向上溯源发现是和v32有关,v32是LoadInst类型的第0个参数
//说明LoadInst类型的第0个参数应该又是个StoreInst,怎么做到呢?
//利用flag="./flag"这种赋值,然后再调用 func3(flag);这种传参就可以做到
v26 = (llvm::StoreInst *)llvm::dyn_cast<llvm::StoreInst,llvm::User>(User);
if ( v26 )
{
v16 = llvm::StoreInst::getOperand(v26, 0);
v25 = (llvm::ConstantExpr *)llvm::dyn_cast<llvm::ConstantExpr,llvm::Value>(v16);
if ( v25 )
{
v17 = llvm::ConstantExpr::getOperand(v25, 0);
v24 = (llvm::GlobalVariable *)llvm::dyn_cast<llvm::GlobalVariable,llvm::Constant>(v17);
if ( v24 )
{
Initializer = llvm::GlobalVariable::getInitializer(v24);
v23 = (llvm::ConstantDataSequential *)llvm::dyn_cast<llvm::ConstantDataArray,llvm::Constant>(Initializer);
if ( v23 )
{
v22[0] = llvm::ConstantDataSequential::getAsString(v23);
v22[1] = v19;
llvm::StringRef::str[abi:cxx11](a1, v22);
return a1;
}
}
}
}
llvm::Value::use_iterator_impl<llvm::Use>::operator++(&v29);
}
}
}
}
llvm::ilist_iterator<llvm::ilist_detail::node_options<llvm::Instruction,false,false,void>,false,false>::operator++(&v44);
}
llvm::ilist_iterator<llvm::ilist_detail::node_options<llvm::BasicBlock,false,false,void>,false,false>::operator++(&v48);
}
llvm::ilist_iterator<llvm::ilist_detail::node_options<llvm::Function,false,false,void>,false,false>::operator++(v52);
}
std::allocator<char>::allocator(v21);
std::string::basic_string(a1, "", v21);
std::allocator<char>::~allocator(v21);
}
else
{
v5 = llvm::errs(a1);
llvm::raw_ostream::operator<<(v5, "error: recursion depth exceeded\n");
std::allocator<char>::allocator(v53);
std::string::basic_string(a1, "", v53);
std::allocator<char>::~allocator(v53);
}
return a1;
}
- 要注意的地方,v26 = (llvm::StoreInst *)llvm::dyn_cast llvm::StoreInst,llvm::User (User); 注意看代码中的分析部分,具体是是用以下方式实现
void func4(char *name) {
flag = "./flag";
func3(flag);
}
exp
- exp.c
void WMCTF_OPEN(char *name);
void WMCTF_READ(int cmd);
void WMCTF_MMAP(int cmd);
void WMCTF_WRITE(int cmd);
char *filename = "./flag";
char *flag = "./flag";
int cmd = 0x8888;
void f0(char* name);
void f1(char* name);
void f2(char* name);
void f3(char* name);
void func0(char *name) {
WMCTF_OPEN(filename);
}
void func1(char* name) {
func0(filename);
}
void func2(char* name) {
func1(filename);
}
void func3(char *name) {
func2(filename);
}
void func4(char *name) {
flag = "./flag";
func3(flag);
}
void funcmain() {
WMCTF_MMAP(0x7890);
WMCTF_READ(0x6666);
WMCTF_WRITE(cmd);
}
- 修改过后的ll文件,主要就是把filename这个名称变为.addr,其他的无需改动
; ModuleID = 'test.c'
source_filename = "test.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"
@.str = private unnamed_addr constant [7 x i8] c"./flag\00", align 1
@.addr = dso_local global i8* getelementptr inbounds ([7 x i8], [7 x i8]* @.str, i32 0, i32 0), align 8
@flag = dso_local global i8* getelementptr inbounds ([7 x i8], [7 x i8]* @.str, i32 0, i32 0), align 8
@cmd = dso_local global i32 34952, align 4
; Function Attrs: noinline nounwind optnone uwtable
define dso_local void @func0(i8* noundef %0) #0 {
%2 = alloca i8*, align 8
store i8* %0, i8** %2, align 8
%3 = load i8*, i8** @.addr, align 8
call void @WMCTF_OPEN(i8* noundef %3)
ret void
}
declare void @WMCTF_OPEN(i8* noundef) #1
; Function Attrs: noinline nounwind optnone uwtable
define dso_local void @func1(i8* noundef %0) #0 {
%2 = alloca i8*, align 8
store i8* %0, i8** %2, align 8
%3 = load i8*, i8** @.addr, align 8
call void @func0(i8* noundef %3)
ret void
}
; Function Attrs: noinline nounwind optnone uwtable
define dso_local void @func2(i8* noundef %0) #0 {
%2 = alloca i8*, align 8
store i8* %0, i8** %2, align 8
%3 = load i8*, i8** @.addr, align 8
call void @func1(i8* noundef %3)
ret void
}
; Function Attrs: noinline nounwind optnone uwtable
define dso_local void @func3(i8* noundef %0) #0 {
%2 = alloca i8*, align 8
store i8* %0, i8** %2, align 8
%3 = load i8*, i8** @.addr, align 8
call void @func2(i8* noundef %3)
ret void
}
; Function Attrs: noinline nounwind optnone uwtable
define dso_local void @func4(i8* noundef %0) #0 {
%2 = alloca i8*, align 8
store i8* %0, i8** %2, align 8
store i8* getelementptr inbounds ([7 x i8], [7 x i8]* @.str, i64 0, i64 0), i8** @flag, align 8
%3 = load i8*, i8** @flag, align 8
call void @func3(i8* noundef %3)
ret void
}
; Function Attrs: noinline nounwind optnone uwtable
define dso_local void @funcmain() #0 {
call void @WMCTF_MMAP(i32 noundef 30864)
call void @WMCTF_READ(i32 noundef 26214)
%1 = load i32, i32* @cmd, align 4
call void @WMCTF_WRITE(i32 noundef %1)
ret void
}
declare void @WMCTF_MMAP(i32 noundef) #1
declare void @WMCTF_READ(i32 noundef) #1
declare void @WMCTF_WRITE(i32 noundef) #1
attributes #0 = { noinline nounwind optnone uwtable "frame-pointer"="all" "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
attributes #1 = { "frame-pointer"="all" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
!llvm.module.flags = !{!0, !1, !2, !3, !4}
!llvm.ident = !{!5}
!0 = !{i32 1, !"wchar_size", i32 4}
!1 = !{i32 7, !"PIC Level", i32 2}
!2 = !{i32 7, !"PIE Level", i32 2}
!3 = !{i32 7, !"uwtable", i32 1}
!4 = !{i32 7, !"frame-pointer", i32 2}
!5 = !{!"Ubuntu clang version 14.0.0-1ubuntu1.1"}
- 相关sh脚本
opt -load ./WMCTF.so -WMCTF -enable-new-pm=0 ./test.ll
clang -emit-llvm -S test.c -o test.ll
- 至此就打通了
后记与反思
笔者还剩一处没有懂的地方,就是这里的递归调用次数,这里笔者认为的递归次数比实际exp少1,但是通过调试发现只有exp这种形式才打得通,目前尚未解决这个问题
if ( (llvm::StringRef::contains(v35, v34[0], v34[1]) & 1) != 0 )
{
//进行这个函数的递归,v54初值为0
anonymous namespace::WMCTF::getFunctionCallValue[abi:cxx11](a1, a2, v56, v50, v54 + 1);
return a1;
}
//0,1,2,3,第3次函数调用应当不包含.addr(从第0次开始计数),防止进入if语句
在打WMCTF的时候此题最主要卡的地方是LoadInst这个东西,因为笔者逆向能力不是很强,所以想通过pwndbg调试查看执行流程以及堆栈情况看看函数在做些什么,但对于这个复杂的过程,动调真的很难看懂,看来看去就看到开辟了好复杂的堆空间,对解题没有一点帮助。
最终解决LoadInst是在尝试的过程中,偶然发现什么时候会出现load和store这两种指令,然后根据逆向的猜测,发现实际情况确实如此,再结合getOperand这个函数,推测LoadInst,StoreInst和CallInst类似,也是可以获取它的第几个参数(但一般都是第0个),从而豁然开朗,明白如何绕过这些条件判断控制流程
要加强逆向能力,要有耐心逆向,逆不出来多搜索,能否找到相近源码,或者不明白具体哪一个函数,网上搜搜是否有对应解答,都能帮助逆向。ida有时候看代码不方便,可以放到vscode里面帮助阅读。想要仅靠动调就明白函数的具体作用,笔者认为是很难的。总之逆向是一种综合能力的体现,多练,耐心,才能增强逆向能力。