hook_fish

运行应用提示输入一串字符并要求联网。

jadx分析,发现在main中,在点击按钮后会调用encrypt()方法对我们输入的数据加密,然后调用fish()方法下载了一个“hook_fish.dex”文件。通过下面的registerReceiver方法创注册了一个广播接收器,只要收到系统下载完成的广播时,就会调用onReceive()方法。

1
2
registerReceiver(this.downloadCompleteReceiver, new IntentFilter("android.intent.action.DOWNLOAD_COMPLETE"));

在onReceive()方法中把加密后的的输入传入了MainActivity.this.loadClass();,加载下载来的.dex文件并反射调用了encode(input0);对输入再次加密,最后调用check()方法检查结果。然后删除本地的“hook_fish.dex”文件。

我们先hook file.delete()方法,在删除前转移出dex文件(脚本1);

在savedDexFile.dex中我们可以发现是一个自定义的加密方法,并且还附带了解密函数,和密文,我们直接拿下来用,用java写解题脚本就行(脚本2)。(在官方的脚本中通过hook调用decode方法先解密密文,再逆向写出decrypt解密)。

脚本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
Java.perform(function(){ 
// var File = Java.use('java.io.File');
// File.delete.implementation = function() {
// var filePath = this.getAbsolutePath(); // 获取文件路径
// console.log("文件将在删除之前被保存: " + filePath);

// // 在删除之前复制文件到其他位置
// var newFile = Java.use('java.io.File');
// var destPath = "/data/data/com.example.hihitt/files/savedDexFile.dex"; // 新文件路径
// var sourceFile = this;
// var inputStream = Java.use('java.io.FileInputStream').$new(sourceFile);
// var outputStream = Java.use('java.io.FileOutputStream').$new(destPath);

// var buffer = Java.array('byte', [1024]); // 缓冲区
// var bytesRead;
// while ((bytesRead = inputStream.read(buffer)) !== -1) {
// outputStream.write(buffer, 0, bytesRead);
// }
// inputStream.close();
// outputStream.close();
// console.log("文件已保存到: " + destPath);

// // 执行原本的删除操作
// return this.delete();
// };

let MainActivity = Java.use("com.example.hihitt.MainActivity");
MainActivity["code"].implementation = function (a, index) {
console.log(`MainActivity.code is called: a=${a}, index=${index}`);
this["code"](a, index);
};

MainActivity["encrypt"].implementation = function (str) {
console.log(`MainActivity.encrypt is called: str=${str}`);
let result = this["encrypt"](str);
console.log(`MainActivity.encrypt result=${result}`);
return result;
};
})


脚本2(不太会处理java的数据,最后用python来进行最后的异或)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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
import java.util.HashMap;

public class Main {
public static void main(String[] args) {

String strr = "jjjliijijjjjjijiiiiijijiijjiijijjjiiiiijjjjliiijijjjjljjiilijijiiiiiljiijjiiliiiiiiiiiiiljiijijiliiiijjijijjijijijijiilijiijiiiiiijiljijiilijijiiiijjljjjljiliiijjjijiiiljijjijiiiiiiijjliiiljjijiiiliiiiiiljjiijiijiijijijjiijjiijjjijjjljiliiijijiiiijjliijiijiiliiliiiiiiljiijjiiliiijjjliiijjljjiijiiiijiijjiijijjjiiliiliiijiijijijiijijiiijjjiijjijiiiljiijiijilji";
hookfish hf = new hookfish();
String result = hf.decode(strr);

// 输出解码后的结果
System.out.println("解码结果: " + result);
String decryptedString = decrypt(result);
System.out.println("解密后的字符串: " + decryptedString);
}
// 解密方法
public static String decrypt(String str) {
// 将加密后的字符串转回字符数组
char[] str3 = str.toCharArray();

// 逆操作字符数组变换
for (int i = 0; i < str3.length; i++) {
if ((char) ((str3[i] - (i % 4)) + '1') >= 'a' && (char) ((str3[i] - (i % 4)) + '1')<= 'f') {
str3[i] = (char) ((str3[i] - (i % 4)) + '1');
} else {
str3[i] = (char) (str3[i] - ('7' + (i % 10)));
}
}
// 恢复异或操作
code(str3, 0);

// 将字符数组转为十六进制字符串
String str2= new String(str3);

// 将字节数组转换为原始字符串并返回
return str2;
}

// 逆操作异或
private static void code(char[] a, int index) {
if (index >= a.length - 1) {
return;
}
a[index] = (char) (a[index] ^ a[index + 1]);
a[index + 1] = (char) (a[index] ^ a[index + 1]);
a[index] = (char) (a[index] ^ a[index + 1]);
code(a, index + 2);

}


}

