<目录>
1.废话一堆
2.覆盖原先的ImageBase
3.基址重定位并完全在内存中运行
4.又是废话一堆

<正文>Loading...

1.废话一堆
  抱怨一下,刚才发了N长时间的帖子点了一下预览HTTP链接超时...文章全完了。现在重新开始编辑。
这里我实现了两个PE LOADER.一种是覆盖原先的ImageBase,一种是基址重定位并完全在内存中运行
前者的稳定性来说要比后者要强的多。后者的技术可以让壳出现很多的花样。

2.覆盖原先的ImageBase
  覆盖原先的ImageBase式的PE LOADER的原理很简单,就是讲处于在以文件对齐形式的被保护程序复写到
原来的ImageBase按照节对齐。最终修复引入表就可以了。这里有一个技巧上的问题。如果将壳感染到
被保护的程序中,那么这种PE LOADER的用处是不大的。本身就处于正确的ImageBase内。直接加解密就
好了。当时换个想法,如果把被保护的程序感染到壳上,那么这个技巧就相当有用了。可以让壳多余
出一个数据节,然后讲被保护的程序加载到原先ImageBase上。这里有一个问题就是壳的ImageBase要和被
保护程序的ImageBase一致至少99.99%的程序的基地址都是一样的。这种LOADER的稳定性很高的。好处就是
可以用C,Delphi等写壳身。用汇编花很长时间写出来的东西,至少用高级语言会写的很方便。并且你可以
让你的壳看起来像一个3D游戏而不是一个壳。让破解者就累死到这里。PS:让杀毒软件的虚拟机也累到这里。
先来段Pack的代码吧。首先我们自己做一个壳,在这里壳里实现自修改,反破解,反调试等等。如果你够BT的话
可以在这里实现另外一个程序。累死丫的。 等垃圾运行完毕后。将被保护的程序读入到争取的ImageBase后
执行。
这里先SHOW一段打包代码.讲被保护的程序添加的壳当中。壳只有3个段1代码段,2引入表,3数据段,我们把
被保护的程序打包到数据段中,这个段是没有用处的。就是为了打包被保护的程序

代码:
;; ----------------------------------------
;; Name:ReBuildNewExe
;; Author:logic_yan@hotmail.com
;; Data:2009-3-3
;; Describe:pack orig program to lprotecter
;; data section
;; Arguments:
;; szFileName:file name
;; dwNewSectionSize:new section size
;; dwKeySeed:generate key seed
;; Return:
;; Success:1
;; Failed:0
;; ----------------------------------------
ReBuildNewExe proc uses ebx ecx edx esi edi, szFileName : LPSTR, dwKeySeed : DWORD
  LOCAL hFile : HANDLE
  LOCAL hMap : HANDLE
  LOCAL pLProtecter : LPVOID
  LOCAL pPackFile : LPVOID
  LOCAL dwPackFileSize : DWORD
  LOCAL dwLProtecterSize : DWORD
  LOCAL LProtecterFileInfo : FILEMAPINFO
  
  ;; init data
  mov pLProtecter, NULL
  mov pPackFile, NULL
  ;; map pack file
  invoke MapFile2Mem, szFileName, 0, addr pPackFile, 1
  test eax, eax
  jz Error_ReBuildNewExe
  mov dwPackFileSize, eax              ; eax = target file size
  ;; delete file
  invoke DeleteFile, szFileName
  ;; copy a LProtect
  invoke CopyFile, offset g_szLProtect, szFileName, FALSE
  ;; get lportecter file info
  lea eax, LProtecterFileInfo            ; eax = lprotecter file info
  invoke GetFileMapInfoByFileName, szFileName, eax
  lea eax, LProtecterFileInfo
  assume eax : ptr FILEMAPINFO
  mov eax, dword ptr [eax].dwFileAlign      ; eax = lprotecter file align
  invoke PEAlign, dwPackFileSize, eax        ; eax = packfile file align size
  mov ecx, eax                  ; ecx = packfile file align size
  ;; map lprotect
  invoke MapFile2MemNotClose, szFileName, ecx, addr hFile, addr hMap, addr pLProtecter, 1
  test eax, eax
  jz Error_ReBuildNewExe
  mov dwLProtecterSize, eax            ; eax = lprotecter size
  
  ;; rebuild file
  mov esi, pLProtecter
  add esi, dword ptr [esi+03ch]          ; esi = lprotecter pe header
  assume esi : ptr IMAGE_NT_HEADERS
  mov edi, esi
                          ; edi = lprotecter data section table
  lea edi, [edi+(sizeof IMAGE_NT_HEADERS+sizeof IMAGE_SECTION_HEADER+sizeof IMAGE_SECTION_HEADER)]
  assume edi : ptr IMAGE_SECTION_HEADER
  ;; modify the data section
  ;mov ecx, dword ptr [edi].Misc.VirtualSize
  mov ecx, dwPackFileSize              ; ecx = new data section virtual size
  mov ebx, dword ptr [edi].Misc.VirtualSize    ; ebx = orig data section virtual size
  mov dword ptr [edi].Misc.VirtualSize, ecx
                          ; eax = lprotecter file alignment
  mov eax, dword ptr [esi].OptionalHeader.FileAlignment
  add ecx, ebx                  ; ecx = total virtual size for data section
  invoke PEAlign, ecx, eax            ; eax = new data section file alignment size
  mov dword ptr [edi].SizeOfRawData, eax
  ;; set pointer
  mov edi, dword ptr [edi].PointerToRawData
  add edi, pLProtecter              ; edi = to address
  mov esi, pPackFile                ; esi = from address
  mov ecx, dwPackFileSize              ; ecx = packet file size

  ;; get the crc32 value
  invoke CRC32, esi, ecx              ; eax = CRC32
  mov edx, pLProtecter              ; edx = to address
  add edx, 02h                  ; move to 2 bytes
  mov dword ptr [edx], eax            ; set CRC32 value
  
  ;; get key
  invoke GetRandom, dwKeySeed
  mov ebx, eax                  ; bx = key
