TSG CTF 2024 | Re


[Reversing, beginner] Misbehave

题目分类:

< 随机数 > <自定义memcmp>

题目信息:

Author: mikanami

There’s something strange about this binary file…

Hints for beginners…

The attached file is an ELF executable for x86-64 Linux. Running it and entering the correct FLAG will display Correct!. Use tools like Ghidra or IDA Free to get an overview of the process.

Use gdb to observe its behavior while running. You don’t need to fully understand every single process. Sometimes, it’s enough to identify the inputs and outputs.

misbehave.tar.gz

题目总览:

file查看文件信息

image-20241215225702771

扔IDA看逻辑:

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
int __fastcall main(int argc, const char **argv, const char **envp)
{
_DWORD v4[12]; // [rsp+0h] [rbp-40h] BYREF
int v5; // [rsp+30h] [rbp-10h]
int v6; // [rsp+34h] [rbp-Ch]
int i; // [rsp+38h] [rbp-8h]
char v8; // [rsp+3Fh] [rbp-1h]

v8 = 1;
v6 = 4;
input_flag(v4, 48LL, envp); //flag有48位
init(11447LL, 34LL); //初始化v4
for ( i = 0; i <= 11; ++i )
{
v5 = gen_rand(); //生成随机数
v4[i] ^= v5; //与随机数异或
if ( memcmp(&v4[i], (char *)&flag_enc + 4 * i, v6) ) //加密后与答案比较
v8 = 0;
}
if ( v8 )
puts("Correct!");
else
puts("Wrong...");
return 0;
}

看初始化v4:

1
2
3
4
5
6
7
8
9
10
11
char *__fastcall init(__int64 offset_target_23B7, __int64 offset_source_22)//这里修改传参名称,结合以下函数功能,23B7和22是main函数传入
{
char *result;

state = 0xFEEDF00DDEADBEEFLL; //初始化state
result = (char *)&loc_1381 + offset_source_22;
*(_QWORD *)((char *)&loc_1381 + offset_target_23B7) = (char *)&loc_1381 + offset_source_22; //强制类型转换,将字节指针转换为指向 64 位数据的指针 //这里解释了main中检查密文只循环了12次
//!!!!******这里!使用init函数内地址loc_1381的相对偏移量将uint64_t值写入该地址。重写的地址是memcmp@got,重写后的内容是函数13A3,也就是说,main函数调用的是23A3函数,而不是memcmp函数!!!!!***********

return result;
}

随机数生成函数,因为v5的变化在检查循环内,所以每个循环都会更新state的值

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
__int64 gen_rand()
{
int j; // [rsp+1Ch] [rbp-24h]
int i; // [rsp+20h] [rbp-20h]
unsigned int v3; // [rsp+24h] [rbp-1Ch]
unsigned __int64 v4; // [rsp+28h] [rbp-18h]
unsigned __int64 v5; // [rsp+30h] [rbp-10h]
unsigned __int64 v6; // [rsp+38h] [rbp-8h]

v6 = state & 0x1FF;
v5 = ((unsigned __int64)state >> 9) & 0x7FF;
v4 = ((unsigned __int64)state >> 20) & 0x1FFF;
for ( i = 0; i <= 31; ++i )
{
for ( j = 0; j <= 30; ++j )
{
v6 = ((v6 >> 4) ^ BYTE1(v6)) & 1 | (2 * (_WORD)v6) & 0x1FF;
v5 = (BYTE1(v5) ^ (v5 >> 10)) & 1 | (2 * (_WORD)v5) & 0x7FF;
v4 = ((v4 >> 11) ^ (v4 >> 10) ^ (v4 >> 7) ^ (v4 >> 12)) & 1 | (2 * (_WORD)v4) & 0x1FFF;
}
v3 = (v5 & (unsigned __int8)v6 | (unsigned __int8)(~(_BYTE)v6 & v4)) & 1 | (2 * v3);
}
state = v6 | (v4 << 20) | (v5 << 9); //<---这里变化
return v3;
}

