XXXXXXXXh: ZZZZZZZZh ; ZZZZZZZZh is inside a
; buffer dynamically
; allocated by
; the packer, so it will
; not exist if you
; remove the packer.
ZZZZZZZZh: push ebp ; (*)
ror eax, 16h
pushf
popf
mov ebp, esp ; (*)
call @@1
db 68h
@@1:
add eax, 134h
....
jmp ACTUAL_ENTRY_POINT_OF_API + k
; first, we create the event (choose a random name for your event)
;首选,我们创建一个事件(为该事件随意选个名字)
push offset zsEventName ; name of event
push FALSE ; initial status = FALSE
...
call CreateEventA
; Now, we create the target as CREATE_SUSPENDED.
; The call returns a handle to the created process we need for later.
; The address space of the process has been initialised, but its
; primary thread is suspended. Now, we allocate some memory into
; the target to host our code.
push PAGE_EXECUTE_READWRITE ; attributes for the allocated
;memory
...
call VirtualAllocEx
; The return is the image base of the allocated memory. Finally, we can write to it, with
; kernel32.WriteProcessMemory, and run our code with kernel32.CreateRemoteThread.
; At this point, the logger is running while the main thread is suspended.
; The logger will change the status of the event
; we have created at some moment, we need to wait until then before to
; resume the main thread:
经被保存起来了。
对于给定的DLL(如kernel32),我们要做的如下:
1)获取kernel32的映像基址
2) 从PE-header得到(take) SizeOfImage
3)将对整个kernel32映像的权限更改到PAGE_EXECUTE_WRITECOPY
(Change permissions over the whole image of kernel32
to PAGE_EXECUTE_WRITECOPY)
4)保存DLL中的所有输出函数的原始入口点,可以在IMAGE_EXPORT_DIRECTORY.ED_AddressOfFunctions.里找到它们。
5)N= 由kernel32输出的API的总数,位于IMAGE_EXPORT_DIRECTORY.ED_NumberOfFunctions.
6)将kernel32的每一个API转移到我们的缓冲区。
7)存储对DLL的权限(一些壳用DLL激活异常)
我们来详细看看如何将所有的APIs重定向到缓冲区,注意要在壳运行前完成。
我们称我们使用的缓冲区为DivBuffer。该缓冲区大小为N*M,M是所有缓冲区中的最大值。我们要做的如下:
for (i=0; i<N; ++i){
Change the entry point
of the i-th API to DivBuffer[i*M].
Generate random garbage
and write it to DivBuffer[i*M].
Write some instructions, after the
garbage, to save the value of i.
Write a jump, after the previous
instructions, to our hooker procedure.
将i-th的API的入口点改变到DivBuffer[i*M].
产生随机垃圾并把它们写到DivBuffer[i*M]。
在垃圾代码后写一些指令保存i的值。
在上面的指令后面写一跳转,转到我们的钩子程序。
}
算法中,M只是垃圾代码大小的上界(对目前的壳而言,M=30是个好的数值)。我们创建的所有的垃圾缓冲区会来到(lead to)钩子程序(hooker
1)定位API的基址
2)以名字查找,通常比较……
3)模拟第i-th个缓冲区(假设它调用第i-th API)的前K个指令
4)跳转到第(K+1)-th 垃圾指令
最后,我们就能在我们的程序“记录器”的入口点挂钩API调用。
壳看起来并没有任何不同之处,但我们要让调用看起来它好像没有受到保护一样。(The packer doesn’t see any difference but we have
kept the call as if it was done without the protection.)
--写入目标调用的APIs的名字
--做一个从十六进制十进制到可打印字符串的小步骤(记住偏移量是以endian order形式给出的)
(Do a small procedure passing from hexadecimal to a printable string (remember that offsets are given in endian order).
--使用映射你的日志文件的共享文件,以便你不用从内存中抓取(dumping)就可以随时看到并保存它。从而使你控制可执行程序。
--记住文件映射不能在内存中扩大,用个大点的(1Mb)。
我们推荐(作为无版权目标程序)Yoda的加密器作实验。它重定向API调用,有少数你必须绕过的异常或小把戏。
lpSystemTime。下面的代码演示如何进行完整的过程:
;--------------------------------------------------------; hooking the return address from the call
;--------------------------------------------------------
; first, we save the bytes at the return address because we
; are going to overwrite them with a jump to our code
cld ; clear direction flag
mov esi, dword ptr [esp] ; take return
mov edi, offset your_buffer ;
mov ecx, 6 ; the size of a push/ret
repnz stosb
;
; next we overwrite them with a push/ret leading to our code
mov edi, dword ptr [esp] ; take return address
mov al, 68h ; write the push
stosb ;
lea eax, [My_GetLocalTime] ; write the address of my
;procedure
stosd ;
mov al, 0C3h ; write the ret
stosb ;
; we also need to keep track of lpSystemTime
mov eax, dword ptr [esp+4] ; store the parameter lpSystemTime
mov dword ptr [ebp+lpSystemTime], eax
现在,我们让目标程序完成对kernel32.GetLocalTime的调用,因为它已经钩住我们的代码了。注意,目标程序将对它所有的区块具有读/写权
限,所以你对此不必担心。现在,我们要改变返回值:
;--------------------------------------------------------
; Changing the return to our fake value
;--------------------------------------------------------
; The target jumps here when hooked:
My_GetLocalTime PROC
pushad
pushf ; not needed in this case but you might have
;to add it too
; compute the delta handle in ebp
call @@1
@@1:pop ebp
sub ebp, @@1
; modify the returned structure
.mov eax, dword ptr [ebp+lpSystemTime] ; point to the
;SYSTEMTIME structure
mov [eax.wYear], 2004
mov [eax.wMonth], 4
mov [eax.wDayOfWeek], 1
mov [eax.wDay], 8
; write back the bytes at the return instruction
cld
lea esi, [ebp+your_buffer]
lea edi, [ebp+API_ReturnAddress] ; the value we had at esp
;at the hooker
mov ecx, 6
repnz stosb
offset 0: ?????????
; dd immediately before the IAT
offset 1: DLL1_API1
; first API imported from this DLL
offset 2: DLL1_API2
; second API imported from this DLL
offset 3: ...
;
offset N: 0
; null terminating dd
; input: eax = guessed IAT address
; ouput: reconstruction of the
; imports table for the DLL to
; which eax belongs to
xor ecx, ecx ; counter
while ([eax+4*ecx] != 0)
{
push ecx ; save ecx
push eax ; save eax
call dword ptr [eax+4*ecx] ; Compel the target
; to do the call.
; This sends us to
; the buffer created
; by the packer to
; emulate the first
; instructions of
; the call.
get the API at our logger ;
restore the stack ;
pop eax ; restore eax, ecx
pop ecx ;
inc ecx ; next
}
我们有个在钩子程序调用的API,把它与一个固定编码值(hardcoded value)进行比较,用个计数器记住该API被调用的次数。完工。
对断点本身在很多选项,我们所用的用于显示一个关于信息,然后进入一个无限循环,如:here: jmp here。现在,你可以附加到你的调试器了(attach your debugger),暂停程序,NOP掉jmp,开始调试。
The savings are spectacular。
六、记录所有的异常日志
获得由壳激活的异常列表同样有帮助。如果我们想附加调试器并使用追踪,它特别有帮助,因为我们知道,它不会被任何异常杀掉。理解异常的最好教程是由Jeremy Gordon写的“Exception for assembler
Programmers“,必读。我们假定本文的最小背景。众所周知,钩住NTDLL.ZwContinue会给我们带来很多异常,但不是所有的信息我们都要。当我们有所松懈或(可能译得不对,原文是:we have unwindings)当句柄拒绝修复异常并把它传给下一个时, 问题来了。这种情况下,我们需要对工程做点逆向以找到正确的断点。结果如下:
References
[1] Havok, “Asprotected notepad” Codebreakers-Journal, First Issue
2004.
[2] Labir, E., “Adding imports by hand” Codebreakers-Journal, First
Issue 2004.
[3] Natzgul, “How to access the memory of a process” available at
Fravia.
[4] Kruse, T. “Processless Applications - Remote threads on Microsoft
Windows 2000, XP and 2003” Codebreakers-Journal,
First Issue 2004.