本文最后更新于:2024年9月9日 下午
这道题比赛的时候做了半天也没有做出了,加密部分倒是搞懂了,就是得不到正确的flag
做题前置知识
TLS反调试机制
参考文章1
参考文章2
TLS
是Thread Local Storage
的缩写,即线程局部存储。主要是为了解决多线程中变量同步的问题。
根据我们所学习的OS
的知识,进程中的全局变量和函数内的静态变量,是各个线程都可以访问的共享变量,即线程a
修改后,线程b
访问时就不是原来的了,当然OS
利用信号量解决这种同步问题。
这造成昂贵的同步开销,所以我们选择一种方式使得每个线程可以保有自己的内部变量,被称为static memory local to a thread
线程局部静态变量。这一种新的实现机制就是TLS
强网杯这道题具体用的应该是静态绑定
那么关于TLS在C++中的具体实现应该如下:
编译器声明使用TLS回调
1 2 3 4 5 6 7
| #ifdef _WIN64 #pragma comment (linker, "/INCLUDE:_tls_used") #pragma comment (linker, "/INCLUDE:tls_callback_func") #else #pragma comment (linker, "/INCLUDE:__tls_used") #pragma comment (linker, "/INCLUDE:_tls_callback_func") #endif
|
编写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); } }
|
注册TLS回调函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| #ifdef _WIN64 #pragma const_seg(".CRT$XLF") EXTERN_C const #else #pragma data_seg(".CRT$XLF") EXTERN_C #endif PIMAGE_TLS_CALLBACK tls_callback_func[] = { tls_callback,0 };
#ifdef _WIN64 #pragma const_seg() #else #pragma data_seg() #endif
|
至于TLS在程序运行过程中的变化应该是这样的:
- OS加载可执行文件,查找所有TLS回调函数,不用从OEP进去后到主线程执行
- OS在重新启动时(或者线程创建销毁),调用TLS回调函数
- 进入(返回)主函数
TEA加密算法
参考文章
算法特点:
- 使用64位(8byte)明文分组和128位密钥(4*32bit,通常是4个8byte数组)
- sum是用于每轮混淆,每次加δ
- 使用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 using namespace std;
void Encrypt(unsigned long *EntryData,unsigned long *Key){ 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 -= (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 += delta; v1 += ((v0<<4)^(v0>>5) + v0) ^ (sum + Key[(sum>>11)&3]); } 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
发现交叉引用,发现在两个TLS
的callback
函数。
点击到一个回调函数分析:和我上面列举的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; 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); }
|