<目录>
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
中判断是否是病毒,虚伪的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
接下来的事情就是修复引入表了。修复的过程很简单。使用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
接下来改修复基地址了
如果是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
如果是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
接下来就是解决获取句柄的问题了
我们可以通过使用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
GetModuleHandleW
GetModuleHandleA
GetModuleHandleExA
GetModuleHandleExW
GetModuleFileNameA
GetModuleFileNameW
等等相关操作句柄的函数。还有些程序是动态获取函数地址
所以我们要把GetProcAddress和LoadLibrary的地址获取到。并当返回时返回我们自己的函数
当然还可以修改FS指向的TIB PEB来实现
这里引用一下FS的指向
当所有的运行完毕后,检查这个程序是否有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
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
这次就不写出完整代码了,前者的LOADER因为给朋友做了一个壳,朋友不愿意发出来,后面的稳定性太差,没有时间去完善了。
等有时间的时候,我再将这个基址重定位函数完成后发出来。如果原理掌握了这种壳一下午可以写出来一卡车鸟。。。
这些天越来越喜欢上写壳了。在这里非常希望每个人都可以去写壳。去了解壳的艺术。