第一次写 LLVM 的题目,感觉很难读懂,即使写完了也有一点懵懵的
runOnFunction函数位于虚表中的最后一个位置,比赛题的漏洞基本就是这个,所以在做LLVM Pass pwn的时候定位函数的位置可以从虚表入手:可以直接搜索虚表 vtable 的位置

sub_19D0是虚表中的最后一个,也就是我们常说的 runOnFunction

进入之后 F5 反编译一下:


坐牢,代码很长,这里不放出来了,一点一点分析吧:

r0oDkc4B 本身是小端序的十六进制的数值,转换成字符串之后倒序一下就是 B4ckDo0r
不难看出是 backdoor

剩下的程序过于冗杂难以分析,我们直接看汇编的流程:
这里是判断 cmp rdx,8;如果值相等就执行 mov rcx backdoor
之后的很多博主都建议依据动静结合的方法去调试程序:写一个exp.c再用clang-8编译成exp.ll
1 | //clang-8 -S -emit-llvm exp.c -o exp.ll |


我们先把断点打在跳转的位置:.so 基地址 + 0x1A14

然后单步运行,发现最后会发生跳转

1 | //clang-8 -S -emit-llvm exp.c -o exp.ll |
当我们加入 printf(“hello”); 之后会发现不会再跳转了
当执行到0x1A9E这里时会调用llvm::Value::getName,在这个函数当中会获取到 printf 函数 –> RAX

继续单步执行

获取到了printf这个函数,并且长度放入rdx中,继续调试,会执行到if ( !(unsigned int)std::string::compare(&fnc_name, "save") )这条语句,RDI 一参是printf函数的名字,RSI 二参是save,这个意思就是判断是否在B4ckDo0r中调用了save函数
save

这样的话,我们只需要在 exp.c 当中多加一个 save()即可绕过
1 | //clang-8 -S -emit-llvm exp.c -o exp.ll |


1 | * (unsigned int)((v14 + 24 * v17 - 24 * (unsigned __int64)NumTotalBundleOperandsEv - v19) >> 3) == 2 ) |
表明 save 当中很有可能是需要传入两个参数
1 |
|
执行成功之后继续往下走:就到了核心的地方,v24 和 27 都是参数,会被 memcpy 放入内存为 0x18 的 chunk


那么 save 的作用就是申请一个大小为 0x20 的堆块,传入两个参数
takeaway

takeaway 当中也有:
1 | * (unsigned int)((v14 + 24 * v35 - 24 * (unsigned __int64)NumTotalBundleOperandsEv_1 - v37) >> 3) != 1 ) |
判断是传入了一个参数,走到最后是给释放掉了

俗称没什么用
stealkey:

把 heap 的第一个八字节赋值给 byte_204100
fakekey:


fake 有一个参数,与byte_204100 相加并赋值给chunk的前8字节
run:


run 没有参数
但是
1 | ((void (__fastcall *)(_QWORD, _QWORD, _QWORD, _QWORD, _QWORD, _QWORD, _QWORD, _QWORD, _QWORD, _QWORD, _QWORD, _QWORD, _QWORD, _QWORD, _QWORD, _QWORD, _QWORD, _QWORD, _QWORD, _QWORD))*::heap) |
会调用 chunk 的前八个字节 4
EXP:
1 | void save(char *a,char *b); |
- save 会创建一个 0x20 的堆,放入两个参数
- stealkey,会将save的第一个参数放入 byte_204100
- fakekey 会先传入一个参数,然后和 byte_204100 相加
- run 会执行
*heap_ptr
因为最后会执行 *heap_ptr,所以我们可以将 *heap_ptr 改成one_gadget

我们可以发现 Tcachebin 已经挂满了,我们再创建堆就会到 UnSortedbin 当中,再创建时 UnSortedbin 就会到 smallbin 当中,这个时候 chunk 中就会有残余的 libc 指针,我们就可以通过 stealkey 把第一个参数赋值给
这里可以看到,save会malloc一个0x20大小的chunk,调试发现,save一次后,tcache中没有符合要求的chunk了,再save一次,就会变成smallbins,这时候chunk中会有残留的libc指针,再通过stealkey把指针赋值给byte_204100
再用fakekey对指针进行偏移的加减,改为one_gadget,执行run函数,即可

最终可以获取到 shell:
1 | void save(char *a, char *b); |
1 | ./opt -load ./SAPass.so -SAPass ./exp.ll -disable-output |

让人很莫名其妙的是:没有任何操作,只是加载下面的空操作
1 | void save(char *a, char *b); |
然后 bins 就会变成这样子,并且就算后面操作也一直是这种情况,不清楚是怎么回事。。。。。。

说些什么吧!