前言:
水区的朋友们,年轻就是资本,和我一起学逆向逆天改命吧,我的学习过程全部记录及学习资源:https://www.52pojie.cn/forum.php?mod=viewthread&tid=2093516&page=1#pid54862410
立帖为证!--------记录学习的点点滴滴
0x1 环境准备
1.题目在这里下载:https://adworld.xctf.org.cn/,攻防世界-历年真题-精选免费练习题-reserve方向-crypt。
2.这个题目提示rc4算法,程序是64位exe程序,关于rc4算法见https://www.52pojie.cn/thread-1221481-1-1.html。
3.这里关注一下RC4的特性:解密过程与加密过程完全相同,因为XOR操作的自反性
密文[i]=明文[i]⊕密钥流[i]
明文[i]=密文[i]⊕密钥流[i]
4准备的工具IDA7.7(win7环境+Python3.8),今天使用的AI是阿里千问(对话一直无反应,看热搜似乎deepseek崩了)
0x2 逆向分析
1.一打开就看到了main函数,直接F5反编译,逻辑比较清晰,给它标上注释。
int __cdecl main(int argc, const char **argv, const char **envp)
{
strcpy(Str, "12345678abcdefghijklmnopqrspxyz"); //拷贝32位字符串到Str
memset(&Str[32], 0, 0x60ui64); //填充0
memset(v10, 0, 0x17ui64); //填充0
sub_1400054D0("%s", v10); //疑似scanf函数
v9 = malloc(0x408ui64); //分配内存
v3 = strlen(Str); //计算Str长度,v3=32
sub_140001120(v9, Str, v3); //似乎是Str内存拷贝给v9
v4 = strlen(v10); //v10是我们输入的,这里v4就是我们输入的长度
sub_140001240(v9, v10, v4); //关键加密函数,估计就是题目给出的rc4
for ( i = 0; i < 22; ++i ) //循环比较22次
{
if ( ((unsigned __int8)v10[i] ^ 0x22) != byte_14013B000[i] ) //关键验证,将v10异或0x22逐位与byte_14013B000比较是否相等。
{
v5 = (void *)sub_1400015A0(&off_14013B020, "error");
_CallMemberFunction0(v5, sub_140001F10);
return 0;
}
}
v7 = (void *)sub_1400015A0(&off_14013B020, "nice job");
_CallMemberFunction0(v7, sub_140001F10);
return 0;
}
2.去byte_14013B000扣数据,至少扣22位,本来想用vscode复制以前学习时用的函数,结果环境罢工了,干脆让阿里千问转换成java代码
.data:000000014013B000 byte_14013B000 db 9Eh, 0E7h, 30h, 5Fh, 0A7h, 1, 0A6h, 53h, 59h, 1Bh, 0Ah, 20h, 0F1h, 73h, 0D1h, 0Eh, 0ABh, 9, 84h, 0Eh
.data:000000014013B000 ; DATA XREF: main+E5↑o
.data:000000014013B000 db 8Dh, 2Bh, 0, 0
byte[] byte_14013B000 = {
(byte) 0x9E, (byte) 0xE7, (byte) 0x30, (byte) 0x5F, (byte) 0xA7, (byte) 0x01, (byte) 0xA6, (byte) 0x53,
(byte) 0x59, (byte) 0x1B, (byte) 0x0A, (byte) 0x20, (byte) 0xF1, (byte) 0x73, (byte) 0xD1, (byte) 0x0E,
(byte) 0xAB, (byte) 0x09, (byte) 0x84, (byte) 0x0E, (byte) 0x8D, (byte) 0x2B, (byte) 0x00, (byte) 0x00
};
注意:在Java中,byte 类型是有符号的(范围是 -128 到 127),而十六进制如 0xFF 的值是255。如果不加 (byte) 强制类型转换,编译器会报错,因为它认为 0xFF 超出了 byte 的范围。强制转换后,0xFF 会被解释为 -1。上面的格式是标准的Java写法。
3.接下来扣for循环代码,先找出v10密文。
byte[] v10 = new byte[32];
byte[] byte_14013B000 = {
(byte) 0x9E, (byte) 0xE7, (byte) 0x30, (byte) 0x5F, (byte) 0xA7, (byte) 0x01, (byte) 0xA6, (byte) 0x53,
(byte) 0x59, (byte) 0x1B, (byte) 0x0A, (byte) 0x20, (byte) 0xF1, (byte) 0x73, (byte) 0xD1, (byte) 0x0E,
(byte) 0xAB, (byte) 0x09, (byte) 0x84, (byte) 0x0E, (byte) 0x8D, (byte) 0x2B, (byte) 0x00, (byte) 0x00
};
for (int i = 0; i < 22; i++) {
v10[i]= (byte) (byte_14013B000[i]^0x22);
System.out.print("0x"+Integer.toHexString(v10[i] & 0xFF)+",");
}
运行输出
0xbc,0xc5,0x12,0x7d,0x85,0x23,0x84,0x71,0x7b,0x39,0x28,0x2,0xd3,0x51,0xf3,0x2c,0x89,0x2b,0xa6,0x2c,0xaf,0x9
4.sub_140001240(v9, v10, v4);v4是明文的长度,v10是明文,v9疑似12345678abcdefghijklmnopqrspxyz,那么接下来继续看函数实现
_DWORD *__fastcall sub_140001240(_DWORD *a1, __int64 a2, int a3)
{
v5 = *a1;
v6 = a1[1];
v9 = a1 + 2;
for ( i = 0; i < a3; ++i )
{
v5 = (unsigned __int8)(v5 + 1);
v7 = v9[v5];
v6 = (unsigned __int8)(v7 + v6);
v8 = v9[v6];
v9[v5] = v8;
v9[v6] = v7;
*(_BYTE *)(a2 + i) ^= LOBYTE(v9[(unsigned __int8)(v8 + v7)]);
}
*a1 = v5;
result = a1;
a1[1] = v6;
return result;
}
5.根据rc4算法加密和逻辑相同的原理,把这段代码改写成java代码,有点晕,去动态一下这个函数,局部变量v5,v6,v9指向a1的第一个,第二个,第三个元素,v9指向的字符串似乎不是12345678abcdefghijklmnopqrspxyz,这个看来是我分析的时候猜错了,应该是生成S盒用的,看一下这个函数让ai分析一下,说这是标准RC4算法的密钥调度算法(KSA)。
__int64 __fastcall sub_140001120(_DWORD *a1, __int64 a2, int a3)
{
// ... 局部变量声明 ...
// 1. 初始化 S-box 的索引 i 和 j
*a1 = 0; // a1[0] = 0 (对应代码中的 i)
a1[1] = 0; // a1[1] = 0 (对应代码中的 j)
// 2. 让 v9 指向 S-box 的起始位置
v9 = a1 + 2; // S-box 从 a1 的第3个元素开始存储 (索引2)
// 3. KSA - 初始化 S-box 为 [0, 1, 2, ..., 255]
for ( i = 0; i < 256; ++i )
v9[i] = i; // S[i] = i
// 4. KSA - 使用密钥打乱 S-box
v6 = 0; // v6 是一个循环计数器,用于遍历密钥 (对应代码中的 k)
result = 0i64; // 返回值
LOBYTE(v7) = 0; // v7 是计算 j 时的临时变量,初始为 0 (对应代码中的 j)
// 循环 256 次,完成 S-box 的打乱
for ( j = 0; j < 256; ++j ) // j 是 S-box 的主循环索引
{
v8 = v9[j]; // v8 = S[j] (临时存储)
// 核心更新 j 的公式: j = (j + S[j] + key[k]) % 256
// *(_BYTE *)(a2 + v6) 从 a2 (密钥地址) 读取一个字节 key[k]
// v7 对应 j, v8 对应 S[j]
v7 = (unsigned __int8)(*(_BYTE *)(a2 + v6) + v8 + v7);
// 交换 S[j] 和 S[j_new]
v9[j] = v9[v7]; // S[j] = S[j_new]
v9[v7] = v8; // S[j_new] = temp_S_j
// 更新 k (v6),如果超出密钥长度就循环回去
if ( ++v6 >= a3 )
v6 = 0;
result = (unsigned int)(j + 1); // 更新返回值
}
return result;
}
6.让ai告诉我java如何进行RC4解密,java有成熟的函数类。
使用Java加密库进行RC4解密
SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, "RC4");
Cipher cipher = Cipher.getInstance("RC4");
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
// 解密
byte[] user_input_bytes = cipher.doFinal(rc4EncryptedData);
7.再把我直接写的代码略微修改,形成最终的解密脚本。
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
public class test01 {
public static void main(String[] args) throws NoSuchAlgorithmException, UnsupportedEncodingException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException, NoSuchPaddingException {
byte[] v10 = new byte[22];
byte[] byte_14013B000 = {
(byte) 0x9E, (byte) 0xE7, (byte) 0x30, (byte) 0x5F, (byte) 0xA7, (byte) 0x01, (byte) 0xA6, (byte) 0x53,
(byte) 0x59, (byte) 0x1B, (byte) 0x0A, (byte) 0x20, (byte) 0xF1, (byte) 0x73, (byte) 0xD1, (byte) 0x0E,
(byte) 0xAB, (byte) 0x09, (byte) 0x84, (byte) 0x0E, (byte) 0x8D, (byte) 0x2B, (byte) 0x00, (byte) 0x00
};
System.out.println("密文16进制如下:");
for (int i = 0; i < 22; i++) {
v10[i]= (byte) (byte_14013B000[i]^0x22);
System.out.print("0x"+Integer.toHexString(v10[i] & 0xFF)+",");
}
byte[] keyBytes = "12345678abcdefghijklmnopqrspxyz".getBytes();
//使用Java加密库进行RC4解密
SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, "RC4");
Cipher cipher = Cipher.getInstance("RC4");
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
// 解密
byte[] user_input_bytes = cipher.doFinal(v10);
System.out.println("\n明文16进制如下:");
for (int i = 0; i < 22; i++) {
System.out.print("0x"+Integer.toHexString(user_input_bytes[i] & 0xFF)+",");
}
System.out.println("\n解密后的flag String如下:");
System.out.println(new String(user_input_bytes));
}
}
8.运行后成功得到flag{nice_to_meet_you},验证通过。
密文16进制如下:
0xbc,0xc5,0x12,0x7d,0x85,0x23,0x84,0x71,0x7b,0x39,0x28,0x2,0xd3,0x51,0xf3,0x2c,0x89,0x2b,0xa6,0x2c,0xaf,0x9,
明文16进制如下:
0x66,0x6c,0x61,0x67,0x7b,0x6e,0x69,0x63,0x65,0x5f,0x74,0x6f,0x5f,0x6d,0x65,0x65,0x74,0x5f,0x79,0x6f,0x75,0x7d,
解密后的flag String如下:
flag{nice_to_meet_you}
0x3 总结
1、让AI分析时,得自己先找到关键函数,然后因为提供的代码只是片段,因此AI会产生猜测(幻觉),所以我们自己得有清晰头脑,记住每一步分析出的流程。
2、对于与AI有分歧的地方,记得动态调试验证一下,正确的就继续分析,不正确的话得给ai喂堆栈数据,因为ai并不能执行代码,不知道实际运行时的数据。
3、不要完全依赖ai帮你写完整脚本,经常会出错,然后你还不知道错在哪里,你可以用代码写出大概解密逻辑,让ai照着你的逻辑去实现,发现解密代码与你要求不一致时好及时纠正。
4、听说现在mcp很强,本来想试试ida mcp的,可惜我这台i5 4代的cpu装不上win10,高版本IDA和Python装不上。