收获

  • TLS 回调函数的反调试,创建两个独立线程,使用 TLS 修改变量的值

  • rc4() 加密的伪代码形式,以及加密与解密


(2023年6月10日)【安洵杯 2023】babythread


思路

下载文件,双击打开:

安洵杯2023-babyThread1.png

在 IDA 分析:

安洵杯2023-babyThread2.png

安洵杯2023-babyThread3.png

通过 CreateThread() 创建了两个线程

跟进 StartAddress

安洵杯2023-babyThread4.png

只有一个复制操作,Str = “D..^!ARBIh@;K:dAU-K`“

跟进 sub_731316(),会执行 sub_731B80()

安洵杯2023-babyThread5.png

可以看到程序的主要逻辑,用户输入 Str,然后进行 sub_73129E(16, Str, v5, Buf2) 的处理,最后将 Buf2unk_73B018 处的数据比对,如果相同则校验通过

查看 unk_73B018

安洵杯2023-babyThread6.png

使用 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()

安洵杯2023-babyThread7.png

查看 Str 内容:

安洵杯2023-babyThread9.png

Str = “FD,B0?YORg@:*VTCLnY4”

后面那一段代码形似 rc4() 的加密算法,跟进 sub_731127(),会执行 sub_7320E0()

安洵杯2023-babyThread8.png

明显是 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” 解密都不正确

在字符串中也没有其他形似这样的格式:

安洵杯2023-babyThread10.png

可见这个题主要问题在于寻找真正的密钥 Key

于是想通过动态调试获得密钥 Key,但是发现无法调试(后来发现在 OllyDBG 中是可以的,因为 OllyDBG 具有反反调试的功能)

程序中存在 TlsCallback() 回调函数,可以反调试:

安洵杯2023-babyThread11.png

发现这里的 Str 就是前面的 “FD,B0?YORg@:*VTCLnY4”

安洵杯2023-babyThread12.png

所以 rc4() 的密钥是被 TLS 回调函数修改过的

后面在网上才了解到 TLS 回调函数的反调试

线程局部存储(Thread Local Storage,TLS)是一种线程级别的存储机制,它允许每个线程在运行时都拥有自己的私有变量,这些变量只能被该线程访问,而不会被其他线程所共享

TLS 回调函数是指,每当创建/终止进程的线程时会自动调用执行的函数。创建的主线程也会自动调用回调函数,且其调用执行先于 EP 代码。

根据 Stream = fopen("babyThread.exe", "rb") 打开 babyThread.exe 文件

fseek() 函数可以用于移动文件指针到指定的位置,从而在文件中进行随机访问或定位
fseek(Stream, 77, 0) 指将文件指针偏移 77 个字节

将程序拖入 010Editor:

安洵杯2023-babyThread13.png

指针偏移 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!!!!}

安洵杯2023-babyThread14.png

安洵杯2023-babyThread15.png