【安洵杯 2023】babythread
收获
TLS 回调函数的反调试,创建两个独立线程,使用 TLS 修改变量的值
rc4()
加密的伪代码形式,以及加密与解密
(2023年6月10日)【安洵杯 2023】babythread
思路
下载文件,双击打开:
在 IDA 分析:
通过 CreateThread()
创建了两个线程
跟进 StartAddress
:
只有一个复制操作,Str = “D..^!ARBIh@;K:dAU-K`“
跟进 sub_731316()
,会执行 sub_731B80()
:
可以看到程序的主要逻辑,用户输入 Str
,然后进行 sub_73129E(16, Str, v5, Buf2)
的处理,最后将 Buf2
与 unk_73B018
处的数据比对,如果相同则校验通过
查看 unk_73B018
:
使用 IDA 将数据导出:unsigned char unk_73B018[32] = {0xDE, 0x1C, 0x22, 0x27, 0x1D, 0xAE, 0xAD, 0x65, 0xAD, 0xEF, 0x6E, 0x41, 0x4C, 0x34, 0x75, 0xF1, 0x16, 0x50, 0x50, 0xD4, 0x48, 0x69, 0x6D, 0x93, 0x36, 0x1C, 0x86, 0x3B, 0xBB, 0xD0, 0x4C, 0x91}
跟进 sub_73129E(16, Str, v5, Buf2)
,会执行 sub_731DA0()
:
查看 Str
内容:
Str = “FD,B0?YORg@:*VTCLnY4”
后面那一段代码形似 rc4()
的加密算法,跟进 sub_731127()
,会执行 sub_7320E0()
:
明显是 rc4()
的初始化
因此 sub_73129E(16, Str, v5, Buf2)
做的是 rc4()
加密,分析可知 Str
为密钥,Buf2
为密文,unk_73B018
为加密后的 flag
结合 sub_731127(v12, a1, v6)
可知,v12
是密钥,Buf2
是密文
但是分析发现密钥有很多不同的值
尝试用 “D..^!ARBIh@;K:dAU-K`“ 和 “FD,B0?YORg@:*VTCLnY4” 解密都不正确
在字符串中也没有其他形似这样的格式:
可见这个题主要问题在于寻找真正的密钥 Key
于是想通过动态调试获得密钥 Key
,但是发现无法调试(后来发现在 OllyDBG 中是可以的,因为 OllyDBG 具有反反调试的功能)
程序中存在 TlsCallback()
回调函数,可以反调试:
发现这里的 Str
就是前面的 “FD,B0?YORg@:*VTCLnY4”
所以 rc4()
的密钥是被 TLS 回调函数修改过的
后面在网上才了解到 TLS 回调函数的反调试
线程局部存储(Thread Local Storage,TLS)是一种线程级别的存储机制,它允许每个线程在运行时都拥有自己的私有变量,这些变量只能被该线程访问,而不会被其他线程所共享
TLS 回调函数是指,每当创建/终止进程的线程时会自动调用执行的函数。创建的主线程也会自动调用回调函数,且其调用执行先于 EP 代码。
根据 Stream = fopen("babyThread.exe", "rb")
打开 babyThread.exe 文件
fseek()
函数可以用于移动文件指针到指定的位置,从而在文件中进行随机访问或定位fseek(Stream, 77, 0)
指将文件指针偏移 77 个字节
将程序拖入 010Editor:
指针偏移 77 个字节后指向 0x21,即:'!'
处
然后使用 fread(Str, 1u, 0x14u, Stream)
从 Stream 中读取当前指针后 20 字节,并存储在 Str
中
得到 Str = “!This_program_cannot”
所以真正的 Key
被修改为了 “!This_program_cannot”,按照程序逻辑编写解密脚本即可
脚本一
#include <stdio.h>
#include <iostream>
using namespace std;
unsigned char HIBYTE(unsigned int w) // 取四字节数据的最高一字节
{
return (w >> 8 * 3) & 0xFF;
}
unsigned char BYTE2(unsigned int w) { // 取四字节数据的次高一字节
return (w >> 8 * 2) & 0xFF;
}
unsigned char BYTE1(unsigned int w) // 取四字节数据的次低一字节
{
return (w >> 8 * 1) & 0xFF;
}
void rc4_init(unsigned char *s, unsigned char *key, unsigned long key_Len)
{
int i = 0, j = 0;
unsigned char k[256] = {0}; //临时向量 k unsigned char tmp = 0;
for(i = 0; i < 256; i++) {
s[i] = i;
k[i] = (unsigned char) key[i % key_Len]; //Len = strlen(key),密钥的长度
}
for(i = 0; i < 256; i++) { //打乱s表
j = (j + s[i] + k[i]) % 256;
tmp = s[i];
s[i] = s[j]; //交换s[i]和s[j]
s[j] = tmp;
}
}
void rc4_crypt(unsigned char *s, unsigned char *Data, unsigned long Data_Len)
{
int i = 0, j = 0, t = 0;
unsigned long k = 0;
unsigned char tmp;
for(k = 0; k < Data_Len; k++)
{
i = (i + 1) % 256;
j = (j + s[i]) % 256;
tmp = s[i];
s[i] = s[j]; //交换s[i]和s[j]
s[j] = tmp;
t = (s[i] + s[j]) % 256;
Data[k] ^= s[t];
}
}
int main() {
int v7[7]; // [esp+1F0h] [ebp-64h]
int v8; // [esp+20Ch] [ebp-48h]
int i; // [esp+218h] [ebp-3Ch]
int v10; // [esp+224h] [ebp-30h]
unsigned int v11; // [esp+230h] [ebp-24h]
unsigned char v12[24]; // [esp+23Ch] [ebp-18h] BYREF
// string Destination = "FD,B0?YORg@:*VTCLnY4";
// string Destination = "D..^!ARBIh@;K:dAU-K`";
string Destination = "!This_program_cannot";
v7[0] = 1;
v7[1] = 85;
v7[2] = 7225;
v7[3] = 614125;
v7[4] = 52200625;
v11 = 0;
v10 = 0;
cout<<"v8: ";
while ( v11 < Destination.length() )
{
v8 = 0;
for ( i = 0; i < 5; ++i )
{
if ( Destination[i + v11] == 122 )
v12[i + v10] = 0;
else
v8 += v7[4 - i] * (Destination[i + v11] - 33);
}
printf("0x%.8x ", v8);
v12[v10 + 3] = v8 & 0xFF;
v12[v10 + 2] = BYTE1(v8);
v12[v10 + 1] = BYTE2(v8);
v12[v10] = HIBYTE(v8);
v11 += 5;
v10 += 4;
}cout<<endl;
unsigned char enc[] = {0xDE, 0x1C, 0x22, 0x27, 0x1D, 0xAE, 0xAD, 0x65, 0xAD, 0xEF, 0x6E, 0x41, 0x4C, 0x34, 0x75, 0xF1, 0x16, 0x50, 0x50, 0xD4, 0x48, 0x69, 0x6D, 0x93, 0x36, 0x1C, 0x86, 0x3B, 0xBB, 0xD0, 0x4C, 0x91};
unsigned char s_box[256];
cout<<"key: ";
for (int i = 0; i < 16; ++i) {
printf("0x%.2x ", v12[i]);
}cout<<endl;
rc4_init(s_box, v12, 16);
rc4_crypt(s_box, enc, 32);
for (int i = 0; i < 32; ++i) {
printf("%c", enc[i]);
}
return 0;
}
脚本二
Destination = "!This_program_cannot"
Key = [0] * 16
v7 = [1, 85, 7225, 614125, 52200625]
v11 = 0
v10 = 0
print("v8: ", end='')
while v11 < len(Destination):
v8 = 0
for i in range(5):
if Destination[i + v11] == 122:
Key[i + v10] = 0
else:
v8 += v7[4 - i] * (ord(Destination[i + v11]) - 33)
print(hex(v8), end=' ')
Key[v10 + 3] = v8 & 0xFF
Key[v10 + 2] = (v8 >> 8 * 1) & 0xFF
Key[v10 + 1] = (v8 >> 8 * 2) & 0xFF
Key[v10] = (v8 >> 8 * 3) & 0xFF
v11 += 5
v10 += 4
print()
print("Key: ", end='')
for i in range(16):
print(hex(Key[i]), end=' ')
print()
Str = [0xDE, 0x1C, 0x22, 0x27, 0x1D, 0xAE, 0xAD, 0x65, 0xAD, 0xEF, 0x6E, 0x41, 0x4C, 0x34, 0x75, 0xF1, 0x16, 0x50, 0x50,
0xD4, 0x48, 0x69, 0x6D, 0x93, 0x36, 0x1C, 0x86, 0x3B, 0xBB, 0xD0, 0x4C, 0x91] # 待加解密的内容
flag = "" # 存放加解密后的结果
# ---------- rc4_init ----------
s_box = [] # 定义 s 盒
for i in range(256): # 生成初始 s 盒
s_box.append(i)
# T[i] = K[i mod len(Key)] # 这个算法里没有 T[i],下面会解释
t = 0
j = 0
for i in range(256): # 打乱 s 盒顺序
tmp = s_box[i]
j = (j + s_box[i] + Key[t]) & 0xff # j = (j + S[i] + T[i]) mod 256
s_box[i] = s_box[j]
s_box[j] = tmp
t = t + 1 # 这里引入的 t 加一个 if 条件其实就是为了做 t = i % len(Key)
if t >= len(Key): # Key[t] 配合 t = i % len(Key) 就是实现了 T[i] = K[i mod len(Key)]
t = 0 # 小细节写法不同而已,大致思路是一样的
# ---------- rc4_crypt ----------
i = 0
j = 0
for k in range(len(Str)):
i = (i + 1) & 0xff
j = (j + s_box[i]) & 0xff # & 0xff 是为了做 % 256,两者效果相同
tmp = s_box[i]
s_box[i] = s_box[j]
s_box[j] = tmp
t = (s_box[i] + s_box[j]) & 0xff # & 0xff 是为了做 % 256,两者效果相同
flag += chr(Str[k] ^ s_box[t]) # 明文异或得密文,密文异或得明文
print(flag)
结果
SYC{Th1s_is_@_EasY_3ncryptO!!!!}