;; show debug information
ifdef DEBUG_LCODEBUILDER
  pushad
  lea eax, g_szLCodeBuilderBuffer
  invoke crt_sprintf, eax, offset g_szKeyFormat, ebx
  lea eax, g_szLCodeBuilderBuffer
  invoke crt_printf, eax
  popad
endif
  ;; encode = (x xor bl) - bh
  @@:
  lodsb
  xor al, bl
  sub al, bh
  stosb
  loop @B
  
  ;; modify lprotecter size of image
  mov esi, pLProtecter
  add esi, dword ptr [esi+03ch]          ; esi = lprotecter pe header
  assume esi : ptr IMAGE_NT_HEADERS
  mov edi, esi
                          ; edi = lprotecter data section table
  lea edi, [edi+(sizeof IMAGE_NT_HEADERS+sizeof IMAGE_SECTION_HEADER+sizeof IMAGE_SECTION_HEADER)]
  assume edi : ptr IMAGE_SECTION_HEADER
  ;mov edx, pPackFile
  ;add edx, dword ptr [edx+03ch]          ; edx = pack file pe header
  ;assume edx : ptr IMAGE_NT_HEADERS
  mov ecx, dword ptr [edi].VirtualAddress
  add ecx, dword ptr [edi].Misc.VirtualSize    ; ecx = lprotecter virtual size
  ;mov ecx, dword ptr [esi].OptionalHeader.SizeOfImage
                          ; ecx = tow old size of image add
  ;add ecx, dword ptr [edx].OptionalHeader.SizeOfImage
                          ; eax = section alignment
  mov eax, dword ptr [esi].OptionalHeader.SectionAlignment
  invoke PEAlign, ecx, eax            ; eax = new size of image
  mov dword ptr [esi].OptionalHeader.SizeOfImage, eax
  
  ;; modify init data size
  ;mov eax, dword ptr [edi].SizeOfRawData      ; eax = data section raw size
  ;mov dword ptr [esi].OptionalHeader.SizeOfInitializedData, eax
  
  ;; free memory
  invoke FreeMemory, addr pPackFile
  ;; free file memory
  invoke FreeFileMemory, addr hFile, addr hMap, addr pLProtecter
  mov eax, 1                    ; set return value
Exit_ReBuildNewExe:
  assume esi : nothing
  assume edi : nothing
  ;assume edx : nothing
  ret
Error_ReBuildNewExe:
  cmp pPackFile, NULL                ; check pPointer alloc
  jz @F
  invoke FreeMemory, addr pPackFile
  @@:
  cmp pLProtecter, NULL
  jz @F
  invoke FreeFileMemory, addr hFile, addr hMap, addr pLProtecter
  @@:
  xor eax, eax                  ; eax = 0
  jmp Exit_ReBuildNewExe
ReBuildNewExe endp
打包后的程序起码从PE编辑器中是很难看出和正常PE程序有什么区别,这也是这种思路的好处。(例如小红伞这种玩意常常从一个PE节的构造
中判断是否是病毒,虚伪的AVXKER们给这种东西起了个好听的名字-"启发式搜索"我K),TMD壳有一个隐藏PE的选项
至少这种原理要比那种作法更加的隐藏。欺骗死PEID不偿命。

下面就是PE Loader了由于是加载到正确的基地址所以很多东西不用去管它。以下的LoadFileToMemory就是将处于壳
数据节的东西解密后按照内存对齐。将到这里我科普一下,文件对齐和节对齐,这个在PE头中有两个对齐粒度一般的
值为文件对齐为0200h,内存对齐为01000h,在文件中一般每个节的内容是按照文件对齐粒度来对齐的。加载到内存中
每个节是按照内存对齐粒度来调整大小的。所以导入的时候按照内存对齐把正处于文件对齐的数据导入。LOADER有这样几个步骤
1.使用VirtualProtect函数将原ImageBase-ImageBase+被保护程序的SizeOfImage处设置为可写
2.将处于壳末尾节的被保护程序按照内存对齐读入到原先ImageBase处
3.修复引入表
4.跳入执行被保护的程序
这里先给出一个VirtualProtect的用法。很简单

代码:
;; ----------------------------------------
;; Name:LoadFileToMemory
;; Author:logic_yan@hotmail.com
;; Data:2009-3-4
;; Describe:load file to memory on section
;; alignment
;; Arguments:
;; 1th argument:to memory is size on 
;; section
;; 2th argument:from memory is size on file
;; 3th argument:from memory size
;; Return:none
;; ----------------------------------------
LoadFileToMemory:
  push ebp
  mov ebp, esp
  sub esp, 0100h                  ; create stack
  
  push ebx
  push ecx
  push edx
  push esi
  push edi
  
  ;; copy PE header
  mov ecx, dword ptr [ebp+0Ch]
  add ecx, dword ptr [ecx+03ch]
  add ecx, sizeof IMAGE_NT_HEADERS        ; ecx = 1th section table
  assume ecx : ptr IMAGE_SECTION_HEADER
  mov ecx, dword ptr [ecx].PointerToRawData    ; ecx = PE header size
  mov edi, dword ptr [ebp+08h]          ; edi = to memory start
  mov esi, dword ptr [ebp+0Ch]          ; esi = from memory start
  rep movsb                    ; copying
  
  mov edx, dword ptr [ebp+0Ch]          ; edx = from memory
  add edx, dword ptr [edx+03ch]          ; edx = PE header
  assume edx : ptr IMAGE_NT_HEADERS
                          ; ecx = number of sections
  movzx ecx, word ptr [edx].FileHeader.NumberOfSections
  mov ebx, dword ptr [ebp+0Ch]          
  add ebx, dword ptr [ebx+03ch]          ; edx = PE header
  add ebx, sizeof IMAGE_NT_HEADERS        ; ebx = section table
  assume ebx : ptr IMAGE_SECTION_HEADER
  
