l3hctf-re-wp

l3hctf

参考

Tauri 框架的静态资源提取方法探究 | yllhwa's blog

oacia/stalker_trace_so: 一个IDA插件,利用frida-stalker在加载so时打印出所有函数调用,解决frida-trace无法在so加载时trace的问题

frida-stalker-trace 使用并实战某音 | mgaic

‍

‍

‍

‍

ez_android

一开始想拿插件去trace的但是崩溃了 但是这个题不用trace也能做

image

tauriactivity的app 解包静态资源

image

分别对应文件名及其长度 文件内容及其长度

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
import os
import brotli

addr = 0xC9498 # 这里的地址是压缩内容的地址
endadd = 0xC9498+0xeb
dump = [0] * (endadd - addr)

for i in range(addr, endadd):
dump[i - addr] = get_wide_byte(i)

file_path = r'dump.br'

with open(file_path, 'wb') as file:
file.write(bytes(dump))

print(f"Dump written to {file_path}")
import brotli

compressed_file_path = "dump.br"
output_file_path = "dumpp"


content = open(compressed_file_path, "rb").read()
print(f"Compressed file size: {len(content)} bytes")


def try_decompress(data):
try:
return brotli.decompress(data)
except brotli.error:
return None


for i in range(len(content), 0, -1):
decompressed = try_decompress(content[:i])
if decompressed:
break


if decompressed:
open(output_file_path, "wb").write(decompressed)
print(f"Decompressed content written to {output_file_path}")
else:
print("Failed to decompress the content.")

这里直接拿idapython更快点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Tauri + Vue + Typescript App</title>
<script type="module" crossorigin src="/../blogpic/index-BsFf5qny.js"></script>
<link rel="stylesheet" crossorigin href="/../blogpic/index-CJgrqWFO.css">
</head>

<body>
<div id="app"></div>

</body>
</html>

解压的内容 就是js

1
{"$schema":"https://schema.tauri.app/config/2","productName":"ez_android","version":"0.1.0","identifier":"com.l3hctf.ezandroid","app":{"windows":[{"label":"main","create":true,"url":"index.html","dragDropEnabled":true,"center":false,"width":800.0,"height":600.0,"resizable":true,"maximizable":true,"minimizable":true,"closable":true,"title":"ez_android","fullscreen":false,"focus":true,"transparent":false,"maximized":false,"visible":true,"decorations":true,"alwaysOnBottom":false,"alwaysOnTop":false,"visibleOnAllWorkspaces":false,"contentProtected":false,"skipTaskbar":false,"titleBarStyle":"Visible","hiddenTitle":false,"acceptFirstMouse":false,"shadow":true,"incognito":false,"zoomHotkeysEnabled":false,"browserExtensionsEnabled":false,"useHttpsScheme":false,"javascriptDisabled":false,"allowLinkPreview":true,"disableInputAccessoryView":false}],"security":{"freezePrototype":false,"dangerousDisableAssetCspModification":false,"assetProtocol":{"scope":[],"enable":false},"pattern":{"use":"brownfield"},"capabilities":[]},"macOSPrivateApi":false,"withGlobalTauri":false,"enableGTKAppId":false},"build":{"devUrl":"http://localhost:1420/","frontendDist":"../dist","beforeDevCommand":"pnpm dev","beforeBuildCommand":"pnpm build","removeUnusedCommands":false},"bundle":{"active":true,"targets":"all","createUpdaterArtifacts":false,"icon":["icons/32x32.png","icons/128x128.png","icons/128x128@2x.png","icons/icon.icns","icons/icon.ico"],"useLocalToolsDir":false,"windows":{"digestAlgorithm":null,"certificateThumbprint":null,"timestampUrl":null,"tsp":false,"webviewInstallMode":{"type":"downloadBootstrapper","silent":true},"allowDowngrades":true,"wix":null,"nsis":null,"signCommand":null},"linux":{"appimage":{"bundleMediaFramework":false,"files":{}},"deb":{"files":{}},"rpm":{"release":"1","epoch":0,"files":{}}},"macOS":{"files":{},"minimumSystemVersion":"10.13","hardenedRuntime":true,"dmg":{"windowSize":{"width":660,"height":400},"appPosition":{"x":180,"y":170},"applicationFolderPosition":{"x":480,"y":170}}},"iOS":{"minimumSystemVersion":"13.0"},"android":{"minSdkVersion":24}},"plugins":{}}

image

