这个程序好像是一道CTF的题,对我这样的新手来说难度很大,解题过程中遇到了不少坑,还学到了新的反调试技巧。下面我将记录下我逆向这个程序的过程。
一、去花指令,过反调试,分析TLS回调函数功能
这个程序用到了TLS反调试技术,很遗憾,我用的编程达人OD和X64DBG都有反反调试插件,直接把TLS过了,所以我刚开始甚至没意识到反调试的存在。
然而,如果您使用无插件的OD,那么您运行该程序可能会崩溃,又或者即使输入了正确密钥,程序也会提示密钥错误,这是因为,程序会检测当前是否正在被调试,然后会根据这个判断结果修改随机数种子。如果正在被调试,那么生成的种子也是错的。
关于TLS反调试,我也写了一篇博客记录。https://blog.youkuaiyun.com/Kwansy/article/details/108570075
要逆向这个程序,首先必须了解TLS的原理,TLS回调函数会在主函数调用前由操作系统调用,作者把一些关键操作放在TLS回调里做了,所以我们必须跟进TLS函数,看看作者干了些啥。
打开StrongOD插件选项,勾上“在TLS断下”
这样,就能跟进第一个TLS回调函数。
关于TLS函数的位置,我们可以打开PE工具查看
然后我们会发现作者弄了很多花指令,模板有两种,第一种作者喜欢放到函数开头附近,402006 的call就是一个花指令,是通过一系列的 CALL, ADD [ESP] 和 RET 来实现的,分析清楚他的模板之后,可以NOP掉了。
后面还有很多花指令,基本都是CALL的模板,全部NOP掉,分析第一个TLS回调,我在OD和IDA都分析过了,直接说结论,第一个TLS的功能是反调试,和检测调试状态,最后,动态修复第二个TLS回调函数的地址。
void __stdcall TlsCallback_0(int a1, int a2, int a3)
{
void (__stdcall *TlsCallback_1)(int, int, int); // edi
unsigned int v4; // et0
void *hThread; // eax
void *hProcess; // eax
unsigned int v7; // [esp+0h] [ebp-10h]
if ( a2 == 1 )
{
TlsCallback_1 = ::TlsCallback_1;
v4 = __readeflags();
v7 = v4;
if ( v4 & 0x100 )
TlsCallback_1 = 0;
__writeeflags(v7);
if ( *(_DWORD *)&NtCurrentPeb()->InheritedAddressSpace & 0x10000 )
TlsCallback_1 = 0;
hThread = (void *)GetCurrentThread();
NtSetInformationThread(hThread, ThreadHideFromDebugger, 0, 0);
hProcess = (void *)GetCurrentProcess();
NtQueryInformationProcess(hProcess, ProcessDebugPort, &isdebug, 4u, 0);
if ( isdebug )
TlsCallback_1 = 0;
dword_404036 = (int)TlsCallback_1; // 动态patch第二个TLS回调
}
}
只要我们把反调试过掉,第二个TLS回调就有了,同样的道理,跟进TLS2,去掉花指令,分析代码。
void __stdcall TlsCallback_1(int a1, int a2, int a3)
{
void *hProcess; // eax
if ( a2 == 1 )
{
hProcess = (void *)GetCurrentProcess();
NtQueryInformationProcess(hProcess, ProcessDebugPort, &isdebug, 4u, &isdebug);
isdebug ^= 0x31333337u;
dword_40403A = 0;
isdebug ^= *(unsigned __int8 *)start;
}
}
第二个TLS回调函数的功能就是修改了isdebug这个全局变量的值,然后给TLS数组的第三个位置填0,所以就没有第三个TLS回调了。
到这里为止,我们完成了去花指令,反反调试的工作,下一步就是分析算法。
二、分析算法
怎么找到密钥检验算法就略过了,用IDA反编译函数,根据功能分析函数,重命名一些变量后,得到如下代码:
int __stdcall CheckKey(char *key)
{
int result; // eax
int v2; // edi
unsigned __int8 v3; // al
char *v4; // esi
unsigned int i; // ebx
unsigned int v6; // eax
unsigned int v7; // edx
unsigned int v8; // edx
unsigned int v9; // edx
unsigned int v10; // edx
unsigned int v11; // edx
unsigned int v12; // edx
unsigned int v13; // edx
unsigned int v14; // edx
unsigned int v15; // edx
unsigned int v16; // edx
unsigned int v17; // edx
unsigned int v18; // edx
unsigned int v19; // edx
unsigned int v20; // edx
unsigned int v21; // edx
if ( strlen(key) != 42 )
return MessageBoxA(0, aThinkAgain, 0, 0);
v2 = 0;
v3 = *key;
v4 = key + 1;
while ( v3 )
{
v2 += v3;
v3 = *v4++;
}
srand(isdebug ^ v2