CopyEachSection_Loop:
  ;; write each section
  push ecx
  mov esi, dword ptr [ebp+0Ch]
  push dword ptr [ebx].VirtualAddress    ; esi = section file rva
  push esi
  call RVA2Offset              ; eax = section file offset
  add esi, eax              ; esi = section file address
  mov edi, dword ptr [ebp+08h]
  add edi, dword ptr [ebx].VirtualAddress      ; edi = section address
  mov ecx, dword ptr [ebx].Misc.VirtualSize    ; ecx = section virtual size
  cmp ecx, dword ptr [ebx].SizeOfRawData      ; virtual size VS raw size
  jbe @F                      ; if vsize <= rsize ecx = vsize 
  mov ecx, dword ptr [ebx].SizeOfRawData      ; else ecx = rsize
  @@:
  rep movsb
  pop ecx                      ; ecx = number of sections
  add ebx, sizeof IMAGE_SECTION_HEADER      ; next section table
  loop CopyEachSection_Loop
  
  ;; rebuild the import table
  push dword ptr [ebp+08h]            ; image base
  call BuildImportTable
  
  ;; exit
Exit_LoadFileToMemory:
  assume ebx : nothing
  assume ecx : nothing
  assume edx : nothing
  pop edi
  pop esi
  pop edx
  pop ecx
  pop ebx

  mov esp, ebp                  ; clean stack
  pop ebp
  retn 0Ch

;; ----------------------------------------
;; Name:RVA2Offset
;; Author:logic_yan@hotmail.com
;; Data:2009-3-4
;; Describe:RVA to offset
;; Arguments:
;; 1th argument:file map
;; 2th argument:RVA
;; Return:
;; Success:offset
;; Failed:0
;; ----------------------------------------
RVA2Offset:
  push ebp
  mov ebp, esp                  ; create stack
  
  push ebx
  push ecx
  push edx
  push esi
  push edi
  
    mov esi, dword ptr [ebp+08h]           ; esi = file map base 
    add esi, dword ptr [esi+03ch]          ; esi = PE header 
    assume esi : ptr IMAGE_NT_HEADERS 
    mov edi, dword ptr [ebp+0Ch]           ; edi == RVA 
    mov edx, esi                  ; edx = PE header
    add edx, sizeof IMAGE_NT_HEADERS        ; edx = 1th section table 
    movzx ecx, [esi].FileHeader.NumberOfSections  ; ecx = number of sections
    assume edx : ptr IMAGE_SECTION_HEADER 
RVA2Offset_Loop:
    cmp edi, dword ptr [edx].VirtualAddress      ; if ecx >= [edx].VirtualAddress
    jb Continue_RVA2Offset_Loop
    mov eax, dword ptr [edx].VirtualAddress      ; eax = current section RVA           
    add eax, [edx].SizeOfRawData           ; eax = current section end RVA
    cmp edi, eax                  ; if edi < eax, The address is in this section
    jae Continue_RVA2Offset_Loop
    mov eax, dword ptr [edx].VirtualAddress      ; eax = current section RVA
    sub edi, eax                  ; edi = RVA offset
     mov eax, dword ptr [edx].PointerToRawData    ; eax = section file offset 
    add eax, edi                   ; eax = file offset 
  jmp RVA2OffsetExit                ; just exit
Continue_RVA2Offset_Loop:
    add edx, sizeof IMAGE_SECTION_HEADER      ; edx = next section table
  loop RVA2Offset_Loop
    xor eax, eax
RVA2OffsetExit:
  assume esi : nothing
  assume edx : nothing
  
  pop edi
  pop esi
  pop edx
  pop ecx
  pop ebx
  
  mov esp, ebp
  pop ebp                      ; clean stack
  retn 08h
;; ----------------------------------------
;; Name:PEAlign
;; Author:logic_yan@hotmail.com
;; Data:2009-3-4
;; Describe:number to align
;; Algorithms:
;; $1 = dwTarNum / dwAlignTo
;; if remain != 0
;; $r = $1 + 1 * dwAlignTo
;; return $r
;; Arguments:
;; 1th argument:target number
;; 2th argument:alignment
;; Return:
;; Success:alignment number
;; Failed:0
;; ----------------------------------------
PEAlign:
  push ebp
  mov ebp, esp                  ; create stack
  push ecx
  push edx

    mov ecx, dword ptr [ebp+0Ch]          ; ecx = align to
    mov eax, dword ptr [ebp+08h]          ; eax = target number
    xor edx, edx                  ; edx = 0
    div ecx                      ; edx = edx / ecx
    cmp edx, 0                    ; cmp edx, 0
    jz AlreadyAligned
    inc eax                      ; eax = eax + 1
