接触脱壳很久了。由于一直也不求上进,进步很慢。不过慢慢的也算有一个整体的认识。个人觉得脱壳的真谛。就是对抗各种反调试手段。恢复各种被壳破坏的PE结构。熟悉反调试手段、熟悉PE结构,是脱壳的必备技能。
本篇主要是分析一个简单的反调程序,介绍一下反调试的手段和思路。最近在看PracticalMalwareAnalysis,昨天晚上看到Anti-Debugger一章,在做书上的练习的时候由于海风大牛的SOD插件很强大,所以对这些练习都没当回事。到分析练习2的时候发现被检测到调试器的存在了。所以深入分析了一下。下面主要是这个程序的分析流程,和一些个人对于反调试的看法。
例子程序作为附件以上传。程序是一个判断输入的密码是否正确的程序。类似于一个CrackMe.难度不高。只要绕过所有的反调试可以在程序运行过程中在堆上发现正确的密码。关键就在于反调试。然后设置好SOD。SOD要按我说的设置。要不然就不好玩了(因为其实正确设置好SOD可以绕过本反调试。这是在我手工对抗成功以后才发现的本文就是要看这些反调试所以先按照错误的设置来)。我的一般设置是Skip Some Exceptions 、Remove Ep One shot, Break On LDr不选。也就是设置对话框的右侧列从下属第二个开始网上的3个都不选。原因其实也挺奇葩的。Skip Exceptions因为曾经这个影响了一次我的脱壳操作。Remove One Ep shot应该是移除oep处的One Shot的断点由于我平常面对的都是菜壳所以无需选择。break on ldr..其实。。我就不知道这个是干什么用的。按名字解释是加载模块的时候断下。基本没用过。不予评论。到这我已经羞愧的不行了。实在是水平很菜。然后就可以将程序载入OD开始调试。程序用了3处反调试。我们只主要讨论一处。.tls回调。
运行程序以后。由于sod插件的作用。断在.tls回调的入口处。
00401060 /. 55 PUSH EBP
00401061 |. 8BEC MOV EBP,ESP
00401063 |. 837D 0C 01 CMP DWORD PTR SS:[EBP+0xC],0x1
00401067 |. 75 18 JNZ SHORT Lab16-02.00401081
00401069 |. 6A 00 PUSH 0x0 ; /Title = NULL
0040106B |. 68 50804000 PUSH Lab16-02.00408050 ; |Class = "OLLYDBG"
00401070 |. FF15 C0704000 CALL DWORD PTR DS:[<&USER32.FindWindowA>>; \FindWindowA
00401076 |. 85C0 TEST EAX,EAX
00401078 |. 74 07 JE SHORT Lab16-02.00401081
0040107A |. 6A 00 PUSH 0x0
0040107C |. E8 AC0F0000 CALL Lab16-02.0040202D
00401081 |> 837D 0C 02 CMP DWORD PTR SS:[EBP+0xC],0x2
00401085 |. 75 05 JNZ SHORT Lab16-02.0040108C
00401087 |. E8 94FFFFFF CALL Lab16-02.00401020
0040108C |> 5D POP EBP
0040108D \. C2 0C00 RETN 0xC
大致看一下。这好像仅仅是个查看OllyDbg窗口是否存在的反调试。下面详细分析。
tls回调函数的原型。类似与DllMain函数:
void __stdcall TlsCallBack(PVOID DllHandle,ULONG Reason,PVOID Reserved)
也就是说。对于进程和线程的创建或退出。tls回调都会被调用。这时候在回去看,要注意00401081处的代码。比较了[ebp+0c]也就是第二个参数Reason与2。这时判断是否是线程的创建导致了tls回调被调用。因为SOD之在SOD运行前的这次进程创建为原因的对tls的调用断下。如果你忽略这里,。你很可能会把线程调用时存在与tls回调中的反调试也忽略了(好吧。。真相是因为笔者把这忽略了。所以当初调试的时候各种莫名其妙怎么被反调试发现的)。【00401087 |. E8 94FFFFFF CALL Lab16-02.00401020 】这个call是Dll_THREAD_ATTACH的主体。进去看一下。会发现下面的这个反调试手段:
SetLastError(3909);
OutputDebugString("b");
dwError = GetLastError();
if (dwError != 3909)
{
g_Magic += 1;
}
看到这段。。大家应该很熟悉。很多文档里都提及这这段反调试。后来我发现如果设置SOD的Skip Some Exception。这段反调试不会起作用。这也是我为什么要大家把SOd设置成我说的样子。
这个时候在.tls入口处【00401060 /. 55 PUSH EBP】下断。使每次反调试tls运行的时候我们都能调试这个函数。
然后F9让程序在oep断下。可以看到程序很简单。判断了一下参数的个数然后创建一个新线程调用Sleep()休眠了一会。之后就是【0040123A |. E8 D10E0000 CALL Lab16-02.00402110】处调用字符串比较函数了。我怎么知道这里是字符串比较函数的??因为我把程序放进Ida里分析了一下ida识别出这里是一个strncmp。所以到这我又要多说两句了。Ida和OD分属于静态反汇编和动态调试两种不同的工具。他们能提供给我们很多不同的信息。有必要使用两种工具共同分析,提高我们的效率。有Ida这么强大的识别工具存在。就无需我们自己去费力的挖掘这个strncmp函数了。可以看到strncmp的的参数中有一个是全局变量00408030。在Dump窗口查看一下这个变量的值。发现这个时候他还不是个正常的字符串(因为包含一些不是字符的字节)。这时候刚才发现那个CreateThread()创建的线程肯定是对此字符串做了一些操作。运行到CreateThread()..可以看到新线程的StartAddress.在StartAddress()下断(为了让OD在新线程断下)。在Strncmp下断(为了让新线程结束后OD在主线程再次断下)。然后Shift + F9 让OD在新线程中断下。由于我们在tls回调的入口处下了断点。所以新线程首先断在tls回调入口。单步运行,发现程序确实进入了DLL_THRAD_ATTACH的处理Call.
00401020 55 PUSH EBP
00401021 8BEC MOV EBP,ESP
00401023 51 PUSH ECX
00401024 C745 FC 39300>MOV DWORD PTR SS:[EBP-0x4],0x3039
0040102B |. 8B45 FC MOV EAX,DWORD PTR SS:[EBP-0x4]
0040102E |. 50 PUSH EAX ; /Error
0040102F |. FF15 08704000 CALL DWORD PTR DS:[<&KERNEL32.SetLastErr>; \SetLastError
00401035 |. 68 4C804000 PUSH Lab16-02.0040804C ; /String = "b"
0040103A |. FF15 04704000 CALL DWORD PTR DS:[<&KERNEL32.OutputDebu>; \OutputDebugStringA
00401040 |. FF15 00704000 CALL DWORD PTR DS:[<&KERNEL32.GetLastErr>; [GetLastError
00401046 |. 3B45 FC CMP EAX,DWORD PTR SS:[EBP-0x4]
00401049 |. 75 0F JNZ SHORT Lab16-02.0040105A
0040104B |. 8A0D 68A94000 MOV CL,BYTE PTR DS:[0x40A968]
00401051 |. 80C1 01 ADD CL,0x1
00401054 |. 880D 68A94000 MOV BYTE PTR DS:[0x40A968],CL
0040105A |> 8BE5 MOV ESP,EBP
0040105C |. 5D POP EBP
0040105D \. C3 RETN
单步运行。到【00401049 |. 75 0F JNZ SHORT Lab16-02.0040105A】发现由于是在调试器环境中。OutputDebugString()正确运行所以错误代码还是SetLastError()设置的。所以程序会进入0040104b处开始的流程。这个流程会更改一个全局变量的值,记住这个变量的地址[40a968]然后将z标志位置0。运行。这次会断在新线程的StartAddress.浏览一下这个函数,果然是这个函数操作了strncmp中的参数指向的字符串。而且在函数的开头就发现了。刚才tls回调中用到的全局变量[40a968]的身影。因为我们刚才没有对其进行操作。所以只需观察此变量会对程序有什么影响,无需更改此变量的值。单步运行。发现【00401111 |. 001D 31804000 ADD BYTE PTR DS:[0x408031],BL】处[40a968]变量的值会影响字符串的第二个字符的值。下面的【0040112B |. 64:8B1D 30000>MOV EBX,DWORD PTR FS:[0x30]】和【0040118B |. 8A5B 02 MOV BL,BYTE PTR DS:[EBX+0x2]】是程序的第三处反调试。fs:[30h]得到PEB的指针。PEB的+0x2偏移处是著名的BeingDebugged标志。如果按我说的SOD设置来。此标志应该被Patch成了0.在笔者的环境上如果选上了Break On Ldr会发现。此处的值还是1.不知道是由于笔者的环境问题。还是由于Break On Ldr。的需要。还希望大家有知道的还请告知一下笔者。【004011A2 |. 001D 32804000 ADD BYTE PTR DS:[0x408032],BL】此处代码用到了BeingDebugger标志的值影响了字符串的第三个字符。至此一下都是简单的运算和比较的流程了。此函数运行过后。[0x408030]已经是一个正常的字符串byrrp@ss...了 由于strncmp之用了此字符串的前4个字符所以正确的密码是byrr.如果。。你发现你的结果不一样。考虑.tls中的OutputDebugString()和 Peb的BeingDebugged的标志位的问题。
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
上传的附件: