2023强网杯babyre

本文最后更新于:2024年9月9日 下午

这道题比赛的时候做了半天也没有做出了,加密部分倒是搞懂了,就是得不到正确的flag

做题前置知识

TLS反调试机制

参考文章1

参考文章2

TLSThread Local Storage的缩写,即线程局部存储。主要是为了解决多线程中变量同步的问题

根据我们所学习的OS的知识,进程中的全局变量和函数内的静态变量,是各个线程都可以访问的共享变量,即线程a修改后,线程b访问时就不是原来的了,当然OS利用信号量解决这种同步问题。

这造成昂贵的同步开销,所以我们选择一种方式使得每个线程可以保有自己的内部变量,被称为static memory local to a thread线程局部静态变量。这一种新的实现机制就是TLS

强网杯这道题具体用的应该是静态绑定

那么关于TLS在C++中的具体实现应该如下:

  1. 编译器声明使用TLS回调

    1
    2
    3
    4
    5
    6
    7
    #ifdef _WIN64       //64位
    #pragma comment (linker, "/INCLUDE:_tls_used")
    #pragma comment (linker, "/INCLUDE:tls_callback_func")
    #else //32位
    #pragma comment (linker, "/INCLUDE:__tls_used")
    #pragma comment (linker, "/INCLUDE:_tls_callback_func")
    #endif
  2. 编写TLS回调函数

    1
    2
    3
    4
    5
    6
    7
    void NTAPI tls_callback(PVOID Dllhandle, DWORD Reason, PVOID Reserved) {
    BOOL ret;
    CheckRemoteDebuggerPresent(GetCurrentProcess(), &ret);//检测是否被调试
    if (ret) {
    ExitProcess(0);//退出程序
    }
    }
  3. 注册TLS回调函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #ifdef _WIN64                           //64位
    #pragma const_seg(".CRT$XLF")
    EXTERN_C const
    #else
    #pragma data_seg(".CRT$XLF") //32位
    EXTERN_C
    #endif
    PIMAGE_TLS_CALLBACK tls_callback_func[] = { tls_callback,0 };
    //这里是个回调函数数组,可以定义多个,可以为空,0必须加上用于结束
    #ifdef _WIN64 //64位
    #pragma const_seg()
    #else
    #pragma data_seg() //32位
    #endif //_WIN64

至于TLS在程序运行过程中的变化应该是这样的:

  1. OS加载可执行文件,查找所有TLS回调函数,不用从OEP进去后到主线程执行
  2. OS在重新启动时(或者线程创建销毁),调用TLS回调函数
  3. 进入(返回)主函数

TEA加密算法

参考文章

算法特点:

  1. 使用64位(8byte)明文分组和128位密钥(4*32bit,通常是4个8byte数组)
  2. sum是用于每轮混淆,每次加δ
  3. 使用Feistel分组加密框架(使用上一轮生成的数据和原始数据的另一半进行XOR异或操作,作为下一轮轮函数的输入),一般为32轮
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
#include<iostream>
#define delta 0x9E3779B9 //常数Delta
using namespace std;

// 加密
void Encrypt(unsigned long *EntryData,unsigned long *Key){
// 根据算法,分为8字节一轮
unsigned long v0 = EntryData[0];
unsigned long v1 = EntryData[1];

unsigned long sum = 0;
for (size_t i = 0;i < 32;i++){
sum += delta;
v0 += (v1<<4 + Key[0])^(v1 + sum)^(v1>>5 + Key[1]);
v1 += (v0<<4 + Key[2])^(v0 + sum)^(v0>>5 + Key[3]);
}
EntryData[0] = v0;
EntryData[1] = v1;
}
// 解密,加密反过来就行
void Decrypt(unsigned long *Encrypted_data,unsigned long *Key){
unsigned long sum = delta*32;
unsigned long v0 = Encrypted_data[0];
unsigned long v1 = Encrypted_data[1];
for (size_t i = 0;i < 32;i++){
// 先算v1
v1 -= (v0<<4 + Key[2])^(v0 + sum)^(v0>>5 + Key[3]);
v0 -= (v1<<4 + Key[0])^(v1 + sum)^(v1>>5 + Key[1]);
sum -= delta;
}
Encrypted_data[0] = v0;
Encrypted_data[1] = v1;
}

int main(){
unsigned long Data[3]={0x48535833,0x21455230,0x0};
cout << "明文:" << (char*)Data <<endl;
unsigned long Key[4] = { 0x212A3D44,0x15667788,0x92AABBCC,0xD12EFF11};
Encrypt(Data,Key);
cout << "密文:" << *Data <<" "<< *(Data+1) <<endl;
Decrypt(Data,Key);
cout << "明文:" << (char*)Data <<endl;
}

XTEA加密算法

XTEA(TEAN)算法相比于TEA的改变在于:取哪一个密钥通过sum计算得到,而TEA是固定下标

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
#include<iostream>
#define delta 0x9E3779B9
using namespace std;

// 加密
void Encrypt(unsigned long *Data,unsigned long *Key){
unsigned long sum = 0;
unsigned long v0 = Data[0];
unsigned long v1 = Data[1];
for (size_t i = 0;i < 32;i++){
v0 += ((v1<<4)^(v1>>5) + v1) ^ (sum + Key[sum&3]);//sum&3:00,01,11
sum += delta;
v1 += ((v0<<4)^(v0>>5) + v0) ^ (sum + Key[(sum>>11)&3]);//将sum高五位中的低两位获取
}
Data[0] = v0;
Data[1] = v1;
}
// 解密
void Decrypt(unsigned long *Encrypted_data,unsigned long *Key){
unsigned long sum = delta*32;
unsigned long v0 =Encrypted_data[0];
unsigned long v1 =Encrypted_data[1];
for (size_t i = 0;i < 32;i++){
v1 -= ((v0<<4)^(v0>>5) + v0) ^ (sum + Key[(sum>>11)&3]);
sum -= delta;
v0 -= ((v1<<4)^(v1>>5) + v1) ^ (sum + Key[sum&3]);
}
Encrypted_data[0] = v0;
Encrypted_data[1] = v1;
}
int main(){
cout<< "这是XTEA算法!"<<endl;
unsigned long Data[3]={0x48535833,0x21455230,0x0};
cout << "明文:" << (char*)Data <<endl;
unsigned long Key[4] = { 0x212A3D44,0x15667788,0x92AABBCC,0xD12EFF11};
Encrypt(Data,Key);
cout << "密文:" << *Data <<" "<< *(Data+1) <<endl;
Decrypt(Data,Key);
cout << "明文:" << (char*)Data <<endl;
}

babyre

解决TLS反调试

首先我们已经知道了这道题加了TLS反调试,然后学习了具体的原理,现在就是实践中去摸索到底时如何实现的。

寻觅TLS

打开IDA调试,首先我们进入到程序入口位置,

然后一直点击到内层函数,直到此处,重命名为check_DBG,点击进去分析就知道为啥这里是了

然后里面是这样的:这里已经很明显用了TLS反调试了,如果返回值表示正在被调试,那么直接退出进程;反之才会进入我们的encrypt_func()

当然为了更加确认,我们可以在IDA中搜索字符串,发现一个SomeThing Go Wrong\n,点击到该处位置,对函数按x发现交叉引用,发现在两个TLScallback函数。

点击到一个回调函数分析:和我上面列举的TLS反调试例子就很像了

所以可以发现,如果存在tlsCallback_之类的函数就得小心了,IDA按shift+F7查看代码段,如果存在.tls也得小心了

反反调试

至此我们确定babyre这道题使用了TLS来反调试,那么接下来的问题就是如何反反调试,这里首先有个简单的方法,首先自己运行程序,然后用x64dbg附加进程,就可以动态调试了,但是这样不太方便,每次都要重新跑程序。

通过上面的方法拿到的密文和密钥:

1
2
3
4
E0 F2 23 95 93 C2 D8 8E 93 C3 68 86 BC 50 F2 DD
99 44 0E 51 44 BD 60 8C F2 AB DC 34 60 D2 0F C1