public class hookfish {
private HashMap<String, Character> fish_dcode;
private HashMap<Character, String> fish_ecode;
private String strr = "jjjliijijjjjjijiiiiijijiijjiijijjjiiiiijjjjliiijijjjjljjiilijijiiiiiljiijjiiliiiiiiiiiiiljiijijiliiiijjijijjijijijijiilijiijiiiiiijiljijiilijijiiiijjljjjljiliiijjjijiiiljijjijiiiiiiijjliiiljjijiiiliiiiiiljjiijiijiijijijjiijjiijjjijjjljiliiijijiiiijjliijiijiiliiliiiiiiljiijjiiliiijjjliiijjljjiijiiiijiijjiijijjjiiliiliiijiijijijiijijiiijjjiijjijiiiljiijiijilji";

public hookfish() {
encode_map();
decode_map();
}

public void encode_map() {
HashMap<Character, String> hashMap = new HashMap<>();
this.fish_ecode = hashMap;
hashMap.put('a', "iiijj");
this.fish_ecode.put('b', "jjjii");
this.fish_ecode.put('c', "jijij");
this.fish_ecode.put('d', "jjijj");
this.fish_ecode.put('e', "jjjjj");
this.fish_ecode.put('f', "ijjjj");
this.fish_ecode.put('g', "jjjji");
this.fish_ecode.put('h', "iijii");
this.fish_ecode.put('i', "ijiji");
this.fish_ecode.put('j', "iiiji");
this.fish_ecode.put('k', "jjjij");
this.fish_ecode.put('l', "jijji");
this.fish_ecode.put('m', "ijiij");
this.fish_ecode.put('n', "iijji");
this.fish_ecode.put('o', "ijjij");
this.fish_ecode.put('p', "jiiji");
this.fish_ecode.put('q', "ijijj");
this.fish_ecode.put('r', "jijii");
this.fish_ecode.put('s', "iiiii");
this.fish_ecode.put('t', "jjiij");
this.fish_ecode.put('u', "ijjji");
this.fish_ecode.put('v', "jiiij");
this.fish_ecode.put('w', "iiiij");
this.fish_ecode.put('x', "iijij");
this.fish_ecode.put('y', "jjiji");
this.fish_ecode.put('z', "jijjj");
this.fish_ecode.put('1', "iijjl");
this.fish_ecode.put('2', "iiilj");
this.fish_ecode.put('3', "iliii");
this.fish_ecode.put('4', "jiili");
this.fish_ecode.put('5', "jilji");
this.fish_ecode.put('6', "iliji");
this.fish_ecode.put('7', "jjjlj");
this.fish_ecode.put('8', "ijljj");
this.fish_ecode.put('9', "iljji");
this.fish_ecode.put('0', "jjjli");
}

public void decode_map() {
HashMap<String, Character> hashMap = new HashMap<>();
this.fish_dcode = hashMap;
hashMap.put("iiijj", 'a');
this.fish_dcode.put("jjjii", 'b');
this.fish_dcode.put("jijij", 'c');
this.fish_dcode.put("jjijj", 'd');
this.fish_dcode.put("jjjjj", 'e');
this.fish_dcode.put("ijjjj", 'f');
this.fish_dcode.put("jjjji", 'g');
this.fish_dcode.put("iijii", 'h');
this.fish_dcode.put("ijiji", 'i');
this.fish_dcode.put("iiiji", 'j');
this.fish_dcode.put("jjjij", 'k');
this.fish_dcode.put("jijji", 'l');
this.fish_dcode.put("ijiij", 'm');
this.fish_dcode.put("iijji", 'n');
this.fish_dcode.put("ijjij", 'o');
this.fish_dcode.put("jiiji", 'p');
this.fish_dcode.put("ijijj", 'q');
this.fish_dcode.put("jijii", 'r');
this.fish_dcode.put("iiiii", 's');
this.fish_dcode.put("jjiij", 't');
this.fish_dcode.put("ijjji", 'u');
this.fish_dcode.put("jiiij", 'v');
this.fish_dcode.put("iiiij", 'w');
this.fish_dcode.put("iijij", 'x');
this.fish_dcode.put("jjiji", 'y');
this.fish_dcode.put("jijjj", 'z');
this.fish_dcode.put("iijjl", '1');
this.fish_dcode.put("iiilj", '2');
this.fish_dcode.put("iliii", '3');
this.fish_dcode.put("jiili", '4');
this.fish_dcode.put("jilji", '5');
this.fish_dcode.put("iliji", '6');
this.fish_dcode.put("jjjlj", '7');
this.fish_dcode.put("ijljj", '8');
this.fish_dcode.put("iljji", '9');
this.fish_dcode.put("jjjli", '0');
}

public String encode(String str) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < str.length(); i++) {
sb.append(this.fish_ecode.get(Character.valueOf(str.charAt(i))));
}
return sb.toString();
}

public String decode(String str) {
StringBuilder sb = new StringBuilder();
int i = 0;
int i2 = 0;
while (i2 < str.length() / 5) {
int i3 = i + 5;
sb.append(this.fish_dcode.get(str.substring(i, i3)));
i2++;
i = i3;
}
return sb.toString();
}

public boolean check(String str) {
if (str.equals(this.strr)) {
return true;
}
return false;
}
}


//VNCTF{u_re4l1y_kn0w_H0Ok_my_f1Sh!1l}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

hex_str = "9a9287988abfb9a3b6a978b075bda3afb274bba38c7493afa3b1bda3aa7597ac6575b0c1"

# 将十六进制字符串按每8字节(即16个字符)分块
chunk_size = 2
chunks = [hex_str[i:i+chunk_size] for i in range(0, len(hex_str), chunk_size)]

# 将每个块转换为十进制并存入列表
decimal_list = [int(chunk, 16) for chunk in chunks]

# 打印结果
print(decimal_list)
print(chunks)

for i in decimal_list:
print(chr(i - 68),end='')



kotlindroid

在SearchActivityKt初始化加密并验证flag。密文是”MTE0NTE0HMuJKLOW1BqCAi2MxpHYjGjpPq82XXQ/jgx5WYrZ2MV53a9xjQVbRaVdRiXFrSn6EcQPzA==”,iv是”114514”,加密方式是”AES/GCM/NoPadding”。密钥在Button$lambda$7$lambda$6方法内由两部分异或加密后拼接而成,后面传入了check函数中,hook一下就能拿到(也可以自己解)。但是没看到加密的位置,hook一下check的参数,发现输入的数据在经过sec()方法后就被加密了,查看sec方法后发现,数据被传入了SearchActivityKt$sec$1类。

加密逻辑在SearchActivityKt$sec$1类里面,在SearchActivityKt$sec$1方法下我们可以看到,被传入的函数被作为结果函数在最后执行,并接受明文和密钥。加密逻辑在invokeSuspend()方法里。

image-20250214003720064

这里还用JNI.INSTANCE.getAt()方法获取了一个从so层传来的字符串并在updateAAD(bytes)中作为加密的ADD传入。之后进行AES-gcm的加密,最后再转为base64。hook一下JNI.INSTANCE.getAt()获得ADD。从题目提示得知tag也是密文的一部分。AES-GCM的密文结果一般是”iv | encrypt | tag(16bit)”。我们把密文base64解码后获取中间的密文部分。再提取tag,输入数据解密就行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from base64 import b64decode
from Crypto.Cipher import AES

key = b"atrikeyssyekirta"
data = "MTE0NTE0HMuJKLOW1BqCAi2MxpHYjGjpPq82XXQ/jgx5WYrZ2MV53a9xjQVbRaVdRiXFrSn6EcQPzA=="
aad = b"mysecretadd"