直接来到greet函数 单字节直接爆

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
#include<stdio.h>
#include"defs.h"
#include<stdint.h>
// #include <stdio.h>
#include <string.h>
int main(){
__int64 v8; // x23
_QWORD *v9; // x0
// _QWORD *v10; // x20
unsigned __int64 index; // x8
unsigned __int64 n0xE_1; // x15
unsigned __int8 v13; // w13
__int64 n16; // x22
_QWORD *v18; // x0
__int64 n16_1; // x8
void *result; // x0
uint8_t v10[27] = {0}; // This appears to be the target buffer
uint8_t byte_CE031[] = {0x64,0x47,0x68,0x70,0x63,0x32,0x6C,0x7A,0x59,0x57,0x74,0x6C,0x65,0x51};
uint8_t v21[11] = {0};

memcpy(&v21[8], "O2*", 3);
*(uint64_t *)v21 = 0xFC020A4C0E2C7290LL;

*(uint64_t *)v10 = 0xA409663A025150CLL;
*(uint64_t *)(v10 +8)= 0x1FE106294065165CLL;
*(uint64_t *)(v10 +16) = 0xFC020A4C0E2C7290LL;
*(uint64_t *)(v10 + 19) = *((uint64_t *)&v21[3]);



for (unsigned int index = 0; index < 27; ++index) {
for (int flag = 0; flag < 256; ++flag) {
unsigned int n0xE_1 = index;
if (index >= 14) {
n0xE_1 = index - 14;
}
uint8_t temp = ((2 * index) | 1);
uint8_t idx = (temp - 14 * ((147 * temp) >> 11));
uint8_t v13 = byte_CE031[idx] + (flag ^ byte_CE031[n0xE_1]);

uint8_t rot_amount = byte_CE031[(index + 3) % 14] & 7;
uint8_t data = byte_CE031[(index + 4) % 14] ^
((v13 << rot_amount) | (v13 >> (8 - rot_amount)));

if (data == v10[index]) {
printf("%c", flag);
break;
}

}
}

}

//L3HCTF{ez_rust_reverse_lol}

‍

‍

snake

遗憾败北 一开始就找到分数的地方 但是旁边就是rc4影响了判断 以为又是解密字符串 不然的话早出了 寄~~~~~~~~~~~~~~~~~~~~~~~

反调试我们使用

image

快速过掉 字符串查找可以看到一个base表 断读写断点

image

直接f9就到了使用base表解出 提示移动键的字符串 当然这里只是对程序的分析 接下来我们使用findcrypto去找 可以找到xxtea的内容 再去查看引用 断点

image

image

可以看到我们的内容是已经输出了 但是还没开始动 继续调试查找 判断逻辑

image

image

这里就是分数的地方 这里最好看汇编 c反编译的有点 这里就是判断全给他删除

image

‍

obfuscate

小混淆问题不大

image

这些都不太影响执行走一遍就出来了 有几处反调试想要过一下 看看哪里跑飞了就进去断点慢慢调试 当然了你可以直接在scanf断点就完了

image

image

‍

‍

‍

image

给我们的输入断个点好跟一下

到了这里

image

image

一路猛调就到了这里

image

‍

image

其中有个地方赋值了

image

也就是key 继续分析

image

这一段就开始使用key生成一些东西

image

跑完那一段 可以看到key也被改了 断点继续跟踪

image

再次被引用

image

