首页
论坛
课程
招聘
[翻译]Anti-Anti-Dump and nonintrusive tracers(上)
2006-5-2 13:31 21736

[翻译]Anti-Anti-Dump and nonintrusive tracers(上)

2006-5-2 13:31
21736
【文章标题】: 反--反转存和非侵入性跟踪器(上)
【文章作者】: deroko/ARTeam;译者:kkbing/看雪论坛
【作者声明】: 此文时看雪老大从ARETeam移入,见其有些价值拿来翻译。其中存在很多不足甚至是错误的地方,欢迎批评指正。特别感谢aalloverred 的指正
--------------------------------------------------------------------------------
【详细过程】
  译文:      
  1. 摘要
  2..必须的知识
  2.1.自定位代码
  2.2.找回kernel32.dll基址和APIs
  2.3. 内存补丁注入
  2.4 混合钩子的方法
  2.5 接下来呢?
  3.内存管理
  3.1.扩展程序空间
  3.2.用VirtualAlloc 和 VirtualFree管理内存
  3.3.Delphi代码的问题
  3.4.内存管理的结论
  关键词:编码,钩子,反-转存,非入侵,内存管理
  1.  摘要
  现在有许多壳都是把代码分割并重定位到新的缓冲区来阻止转存。这样的缓冲区有时很难修复,从而许多人在这里放弃了。我可以列举几例,运用得就是这种方法,如:Armadillo, ASProtect SKE, krypton等。到目前我们是运用od脚本或外面的工具去修复被分割的代码或被除去的输入地址表。然而我们并不知道他的思想方法,最终也学不到新东西。
  我在这里展示一些思想方法,它已经被运用到解决以上的问题了,但是要理解它是有些困难的。首先你应该熟悉汇编,PE格式和钩子。最好有些基础,因为在这篇文章里会涉及他们中的一些重要部分。
  我想这些技巧还没有运用于RCE,至少我还没见到。无论如何我会涉及到RCE的一些有趣的方面。
  2.  必须的知识
  如果你已知道所有的这方面材料。就可以进入第3部分了,因为我将覆盖自定位代码和向目标进程注入的一些基础知识。
  2.1.自定位代码
  自定位代码是一种可以在任何特定的内存区域中执行的代码。这样的代码不仅用在病毒和shell中,而且一些壳也用到。自定位代码的主要问题是访问数据的能力。因此,病毒作者用“delta”偏移去访问自身的变量。在这一点上我将走的很快,并直接展示代码以及如何在自定位代码中参考变量。
                      call delta
  delta:              pop ebp
                      sub ebp, offset delta
  
                      mov eax, [ebp+kernel32]
  
                      call [ebp+GetModuleHandleA]
  kernel32             dd     ?
  GetModuleHandleA     dd     ?
  可见,自定位代码中熟练操作数据并不困难。这里有一些规则:
  ――ebp经常用作delta, esi, edi和ebx 也可能被用作delta ,因为在调用API时,他们的值并不会改变。我一直用的是ebp,因为esi/edi经常联合应用于数据的拷贝,而ebx被用来当作重要数据的指针(如:在病毒感染期间,可以用它指向任何我们所需的PE文件。)(译者注:call/pop/sub三个指令的组合是经典的解决代码重定位的方法,几乎所有的病毒开始就是这三句)
  以下是由Super/29a 提出,稍后由Benny/29a [1]描述的一个运用delta的一个技巧,可以使你编译的代码更小。
                      call delta
  delta:              pop ebp
                      mov eax, [ebp+kernel32-delta]
                      call [ebp+GetModuleHandleA-delta]
  kernel32            dd   ?
  GetModuleHandleA    dd   ?
  事实上,这是一个非常好的技巧,但是我们现在不太多关心代码的大小.我们可以运用以前的方法,因为它在调试时的可读性强,并且在编写自定位代码时键入的更少。好了,我要告诉你们的关于自定位代码的就是这些了。不要只学习它的定义(你会找到许多关于它的),更重要的是它的原理。
  2.2 重新得到 kernel32.dll 基址和 APIs
  对于自定位代码来说,下一个重要的问题就是在Win32环境下调用APIs。为了能这样做,我们需要定位kernel32.dll的基址。以下是一些我们可以完成它的方法。
  -扫描 SEH
  -由Ratter/29a 提供的 PEB 技巧
  -获得硬编码。(译者注:比如Win2k下一般是77e60000h,WinXP SP1
            是77e40000h,SP2是7c800000h等。但是这么做不具有通用性)
  扫描 SEH---首先我们需要知道描述的SEH(结构化异常处理)的数据结构表
  kd> dt nt!_EXCEPTION_REGISTRATION_RECORD
  +0x000 Next       : 前一个 _EXCEPTION_REGISTRATION_RECORD结构
  +0x004 Handler     : 异常处理回调函数地址
  kd>
  (译者注:这是windbg中的命令,用来获取结构的信息,很实用)
  
  任何SEH链将会赋予一个指向kernel32.dll中某个值的句柄,当然,如果这是最后一个EXCEPTION_REGISTRATION_RECORD,那么_EXCEPTION_REGISTRATION_RECORD 结构的第一个参数名将被置为-1,这样我们就知道何时在kernel32.dll中获取地址了
  
  getkernelbase:
                  pushad
                  xor edx, edx
                  mov esi, dword ptr FS:[edx]
  __seh:         lodsd
                  cmp eax, 0FFFFFFFFh
                  je __kernel
                  mov esi, eax
                  jmp __seh
  __kernel:       mov edi, dword ptr[esi + 4]
                  and edi, 0FFFF0000h
  __spin:         cmp word ptr[edi], 'ZM'
                  jz __test_pe
                  sub edi, 10000h
                  jmp __spin
  __test_pe:      mov ebx, edi
                  add ebx, [ebx.MZ_lfanew]
                  cmp word ptr[ebx],'EP'
                  je __exit_k32
                  sub edi, 10000h
                  jmp __spin
  __exit_k32:     mov [esp.Pushad_eax], edi
                  popad
                  ret
  
  这个代码并不是最优化的,但是它展示了逻辑。扫描SHE直到这里等于-1,简单的获得句柄的地址和搜索MZ与PE符号。一旦我们找到了他们,我们就获了kernel32.dll的地址。
  PEB 方法---这个方法是由Ratter/29a发现和提出的,我会给出例子,但是要想获得更多的解释请回到[2](译者注:应指的是文后参考文献[2],也可以参见http://www.nsfocus.net/index.php?act=magazine&do=view&mid=2002)
  
  mov eax, dword ptr FS:[30h]
  mov eax, dword ptr[eax+0ch]
  mov eax, dword ptr[eax+1ch]
  mov eax, dword ptr[eax]
  mov eax, [eax+8]
  
  首先我们找回PEB的值,接着找到PEB_LDR_DATA,随后我们就进入到InInitializationOrderModuleList,头一个LIST_ENTRY指向ntdll.dll ,再进入下一个表的入口,瞧,kernel32.dll 的基址我们就接收到了。
  
  获得装入时硬编码的值----这个非常简单,并不需要太多的知识,我们用GetModuleHandleA去重新获得kernel32.dll的值并储存到偏移值中。
  例码:
             pushs <"kernel32.dll">
             call GetModuleHandleA
             mov kernel32, eax
  loader:
  ...
  kernel32   dd   ?
  
  重新获取API的地址也很简单,一旦我们获得了kernel32.dll,我们就可以写出我们自己的GetProcAddress,在kerlnel32.dll的输出表中查找我们所需的APIs
  输出表被装载在离PE头偏移78h处,它的结构如下:
  typedef struct _IMAGE_EXPORT_DIRECTORY {
        DWORD Characteristics;
        DWORD TimeDateStamp;
        WORD MajorVersion;
        WORD MinorVersion;
        DWORD Name;
        DWORD Base;
        DWORD NumberOfFunctions;
        DWORD NumberOfNames;
        DWORD AddressOfFunctions; // 指向导出函数地址表的RVA
        DWORD AddressOfNames; // 指向函数名地址表的RVA
        DWORD AddressOfNameOrdinals; // 指向函数名序号表的RVA
  } IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
  
  在这个结构中有三个重要的成员:
  DWORD AddressOfFunctions; //指向导出函数地址表的RVA
  DWORD AddressOfNames; //指向函数名地址表的RVA
  DWORD AddressOfNameOrdinals; //指向函数名序号表的RVA
  以下的表格将会表示的更加清晰。
  
  array of name RVA          oridnals           array of functions RVA
  +---------------+     +---------------+     +---------------+
  | RVA1 name |  <----->  | ordinal1 |    <----->| RVA of API1 |
  +---------------+ +   ---------------+      +---------------+
  | RVA2 name |  <----->  | ordinal2 |    <----->| RVA of API2 |
  +---------------+ +-  --------------+      +---------------+
  | RVA3 name |  <----->  | ordinal3 |    <----->| RVA of API3 |
  +---------------+ +-  --------------+       +---------------+
  | RVA4 name |  <----->  | ordinal4 |    <----->| RVA of API4 |
  +---------------+ +--  -------------+       +---------------+
  | RVA5 name |  <----->  | ordinal5 |     <----->| RVA of API5 |
  +---------------+  +--  -------------+       +---------------+
  我们先在AddressOfNames中搜寻我们所要API的名字会得到他的索引,然后再依此索引在AddressOfFunctions中得到oridnal的索引,接着就可以获得本程序中API的RVA(相对虚拟地址),最后加上dll的基址就是我们所需API的地址。就是这么简单(译者注:可以参见罗云彬的书,讲的比较详细)
  为了缩减病毒和后来的shellcodes的大小,并逃脱扫描检测特殊字符串,病毒作者运用hash或crc32校验来寻找APIs。最简单,迄今最快的hashing algo由z0mbie介绍,由rol/xor组成。(译者注:参见29A-4.227)
  __1:   rol eax, 7 ;hash algo (x) by z0mbie
            xor al, byte ptr [edx]
            inc edx
            cmp byte ptr [edx], 0
            jnz __1
  2.3.内存补丁注入
  内存补丁注入事实上是简单的装入,它将注入把我们的自定位代码注入目标进程,这样的注入为我们干一些卑鄙的工作(如:钩子),也许你会奇怪我为什么不用dll注入的方法。简单的说我不喜欢在一个文件夹下有两个源文件,而且作为病毒程序,除了独立偏移外我不喜欢任何其它的事情。记住,自定位代码是非常好的,而dll注入在C程序中很普遍。
  好了,让我们来看看如何把自定位代码注入到目标中。首先我们用CreateProcessA创建一个挂起的进程,接着用VirtualAllocEx和WriteProcessMemory在目标中写我们的自定位代码。接着在目标的入口处储存钩子直到钩子到达。一旦我们成功装好钩子,则自定位代码就承担起钩子的任务,做所有卑鄙的事,储存原始字节并返回目标进程的入口点。
                push offset pinfo
                push offset sinfo
                push 0
                push 0
                push CREATE_SUSPENDED
                push 0
                push 0
                push 0
                push offset progy
                push 0
                callW CreateProcessA
                push PAGE_EXECUTE_READWRITE
                push MEM_COMMIT
                push 2000h
                push 0
                push pinfo.pi_hProcess
                callW VirtualAllocEx ;allocate big enough block
                mov mhandle, eax
                push 0
                push 2
                push offset infinite
                push 401000h
                push pinfo.pi_hProcess
                callW WriteProcessMemory ;store jmp $ at entry point
                push pinfo.pi_hThread
                callW ResumeThread
                mov ctx.context_ContextFlags, CONTEXT_FULL
  __cycle_ep:
                push 100h
                callW Sleep
                push offset ctx
                push pinfo.pi_hThread
                callW GetThreadContext
                cmp ctx.context_eip, 401000h
                jne __cycle_ep
                push pinfo.pi_hThread
                callW SuspendThread
                push 0
                push size_loader ;size of loader
                push offset loader ;loader code
                push mhandle ;allocated mem block
                push pinfo.pi_hProcess
                callW WriteProcessMemory
                push mhandle
                pop ctx.context_eip ;eip == my code
                push offset ctx
                push pinfo.pi_hThread
                callW SetThreadContext;set context
                push pinfo.pi_hThread
                callW ResumeThread ;resume thread 
        
  2.4混合hooking的途径
  我们有两种下钩子的方法,但如果要对付壳的话,就只有一种选择了。下钩子的两种方法分别是IAT hooking和APIs hooking.前者不是我们的选择,因为壳可以自动找到或运用GetProcAddress去找到所有的APIs,所以是不实际的。更好的选择是第二种方法,它由在API入口处或结束处储存钩子组成,所以能控制它的输出。
  让我们看一个k32中的API:
  .text:7C809A81 VirtualAlloc proc near
  .text:7C809A81
  .text:7C809A81            mov edi, edi
  .text:7C809A83     push ebp
  .text:7C809A84     mov ebp, esp
  .text:7C809A86     push [ebp+arg_10] ; flProtect
  .text:7C809A89     push [ebp+flProtect] ; flAllocationType
  .text:7C809A8C     push [ebp+flAllocationType] ; dwSize
  .text:7C809A8F     push [ebp+dwSize] ; lpAddress
  .text:7C809A92     push 0FFFFFFFFh ; hProcess
  .text:7C809A94     call VirtualAllocEx
  .text:7C809A99     pop ebp
  .text:7C809A9A     retn 10h
  .text:7C809A9A VirtualAlloc endp
  .text:7C809A9D     db 90h
  .text:7C809A9E     db 90h
  我们就用这种方法去钩住VirtualAlloc,这样一来它就只能调用我们的代码,并分配和释放内存。不同程序在dll中的输出都最终在k32.dll和ntdll.dll中结束并去调用native APIs。所以,如果我们知道将要hooking的是什么,那么也可以调用随后的API.。看一看VirtualAlloc,它将调用VirtualAllocEx,我们可以模仿这些,但没有必要储存和执行原始指令,这会使钩子太冗长。这也会节约很多时间,因为我们没有用Length Disassemble Engine去决定指令的长度。注意,运用Length Disassemble Engine和储存旧的字节并不难办到,但是对于这篇文章没有必要。当我们在ret/retn处hooking时,我们必须用Length Disassemble Engine去定位ret/retn.我hooking kernel32.dll的方法是运行LDE去寻找ret/rent,并查看是否填充nops。(译者注:此方法译者不很明白,所以比较生硬,建议参看原文和以下原代码)
  让我们看一些代码:
                      ;ebx - where to redirect
                      ;edi - pointer to api
                      ;esi - CMD_RETN - hook api at ret/retn
                      ; - CMD_ENTRY - hook api at entry
  CMD_RETN     equ 1
  CMD_ENTRY   equ 2
  hook_    api: pusha
                      lea ecx, [ebp+dummy]
                      push ecx
                      push PAGE_EXECUTE_READWRITE
             push 1000h
                      push edi
                      call [ebp+VirtualProtect]
                      test esi, CMD_ENTRY
                      jz __hook_at_ret
                      mov ecx, edi
                      mov al, 0e9h
                      stosb
                      add ecx, 5
                      sub ebx, ecx
                      mov [edi], ebx
                      jmp __exit_hook
                      ;most APIs are paded with nop, if no nop, fail!!!
  __hook_at_ret:   push edi
                      call ldex86
                      add edi, eax
                      cmp byte ptr[edi], 0c3h
                      je __check_ret
                      cmp byte ptr[edi], 0c2h
                      jne __hook_at_ret
                      cmp word ptr[edi+3], 9090h
                      jne __exit_hook ;failed
                      jmp __hook_api
  __check_ret:   cmp dword ptr[edi+1], 90909090h
                      jne __exit_hook
  __hook_api:  mov ecx, edi
                      mov al, 0e9h
                      stosb
                      add ecx, 5
                      sub ebx, ecx
                      mov [edi], ebx
  __exit_hook:   popa
                      Retn
  就如同你在上面代码中所见,这就是混合的hooking方法,也是这篇文章在这方面所需要的一切。你将会理解,我什么一旦涉及内存管理和被保护程序区段重定位时就用这种方法。
  
  2.5.下面是什么呢?
   我将给你展示怎样在ring3上写内存管理以及如何为你的目标写非入侵式跟踪。
  
  3.内存管理
  也许你会问在反-反转存中为什么会用到内存管理。其实非常简单,我会用最简单的方式来讲解的。当你用一些保护性壳(如:aspr,armadillo)时,它会分配许多缓冲区,而原来此处的代码已经被转移了。由于这个事实,有时去转存是根本不可能的,在目标程序被解压释放时,这些大量的缓冲区完全是由它自己去分配和释放的。一个简单的方法是去钩住VirtualAlloc,往后是VirtualAllocEx,甚至是ntdll.dll中的NtAllocateVirtualMemory,就可以返回去程序缓冲区,随后也就容易转存了。像这样的保护会重分配所有的数据在我们可能转存的范围内,这种情况下可以用LordPE的一个插件,尽管是我以前写的,但在这种情况下运用是很有魅力的。为了避免太多内存的消耗和对内存的分配和释放的跟踪,我将用一个相似的概念,在Intel CPU中被用来把虚拟内存转换成物理内存。我也可以用堆链表来组织内存管理,但这需要更多的代码。
  
  3.1 扩张程序空间
  假如说程序的大小是9000h字节,我们不能保证任何节区在重定位一些缓冲区时被重写。在这种况下,为了后来转存没有问题,我们需要在目标程序中分配缓冲区。我们将用内存补丁去增加程序的大小。
  - 用CreateProcessA 创建挂起状态的进程
  - 用ReadProcessMemory读整个程序的内存
  - 用 NtUnmapViewOfSection 释放目标程序中被用的内存(译者注:可参见http://undocumented.ntinternals.net/UserMode/Undocumented%20Functions/NT%20Objects/Section/NtUnmapViewOfSection.html)
  - 用 VirtualAllocEx 在目标程序中相同的基址处分配更多的缓冲区
  - 用WriteProcessMemory写回原来的程序
  
  样品代码:
                      push offset pinfo
                      push offset sinfo
                      push 0
                      push 0
                      push CREATE_SUSPENDED
                      push 0
                      push 0
                      push 0
                      push offset progy
                      push 0
                      callW CreateProcessA
                      push PAGE_READWRITE
                      push MEM_COMMIT
                      push c_size
                      push 0
                      push -1
                      callW VirtualAllocEx
                      mov esi, eax
                      push 0
                      push c_size
                      push esi
                      push c_start
                      push pinfo.pi_hProcess
                      callW ReadProcessMemory
                      push c_start
                      push pinfo.pi_hProcess
                      callW NtUnmapViewOfSection
                      mov eax, c_size
                      add eax, NEW_MEM_RANGE
                      push PAGE_EXECUTE_READWRITE
                      push MEM_COMMIT or MEM_RESERVE
                      push eax
                      push c_start
                      push pinfo.pi_hProcess
                      callW VirtualAllocEx
                      push 0
                      push c_size
                      push esi
                      push eax
                      push pinfo.pi_hProcess
                      callW WriteProcessMemory
  c_start - base of progy
  c_size - size of progy
  NEW_MEM_RANGE - increased size of our target
  
  在这以后我们把自定位代码注入到了目标中,它将hook VirtualAlloc 和VirtualFree并返回内存范围而不是新分配程序范围负责。但是真正的问题是如何写这样的内存管理。(译者注:作者是在程序尾添加区段,也可以在程序各区段之间,如果每个区段之间的空间都不够,还可以把自定位代码分开分别加入其中,CIH就是这种方法)
  
  3.2 用VirtualAlloc and VirtualFree管理内存
  我们是否知道新的缓冲区被定位了呢?答案是肯定的,它就在我们原程序的结尾处。为了描述我们缓冲区每一页的状态,我也将用4字节大小的表来描述每页的状态(我将称它为PTE)我们不得不时刻跟踪被VirtualAlloc分配的任何区域,因为一旦调用了VirtualFree我们可以在这页上做还未使用的记号,使得再次调用VirtualAlloc时可以返回。如果没有这些,我们甚至可能益出缓冲区,并导致页失败.首先用一个内存管理结构来描述被分配缓冲区的开始,范围和类型,与堆相似,但后来我会指出这样做太慢而且会导致内存泄漏。这时我们记得Intel CPU是如何把虚拟内存转变为物理内存的。在PDE/PTE中虚拟地址被用作索引,并包括物理结构和每页的状态,所以为什么不采用这样的技术和在ring 3级上做相同的事情和内存管理呢?它的速度很快,用页索引去访问存有每页状态的数据。
  短暂的思考后,我就得到了我想要的,页的入口:(译者注:作者在ring3级上模仿虚拟内存转变为物理内存的方法非常有效,巧妙,请仔细阅读)
  31                      2  1  0
  +---------------------------+---+---+
  | FIRST PAGE INDEX       | R | P |
  +---------------------------+---+---+
  P-当前的位显示,页是否在用或空闲。
  R-保存位,仅仅在VirtualAlloc以MEM_RESERVE被调用。
  FIRST PAGE INDEX又称为FPI-拥有第一页的索引,用来决定块大小。
  
  缓冲区的格局如下::
  +--------------+----------------------------------+--------------+
  | Progy     | VirtualAlloc/Free buffer      |    PTE   |
  +--------------+----------------------------------+--------------+
  你的PTE大小也许是=(buffer/1000h)*4,在我这里,我分配了1000页并用4页去描述每页的状态。
  为了获得PTE,你应该获得每页的索引,而获得它是非常简单。假如我们的程序开始于400000h,结束于500000h,则程序的结尾就是我们缓冲区的开始,PTE是被定位在我们缓冲区的最后4页
           mov esi, [edi+memstart] ;esi=500000h
           add esi, NEW_MEM_RANGE ;esi+1004000h
           sub esi, 4000h ;structs for memory manager(PTEs)
  memstart       = end of progy
  NEW_MEM_RANGE = 1004000h ;1000*1000 pages + 4000h for PTE
  现在我们简单的来获取索引,假定eax是虚拟地址。
            mov edx, eax
            sub edx, [ebp+memstart] ;-500000h
            shr edx, 0ch ;index into edx
  瞧,用简单的[esi+edx*4]就可以看见是否这页被分配了,被保留或可用。当然,这是我的执行,在你的执行中组织可以是不同的PTE, 应该给PTEs分配足量的空间以来满足我们的要求。基本上我们用4000h来描述缓冲区1000000h字节的状态,难道这不美好?你不得不去喜欢Intel和他们把物理内存转换成虚拟内存的思想。当然,你可以分配更小的缓冲区和更小的PTE,那将完全取决于你。
  现在我将告诉你关于FIRST PAGE INDEX以及它为什么这么重要。我随后将给你展示如何在这样的缓冲区中运用nonintrusive tracers,还是先告诉你FPI.FPI用来查看被分配缓冲区的大小和调用VirtualFree时释放缓冲区的大小。FPI含有第一页的索引,并被置于任何一个PTE描述的某个范围。如果FPI是1 in 3 PTEs ,这就意味着这个缓冲区开始于PTE的INDEX 1 ,并且所有的拥有FPI 1 的页都是相同缓冲区的一部分。这将稍后帮助我们编写nonintrusive tracer代码和释放内存,因为有时VirtualFree 被作为 VirtualFree(page_base, 0, MEM_DECOMMIT)来调用,并且如果不知道缓冲区的大小会导致内存泄露。也许你奇怪我为什么不储存第一个PTE的大小并在接着的PTEs上做记号,而是去储存它们每个的FPI。简单的说,FPI将使我们知道nonintrusive tracer中必须改变的内存缓冲区的大小.如果异常在第三页发生,你仅仅只用在第三页改变保护,但是我们想在调用VirtualAlloc时改变整个范围的保护是,这就更有意义了。
  
  看一段代码,我想你会理解
  allocatememory      proc
                      arg virtualbase
                      arg range
                      arg flags
                      arg memprotection
                      local numofpages:dword
                      local dummy_var:dword
                      local virtualaddress:dword
                      call deltaalloc
  deltaalloc:         pop edi
                      sub edi, offset deltaalloc
                      mov esi, [edi+memstart]
                      add esi, NEW_MEM_RANGE
                      sub esi, 4000h ;structs for memory manager(PTEs)
                      mov eax, range
                      mov edx, eax
                      shr eax, 0ch
                      and edx, 0FFFh
                      test edx, edx
                      jz __mm0
                      inc eax
  __mm0:              mov numofpages, eax
                      cmp virtualbase, 0
                      jne __commitpage ;commit reserved pages???? yep
  ;find free block big enough and commit pages
  ;starting from index 1
                      mov ecx, 1
  __cycle_empty:      test dword ptr[esi+ecx*4], 1 ;committed?
                      jnz __next_pte
                      test dword ptr[esi+ecx*4], 2 ;reserved?
                      jz __check_size
  __next_pte:         inc ecx
                      cmp ecx, 1000h
                      jne __cycle_empty
  __check_size:       mov eax, numofpages
                      add eax, ecx
  __cycle_size:       dec eax
                      test dword ptr[esi+eax*4], 1
                      jnz __next_pte
                      test dword ptr[esi+eax*4], 2
                      jnz __next_pte
                      cmp eax, ecx
                      jne __cycle_size
                      ;at this point we have found PTEs large enough to
                      ;describe needed memory buffer
                      mov eax, numofpages ;ecx is index used to get page
                      add eax, ecx
                      mov edx, ecx
                      shl edx, 2 ;FPI
                      mov ebx, flags ;1 for P or 2 for R
  __add_pages:        dec eax
                      mov dword ptr[esi+eax*4], 0 ;set PTE to 0
                      or dword ptr[esi+eax*4], edx;set FPI
                      or dword ptr[esi+eax*4], ebx;set flags
                      cmp eax, ecx
                      jne __add_pages
  __done:             shl ecx, 0ch
                      add ecx, [edi+memstart]
                      mov virtualaddress, ecx
                      jmp __exitalloc
  __commitpage:       push virtualbase
                      pop virtualaddress
                      mov eax, virtualbase
                      sub eax, [edi+memstart]
                      shr eax, 0ch
                      mov ecx, numofpages
                      mov edx, eax
                      shl edx, 2
  __commit_em:        mov dword ptr[esi+eax*4], 0 ;clear pte
                      or dword ptr[esi+eax*4], 3 ;flags
                      or dword ptr[esi+eax*4], edx ;fpi
                      inc eax
                      loop __commit_em
  __exitalloc:        mov eax, virtualaddress
                      leave
                      retn 10h
                      endp
  注意,如何从PTE的1索引开始。
  ; 从索引1开始
            mov ecx, 1
  这非常重要,空的PTE被我的deallocatememory设置为0,随后被分配和释放的区域将会把FPI置零,在这种情况下,寻找FPI是0的内存范围将返回更大的范围。当然,我们可以检验PTE的R和P位,但是这会扩大代码并且使代码的可读性降低。现在,如果你仔细读这个源代码,你将理解在ring 3级上用PTEs写一个漂亮的内存管理代码是多么容易,VirtualFree写起来也很简单。
  
  deallocatememory:
                   mov esi, [ebp+memstart]
                   add esi, NEW_MEM_RANGE
                   sub esi, 4000h ;pointer to PTE
                   ;freeing using index and FPI to find all pages
                   mov edx, eax
                   sub edx, [ebp+memstart]
                   shr edx, 0ch ;index into eax
                   mov ecx, edx ;FPI into ecx
                   mov eax, edx
                   cmp eax, 1000h
                   jnb __exit_free
  __freemem:       mov edx, [esi+eax*4]
                   shr edx, 2
                   cmp edx, ecx ;FPI...
                   jne __exit_free
                   mov dword ptr[esi+eax*4], 0 ;clear PTE
                   inc eax
                   jmp __freemem
  __exit_free:     retn
  以上就是我要告诉你关于内存管理的一切.
  
  3.3 用Deiphi 编码的问题
  在这种情况下,理解Delphi 是非常重要的。ASProtect SKE的virtual.dll是用Delphi写的,这是一个大的问题。我在玩弄了一会儿ASProtect 和其他Delphi apps后,我的代码都失败了,甚至我用一切正确的方式去模仿。两个小时的跟踪后我就能够确定这样的问题了,这些就在下面。
  - @System@@FreeMem
  - @System@SysFreeMem
  为了让这个引擎工作,程序必须成功返回0,否则Delphi app会退出 。甚至我模仿任何事情,他都无故失败,所以唯一的方法是用以下两句程序去修补aspr virtual.dll或其他Delphi程序。
  ----------
  mov eax, 0
  retn
  接下来的问题是如何定位这两句程序?此时运用签名的方法在我脑海中闪过。立刻调用VirtualAlloc,它将为aspr virtual.dll分配地址,所以可以储存地址,也可以插入非入侵调试,一旦我们下int3h断点时就可以获得。我们也就知道了扫描signatures的最佳时机。
  更简的方法是在virtual.dll的入口处转存和在IDA中扫描这两句程序的地址。那将非常的方便和简单
  .dumped:00496564 ; __fastcall System::__linkproc__ FreeMem(void)
  .dumped:00496564 @System@@FreeMem$qqrv proc near
  .dumped:00496564
  .dumped:00496564     test eax, eax
  .dumped:00496566     jz short locret_496572
  .dumped:00496568     call ds:off_4CE01C
  .dumped:0049656E     or eax, eax
  .dumped:00496570     jnz short loc_496573
  .dumped:00496572
  .dumped:00496572 locret_496572:
  .dumped:00496572     retn
  .dumped:00496573
  .dumped:00496573 loc_496573:
  .dumped:00496573     mov al, 2
  .dumped:00496575     jmp sub_4965CC
  .dumped:00496575 @System@@FreeMem$qqrv endp
  And also:
  .dumped:00497114 ; __fastcall System::SysFreeMem(void *)
  .dumped:00497114 @System@SysFreeMem$qqrpv proc
  .dumped:00497114
  .dumped:00497114     var_4 = dword ptr -4
  .dumped:00497114
  .dumped:00497114     push ebp
  .dumped:00497115     mov ebp, esp
  .dumped:00497117     push ecx
  .dumped:00497118     push ebx
  .dumped:00497119     push esi
  .dumped:0049711A     push edi
  .dumped:0049711B     mov ebx, eax
  .dumped:0049711D     xor eax, eax
  .dumped:0049711F     mov ds:dword_4D042C, eax
  .dumped:00497124     cmp ds:byte_4D0428, 0
  .dumped:0049712B     jnz short loc_49714C
  .dumped:0049712D     call @System@_16436 ; System::_16436
  .dumped:00497132     test al, al
  .dumped:00497134     jnz short loc_49714C
  .dumped:00497136     mov ds:dword_4D042C, 8
  .dumped:00497140     mov [ebp+var_4], 8
  .dumped:00497147     jmp loc_4972AD
  .dumped:0049714C
  
  3.4 内存管理总结
  如果你已理解上面的一切,那么试图反转存将会失败。你可以在任何一个转存范围内得到所有的东西。当然,为了能在LordPE中看见整个程序范围,你必须确定增加了PEB的大小。
                               mov eax, dword ptr fs:[30h]
                               mov eax, [eax+0ch]
                               mov eax, [eax+14h]
                               add dword ptr[eax+18h], NEW_MEM_RANGE
  这就是所有在ring3上关于内存管理的知识 。我希望你获得其中的思想,并且能自己写出在你的目标中的内存管理。祝你好运。
  
--------------------------------------------------------------------------------
【版权声明】: 本文原创于看雪技术论坛, 转载请注明作者并保持文章的完整, 谢谢!

                                                       2006年05月18日 2:22:02

[招生]科锐逆向工程师培训46期预科班将于 2023年02月09日 正式开班

上传的附件:
收藏
点赞0
打赏
分享
最新回复 (19)
雪    币: 219
活跃值: 活跃值 (20)
能力值: ( LV9,RANK:1140 )
在线值:
发帖
回帖
粉丝
冷血书生 活跃值 28 2006-5-2 15:14
2
0
这样看得很辛苦~

建议一份中文,一份英文的~

虽然如此,还是支持楼主的辛勤劳动~~
雪    币: 10184
活跃值: 活跃值 (12807)
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
kanxue 活跃值 8 2006-5-2 16:59
3
0
kkbing辛苦了,利用5.1休息时间为大家翻译这么好的文章。
雪    币: 200
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
木头 活跃值 2006-5-2 18:32
4
0
辛苦了!收藏中.
雪    币: 217
活跃值: 活跃值 (54)
能力值: ( LV9,RANK:1210 )
在线值:
发帖
回帖
粉丝
softworm 活跃值 30 2006-5-2 21:30
5
0
Offset independent code似乎可译为偏移无关代码?或者直接译为自定位代码,一已之见仅供参考
雪    币: 204
活跃值: 活跃值 (14)
能力值: ( LV9,RANK:330 )
在线值:
发帖
回帖
粉丝
hrbx 活跃值 8 2006-5-2 22:28
6
0
翻译辛苦,支持一下!
雪    币: 454
活跃值: 活跃值 (27)
能力值: ( LV12,RANK:660 )
在线值:
发帖
回帖
粉丝
prince 活跃值 16 2006-5-3 00:21
7
0
辛苦了, 建议贴出来中文版, 其他版本可以打包提供下载, 这样看起来舒服些.
雪    币: 236
活跃值: 活跃值 (43)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
ah007 活跃值 2 2006-5-3 13:31
8
0
辛苦!!!!
雪    币: 200
活跃值: 活跃值 (10)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
kkbing 活跃值 3 2006-5-3 16:43
9
0
最初由 softworm 发布
Offset independent code似乎可译为偏移无关代码?或者直接译为自定位代码,一已之见仅供参考

"自定位代码“好,立刻修改。谢谢
雪    币: 200
活跃值: 活跃值 (10)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
kkbing 活跃值 3 2006-5-3 16:45
10
0
最初由 冷血书生 发布
这样看得很辛苦~

建议一份中文,一份英文的~

虽然如此,还是支持楼主的辛勤劳动~~


谢谢书生!
雪    币: 200
活跃值: 活跃值 (10)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
kkbing 活跃值 3 2006-5-3 16:47
11
0
最初由 prince 发布
辛苦了, 建议贴出来中文版, 其他版本可以打包提供下载, 这样看起来舒服些.

谢谢王子,还望对其中细节给与指正。
雪    币: 161
活跃值: 活跃值 (15)
能力值: ( LV10,RANK:170 )
在线值:
发帖
回帖
粉丝
thinkSJ 活跃值 4 2006-5-3 21:52
12
0
kkbing辛苦了,我才看到
雪    币: 200
活跃值: 活跃值 (10)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
kkbing 活跃值 3 2006-5-4 00:38
13
0
e原文如下,译文见17楼,感谢看雪老大的鼓励和各位仁兄的支持!
上传的附件:
雪    币: 200
活跃值: 活跃值 (10)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
kkbing 活跃值 3 2006-5-4 00:44
14
0
修改后的译文见第1楼。
雪    币: 159
活跃值: 活跃值 (56)
能力值: ( LV12,RANK:450 )
在线值:
发帖
回帖
粉丝
gzgzlxg 活跃值 11 2006-5-4 09:15
15
0
The next problem is how to locate those two procedures? Using signatures is the only thing that crosses my mind. second call to VirtualAlloc will allocate buffer for aspr virtual.dll so store that address and insert non instrusive debugger that will gain control once we hit int3h - push/ret to aspr virtual.dll and we know when is good time to start scanning for our signatures. Easier solution would be to dump file at entry of virtual.dll and to scan in IDA for addresses of this two procs. That would be very nice and easy locating w/o single chance to fail 
Anyway here are two procedures from IDA and dumped virtual.dll (note that these are present in all Delphi apps):
接下来的问题是如何定位这两个步骤?此时运用签名的方法在我脑海中闪过。

楼主这段似乎没有翻译完。
雪    币: 219
活跃值: 活跃值 (10)
能力值: ( LV9,RANK:250 )
在线值:
发帖
回帖
粉丝
ngaut 活跃值 6 2006-5-4 10:42
16
0
good job!
雪    币: 6
活跃值: 活跃值 (125)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
littlepotato 活跃值 2006-5-9 00:25
17
0
我比较感兴趣的是这篇文章中用到的length disassamble engine 是参考资料中29a-8中的哪个Extend disassembler engine么?还是作者另外改写的呀?有人有代码没呢?
雪    币: 216
活跃值: 活跃值 (78)
能力值: ( LV12,RANK:740 )
在线值:
发帖
回帖
粉丝
aalloverred 活跃值 18 2006-5-10 12:11
18
0
最初由 kkbing 发布
...请回到2部分(译者注:不知作者所知何处)...
...
译者注:应指的是29a[2],也可以参见

...


这里应该指的是参考文献中[2]的内容,其它[1][5,6]等与此相同.

另:
1
――ebp经常用作delta, esi, dei和
dei=>edi
2
-扫描 SHE...
(以及其它地方的SHE,应为SEH)
3
直到下一个等于-1
“下一个”:Next 是_EXCEPTION_REGISTRATION_RECORD 结构的一个参数名,似乎不该翻译
4
...(译者注:在msdn2003  没有查到这个函数,有知者望告知)...

这是未公开的函数,可以google一下,比如看这里:
http://undocumented.ntinternals.net/UserMode/Undocumented%20Functions/NT%

20Objects/Section/NtUnmapViewOfSection.html

==========
吹毛求疵了,请别见怪。
雪    币: 200
活跃值: 活跃值 (10)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
kkbing 活跃值 3 2006-5-10 14:10
19
0
最初由 aalloverred 发布
这里应该指的是参考文献中[2]的内容,其它[1][5,6]等与此相同.

另:
1
――ebp经常用作delta, esi, dei和
........


非常感谢aalloverred兄的指正,立刻改正!
雪    币: 103
活跃值: 活跃值 (13)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
小剑 活跃值 2006-6-5 10:43
20
0
下了,现在看不明,努力中。。。。。。。。。。
游客
登录 | 注册 方可回帖
返回