AlreadyAligned:
    mul ecx                      ; eax = eax * ecx
    ;; exit
    pop edx
    pop ecx     
    
    mov esp, ebp                  ; clear stack
    pop ebp
    retn 08h
上面还有两个辅助函数一个是将RVA转换成文件偏移的,一个是计算偏移的。
接下来的事情就是修复引入表了。修复的过程很简单。使用LoadLibrary + GetProcAddress依次将原先的引入表的
FirstThunk表填写完毕就好了
代码:
;; ----------------------------------------
;; Name:BuildImportTable
;; Author:logic_yan@hotmail.com
;; Data:2009-3-7
;; Describe:build a new import table
;; Arguments:
;; 1th argument:file map pointer
;; Return:
;; Success:import table in file offest
;; Failed:0
;; ----------------------------------------
BuildImportTable:
  push ebp
  mov ebp, esp                  ; create stack
  
  push ebx
  push ecx
  push edx
  push esi
  push edi
  
  mov esi, dword ptr [ebp+08h]          ; esi = file map base
  mov edi, esi
  add edi, dword ptr [esi+03ch]          ; edi = PE loader
  assume edi : ptr IMAGE_NT_HEADERS
  ;; check Import Table
                          ; edi = import table directory
  lea edi, [edi].OptionalHeader.DataDirectory[sizeof IMAGE_DATA_DIRECTORY * IMAGE_DIRECTORY_ENTRY_IMPORT]
  assume edi : ptr IMAGE_DATA_DIRECTORY
  mov eax, dword ptr [edi].VirtualAddress      ; eax = import table RVA
  test eax, eax
  jz Exit_BuildImportTable
  ;; exist Import Table
  ;invoke RVA2Offset, pAlloc, eax
  add eax, esi                  ; eax = import table VA
  xchg eax, edi                  ; edi = import table VA
  assume edi : ptr IMAGE_IMPORT_DESCRIPTOR
  ;; import descriptor loop
ImportDescriptor_Loop:
  ;; load dll
  mov eax, dword ptr [edi].Name1          ; eax = dll name
  ;invoke RVA2Offset, pAlloc, eax
  add eax, esi                  ; esi = file map base
  push eax                    ; eax = dll name
  mov eax, 0
  xLoadLibraryA = dword ptr $-04h
  call eax                    ; LoadLibraryA
  mov ebx, eax
  ;; check use OriginalFirstThunk or FirstThunk
;  mov edx, dword ptr [edi].OriginalFirstThunk
;  test edx, edx
;  jnz UseOrignalFirstThunk
  mov edx, dword ptr [edi].FirstThunk        ; edx = firstthunk
;UseOrignalFirstThunk:
  add edx, esi                  ; edx = api store pointer
  mov eax, dword ptr [edx]            ; eax = api name THUNK_DATA
  ;; get api address in one dll
GetApiAddress_Loop:
  ;; ebx -> dll handler
  ;invoke RVA2Offset, pAlloc, eax
  test eax, IMAGE_ORDINAL_FLAG32          ; it's import by index
  jz GetApiByName
  and eax, 0FFFFh                  ; eax = index
  jmp GetApiNow
GetApiByName:
  add eax, esi                  ; eax = api name pointer VA
  assume eax : ptr IMAGE_IMPORT_BY_NAME
  lea eax, [eax].Name1              ; eax = api name
  ;; get api address
GetApiNow:
  push ecx
  push edx
  push eax                    ; eax = api name
  push ebx                    ; ebx = dll handle
  mov eax, 0
  xGetProcAddress = dword ptr $-04h
  call eax                    ; GetProcAddress
  pop edx                      ; edx = api store pointer
  pop ecx
  ;; set api address
  mov dword ptr [edx], eax            ; set address
  ;; next api
  add edx, 04h                  ; edx = next api store address
  mov eax, dword ptr [edx]            ; eax = api name RVA
  test eax, eax
  jnz GetApiAddress_Loop
  ;; next dll
  add edi, sizeof IMAGE_IMPORT_DESCRIPTOR      ; edi = next dll
  mov eax, dword ptr [edi].Name1          ; eax = dll name RVA
  test eax, eax                  ; if eax == 0 then exit
  jnz ImportDescriptor_Loop
  
Exit_BuildImportTable:
  assume eax : nothing
  assume esi : nothing
  assume edi : nothing
  
  pop edi
  pop esi
  pop edx
  pop ecx
  pop ebx
  
  mov esp, ebp
  pop ebp                      ; clean stack
  retn 04h
最后取出原先的入口点并且跳入执行。
这样的作法非常稳定。也能让壳耍出更多的花样来。用高级语言编写壳段总比使用汇编编写起来要舒服的多。可以用VB写个壳。。。
或者用DELPHI。这几天很怀念用C++写程序的日子。看到汇编有种呕吐的感觉。

3.基址重定位并完全在内存中运行
这种PE LOADER的实现难度复杂。并且实现都不怎么完美。难点在于无法判断一个指令后跟随的是地址还是数据。详细的分析见文章
还有一个问题是由于基地址的不同当有写程序调用获取地址API的时候,无法获取的正确的句柄,所以我们的程序还要让被保护的程序获取到正常的句柄才可以。

1.开辟内存
2.将被保护的程序以内存对齐的方式读入到分配的内存中去
3.基址重定位(如果是DLL的话直接处理基址重定位表就可以这个可以很方便的实现)
4.修复引入表
5.创建新的引入表,并对相关获取句柄的API进行HOOK
6.检查是否拥有TLS节,如果拥有检查是否有回调函数,如果有依次执行
7.跳入到入口点执行
8.释放掉内存
9.退出壳

