昨天打了国赛,记录下pwn题
SATools
大晚上放题,还叫冲刺题,还让人好好睡觉了不
题目是一个llvm的pass,经过分析后是FunctionPass,sub19D0是主要处理函数
大部分都是调试和猜测得知:
- save原型应该是save(const char* s1,const char* s2),返回值和函数体里是怎么样无所谓,因为没有对函数体的东西进行操作
- takeaway的原型是takeaway(const char* s)
- run的原型是run(void)
- stealkey的原型是stealkey(void)
- fakekey的原型是fakekey(int offset)
反编译里显示的:
1 | v19 = llvm::CallBase::getNumTotalBundleOperands((llvm::CallBase *)(v6 - 24)); |
应该是在检查函数参数的个数,比如这里应该是检查函数的参数是不是两个
pass在处理他们的时候会对bss段上的一个结构体进行操作:
1 | .bss:00000000002040F8 ; _QWORD *byte_2040f8 |
save:
- save会把参数1和参数2复制到一个0x18大小的堆中,这里好像可以堆溢出的,但是后面利用的时候并没用用到
takeaway:
- byte_2040f8 = (_QWORD *)byte_2040f8[2];,大概就是这个效果,前面还有一堆的判断
stealkey:
- byte_204100 = *byte_2040f8;
fakekey:
- orig = byte_204100
- byte_204100 = orig + offset;
- *byte_2040f8 = orig + offset;
run:
- call *byte_2040f8
综合上面调试发现,最终的利用思路是:
- 先调用好几个save,使得堆用unsorted bin来进行分配,这样就会残留libc地址,这也是为什么参数s1和s2都不填东西,这样就只会复制一个’\x00’上去
- 接着用stealkey把残留的libc地址放到byte_204100上
- 再用fakekey把one_gadget和残留的libc地址的偏移加上
- 最后调用run即可
exp.c:
1 | int save(const char* s1,const char* s2){ |
用clang编译下:clang -emit-llvm -c exp.c -o exp.bc
pwny
读和写的位置都是我们可以控制的
1 | unsigned __int64 write_() |
两次write,idx都选256,覆盖掉fd,这样后面读都是从输入流读了
exp:
1 | from pwn import * |
lonelywolf
只有一个堆块,但是free没有清空指针,可以UAF,利用UAF修改掉以释放的堆块的key就能double free了,用scanf来触发malloc_consolidate来取得unsorted bin
silverwolf
好像流程和lonelywolf一样,但是只能orw,所以改free_hook为setcontext+53来进行rop
game
只允许open,read,write,mprotect
命令格式:
s应该是size
add_chunk:
1 | op:1\n |
add_desc:
1 | op:2\n |
dele_desc:
1 | op:3\n |
show_desc:
1 | op:4\n |
5,6,7,8都是写入操作,能越界写
channel
arm64的架构,libc也是2.31所以能用tcache,其实就是arm64下的堆题
运行:
1 | mkdir lib |
漏洞点:
1 | void *unregister() |
根据漏洞点,我们可以伪造一个chunk,在伪造chunk里的content来进行任意地址泄露,根据泄露的远程的堆地址,可以知道远程的环境使用qemu-user启动的,故地址都不会变化
泄露了之后,在利用write功能里的赋值来对tcache结构进行攻击,伪造tcache链,最后getshell
1 | void *write_() |
最终exp:
1 | from pwn import * |
要注意的就是本地调试的时候,好像是stdin在堆上分配了一个0x410大小的chunk,故本地泄露的地址和远程泄露的有点不一样,最后比对key的时候也要注意这里