指令变形初识&TLS反调试&脚本去花指令&随机数运算&逆向算法
字数 657 2025-08-06 20:12:41
TLS反调试与花指令去除技术详解
一、程序执行流程与TLS回调
1.1 程序执行顺序
传统认知中main函数是程序执行的起点,但实际上在main之前还有大量初始化代码:
- 全局变量初始化:全局变量的构造函数会在
main之前执行 - TLS回调函数:线程局部存储(TLS)回调函数在程序入口点(OEP)之前执行
验证方法:
// 全局变量示例
class GlobalObj {
public:
GlobalObj() { MessageBox(0, "我是构造函数", 0, 0); }
} g_obj;
int main() {
MessageBox(0, "我是main函数", 0, 0);
return 0;
}
1.2 TLS(线程局部存储)技术
TLS设计初衷是为线程提供访问全局变量的便捷方式,但也可用于反调试:
#pragma comment(linker, "/INCLUDE:__tls_used") // 声明使用TLS
// TLS回调函数
void NTAPI TLS_CALLBACK(PVOID DllHandle, DWORD Reason, PVOID Reserved) {
if (Reason == DLL_PROCESS_ATTACH) {
MessageBoxA(0, "TLS函数执行", 0, 0);
// 反调试代码可放在这里
}
}
// 注册TLS回调
#pragma data_seg(".CRT$XLX")
PIMAGE_TLS_CALLBACK pTLS_CALLBACKs[] = { TLS_CALLBACK, NULL };
#pragma data_seg()
二、TLS反调试技术
2.1 常见反调试手段
- 隐藏线程:
NtSetInformationThread(GetCurrentThread(), ThreadHideFromDebugger, 0, 0);
- 检测调试端口:
DWORD isDebug = 0;
NtQueryInformationProcess(GetCurrentProcess(), ProcessDebugPort,
&isDebug, sizeof(DWORD), NULL);
if (isDebug != 0) {
// 被调试状态处理
}
2.2 TLS反调试示例
#include <Windows.h>
#include "MINT.h" // 包含NTAPI函数声明
#pragma comment(linker, "/INCLUDE:__tls_used")
DWORD isDebug = 0;
void NTAPI TLS_CALLBACK(PVOID DllHandle, DWORD Reason, PVOID Reserved) {
if (Reason == DLL_PROCESS_ATTACH) {
// 方法1: 隐藏线程
NtSetInformationThread(GetCurrentThread(), ThreadHideFromDebugger, 0, 0);
// 方法2: 检测调试器
NtQueryInformationProcess(GetCurrentProcess(), ProcessDebugPort,
&isDebug, sizeof(DWORD), NULL);
}
}
int main() {
MessageBoxA(NULL, "Main函数执行", "提示", MB_OK);
system("pause");
return 0;
}
#pragma data_seg(".CRT$XLX")
PIMAGE_TLS_CALLBACK pTLS_CALLBACKs[] = { TLS_CALLBACK, NULL };
#pragma data_seg()
三、花指令分析与去除
3.1 花指令原理
设计思想:
- 构造恒成立的EIP跳转
- 插入无效数据干扰分析
优缺点:
- 优点:有效防止静态分析
- 缺点:对动态调试影响有限
3.2 花指令识别
常见花指令模式:
E8 01 00 00 00 C2 83 04 24 06 C3
特征:
- 以
E8 01 00 00 00开头 - 以
C3结尾
3.3 手动去除花指令
OD中识别步骤:
call指令跳转到下一条指令- 修改返回地址(通常通过
add [esp], x) retn跳转到修改后的地址- 中间指令为无效花指令,可用
nop填充
3.4 自动化去除脚本
OD脚本示例:
// 从EIP位置查找特征码
find eip, #E80000000081042417000000C3576174636820757220737465702100#
cmp $RESULT, 0
je exit
mov [$RESULT], #90909090909090909090909090909090909090909090909090909090#
// 查找下一个特征码
find eip, #E80000000081042425000000C354686520666C616720626567696E7320776974682022666C61677B2200#
cmp $RESULT, 0
je exit
mov [$RESULT], #909090909090909090909090909090909090909090909090909090909090909090909090909090909090#
// 循环处理通用花指令模式
loop:
find eip, #E801000000?C3#
cmp $RESULT, 0
je exit
mov [$RESULT], #9090909090909090909090#
jmp loop
exit:
MSG "complete! \r\n"
ret
四、逆向算法分析
4.1 关键算法结构
BOOL CheckKey(char* key) {
if (strlen(key) != 42) return FALSE;
int keySum = 0;
for (int i = 0; key[i]; i++)
keySum += key[i];
srand(isdebug ^ keySum); // isdebug = 0x31333359
for (int i = 0; i < 42; ++i) {
unsigned int v6 = key[i] * rand();
// 多次模运算...
unsigned int v22 = ...;
if (v6 % 0xFAC96621 * v22 % 0xFAC96621 != dword_4030B4[i])
return FALSE;
}
return TRUE;
}
4.2 算法破解步骤
- 爆破keySum:
void CalcKeySum() {
char key[5] = "flag";
for (unsigned int keySum = 0; keySum < 255*42; keySum++) {
srand(keySum ^ isdebug);
int i;
for (i = 0; i < 4; i++) {
unsigned int v6 = key[i] * rand();
// ...计算过程...
if (最终结果 != dword_4030B4[i]) break;
}
if (i == 4) {
seed = keySum ^ isdebug;
break;
}
}
}
- 生成随机数序列:
void SetRand() {
srand(seed);
for (int i = 0; i < 42; i++)
Rand[i] = rand();
}
- 爆破密码字符:
for (int i = 0; i < 42; i++) {
for (unsigned char ch = 0; ch < 0xFF; ch++) {
unsigned int v6 = ch * Rand[i];
// ...计算过程...
if (最终结果 == dword_4030B4[i])
putchar(ch);
}
}
4.3 注意事项
- IDA F5反汇编可能遗漏关键指令:
mul edx
div ecx
mov eax, edx ; IDA可能遗漏这行
- 强制类型转换不可省略:
v6 * (unsigned __int64)v6 % 0xFAC96621 // 必须保持强转
通过以上步骤,可以成功破解示例程序的反调试保护和算法验证机制。