您的位置:首页 > 其它

简单的反逆向工程指南(一)

2013-07-14 20:11 190 查看
本文由 @lonelyrains 出品,转载请注明出处。 
文章链接: http://blog.csdn.net/lonelyrains/article/details/9291093

原文在这里,作者2008年发布这篇文章的时候还是高中生,自惭~
本文博客地址:http://blog.csdn.net/lonelyrains/article/details/9291093

简介

       在之前的文章里,曾经简要介绍过初级地使用Win32 API反调试/调试检测的方法。这篇文章里,我打算在逆向工程方面走得远一点,多探索一点中间级技术,消遣一下逆向工程师们。在上一篇文章里,有些评论说我提到的反调试技术很容易被中间级逆向工程玩家攻破。我想说,在开发人员和破解逆向工程师之间有一场正在上演的战役。每次防破解工程时发布一种新技术,逆向工程师们总有特殊的方法绕过这道障碍。这是破解和反逆向的角力。这里提到的大多数技术都可以被轻易破解,还有一些不那么容易。然而,它们都可以以某种手段破解。我在此把这些方法共享,希望有其他人能应用这些方法,并且延伸出新的路数,挑战当前的经典教义。

背景

       所有对逆向工程感兴趣的人需要对汇编有深刻的理解,所以如果你的汇编基础有点粗糙或者你只是个初学者,这里有些网站推荐:

汇编导论
IA-32 导论指引   【注:IA-32指的是Intel Itanium架构32位,itanium architecture。链接打开只是主页,不是预期的内容】
Iczelion的Win32汇编主页 【注:法文网页,打不开了】

内联函数

       我本觉得这部分不值得一提,但是当读到这篇文章或者附上的源码时,可能读者会注意到函数被标记为内联属性。虽然会导致执行文件变大,但它对反逆向工程是非常重要的。如果有非常详尽的函数入口和分区,逆向工程师可以更轻松完成它们的任务。他们将函数被调用时到底做了什么。如果是内联的,就不会这样,他们需要猜测函数的行为。

断点

       逆向工程中有三种类型的可用断点:硬件、内存和INT 3h的断点。断点是逆向工程的基础,没有它们,模块的现场分析无从谈起。在一个程序的任意位置设置断点,都可以让程序暂停执行。逆向工程师们可以在很多地方设置断点,例如Windows API,可以轻易找到‘坏男孩’消息(例如一个消息框,告诉你填了一个错误的序列号)从哪里来的。事实上,这可能是在破解时最有用的技术。唯一的挑战可能就是在程序中进行提示消息的字符串搜索。这就是为什么对例如MessageBox、VirtualAlloc、CreateDialog这样提示和保护用户消息流程的API来说,断点检测至关重要。下面第一个例子将描述使用最典型的INT
3h指令断点的场景。

INT 3

       INT 3h断点在IA-32指令集中有所描述,使用操作码CC(0xCC)。这是这种断点最常见的表达式;然而,它也可用导致一些麻烦的0xCD 0x03 字节序组合来表达。检测这种断点相对简单一些,示例代码如下。然不够,我们必须小心使用,因为使用这种扫描方法可能导致误判(false positives)。

bool CheckForCCBreakpoint(void* pMemory,  size_t SizeToCheck)
{
unsigned char *pTmp = (unsigned char*)pMemory;
for (size_t i = 0; i < SizeToCheck; i++)
{
if(pTmp[i] == 0xCC)
return true;
}

return false;
}
       下面是另一种检测INT 3断点的混淆方法。要知道上面的代码即使对逆向新玩家来说都太傻叉了(stick out like a sore thumb)。通过引入间接方法,你,守卫者,将提高成功保护应用的概率。

bool CheckForCCBreakpointXor55(void* pMemory,  size_t SizeToCheck)
{
unsigned char *pTmp = (unsigned char*)pMemory;
unsigned char tmpchar = 0;

for (size_t i = 0; i < SizeToCheck; i++)
{
tmpchar = pTmp[i];
if( 0x99 == (tmpchar ^ 0x55) ) // 0xCC xor 0x55 = 0x99
return true;
}

return false;
}
内存断点

       内存断点通过调试器使用守卫内存页实现。守卫内存页就像内存页访问的一次性闹铃。简言之(in a nutshell),当内存页被标记为PAGE_GUARD并且被访问时,就会抛出一个STATUS_GUARD_PAGE_VIOLATION异常,然后通过当前程序的异常处理机制捕获和处理。此时,无法精确进行内存断点检测。然而,我们可以使用调试器实现内存断点的技术检测我们的程序当前是否在调试环境运行。大体上(in essense),就是我们先分配一块动态缓存,写一个RET到这个缓存中。然后标记这页为守卫页,并往栈上压入一个潜在的返回地址。这样一来,如果在调试环境下,就跳到我们的页内,特别是OllyDBG,然后出发RET指令,在跳到我们的页内之前,返回到我们往栈上放的地址。否则,抛出一个STATUS_GUARD_PAGE_VIOLATION异常,然后就知道不在被OllyDBG调试。下面是源码样例:

bool MemoryBreakpointDebuggerCheck()
{
unsigned char *pMem = NULL;
SYSTEM_INFO sysinfo = {0};
DWORD OldProtect = 0;
void *pAllocation = NULL; // Get the page size for the system

GetSystemInfo(&sysinfo); // Allocate memory

pAllocation = VirtualAlloc(NULL, sysinfo.dwPageSize,
MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE);

if (pAllocation == NULL)
return false;

// Write a ret to the buffer (opcode 0xc3)
pMem = (unsigned char*)pAllocation;
*pMem = 0xc3;

// Make the page a guard page
if (VirtualProtect(pAllocation, sysinfo.dwPageSize,
PAGE_EXECUTE_READWRITE | PAGE_GUARD,
&OldProtect) == 0)
{
return false;
}

__try
{
__asm
{
mov eax, pAllocation
// This is the address we'll return to if we're under a debugger
push MemBpBeingDebugged
jmp eax // Exception or execution, which shall it be :D?
}
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
// The exception occured and no debugger was detected
VirtualFree(pAllocation, NULL, MEM_RELEASE);
return false;
}

__asm{MemBpBeingDebugged:}
VirtualFree(pAllocation, NULL, MEM_RELEASE);
return true;
}
硬件断点

       硬件断点是Intel在他们的处理器架构中实现的一种技术,通过利用特殊寄存器Dr0~Dr7控制。Dr0~Dr3是保存断点的32位寄存器。Dr4和Dr5是Intel保留用作调试其他寄存器的。Dr6和Dr7用作控制断点的行为(Intel1【注,不知道Intel1代表什么意思】)。Dr6和Dr7寄存器是如何影响断点的行为,这部分的内容有点太多。不过,如果有兴趣,可以参阅Intel®
64 和 IA-32 架构软件开发手册卷3B:系统编程向导:寄存器工作原理深度解释(System Programming Guide for an indepth explanation of how registers work)。

       现在,为了检测并且/或者移除硬件断点,有两种方法我们可以采用:Win32的GetThreadContext和SetThreadContext,或者使用结构化异常处理。在下面第一个例子中,将展示如何使用Win32的API:

// CheckHardwareBreakpoints returns the number of hardware
// breakpoints detected and on failure it returns -1.
int CheckHardwareBreakpoints()
{
unsigned int NumBps = 0;

// This structure is key to the function and is the
// medium for detection and removal
CONTEXT ctx;
ZeroMemory(&ctx, sizeof(CONTEXT));

// The CONTEXT structure is an in/out parameter therefore we have
// to set the flags so Get/SetThreadContext knows what to set or get.
ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;

// Get a handle to our thread
HANDLE hThread = GetCurrentThread();

// Get the registers
if(GetThreadContext(hThread, &ctx) == 0)
return -1;

// Now we can check for hardware breakpoints, its not
// necessary to check Dr6 and Dr7, however feel free to
if(ctx.Dr0 != 0)
++NumBps;
if(ctx.Dr1 != 0)
++NumBps;
if(ctx.Dr2 != 0)
++NumBps;
if(ctx.Dr3 != 0)
++NumBps;

return NumBps;
}
       SEH方法来操作调试寄存器在反逆向工程的程序中更常见,并且通过汇编更容易实现,代码示例如下:

; One quick note about this little prelude; in Visual Studio 2008
; release builds are compiled with the /SAFESEH flag which helps
; prevent exploitation of SEH by shellcode and the likes.
; What this little snippet does is add our SEH Handler to a
; special table containing a list of "safe" exceptions handlers , which
; if we didn't in release builds our handler would never be called,
; this problem plauged me for a long time, and im considering writing
; a short article on it

ClrHwBpHandler proto
.safeseh ClrHwBpHandler

ClearHardwareBreakpoints proc
assume fs:nothing
push offset ClrHwBpHandler
push fs:[0]
mov dword ptr fs:[0], esp ; Setup SEH
xor eax, eax
div eax ; Cause an exception
pop dword ptr fs:[0] ; Execution continues here
add esp, 4
ret
ClearHardwareBreakpoints endp

ClrHwBpHandler proc
xor eax, eax
mov ecx, [esp + 0ch] ; This is a CONTEXT structure on the stack
mov dword ptr [ecx + 04h], eax ; Dr0
mov dword ptr [ecx + 08h], eax ; Dr1
mov dword ptr [ecx + 0ch], eax ; Dr2
mov dword ptr [ecx + 10h], eax ; Dr3
mov dword ptr [ecx + 14h], eax ; Dr6
mov dword ptr [ecx + 18h], eax ; Dr7
add dword ptr [ecx + 0b8h], 2 ; We add 2 to EIP to skip the div eax
ret
ClrHwBpHandler endp
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息