附件下载链接

脱壳技术

寻找 OEP

根据跨段指令寻找 OEP

绝大多数加壳程序会在被加密的程序中加上一个或多个段(Section),在最后跳转至 OEP 时一般都是通过一个跨段跳转指令进行转移,所以依据跨段的转移指令(JMP 等)就可找到真正的入口点,并且在该跳转指令前一般会有 POPAD / POPFD 指令出现(恢复入口现场)。

在 x32dbg 中 Ctrl+B 搜索 E9 找到长跳转指令 在该位置下断点后执行即可进入程序真正的入口点,此时程序已完成脱壳。 使用 Scylla 将程序 dump 下来。虽然 dump 下来的程序无法运行,但是已经可以使用 IDA 进行静态分析。

根据堆栈平衡原理寻找 OEP

该方法即为广为人知的“ESP 定律”法,对绝大部分压缩壳都很有效,原理便是基于栈平衡,壳段代码在保存现场后,我们对 ESP 指向的内存单元下硬件访问断点,那么在壳段代码恢复现场时定会访问到这个单元,此时我们 EIP 所处的位置处于壳段代码末尾,距离跨段指令也就不远了。

当壳程序入口点执行完 pushad 后再栈顶下一个硬件断点。 运行程序即可在壳段代码末尾断下来。

观察法直接寻找 OEP

在有了一定的逆向分析经验后,我们可以对各种壳的特征略知一二,例如对于 UPX 来说,其跨段 JMP 往往处于壳段代码的靠后位置,如果我们了解这一点,可以直接去壳段代码尾寻找跨段 JMP,下普通断点,最后得到 OEP,这种方法非常简便,但需要有一定的逆向经验储备。

利用 OllyDump 插件寻找 OEP

OllyDump 插件自带寻找 OEP 的功能。 此时利用 OllyDump 脱壳。 自动修复导入表后脱壳程序可以正常运行。

输入表重建

在 Dump 操作结束之后,程序并不能直接运行,很大程度上是因为输入表并没有被修复好。

导入表结构:

利用 ImportREConstructor 修复导入表

运行未脱壳的程序,然后按如下步骤进行,注意查看找到的导入表是否有效。

修正转储应选择前面 x32Dbg dump 出的程序文件,修复后生成一个新文件。该文件可正常运行。

手工修复 IAT 表

调试发现程序调用的3种 API 函数。 由此可找到程序的 IAT 表。

在 dump 出的文件中选择一处空白位置构造 IAT 表。

首先先构造 IMAGE_IMPORT_DESCRIPTOR 的 Name 成员指向的字符串 Kernel32.dll 和 User32.dll 。 然后构造 IMAGE_IMPORT_BY_NAME 数组。 之后构造 INT 表,注意 RVA 和 FOA 的转换。 构造 IMAGE_IMPORT_DESCRIPTOR 表,其中 IAT 地址是前面动调得到的。 最后再PE头修复数据目录表项中导入表的地址和大小。保存后和正常运行。

Hook入门

Inline Hook

直接修改指令的 Hook 方式

32位的任务管理器(C:\Windows\SysWOW64目录下)结束任务调用的是 TerminateProcess 函数。

手动修改使得跳过该函数并平衡堆栈,发现目标进程并没有被杀死。因此尝试 Hook 该函数使得任务管理器无法结束进程。

这里采用 Dll 注入的方式进行 HOOK 。 首先编写注入代码:

// dllmain.cpp : 定义 DLL 应用程序的入口点。

#include "pch.h"

BYTE JMP[5],TMP[5];

bool hooked;

void* addr;

HANDLE WINAPI MyTerminateProcess(HANDLE hProcess, UINT uExitCode) {

return NULL;

}

void HookTerminateProcess() {

HMODULE hModule = LoadLibrary(L"Kernel32.dll");

if (hModule == NULL) {

MessageBox(NULL, L"Failed to load library.", NULL, 0);

return;

}

addr = (void*)GetProcAddress(hModule, "TerminateProcess");

if (addr == NULL) {

MessageBox(NULL, L"Failed to get process address.", NULL, 0);

return;

}

DWORD newProtect = PAGE_EXECUTE_READWRITE, oldProtect;

VirtualProtect(addr, 5, newProtect, &oldProtect);

memcpy(TMP, addr, 5);

JMP[0] = 0xE9;

*(DWORD*)&JMP[1] = (DWORD)MyTerminateProcess - (DWORD)addr - 5;

memcpy(addr, JMP, 5);

VirtualProtect(addr, 5, oldProtect, &newProtect);

hooked = TRUE;

}

void UnHook() {

if (!hooked) {

return;

}

DWORD newProtect = PAGE_EXECUTE_READWRITE, oldProtect;

VirtualProtect(addr, 5, newProtect, &oldProtect);

memcpy(addr, TMP, 5);

VirtualProtect(addr, 5, oldProtect, &newProtect);

hooked = FALSE;

}

BOOL APIENTRY DllMain( HMODULE hModule,DWORD ul_reason_for_call,LPVOID lpReserved) {

switch (ul_reason_for_call) {

case DLL_PROCESS_ATTACH:

HookTerminateProcess();

break;

case DLL_THREAD_ATTACH:

break;

case DLL_THREAD_DETACH:

break;

case DLL_PROCESS_DETACH:

UnHook();

break;

}

return TRUE;

}

利用注入工具将该 dll 注入到任务管理器中。

火绒剑分析进程发现注入成功。任务管理器也无法强制结束进程。 使用火绒扫描发现该 HOOK。

查看原文