在软件安全领域,二进制后门技术是一种精妙的对抗策略。其核心思想是:通过极小的修改,例如更改程序中的某个字节或替换一个函数,就能将原本的加密流程转变为解密流程,从而大幅降低逆向工程的分析成本。这类似于在RC4算法中巧妙地切换密钥流的使用方式。本文将通过一个实际案例,探讨如何绕过多种反调试技术,并利用二进制后门手法实现文件解密。
初次分析与反调试对抗
目标程序会加载并解密一个内置的动态链接库(DLL),然后调用该DLL中的加密函数对用户指定的文本文件进行加密。我们的目标是将对加密函数的调用转变为对解密函数的调用,从而直接获取明文。
零地址访问异常反调试
该程序的一个显著特点是采用了访问零地址的异常反调试技术。如果调试器没有正确处理这个异常,程序将无法继续执行。
在调试器中,可以看到程序执行了如下汇编指令,故意触发一个访问违规:
mov dword ptr [ecx], 0 ; 访问 0 地址,触发异常反调试
此时,ECX寄存器的值为0。这条指令执行后,会转入一个由程序自定义的异常处理函数。如果在IDA等工具中进行动态调试时没有经过这个处理函数,程序就会卡住。
解决方法非常直接:将与此次异常触发及后续处理相关的所有汇编指令全部用NOP指令(0x90)替换。这包括触发异常的指令本身以及整个自定义的异常处理函数块。
需要NOP掉的代码区域示例如下:
; 触发异常的代码附近
...
mov [ebp+var_20], 0
mov ecx, [ebp+var_20]
mov dword ptr [ecx], 2Ah ; ‘*‘
...
; 异常处理函数框架
push 0xFFFFFE00
push offset stru_7E3848
push offset __except_handler4
mov eax, large fs:0
push eax
...
通过这种方式,我们移除了这层反调试保护,使程序能够在调试器中顺畅运行。
主函数反编译与花指令
分析主函数时,反编译窗口可能显示异常或无法正确生成伪代码。查看汇编代码,可以发现其中插入了干扰分析和反调试的无效指令(花指令)。
同样,处理方法是定位并NOP掉这些无效指令。清理后,反编译引擎便能正确工作,生成清晰的主函数伪代码。
清理花指令后,主函数的核心逻辑得以显现,主要包括以下几个关键步骤:
- 解密程序内嵌的资源(一个DLL文件)。
- 加载并调用解密出的DLL中的功能。
- 对指定文件进行加密操作。
TLS回调函数反调试
程序还使用了线程本地存储(TLS)回调函数进行反调试。该回调函数会在主函数执行前被调用,如果检测到调试器存在,则直接退出程序。
TLS回调函数 TlsCallback_0 的伪代码如下所示:
BOOL __stdcall TlsCallback_0(int a1, int a2, int a3)
{
BOOL result; // eax
int v4; // [esp+14h] [ebp-1Ch]
result = IsDebuggerPresent();
if ( result )
exit(0);
while ( ++v4 < 23 )
{
result = v4;
aW3lc0m3T0Th3Ct[v4] ^= 0x22u;
}
return result;
}
为了绕过此反调试,只需将调用 exit(0) 的指令NOP掉即可,无需处理其他逻辑。修改后,即使处于调试状态,程序也能继续运行。
内置DLL资源解密
使用资源编辑工具检查程序,可以发现其内嵌了一个经过异或加密的PE文件(DLL)。根据主函数中的解密逻辑,可以推断加密密钥为 0x33。
主函数中加载和解密该资源的C伪代码如下:
hModule = GetModuleHandleW(0);
hResInfo = FindResourceW(hModule, (LPCWSTR)0x65, L"DATA");
// ...
if ( hResInfo )
{
Size = SizeofResource(hModule, hResInfo);
hResData = LoadResource(hModule, hResInfo);
v10 = (void *)sub_402156(Size); // 分配内存
if ( hResData )
{
Src = LockResource(hResData);
if ( Src )
{
memcpy(v10, Src, Size);
// 使用 0x33 进行异或解密
for ( i = 0; i < Size; ++i )
*((_BYTE *)v10 + i) ^= 0x33u;
dword_40541C = sub_401CE0(v10, Size);
}
FreeResource(hResData);
}
j_j_free(v10);
}
成功解密出DLL后,分析其导出函数,可以发现大量以 Crypt 开头的函数,包括加密(Encrypt)和解密(Decrypt)功能。这表明主程序将通过动态加载这个DLL来调用其中的加解密功能,这正是实现二进制后门的基础。
关键函数分析与后门植入
在清理了所有反调试障碍并理解了资源解密流程后,我们聚焦于主函数中最后调用的两个关键函数 sub_401320() 和 sub_402000()。
分析可知,sub_401320 负责加载解密出的DLL并获取其导出函数的地址。sub_402000 则负责处理目标文件(例如 C:......\document\1.txt),并调用DLL中的加密函数对其内容进行加密。
核心的后门思路就在于此:既然程序调用的是DLL中的 CryptEncrypt 函数,我们只需将其改为调用 CryptDecrypt 函数,程序就会自动执行解密操作而非加密。
实施步骤
- 修改导入表:由于原程序可能只导入了
CryptEncrypt 函数,我们需要在二进制文件的导入表中手动添加对 CryptDecrypt 函数的引用。
- 修补调用指令:找到程序中调用加密函数的位置,将调用的目标地址从
CryptEncrypt 改为 CryptDecrypt。
- 调整参数(如果需要):通常解密函数与加密函数的参数一致,但需仔细核对。在某些情况下,可能需要微调栈上的参数。例如,若解密函数比加密函数少一个参数,则需要NOP掉一条对应的
push 指令以平衡堆栈。
通过以上几步修改,我们就在程序中植入了一个“后门”。运行修补后的程序,它将会加载DLL并调用解密函数,直接输出目标文件的原始明文内容,从而实现了“将加密变为解密”的目的。这种技术在 安全/渗透/逆向 领域,尤其是在CTF竞赛和特定的软件分析场景中,是一种高效且巧妙的技巧。
参考资料
[1] 后门函数技术在二进制对抗中的应用, 微信公众号:mp.weixin.qq.com/s/BAOebe1wG5frxNmT42AU6Q
版权声明:本文由 云栈社区 整理发布,版权归原作者所有。