开辟内存就很简单了使用VirtualAlloc等或者开辟一个非常的Stack...呵呵并把它设置为可执行,WIN2003默认不允许在栈中执行代码
下面SHOW一段加载的代码
代码:
LPeLoader proc uses ebx ecx edx esi edi, pMem : LPVOID
  LOCAL pAlloc : LPVOID
  LOCAL dwAllocSize : DWORD
  mov edx, pMem
  add edx, dword ptr [edx+03ch]
  assume edx : ptr IMAGE_NT_HEADERS
  mov eax, dword ptr [edx].OptionalHeader.SizeOfImage
  mov dwAllocSize, eax
  ;; alloc memory
  invoke AllocMemory, eax
  test eax, eax
  jz Error_LPeLoader
  mov pAlloc, eax
  ;; map between dos header to 1th section datas
  mov ebx, edx
  add ebx, sizeof IMAGE_NT_HEADERS
  assume ebx : ptr IMAGE_SECTION_HEADER
  mov ecx, dword ptr [ebx].PointerToRawData
  mov edi, pAlloc
  mov esi, pMem
  cld
  rep movsb
  ;; map section on section alignment
  movzx ecx, word ptr [edx].FileHeader.NumberOfSections
MapEachSection_Loop:
;; ecx -> number of sections
;; ebx -> section table
;; edi -> alloc memory pointer
;; edx -> pe header
;; esi -> file memory pointer
  ;; write each section
  push ecx
  mov edi, pAlloc
  add edi, dword ptr [ebx].VirtualAddress
  mov ecx, dword ptr [ebx].SizeOfRawData
  rep movsb
  pop ecx
  ;; mov to next section
  add ebx, sizeof IMAGE_SECTION_HEADER
  mov esi, dword ptr [ebx].PointerToRawData
  add esi, pMem
  loop MapEachSection_Loop
  ;; build import table
  invoke BuildImportTable, pAlloc
  ;; build relocation table
  invoke BuildRelocation, pAlloc
  ;; fix absolute address
  invoke BaseRelocater, pMem, pAlloc
  ;; make runtime
  invoke MakeOwnRunTime, pAlloc
  ;; handle tls
  invoke HandleTLS, pAlloc
  ;; exit now
  mov eax, pAlloc
Exit_LPeLoader:
  assume ebx : nothing
  assume edx : nothing
  ret
Error_LPeLoader:
  xor eax, eax
  jmp Exit_LPeLoader
LPeLoader endp
代码比较简单。和前一个LOADER并无什么区别
接下来改修复基地址了
如果是DLL的话可以直接按照重定位节进行修订。如下代码所示
代码:
BuildRelocation proc uses ebx ecx edx esi edi, pAlloc : LPVOID
  LOCAL dwType : DWORD
  LOCAL dwOffset : DWORD
  ;; start
  mov esi, pAlloc
  add esi, dword ptr [esi+03ch]
  assume esi : ptr IMAGE_NT_HEADERS
  ;; exist base relocation
  lea edi, [esi].OptionalHeader.DataDirectory[sizeof IMAGE_DATA_DIRECTORY * IMAGE_DIRECTORY_ENTRY_BASERELOC]
  assume edi : ptr IMAGE_DATA_DIRECTORY
  mov eax, dword ptr [edi].VirtualAddress
  test eax, eax
  jz Exit_BuildRelocation
  add eax, pAlloc
  xchg eax, edi
  assume edi : ptr IMAGE_BASE_RELOCATION
  ;; delta?
  mov edx, pAlloc
  mov eax, dword ptr [esi].OptionalHeader.ImageBase
  cmp eax, edx
  jz Exit_BuildRelocation
  sub edx, eax    ; edx -> delta
BuildRelocation_Loop:
;; esi -> TypeOffset
;; edi -> IMAGE_BASE_RELOCATION
;; ecx -> count
;; edx -> delta
;; ebx -> base memory
  mov ebx, dword ptr [edi].VirtualAddress
  add ebx, pAlloc
  ;; esi -> TypeOffset
  mov esi, edi
  add esi, sizeof IMAGE_BASE_RELOCATION
  mov ecx, dword ptr [edi].SizeOfBlock
  sub ecx, sizeof IMAGE_BASE_RELOCATION
  shr ecx, 1      ; ecx -> count
  push ecx
  BuildRelocation_Loop_Inside:
  movzx eax, word ptr [esi]
  mov dwOffset, eax
  and dwOffset, 0FFFh
  mov dwType, eax
  shr dwType, 0Ch
  cmp dwType, IMAGE_REL_BASED_HIGHLOW    ; IMAGE_REL_BASED_HIGHLOW = 3
  jnz Continue_BuildRelocation_Loop_Inside
  ;; relocate now
  mov eax, ebx
  add eax, dwOffset
  add dword ptr [eax], edx
  Continue_BuildRelocation_Loop_Inside:
  loop BuildRelocation_Loop_Inside
  pop ecx
  imul ecx, ecx, 02h
  add esi, ecx
  mov edi, esi
  mov eax, dword ptr [edi].VirtualAddress
  test eax, eax
  jnz BuildRelocation_Loop
Exit_BuildRelocation:
  assume esi : nothing
  assume edi : nothing
  ret