decoded_data = b64decode(data)

iv = decoded_data[:6]
ciphertext = decoded_data[6:-16]
tag = decoded_data[-16:]

enc = AES.new(key, AES.MODE_GCM, nonce=iv)
enc.update(aad)

flag = enc.decrypt_and_verify(ciphertext, tag)

print(flag)

hook脚本

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
Java.perform(function(){ 
let TextKt = Java.use("androidx.compose.material3.TextKt");
TextKt["Text--4IGK_g"].overload('java.lang.String', 'androidx.compose.ui.Modifier', 'long', 'long', 'androidx.compose.ui.text.font.FontStyle', 'androidx.compose.ui.text.font.FontWeight', 'androidx.compose.ui.text.font.FontFamily', 'long', 'androidx.compose.ui.text.style.TextDecoration', 'androidx.compose.ui.text.style.TextAlign', 'long', 'int', 'boolean', 'int', 'int', 'kotlin.jvm.functions.Function1', 'androidx.compose.ui.text.TextStyle', 'androidx.compose.runtime.Composer', 'int', 'int', 'int').implementation = function (text, modifier, color, fontSize, fontStyle, fontWeight, fontFamily, letterSpacing, textDecoration, textAlign, lineHeight, overflow, softWrap, maxLines, minLines, function1, style, $composer, $changed, $changed1, i) {
console.log(`TextKt.m2466Text4IGK_g is called: text=${text}, modifier=${modifier}, color=${color}, fontSize=${fontSize}, fontStyle=${fontStyle}, fontWeight=${fontWeight}, fontFamily=${fontFamily}, letterSpacing=${letterSpacing}, textDecoration=${textDecoration}, textAlign=${textAlign}, lineHeight=${lineHeight}, overflow=${overflow}, softWrap=${softWrap}, maxLines=${maxLines}, minLines=${minLines}, function1=${function1}, style=${style}, $composer=${$composer}, $changed=${$changed}, $changed1=${$changed1}, i=${i}`);
this["Text--4IGK_g"](text, modifier, color, fontSize, fontStyle, fontWeight, fontFamily, letterSpacing, textDecoration, textAlign, lineHeight, overflow, softWrap, maxLines, minLines, function1, style, $composer, $changed, $changed1, i);
};


let SearchActivityKt = Java.use("com.atri.ezcompose.SearchActivityKt");
SearchActivityKt["check"].implementation = function (text, context, key) {
console.log(`SearchActivityKt.check is called: text=${text}, context=${context}, key=${key}`);
this["check"](text, context, key);
};

SearchActivityKt["sec"].implementation = function (context, secretKey, text, function1) {
console.log(`SearchActivityKt.sec is called: context=${context}, secretKey=${secretKey}, text=${text}, function1=${function1}`);
this["sec"](context, secretKey, text, function1);
};

SearchActivityKt["check$lambda$14"].implementation = function (context, flag) {
console.log(`SearchActivityKt.check$lambda$14 is called: context=${context}, flag=${flag}`);

let result = this["check$lambda$14"](context, flag);
console.log(`SearchActivityKt.check$lambda$14 result=${result}`);
return result;
};

let JNI = Java.use("com.atri.ezcompose.JNI");
JNI["getAt"].implementation = function () {
console.log(`JNI.getAt is called`);
let result = this["getAt"]();
console.log(`JNI.getAt result=${result}`);
return result;
};
let Intrinsics = Java.use("kotlin.jvm.internal.Intrinsics");
Intrinsics["checkNotNullExpressionValue"].implementation = function (value, expression) {
console.log(`Intrinsics.checkNotNullExpressionValue is called: value=${value}, expression=${expression}`);
this["checkNotNullExpressionValue"](value, expression);
};
})

幸运转盘

鸿蒙逆向。用abc编译器反编译.hap解压后的etc\modules.abc文件。被混淆了,不过不影响分析,用搜索寻找函数的声明与引用就行。

我们可以看到一个Index页面,这是第一个界面,里面有一个#~@0>@1*^d*#()方法。

image-20250214012747907

