工具集
IDA, gdb & pwndbg, checksec, pwntools, LibcSearcher
漏洞点
拿到程序后用checksec看,64位ELF,发现没有主程序canary,NX以及ALSR
1 | [*] '/home/ctf/pwn/pwn1' |
开IDA看反编译,轻松找到漏洞点位于vul函数中的gets,在输入0x70个字节后可能存在溢出,函数的栈大概长这样,注意64位下每个地址占8个字节
+-----------------+
| retaddr |
+-----------------+
| saved ebp |
ebp->+-----------------+
| |
| |
| |
| 0x70 |
| |
| |
var->+-----------------+
通过写0x70+8个字节后可以写到函数的返回地址,这是一个操纵点。但是由于程序本身函数并不多,所以只能通过调用libc中的函数。通过题目给出的libc可以得到各个函数相对地址,在ALSR关闭的情况下它们的实际相对地址不会改变。
泄露libc
因此第二步就是想办法泄露其中一个函数的绝对地址,这里用__libc_start_main为例,需要泄露出其在PLT表上的真实地址。
考虑到我们能用于显示的函数只有puts
,模仿main函数内调用它的过程,需要将欲要打印的字符串指针送入RDI寄存器,这个时候需要在程序中找到pop rdi并返回的gadget,修改完成寄存器后返回调用puts
,最后为了维持控制还需要返回main。这里查找gadgets部分操作可以用pwntool的ROP模块完成。
有一个坑是题目为了降低难度ELF和对面服务器是默认关闭了ALSR,但是在本机上测试时系统的全局ALSR打开将会覆盖该设置,为此需要临时关闭系统的ALSR
1 | sudo sh -c "echo 0 > /proc/sys/kernel/randomize_va_space" |
上面说的整个泄露libc地址的rop链应该是这样的
1 | rop = "A"*0x70 + "A"*8 + p64(POP_RDI_ADDR) + p64(LIBC_START_MAIN_ADDR) + p64(PUTS_ADDR) + p64(MAIN_ADDR) |
PWN!
接下来根据泄露出的地址计算出libc中函数的偏移地址,找到system函数以及"/bin/sh"
字符串,通过刚才的gadget将字符串指针传入RDI后调用system即可起shell。rop链如下
1 | payload = "A"*0x70 + "A"*8 + p64(POP_RDI) + p64(BIN_SH_STR_ADDR) + p64(SYSTEM_ADDR) |
查找rop可以在脚本里完成,也可以用ROPgadget
预先查找,完整脚本如下
1 | import time |
其实这题作为我第一次PWN还是比较有难度的,不过攻克之后的成就感我至今都难忘。