BuildRelocation endp
重定位节的结构,这里就不剖析了。非常的简单,有兴趣的参见PE格式相关文档吧。
如果是EXE文件的话就要进行基址重定位了,由于没有重定位节这个修复很麻烦,以下的代码实现了JMP和CALL的修复
如果有类似的指令
mov eax, 040010
call eax
或者
push 00401005
ret
可能就会产生错误了。所以我们在这里也要修复类似指令,这里就存在一个问题了。你怎样判断是一个要运算的数据还是
一个地址。这里我给出一个减小误差的方法,就是判断后面的立即数是否在被保护程序的加载地址-(加载地址+SizeOfImage)
的范围内。或者将加载地址替换为首节开始地址。这样只是一个减少误差的方法,因为很少有做运算的立即数长的像被保护程序所用于的地址
范围,如果是在这个范围,那么很可能是一个地址,只要对这个地址进行运算那么就可以保证程序的正确运行
还有要修复的是,所有在IA32编码中拥有SIB的指令编码,因为只有这种有SIB位的才可能出现偏移例如add eax, dword [eax+00403450h]
类似的指令,关于SIB的问题可以参考软件保护壳专题中的反汇编引擎的构建。
我在这里也没有完美的实现这个过程。
废话不说了基地址重定位如下
代码:
BaseRelocater proc uses ebx ecx edx esi edi, pMem : LPVOID, pAlloc : DWORD
  LOCAL FileInfo : FILEMAPINFO          ; file info
  LOCAL Instruction : INSTRUCTION          ; instruction
  LOCAL dwOldImageBase : DWORD          ; old image base
  ;; get file info
  invoke GetFileMapInfoByFileMap, pMem, addr FileInfo
  mov esi, pMem
  add esi, dword ptr [esi+03ch]
  assume esi : ptr IMAGE_NT_HEADERS
  mov eax, dword ptr [esi].OptionalHeader.ImageBase
  mov dwOldImageBase, eax
  lea edi, FileInfo
  assume edi : ptr FILEMAPINFO
  mov esi, dword ptr [edi].pCodeBaseVA
  sub esi, dwOldImageBase
  add esi, pAlloc
  mov ecx, dword ptr [edi].dwCodeSizeOfRawData
  lea edi, Instruction
  assume edi : ptr INSTRUCTION
  xor ebx, ebx
BaseRelocater_Loop:
  ;; ebx -> prior pointer
  ;; esi -> current pointer
  ;; ebx -> current count
  ;; ecx -> total count
  ;; edi -> instruction struct
  mov edx, esi
  invoke LDisEngine, esi, edi
  add esi, eax
  add ebx, eax
  ;; check prefix, if there is FS(64h) in prefix set, skip...
  push esi
  mov eax, dword ptr [edi].dwPrefixLength
  lea esi, [edi].bPrefix1
  invoke FindByteInByteList, esi, JUSTREBASE_COUNT, P_FS  ; P_FS = 064h
  pop esi
  test eax, eax
  jnz Continue_BaseRelocater_Loop
  ;; check JustReBase set
  push esi
  movzx eax, byte ptr [edi].bOpcode1
  lea esi, g_JustReBase
  invoke FindByteInByteList, esi, JUSTREBASE_COUNT, eax
  pop esi
  test eax, eax
  jz CheckHasModRM
  ;; just base
  mov eax, dword ptr [edi].dwImmediate
  sub eax, dwOldImageBase
  add eax, pAlloc
  add edx, dword ptr [edi].dwPrefixLength
  add edx, dword ptr [edi].dwOpcodeLength
  mov dword ptr [edx], eax
  jmp Continue_BaseRelocater_Loop
CheckHasModRM:
  ;; check HasModRM
  cmp dword ptr [edi].dwHasModRM, 0
  jz Continue_BaseRelocater_Loop
  ;; analyze ModRM
  mov al, byte ptr [edi].bModRM
  and al, 0C0h
  shr al, 06h
  cmp al, 00h
  jnz Continue_BaseRelocater_Loop
  ;; analyze Mod + R/M
  mov al, byte ptr [edi].bModRM
  and al, 07h
  ;; Mod = 00, R/M = 101
  cmp al, 05h
  jz BaseRelocater_HandleAddress
  jmp Continue_BaseRelocater_Loop
BaseRelocater_HandleAddress:
  mov eax, dword ptr [edi].dwDisplacement
  sub eax, dwOldImageBase
  add eax, pAlloc
  add edx, dword ptr [edi].dwPrefixLength
  add edx, dword ptr [edi].dwOpcodeLength
  inc edx  ; plus ModRM length (1 byte)
  mov dword ptr [edx], eax
Continue_BaseRelocater_Loop:
  cmp ebx, ecx
  jb BaseRelocater_Loop
Exit_BaseRelocater:
  assume esi : nothing
  assume edi : nothing
  ret
BaseRelocater endp
以上只修复了CALL和JMP的指令的基地址
接下来就是解决获取句柄的问题了
我们可以通过使用HOOK输入表的方式来解决这个问题。在我的程序中,HOOK是这样的我首先自己建立一片内存然后新建一个引入表。
代码:
HOOKJMP struct
  bMovEax        db 0            ; mov eax opcode
  dwAddress      dd 0              ; mov eax, XXXX <- address
  wCallEax      dw 0            ; call eax opcode
  bJmp        db 0            ; jmp opcode
  dwJmpOffset      dd 0            ; jmp YYYY <- offset
  dwReserve      dd 0            ; reserve