这里先加载了一个lib库,里面传入了一些数据,我们ctrl+f搜索一下名字就会发现_lexenv_0_1_.numX是输入数据的长度,lexenv_0_1.pw是我们输入的数据,还有一个常量24也被传入。然后把返回的结果传参传给了pages/MyPage。

我们来看MyPage,往下看会看到一串密文。

image-20250214013405155

在lexenv_0_0_ = Uint8Array(buffer.from(lexenv_0_5.result).buffer);中我们发现_lexenv_0_5就是MyPage这个对象,从中我们获取了result这个属性并转为字符串。这就是从Index传来的参数。

往下看ldlexvar.forEach()这个方法对我们的输入进行了一些操作,第一个是(result+1)^7,第二个就是在和_lexenv_0_1也就是密文进行比较。

现在我们看c层对我们的输入进行的操作,ida打开libhello.so搜索MyCry,这是在调用lib库的时候传入参数的的函数。其实在你第一步解密密文时就会发现这是一个base64加密编码,也可以去字符串表找base64的表用交叉引用定位函数。

image-20250214015928368

有一个+3还有两个RC4魔改加密,试两次就行。估计是等于50的那个。

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 base64

def KSA(key):
""" Key-Scheduling Algorithm (KSA) """
S = list(range(256))
j = 0
for i in range(256):
j = (j + S[i] + key[i % len(key)]) % 256
S[i], S[j] = S[j], S[i]
return S


def PRGA(S):
""" Pseudo-Random Generation Algorithm (PRGA) """
i, j = 0, 0
while True:
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i]
K = S[(S[i] + S[j]) % 256]
yield K


def RC4(key, text):
""" RC4 encryption/decryption """
S = KSA(key)
keystream = PRGA(S)
res = []
for char in text:
res.append(char ^ next(keystream))
return bytes(res)

s = [101, 74, 76, 49, 101, 76, 117, 87, 55, 69, 118, 68, 118, 69, 55, 67, 61, 83, 62, 111, 81, 77, 115, 101, 53, 73, 83, 66, 68, 114, 109, 108, 75, 66, 97, 117, 93, 127, 115, 124, 109, 82, 93, 115]
enc=""

for i in range(len(s)):
enc += chr((s[i]^7) -1)
print(enc)

dec = base64.b64decode(enc.encode())

enc_1 = []
for i in dec:
b = i ^ 0x28
enc_1.append(b)

key = b'Take_it_easy'

ciphertext = RC4(key, enc_1)
flag = ""
for i in ciphertext:
flag += chr(i-3)
print(flag)

# aLJ5aJqO/ApBpA/C9S8gUIsa1MSDBtijKDeqYwsziTYs
# VNCTF{JUst_$ne_Iast_dance_2025!}

AndroidLux

程序在java层用socket与linux虚拟环境进行通信,把输入传给linux进行判断。在Init类下发现解压了assert中的env文件进行初始化,我们解压下来就可以看到linux的rootfs文件。在Service类下可以看到启动了一个服务,就是启动了一个linux虚拟环境并设置执行目录为/root并执行了/root下的env文件。我们在rootfs文件中找到env文件,ida打开分析。里面有些用B和B.IM构成的花指令,用E0 03 00 AA字节码修改掉。发现main函数和一个魔改的base64的加密逻辑。main函数实现了对数据的接受,进行base64编码和最后的比较。直接写出解密脚本发现解密不出。感觉是什么地方被修改了。于是我提取env文件在手机上自启linux虚拟环境运行,同时运行frida进行hook。发现输入的数据并没有被修改。这就纳闷了。那么数据的修改不在env文件里,也不再java层里,那么就在虚拟linux系统上了。

最后看官方的文档,原来是修改了/etc/ld.so.preload配置文件加载了另一个so文件(参考),对read()和strcmp()做了手脚。至于怎么找到的,官方文档说用文件的修改时间对env内所有文件排序就可以看到了。

1
find . -type f -printf "%T@ %p\n" | sort -nr | head -20 | cut -d' ' -f2-

/etc/ld.so.preload,里面指向的是”\usr\libexec\libexec.so”,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
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

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

