指令变形初识&TLS反调试&脚本去花指令&随机数运算&逆向算法
字数 657 2025-08-06 20:12:41

TLS反调试与花指令去除技术详解

一、程序执行流程与TLS回调

1.1 程序执行顺序

传统认知中main函数是程序执行的起点,但实际上在main之前还有大量初始化代码:

  1. 全局变量初始化:全局变量的构造函数会在main之前执行
  2. 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 常见反调试手段

  1. 隐藏线程
NtSetInformationThread(GetCurrentThread(), ThreadHideFromDebugger, 0, 0);
  1. 检测调试端口
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中识别步骤:

  1. call指令跳转到下一条指令
  2. 修改返回地址(通常通过add [esp], x
  3. retn跳转到修改后的地址
  4. 中间指令为无效花指令,可用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 算法破解步骤

  1. 爆破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;
        }
    }
}
  1. 生成随机数序列
void SetRand() {
    srand(seed);
    for (int i = 0; i < 42; i++)
        Rand[i] = rand();
}
  1. 爆破密码字符
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 注意事项

  1. IDA F5反汇编可能遗漏关键指令:
mul edx
div ecx
mov eax, edx  ; IDA可能遗漏这行
  1. 强制类型转换不可省略:
v6 * (unsigned __int64)v6 % 0xFAC96621  // 必须保持强转

通过以上步骤,可以成功破解示例程序的反调试保护和算法验证机制。

TLS反调试与花指令去除技术详解 一、程序执行流程与TLS回调 1.1 程序执行顺序 传统认知中 main 函数是程序执行的起点,但实际上在 main 之前还有大量初始化代码: 全局变量初始化 :全局变量的构造函数会在 main 之前执行 TLS回调函数 :线程局部存储(TLS)回调函数在程序入口点(OEP)之前执行 验证方法: 1.2 TLS(线程局部存储)技术 TLS设计初衷是为线程提供访问全局变量的便捷方式,但也可用于反调试: 二、TLS反调试技术 2.1 常见反调试手段 隐藏线程 : 检测调试端口 : 2.2 TLS反调试示例 三、花指令分析与去除 3.1 花指令原理 设计思想 : 构造恒成立的EIP跳转 插入无效数据干扰分析 优缺点 : 优点:有效防止静态分析 缺点:对动态调试影响有限 3.2 花指令识别 常见花指令模式: 特征: 以 E8 01 00 00 00 开头 以 C3 结尾 3.3 手动去除花指令 OD中识别步骤: call 指令跳转到下一条指令 修改返回地址(通常通过 add [esp], x ) retn 跳转到修改后的地址 中间指令为无效花指令,可用 nop 填充 3.4 自动化去除脚本 OD脚本示例: 四、逆向算法分析 4.1 关键算法结构 4.2 算法破解步骤 爆破keySum : 生成随机数序列 : 爆破密码字符 : 4.3 注意事项 IDA F5反汇编可能遗漏关键指令: 强制类型转换不可省略: 通过以上步骤,可以成功破解示例程序的反调试保护和算法验证机制。