HOOKJMP ends
这个表有以上结构组成.整个调用过程是这样的
应用程序call 输入表.FirstThunk[X]
X -> 新输入表的某个JMPHOOK结构的地址
在JMPHOOK结构内,首先mov eax, 地址,这个地址如果你没有进行HOOK则是一个默认的函数(这个函数自己指定)如果要HOOK则指向新的
地址.调用新的函数后返回执行JMP XXXX的指令这个XXXX是此地址到原API地址的的偏移。所以继续调用原API,在原API返回后返回到
应用程序call输入表.FirstThunk[X]后的地址继续运行。
见代码
代码:
;; ----------------------------------------
;; Name:CreateNewImportTable
;; Author:logic_yan@hotmail.com
;; Data:2009-2-17
;; Describe:create a new import table to PE 
;; file. This Function is used after Build 
;; Import Table !!!
;; Arguments:
;; pAlloc:the file memory pointer
;; pNewImportTable:new import table file 
;; memory pointer
;; Return:
;; Success:the new section offset
;; Failed:0
;; ----------------------------------------
CreateNewImportTable proc uses ebx ecx edx esi edi, pAlloc : LPVOID, pNewImportTable : LPVOID
  mov esi, pAlloc                  ; esi = PE file map address
  add esi, dword ptr [esi+03ch]          ; esi = PE header
  assume esi : ptr IMAGE_NT_HEADERS
  ;; check ImportTable
                          ; edi = import table directory offset
  lea edi, [esi].OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT * sizeof IMAGE_DATA_DIRECTORY]
  assume edi : ptr IMAGE_DATA_DIRECTORY
  mov eax, dword ptr [edi].VirtualAddress      ; eax = import table RVA
  test eax, eax                  ; cmp eax, 0
  jz Exit_CreateNewImportTable
  add eax, pAlloc                  ; eax = import table VA
  mov edi, eax                  ; edi = import table VA
  assume edi : ptr IMAGE_IMPORT_DESCRIPTOR
  mov ebx, pNewImportTable            ; ebx = new import table VA
  xor ecx, ecx                  ; ecx = 0
CreateNewImportTable_Loop:
;; esi -> FirstThunk Pointer
;; edi -> IMAGE_IMPORT_DESCRIPTOR
;; ecx -> count
;; edx -> orig API address
;; ebx -> new API address stub
  mov esi, dword ptr [edi].FirstThunk        ; esi = FirstThunk
  add esi, pAlloc                  ; esi = FirstThunk list VA
  mov edx, dword ptr [esi]            ; edx = one api address
  test edx, edx                  ; cmp edx, 0
  jz Continue_CreateNewImportTable_Loop
CreateNewImportTable_Inside_Loop:
  assume ebx : ptr HOOKJMP
  mov byte ptr [ebx].bMovEax, 0B8h        ; mov eax, XXXX
  mov word ptr [ebx].wCallEax, 0D0FFh        ; call eax
  ;; get eip
  call CreateNewImportTable_Inside_Loop_GetEip
  CreateNewImportTable_Inside_Loop_GetEip:
  pop eax                      ; eax = current eip
                          ; eax = def hook function address
  add eax, offset DefHookFunction_CreateNewImportTable - offset CreateNewImportTable_Inside_Loop_GetEip
  mov dword ptr [ebx].dwAddress, eax
  mov byte ptr [ebx].bJmp, 0E9h          ; jmp YYYY
  ;; work out the jmp offset
  lea eax, [ebx].bJmp                ; HOOKJMP.bJmp VA            
  sub edx, eax                  ; edx = distance
  xchg edx, eax                  ; eax = distance
  sub eax, 05h                  ; sub 0E8 XXXX instruct size
                          ; edx = jmp offset
  mov dword ptr [ebx].dwJmpOffset, eax
  ;; fill API address stub
  mov dword ptr [esi], ebx            ; ebx = HOOKJMP
                          ; set ebx value to FirstThunk list
  add ebx, sizeof HOOKJMP              ; ebx = next HOOKJMP
  inc ecx                      ; ecx = ecx + 1
  add esi, 04h                  ; esi = next api pointer
  mov edx, dword ptr [esi]            ; edx = next api address
  test edx, edx                  ; cmp edx, 0
  jnz CreateNewImportTable_Inside_Loop
  ;; next dll
Continue_CreateNewImportTable_Loop:
  add edi, sizeof IMAGE_IMPORT_DESCRIPTOR      ; edi = next dll
  cmp dword ptr [edi], 0
  jnz CreateNewImportTable_Loop
  
Exit_CreateNewImportTable:
  assume ebx : nothing
  assume edi : nothing
  assume esi : nothing
  ret
;; default hook function
DefHookFunction_CreateNewImportTable:
  retn 0h