unsigned char* base64_table = (unsigned char*)"TUVWXYZabcdefghijABCDEF456789GHIJKLMNOPQRSklmnopqrstuvwxyz0123+/";

unsigned char* base64_decode(unsigned char* code)
{
int table[] = {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 62, 255, 255, 255, 63, 58, 59, 60, 61, 23, 24, 25, 26, 27, 28, 255, 255, 255, 255, 255, 255, 255, 17, 18, 19, 20, 21, 22, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 0, 1, 2, 3, 4, 5, 6, 255, 255, 255, 255, 255, 255, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255};

long len;
long str_len;
unsigned char* res;
int i, j;

//计算解码后的字符串长度
len = strlen((const char*)code);
//判断编码后的字符串后是否有=
if (strstr((const char*)code, "=="))
str_len = len / 4 * 3 - 2;
else if (strstr((const char*)code, "="))
str_len = len / 4 * 3 - 1;
else
str_len = len / 4 * 3;

res = (unsigned char*)malloc(sizeof(unsigned char) * str_len + 1);
res[str_len] = '\0';
unsigned char a, b,c;

//以4个字符为一位进行解码
for (i = 0, j = 0; i < len - 2; j += 3, i += 4)
{
res[j] = (((unsigned char)table[code[i]]) << 2) | (((unsigned char)table[code[i + 1]]) & 3);
res[j + 1] = ((((unsigned char)table[code[i + 1]]) & 0x3c) << 2) | (((unsigned char)table[code[i + 2]]) & 0xf);
a = (unsigned char)table[code[i + 2]];
c = a >> 4 << 6;
b = ((unsigned char)table[code[i + 3]]);
res[j + 2] = c|b;
}

return res;

}

int recmp(unsigned char* data) {
unsigned char* tmp[100];
unsigned char tmp_a13,tmp_d13;

for (int i = 0; i < 53; i++) {
tmp_a13 = data[i] + 13;
tmp_d13 = data[i] - 13;
if (tmp_d13 <= 64 || tmp_d13 > 77) {
if (tmp_a13 <= 77 || tmp_a13 > 90) {
if (tmp_d13 <= 96 || tmp_d13 > 109) {
if (tmp_a13 <= 109 || tmp_a13 > 122) {
continue;
}
else data[i] = tmp_a13;
}
else data[i] = tmp_d13;
}
else data[i] = tmp_a13;
}
else data[i] = tmp_d13;
}
return 0;
}
int rexor(unsigned char* data) {
for (int i = 0; i < 53; i++) {
data[i] ^= 1;
}
return 0;
}

int main() {
unsigned char m[] = "RPVIRN40R9PU67ue6RUH88Rgs65Bp8td8VQm4SPAT8Kj97QgVG==";

recmp(m);

for (int i = 0; i < 53; i++) {
printf("%c", m[i]);
}

unsigned char* result = result = base64_decode(m);;

rexor(result);

printf("\n");
for (int i = 0; i < 37; i++) {
printf("%c", result[i]);
}
printf("\n");

}

这题的逻辑是proot通过ptrace拦截系统调用,并修改调用指向虚拟环境的位置,从而在虚拟机中执行root命令。同时也限制了调试。参考

Fuko’s_starfish

有些多余的东西可以pacth掉,最后有个反调试,在汇编里把判断改成jz就行。前面的游戏都可以patch一下,比如把猜数字的值改成0,贪吃蛇的分数改成0就通过。

image-20250303132754704

搜索字符串可以找到最终的逻辑,里面有两个aes加密。开头加载密钥,交叉引用一下可以看到密钥初始的位置。

image-20250303132830708

下面还有一个CheckRemoteDebuggerPresent函数,如果不处于调试,密钥异或0x17。写脚本解拿到密钥解AES即可。密文在比较的位置。

查找srand的引用可以找到种子114514image-20250303133324237

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
#include <stdlib.h>

int main() {
srand(114514);

for (int i = 0; i < 16; i++) {
int a = rand();
printf("%.2x", (a % 255)^0x17);
}

}
//09e5fdeb683175b6b13b840891eb78d2

密文(3D011C190BA090815F672731A89AA47497362167AB2EB4A09418D37D93E646E7)

image-20250303134804011