62 6F 6D 62

这里采用修改汇编代码的方式把反调试给patch掉,笔者也是第一次做这种操作,太菜了😭

首先需要定位到在哪里我们可以进入正常的加密算法,在上面我们已经分析出来了,就在我们重命名的check_DBG函数里,接下来计算地址,

(期末考试ing,未完待续)

算法分析

进入主逻辑中的加密算法,首先有0-1、2-3、4-5、6-7两个一组进去加密,根据我们刚刚拿到的密文,可以发现就是一共4组,每组64bit,和上面的TEA和XTEA的输入要求是一样。

进入函数分析,发现确实就是一个魔改过的XTEA算法,具体在轮数是33,加密方式变为(v1*5)^(v1>>4),其他的大差不差的😋

1
2
3
4
5
6
7
8
9
10
void Decrypt(unsigned long *Encrypted_data,unsigned long *Key){
unsigned long sum = 0x90508D47 - delta*33*4;
unsigned long v0 = Encrypted_data[0];
unsigned long v1 = Encrypted_data[1];
for (size_t i = 0;i < 33;i++){
sum += delta;
v1 -= ((32*v0)^(v0>>4) + v0) ^ (sum + Key[(sum >> 11) & 3]);
v0 -= ((32*v1)^(v1>>4) + v1) ^ (sum + Key[sum & 3]) ^ sum;
}
}

下面就是魔改后的XTEA算法的解密脚本,然后在主函数中发现之后还有一个切割逻辑,方便和答案比较,在实际的解密脚本中无需太过关心。

下面是完整脚本:

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
#include<iostream>
#include <stdio.h>
#include <stdint.h>
#define delta 0x77BF7F99
using namespace std;
uint32_t Key[4] = {0x62,0x6F,0x6D,0x62};
unsigned char Encrypted_data[] =
{
0xE0,0xF2,0x23,0x95,0x93,0xC2,0xD8,0x8E,
0x93,0xC3,0x68,0x86,0xBC,0x50,0xF2,0xDD,
0x99,0x44,0x0E,0x51,0x44,0xBD,0x60,0x8C,
0xF2,0xAB,0xDC,0x34,0x60,0xD2,0x0F,0xC1
};

void Encrypt(uint32_t *Data,uint32_t *Key){
uint32_t sum = 0x90508D47;
uint32_t v0 = Data[0];
uint32_t v1 = Data[1];
for (size_t j = 0 ; j<4 ;j++){
for (size_t i = 0;i < 33;i++){
v0 += ((32*v1)^(v1>>4) + v1) ^ (sum + Key[sum & 3]) ^ sum;
v1 += ((32*v0)^(v0>>4) + v0) ^ (sum + Key[(sum >> 11) & 3]);
sum -= delta;
}
}
}

void Decrypt(uint32_t Encrypted_data[2],uint32_t Key[4]){
uint32_t sum = 0x90508D47;// - delta*33*4;
for (int i = 0; i < 4; ++i)
{
for (int j = 0; j < 33; ++j)
{
sum -= delta;
}
}
uint32_t v0 = Encrypted_data[0];
uint32_t v1 = Encrypted_data[1];
for (size_t j = 0; j < 4; j++)
{
for (size_t i = 0;i < 33;i++){
sum += delta;
v1 -= (((32*v0)^(v0>>4)) + v0) ^ (sum + Key[(sum >> 11) & 3]);
v0 -= (((32*v1)^(v1>>4)) + v1) ^ (sum + Key[sum & 3]) ^ sum;
}
}
Encrypted_data[0]=v0;
Encrypted_data[1]=v1;
}

int main(){
uint32_t *flag = (uint32_t *)Encrypted_data;
for (int i = 0; i < 4 ; i++){
Decrypt(&flag[i*2],Key);
}
printf("%s",flag);
}

2023强网杯babyre
https://3xsh0re.github.io/2023/12/20/2023强网杯babyre/
作者
3xsh0re
发布于
2023年12月20日
许可协议