也就是这里

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
__int64 __fastcall sub_555555555250(__int64 a1, __int64 a2)
{
_DWORD *v2; // rax
_DWORD *v3; // rax
_DWORD *v4; // rax
_DWORD *v5; // rcx
_DWORD *v6; // rdx
_DWORD *key; // rsi
_DWORD *v8; // rdi
unsigned int *v9; // rdi
unsigned int *key_2; // rax
_DWORD *v11; // rsi
unsigned int *v12; // rcx
unsigned int v13; // edx
int v14; // r8d
unsigned int *v15; // rcx
unsigned int v16; // edx
int *v17; // rax
_DWORD v19[3]; // [rsp+0h] [rbp-70h] BYREF
_DWORD **v24; // [rsp+10h] [rbp-60h]
_QWORD *v25; // [rsp+18h] [rbp-58h]
_DWORD *v26; // [rsp+20h] [rbp-50h]
_DWORD *v27; // [rsp+28h] [rbp-48h]
_DWORD *key_1; // [rsp+30h] [rbp-40h]
_DWORD *v29; // [rsp+38h] [rbp-38h]
_DWORD *v30; // [rsp+40h] [rbp-30h]
_DWORD *v31; // [rsp+48h] [rbp-28h]
_DWORD *v32; // [rsp+50h] [rbp-20h]
__int64 v33; // [rsp+58h] [rbp-18h]
__int64 v34; // [rsp+60h] [rbp-10h]

v33 = a1;
v34 = a2;
v24 = &v19[-4];
v25 = &v19[-4];
v26 = &v19[-4];
v27 = &v19[-4];
key_1 = &v19[-4];
v29 = &v19[-4];
v30 = &v19[-4];
v31 = &v19[-4];
v32 = &v19[-4];
*&v19[-4] = a1;
v19[1] = HIDWORD(a2);
v19[0] = 0;
while ( *v29 < 4u )
{
v2 = v30;
v26[*v29] = 0;
*v2 = 0;
while ( *v30 < 4u )
{
v26[*v29] = *(*v25 + (*v30 + 4 * *v29)) + (v26[*v29] << 8);
++*v30;
}
++*v29;
}
v3 = v29;
**v24 = -1209970333;
*v3 = 1;
while ( *v29 < 0x1Au )
{
(*v24)[*v29] = (*v24)[*v29 - 1] - 1640531527;
++*v29;
}
v4 = v31;
v5 = v32;
v6 = v27;
key = key_1;
v8 = v29;
*v30 = 0;
*v8 = 0;
*key = 0;
*v6 = 0;
*v5 = 78;
*v4 = 0;
while ( *v31 < *v32 )
{
v9 = v30;
key_2 = key_1;
v11 = v26;
v12 = v27;
v13 = ((*v27 + (*v24)[*v29] + *key_1) >> 29) | (8 * (*v27 + (*v24)[*v29] + *key_1));
(*v24)[*v29] = v13;
*v12 = v13;
v14 = (*v12 + v11[*v9] + *key_2) << (*v12 + *key_2);
v15 = v29;
v16 = ((*key_2 + *v27 + v11[*v9]) >> (-19 - (*key_2 + *v27) + 51)) | v14;
v11[*v9] = v16;
*key_2 = v16;
v17 = v30;
*v15 = (*v15 + 1) % 0x1A;
*v17 = (*v17 + 1) & 3;
++*v31;
}
return 1LL;
}

优化起来丢给ai 我反正是看不懂

image

‍

调试起来去看看使用key获取到的内容 由于我们在这里给了输入断点但是并没有发现这部分有关于输入的暂停 这里就是keybox的生成

image

断点在mov的寄存器 继续调试

image

直接大跳就可以来到正确的加密位置 解一下混淆 分析一下流程 编写解密代码

‍

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


def decrypt_block(input_data):
key = [
0x122F2C9C, 0xE3BCCAE7, 0xD0FFC0F2, 0xD9A12544, 0x8A27992F, 0x55B1B935, 0x9110B161, 0x92811564,
0x5CE9B359, 0x77C79A51, 0x4265527A, 0x8AB57C4B, 0x11529FA4, 0x9D9F63FF, 0xA970B936, 0xC8EABA0D,
0x9A0EB4AA, 0xB0BC6E7F, 0x9784B100, 0x70DCD3AE, 0x6057A44E, 0x89187658, 0xE00098A8, 0x45773540,
0xF9374F1A, 0x913FA548
]

# Unpack two 32-bit unsigned integers
A, B = struct.unpack('<II', input_data)

for i in range(12, 0, -1):
A ^= B

B -= key[2 * i + 1]
B &= 0xFFFFFFFF # Ensure 32-bit
shift = (32 - (A % 32)) % 32 # Ensure valid shift count (0-31)
B = ((B << shift) | (B >> (32 - shift))) & 0xFFFFFFFF
B ^= A

A -= key[2 * i]
A &= 0xFFFFFFFF # Ensure 32-bit
shift = (32 - (B % 32)) % 32 # Ensure valid shift count (0-31)
A = ((A << shift) | (A >> (32 - shift))) & 0xFFFFFFFF
A ^= B

A = (A - key[0]) & 0xFFFFFFFF
B = (B - key[1]) & 0xFFFFFFFF

return struct.pack('<II', A, B)


def main():
cipher = [
0xF2A1BB1B, 0x21877CE9, 0x0AFD378A, 0xBC811A94,
0xAAE31E40, 0x3FD82E73, 0x4271B884, 0x398B35CC
]

# Convert cipher to bytes
cipher_bytes = b''.join(struct.pack('<I', x) for x in cipher)

decrypted = bytearray()
for i in range(0, len(cipher_bytes), 8):
block = cipher_bytes[i:i + 8]
decrypted_block = decrypt_block(block)
decrypted.extend(decrypted_block)

# Convert decrypted bytes to string
decrypted_str = decrypted.decode('ascii', errors='ignore')
print(f"L3HCTF{{{decrypted_str[:32]}}}")


if __name__ == "__main__":
main()