CreateNewImportTable endp
要HOOK的函数有类似以下几种
GetModuleHandleW
GetModuleHandleA
GetModuleHandleExA
GetModuleHandleExW
GetModuleFileNameA
GetModuleFileNameW
等等相关操作句柄的函数。还有些程序是动态获取函数地址
所以我们要把GetProcAddress和LoadLibrary的地址获取到。并当返回时返回我们自己的函数
当然还可以修改FS指向的TIB PEB来实现
这里引用一下FS的指向
引用:
FS:[0x00]  Win9x and NT Current SEH frame
FS:[0x04]  Win9x and NT Top of stack
FS:[0x08]  Win9x and NT Current bottom of stack
FS:[0x10]  NT Fiber data
FS:[0x14]  Win9x and NT Arbitrary data slot
FS:[0x18]  Win9x and NT Linear address of TIB(TEB--- 也叫做线程信息块 TIB)
FS:[0x20]  NT Process ID
FS:[0x24]  NT Current thread ID
FS:[0x2C]  Win9x and NT Linear address of the thread local storage array
FS:[0x30]  Pointer to PEB
FS:[0x34]  NT Current error number
FS:[0x38]  CountOfOwnedCriticalSections
FS:[0x3c]  CsrClientThread
FS:[0x40]  Win32ThreadInfo
FS:[0x44]  Win32ClientInfo[0x1f]
FS:[0xc0]  WOW32Reserved
FS:[0xc4]  CurrentLocale
FS:[0xc8]  FpSoftwareStatusRegister
FS:[0xcc]  SystemReserved1[0x36]
FS:[0x1a4] Spare1
FS:[0x1a8] ExceptionCode
FS:[0x1ac] SpareBytes1[0x28]
FS:[0x1d4] SystemReserved2[0xA]
FS:[0x1fc] GDI_TEB_BATCH
FS:[0x6dc] gdiRgn
FS:[0x6e0] gdiPen
FS:[0x6e4] gdiBrush
FS:[0x6e8] CLIENT_ID
FS:[0x6f0] GdiCachedProcessHandle
FS:[0x6f4] GdiClientPID
FS:[0x6f8] GdiClientTID
FS:[0x6fc] GdiThreadLocaleInfo
FS:[0x700] UserReserved[5]
FS:[0x714] glDispatchTable[0x118]
FS:[0xb74] glReserved1[0x1A]
FS:[0xbdc] glReserved2
FS:[0xbe0] glSectionInfo
FS:[0xbe4] glSection
FS:[0xbe8] glTable
FS:[0xbec] glCurrentRC
FS:[0xbf0] glContext
FS:[0xbf4] NTSTATUS
FS:[0xbf8] StaticUnicodeString
FS:[0xc00] StaticUnicodeBuffer[0x105]
FS:[0xe0c] DeallocationStack
FS:[0xe10] TlsSlots[0x40]
FS:[0xf10] TlsLinks
FS:[0xf18] Vdm
FS:[0xf1c] ReservedForNtRpc
FS:[0xf20] DbgSsReserved[0x2]
FS:[0xf28] HardErrorDisabled
FS:[0xf2c] Instrumentation[0x10]
FS:[0xf6c] WinSockData
FS:[0xf70] GdiBatchCount
FS:[0xf74] Spare2
FS:[0xf78] Spare3
FS:[0xf7c] Spare4
FS:[0xf80] ReservedForOle
FS:[0xf84] WaitingOnLoaderLock
FS:[0xf88] StackCommit
FS:[0xf8c] StackCommitMax
FS:[0xf90] StackReserve
这里可以看到FS[18h]指向TIB,TIB的030h处又指向了PEB,而PEB的08h处就当前程序的ImageBase了。修改掉它
当所有的运行完毕后,检查这个程序是否有TLS节如果有,则依次调用其函数
代码:
HandleTLS proc uses ebx ecx edx esi edi, pAlloc : LPVOID
  ;; check exist TLS table ?
  mov esi, pAlloc
  add esi, dword ptr [esi+03ch]
  assume esi : ptr IMAGE_NT_HEADERS
  lea edi, [esi].OptionalHeader.DataDirectory[sizeof IMAGE_DATA_DIRECTORY * IMAGE_DIRECTORY_ENTRY_TLS]
  assume edi : ptr IMAGE_DATA_DIRECTORY
  mov eax, dword ptr [edi].VirtualAddress
  test eax, eax
  jz Exit_HandleTLS
  ;; run TLS callback function
  add eax, pAlloc
  xchg eax, edi
  assume edi : ptr IMAGE_TLS_DIRECTORY
  lea ebx, [edi].AddressOfCallBacks
  mov eax, dword ptr [ebx]
  test eax, eax
  jz Exit_HandleTLS
HandleTLS_Loop:
  call eax
  add ebx, 04h
  mov eax, dword ptr [ebx]
  test eax, eax
  jnz HandleTLS_Loop
Exit_HandleTLS:
  assume esi : nothing
  assume edi : nothing
  ret
HandleTLS endp
最后回到MemoryRunner的控制程序,释放掉内存。这里还有些代码没有写例如把PEB中的指向修改掉还有UNHOOK掉当时的函数等等。
代码:
LMemRunner proc uses ebx ecx edx esi edi, szFileName : LPSTR, pArgument : LPVOID
  LOCAL pMem : LPVOID
  LOCAL pAlloc : LPVOID
  ;; map file
  invoke MapFile2Mem, szFileName, 0, addr pMem, 0
  test eax, eax
  jz Exit_LMemRunner
  ;; pe loader
  invoke LPeLoader, pMem
  test eax, eax
  jz FreeMemory_LMemRunner
  mov pAlloc, eax
  xchg esi, eax
  add esi, dword ptr [esi+03ch]
  assume esi : ptr IMAGE_NT_HEADERS
  mov eax, dword ptr [esi].OptionalHeader.AddressOfEntryPoint
  add eax, pAlloc
  ;; run now
  call eax
FreeMemory_LMemRunner:
  cmp pMem, 0
  jz Exit_LMemRunner
  invoke FreeMemory, pMem
  cmp pAlloc, 0
  jz Exit_LMemRunner
  invoke FreeMemory, pAlloc
;; exit
Exit_LMemRunner:
  assume esi : nothing
  xor eax, eax
  ret
LMemRunner endp
4.又是废话一堆
这次就不写出完整代码了,前者的LOADER因为给朋友做了一个壳,朋友不愿意发出来,后面的稳定性太差,没有时间去完善了。
等有时间的时候,我再将这个基址重定位函数完成后发出来。如果原理掌握了这种壳一下午可以写出来一卡车鸟。。。
这些天越来越喜欢上写壳了。在这里非常希望每个人都可以去写壳。去了解壳的艺术。