hook_fish
运行应用提示输入一串字符并要求联网。
jadx分析,发现在main中,在点击按钮后会调用encrypt()方法对我们输入的数据加密,然后调用fish()方法下载了一个“hook_fish.dex”文件。通过下面的registerReceiver方法创注册了一个广播接收器,只要收到系统下载完成的广播时,就会调用onReceive()方法。
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
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来进行最后的异或)
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}
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()方法里。
这里还用JNI.INSTANCE.getAt()方法获取了一个从so层传来的字符串并在updateAAD(bytes)中作为加密的ADD传入。之后进行AES-gcm的加密,最后再转为base64。hook一下JNI.INSTANCE.getAt()获得ADD。从题目提示得知tag也是密文的一部分。AES-GCM的密文结果一般是”iv | encrypt | tag(16bit)”。我们把密文base64解码后获取中间的密文部分。再提取tag,输入数据解密就行。
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脚本
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#()方法。
这里先加载了一个lib库,里面传入了一些数据,我们ctrl+f搜索一下名字就会发现_lexenv_0_1_.numX是输入数据的长度,lexenv_0_1.pw是我们输入的数据,还有一个常量24也被传入。然后把返回的结果传参传给了pages/MyPage。
我们来看MyPage,往下看会看到一串密文。
在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的表用交叉引用定位函数。
有一个+3还有两个RC4魔改加密,试两次就行。估计是等于50的那个。
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内所有文件排序就可以看到了。
find . -type f -printf "%T@ %p\n" | sort -nr | head -20 | cut -d' ' -f2-
/etc/ld.so.preload,里面指向的是”\usr\libexec\libexec.so”,ida打开就可以看到逻辑。
#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就通过。
搜索字符串可以找到最终的逻辑,里面有两个aes加密。开头加载密钥,交叉引用一下可以看到密钥初始的位置。
下面还有一个CheckRemoteDebuggerPresent函数,如果不处于调试,密钥异或0x17。写脚本解拿到密钥解AES即可。密文在比较的位置。
查找srand的引用可以找到种子114514
#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)