很好的memcmp,可能当作strcmp了(x

在调试起来发现,链接了另一个函数

image-20241215232226386

image-20241215232220596

这里对state也有操作,每组数第一个数对state异或

题目解决:

state初始值:

state = 0xFEEDF00DDEADBEEFLL;

密文知道了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
enflag = [  
0x20, 0x60, 0x6F, 0x90, 0xAE, 0x77, 0x8F, 0xF3, 0xFC, 0x09,
0xA5, 0x5E, 0xDD, 0x6B, 0x39, 0x51, 0xDF, 0xFD, 0x6E, 0x5E,
0xA8, 0x60, 0x88, 0x85, 0xBC, 0xD7, 0x95, 0x52, 0x75, 0xE9,
0x82, 0xF3, 0xB7, 0xA2, 0x04, 0x95, 0x4A, 0x0E, 0x5C, 0x67,
0x53, 0x81, 0x13, 0xBF, 0x34, 0x61, 0x70, 0xC1]
整理一下:
enflag = [
0x906F6020,0xF38F77AE,
0x5EA509FC,0x51396BDD,
0x5E6EFDDF,0x858860A8,
0x5295D7BC,0xF382E975,
0x9504A2B7,0x675C0E4A,
0xBF138153,0xC1706134,
]

没有在memcmp被异或的state中间值:

image-20241215234159080

1
2
3
4
5
6
7
8
initia = '19BC7C670'
tmp = [
0xD3283374,0x74FC6DEF,
0xA03471DD,0x86BF5A2A,
0xECA0F9BC,0xDB9E9D94,
0xA47A61BA,0x5A46820B,
0xABD092BC,0x7908986B,
0x4AE82AEA,0xE73A17DB]

在memcmp自定义函数中,第i+1个state会与第i组明文异或*

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
state_init = 0xFEEDF00DDEADBEEF
enflag = [
0x906F6020,0xF38F77AE,0x5EA509FC,0x51396BDD,
0x5E6EFDDF,0x858860A8,0x5295D7BC,0xF382E975,
0x9504A2B7,0x675C0E4A,0xBF138153,0xC1706134,
]
state_tmp = [ //这里还是偷懒了,直接摘出来的v5,没还原rand函数
0xD3283374,0x9BF431FA,0x6DC16DCD,0x245F34B3,
0x37599EB1,0xB1D70E98,0x21CAB3D2,0xACE4D846,
0xCA3392D0,0x1539787A,0x8822F324,0xC1701C07
]
for i in range(12):
print((state_tmp[i]^enflag[i]).to_bytes(4, byteorder='little'))
'''
b'TSGC'
b'TF{h'
b'1dd3'
b'n_fu'
b'nc7i'
b'0n_4'
b'nd_s'
b'31f_'
b'g07_'
b'0ver'
b'wr17'
b'3}\x00\x00'
#TSGCTF{h1dd3n_func7i0n_4nd_s31f_g07_0verwr173}

测试一下

image-20241216113234897

一些别的:

グローバル変数stateを初期化しています。そして、init関数内部のアドレスloc_1381からの相対オフセットを使ったアドレスへ、uint64_t値を書き込んでいます。計算すると、書き換え先のアドレスはmemcmp@got、書き換え後の内容は13A3の関数でした。つまり、main関数ではmemcmp関数ではなく13A3の関数を呼び出します!

初始化全局变量状态。然后,使用 init 函数内地址 loc_1381 的相对偏移量将 uint64_t 值写入该地址。我算了一下,要重写的地址是memcmp@got,重写后的内容是函数13A3。也就是说,main函数调用的是13A3函数,而不是memcmp函数!

4 字节 memcmp 的比较的同时,全局变量状态将通过与第一个参数的 XOR 结果进行更新。

[Reversing, easy] Warmup SQLite

题目分类

< SQLite opcode>

题目信息

Author: mikitorium08

Let’s get familiar with SQLite’s bytecode

warmup_sqlite.tar.gz

题目分析

一开始没怎么搞懂readme里在说什么

1
2
3
4
5
6
7
8
9
# Warmup SQLite

`dump` is the result of `EXPLAIN <hidden SQL>` with the parameter `~~Your input is filled here~~`.

We use the same sqlite3 as SQLite of Hand, another pwn challenge in TSG CTF 5, to dump this code.

# 预热 SQLite
`dump` 是参数为 `~~Your input is filled here~~` 的 `EXPLAIN <hidden SQL>` 的结果。
我们使用与 SQLite of Hand 相同的 sqlite3 来转储这段代码,SQLite of Hand 是 TSG CTF 5 中的另一项 Pwn 挑战。

看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
import sqlite3
import re
import sys

res = [100, 115, 39, 99, 100, 54, 27, 115, 69, 220, 69, 99, 100, 191, 56, 161, 131, 11, 101, 162, 191, 54, 130, 175, 205, 191, 222, 101, 162, 116, 147, 191, 55, 24, 69, 130, 69, 191, 252, 101, 102, 101, 252, 189, 82, 116, 41, 147, 161, 147, 132, 101, 162, 82, 191, 220, 9, 205, 9, 100, 191, 38, 68, 253]
#for i in range(len(res)):
# print(chr(res[i]),end='')

def check(s):
return bool(re.match('^[a-zA-Z0-9_=}{"]+$', s))

def run(s):
conn = sqlite3.connect('hello.db')
cursor = conn.cursor()

with open('query.sql', 'r') as f:
query = f.read()

try:
cursor.execute(query, (s,))
for (idx, row) in enumerate(cursor.fetchall()):
assert(row[0] == res[idx])
print('correct')
except Exception as _:
cursor.execute(query, (s,))
for (idx, row) in enumerate(cursor.fetchall()):
assert(row[0] == res[idx])
print(row[0])
print('correct')
finally:
conn.close()

def main():
print('input string: ')
s = sys.stdin.readline().strip()
#if not (s and len(s) == 64 and check(s)):
# print("wrong")
# return
run(s)

main()

检测flag是否64位,且match('^[a-zA-Z0-9_=}{"]+$', s),之后对flag处理,与res比较

对flag的check应该是在db里面,

1
2
3
4
5
6
7
#sqlite3 Python library:使用 sqlite3 库来与 SQLite 数据库进行交互,并使用 EXPLAIN 获取 SQL 查询的 opcode 列表。
import sqlite3

conn = sqlite3.connect('example.db')
cursor = conn.cursor()
cursor.execute('EXPLAIN QUERY PLAN SELECT * FROM my_table')
print(cursor.fetchall())

看到文档里crazyman贴了一个:

https://www.sqlite.org/opcode.html

往下滑滑,找到一个很类似的东西,

image-20241220001558188

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ sqlite3 ex1.db
sqlite> explain delete from tbl1 where two<20;
addr opcode p1 p2 p3 p4 p5 comment
---- ------------- ---- ---- ---- ------------- -- -------------
0 Init 0 12 0 00 Start at 12
1 Null 0 1 0 00 r[1]=NULL
2 OpenWrite 0 2 0 3 00 root=2 iDb=0; tbl1
3 Rewind 0 10 0 00
4 Column 0 1 2 00 r[2]=tbl1.two
5 Ge 3 9 2 (BINARY) 51 if r[2]>=r[3] goto 9
6 Rowid 0 4 0 00 r[4]=rowid
7 Once 0 8 0 00
8 Delete 0 1 0 tbl1 02
9 Next 0 4 0 01
10 Noop 0 0 0 00
11 Halt 0 0 0 00
12 Transaction 0 1 1 0 01 usesStmtJournal=0
13 TableLock 0 2 1 tbl1 00 iDb=0 root=2 write=1
14 Integer 20 3 0 00 r[3]=20
15 Goto 0 1 0 00

所以dump应该是对db文件的转储,所以问题变成了,如何将 sqlite opcade 转换为可以阅读的常见指令类型(比如右侧comment)

if SQLite is compiled with the -DSQLITE_ENABLE_EXPLAIN_COMMENTS options. (乐)

难蚌

image-20241220005920304

照着opcode一点一点写规则(?

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
def opcode_to_description(opcode, p1, p2, p3, p4, p5):
descriptions = {
0: f"Start at {p2}",
1: f"R[{p1}] = {p3}",
......
}

return descriptions.get(opcode, f"Unknown opcode {opcode}")

def process_opcode_dump(dump_file_path):
with open(dump_file_path, 'r') as file:
for line in file:
if '|' not in line:
continue
parts = line.strip().split('|')
if len(parts) >= 4:
opcode = int(parts[0])
p1 = int(parts[2])
p2 = int(parts[3])
p3 = int(parts[4])
p4 = str(parts[5])
p5 = int(parts[6])
description = opcode_to_description(opcode, p1, p2, p3, p4, p5)
print(description)

process_opcode_dump('./dump')

一点一点扣规则(?

最后的integer似乎是寄存器?找一些操作行为,比如Ge、add、Multiply

1
2
3
4
5
6
7
8
9
10
11
57|Multiply|30|29|24||0
58|Add|31|24|20||0
59|Remainder|32|20|21||0
89|Integer|1|15|0||0
90|Integer|1|16|0||0
91|Integer|2|18|0||0
92|Integer|1|19|0||0
93|Integer|10|28|0||0
94|Integer|7|30|0||0
95|Integer|2|31|0||0
96|Integer|256|32|0||0

根据寄存器的内容读取第 55 条到第 66 条指令时,循环了 10 次计算 ((something * 7) + 2) % 256

本想着直接爆破来着(x 下面是一个数学方法,

一些别的

ctf_writeups/2024/tsgctf/README.md at master · moratorium08/ctf_writeups

这个师傅SQL恢复的非常好:

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
WITH RECURSIVE
split(input, rest, idx) AS (
VALUES('', ?, -1)
UNION ALL
SELECT
substr(rest, 1, 1),
substr(rest, 2),
idx + 1
FROM split
WHERE rest <> ''
),
tr(val, idx, iter) AS (
SELECT
unicode(input) AS val,
idx,
0 AS iter
FROM split
WHERE input <> ''

UNION ALL

SELECT
(tr.val * 7 + 2) % 256,
idx,
iter + 1
FROM tr
WHERE iter < 10
)
SELECT * from tr WHERE iter = 10 ORDER BY idx;

exp:(很好的数论,让我的大脑短路)# 唉,乘法逆元。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import sqlite3
import re
import sys

res = [100, 115, 39, 99, 100, 54, 27, 115, 69, 220, 69, 99, 100, 191, 56, 161, 131, 11, 101, 162, 191, 54, 130, 175, 205, 191, 222, 101, 162, 116, 147, 191, 55, 24, 69, 130, 69, 191, 252, 101, 102, 101, 252, 189, 82, 116, 41, 147, 161, 147, 132, 101, 162, 82, 191, 220, 9, 205, 9, 100, 191, 38, 68, 253]

n = pow(7, -1, 256) # <----这里
for i in range(10):
numbers = []
for x in res:
m = ((x - 2) * n) % 256
numbers.append(m)
res = numbers

print(''.join(map(chr, res)))

TSGDBinary

题目分类

< ? >

题目信息

Author: iwashiira

Everyday Tools

TSGDBinary.tar.gz

三个文件,start.sh:

1
sudo gdb --nx -x ./tsgdbinary.py ./tsgdbinary

执行 tsgdbinary 二进制文件,同时将 tsgdbinary.py 作为 GDB 脚本加载

。。哭了

serverless

题目分类

题目信息

Author: mikit

Experience the power of serverless computing.

The server is provided for illustration purposes only and there is no need to connect to the server to solve this task.

http://34.146.145.253:20906/TSGCTF{dummy_dummy}

serverless.tar.gz

image-20241223222322354

题目总览

image-20241223224850465

重定向——

很多很多的重定向/明天看看怎么个事儿

题目分析

参考资料

https://tan.hatenadiary.jp/entry/2024/12/16/013044


文章作者: W3nL0u
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 W3nL0u !
  目录