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里面帮助阅读。想要仅靠动调就明白函数的具体作用,笔者认为是很难的。总之逆向是一种综合能力的体现,多练,耐心,才能增强逆向能力。