-
-
[旧帖] [翻译]导入表注入技术详解 0.00雪花
-
发表于: 2012-4-3 17:44 2420
-
今日兴之所至翻译一篇国外关于导入表技术的文章,此文章来自大名鼎鼎的The CodeProject网站,对于学习导入表如何在实战中运用是很好的一篇教程。
我会有选择地翻译并扩充部分内容,由于本人很少翻译国外文章,翻译不到位的地方还望大家海涵。
原文出处:http://www.codeproject.com/Articles/14360/Injective-Code-inside-Import-Table
其中源代码也可以从上面的网站下载到,我这里就不添加附件了~
设想一下,我们通过修改导入表结构使得程序的导入函数指向特殊的函数,以完成我们指定的功能。此外,PE文件保护器会使用此技术植入一些保护函数,而一些后门程序也会利用此技术通过木马植入恶意代码。
在逆向工程中,我们描述此技术为“API重定位技术”。不过我不打算在本文中详述此技术的方方面面,我会重点描述本文所提供源代码之外的思想(译者注:这里我会添加对于源代码中一些核心技术的阐释)。我不会发布有恶意代码倾向的商用源代码,然而,我认为此文实际上也可以用来作为此方面的一个介绍。
1. 了解导入表
PE文件结构包含MS-DOS头,NT头,节表和节的映像,参见图1。MS-DOS自DOS时代起一直沿用至Windows时代。NT头是来自Unix系统下的ELF文件格式(Executable and Linkable Format),PE格式实际上可以算作Linux下ELF文件格式的姊妹。PE文件头包含“PE”标记, COFF(Common Object File Format)头,可选头以及各个节表。
图1,PE文件结构
NT头的定义可以在Virtual C++头文件目录下的<winnt.h>头文件中找到。此信息可以通过DbgHelp.dll动态链接库中得ImageNtHeader()函数获取。当然,你也可以通过DOS头来获取NT头,DOS中得e_lfanew字段代表了NT头相对于文件基址的偏移地址。
在PE文件的可选头中,有一些数据目录表,数据目录表划定了当前进程的虚拟内存中的一些基本的信息表的相对位置。这些表记录的信息包括,资源表,导入表,导出表,重定位表,调试表,线程局部存储表,和COM运行时表等,而对于一个PE文件而言,导入表的地位尤其重要,几乎找不到一个没有导入表的PE文件。导入表包含了DLL的名字和函数名,这些信息会在程序调用外部函数的时候通过虚拟地址找到。资源表在Console程序中是没有的,然而在GUI程序中,资源文件就显得比较重要了。导出表在动态链接库和OLE Active-X容器中应用较多。图2中是PE文件中对于数据目录表的一个展示:
只需要几行代码,我们就可以获取导入表的位置和大小等信息。知道了导入表的位置之后,我们就可以检索出DLL的名称和函数信息了,获取导入表详细信息的内容将在后文中详述
2. 深入导入表结构
导入表入口地址引导我们找到位于文件中的导入表位置。对于每一个动态链接库而言,都会有一个称之为“导入表描述符”的结构,此结构包含了FirstThunk地址和OriginalFirstThunk地址以及指向DLL名称的指针。其中FirstThunk所指向的内容会在程序加载时有系统内核的PE加载器初始化。而OriginalFirstThunk所指向的结构提供了Hint数据域和每一个导入函数的名称。
导入表描述符结构可以参见下面的结构体定义:
结构说明:
OriginalFirstThunk
此字段指向一个IMAGE_THUNK_DATA结构体,其中包含了Hint字段和导入函数的名字。
TimeDateStamp
如果绑定了的话,此字段含有时间/日期戳。如果此字段为0则表示导入的DLL不含绑定信息,将此值设为0xFFFFFFFF表示绑定发生。(译者注:何为绑定,俺也不懂)
ForwarderChain
在旧版本的绑定过程中,此字段作为API的前向链表指针,可以设置为0xFFFFFFFF表示不含有前向指针。
Name
指向DLL名字的相对虚拟地址
FirstThunk
指向IMAGE_THUNK_DATA的虚拟地址,此结构会被系统加载器重新初始化。
图3. 导入表一览
图4. 带有OriginalFirstThunk的导入表
图5. 被PE加载器修正后的导入表
译者注:图3,图4,图5应该连起来一起看,图3和图4是我们的PE文件在磁盘上保存着的时候的样子,而图5则是用户双击了某个PE文件之后,此PE文件被加载到内存后,由系统的PE加载器修改后的样子。具体的原理,我觉得此文介绍比较浅略,如果不太懂的话,建议看看罗云彬的《Win 32汇编教程》。
接下来就是本文的重点,即导入表的注入技术:
实际上本文算法非常简单,首先在当前进程中建立一块虚拟内存空间,构造一个假的导入表,这个假的导入表只含有几个基本函数如LoadLibrary和GetProcAddress,然后修改PE文件中指向原始导入表的数据目录表指针,使其指向我们的假导入表,这样系统就会将我们的假导入表初始化。然后我们就利用被系统初始化的假导入表中的导入函数地址,初始化原始导入表,在手动初始化的同时进行比对,一旦发现我们要替换的函数地址,就用我们自定义函数地址替换真正的API函数地址,以实现导入表注入。
本文的程序对用户选择的PE文件的导入表进行修改,替换Shell32.dll这个动态链接库中得ShellAbout函数,此函数在用户点击了”About”按钮时被弹出。而经过本文作者写的工具对导入表修正后,当用户点击“About”按钮时会执行另外一个函数(这里是弹出一个提示对话框)
下图是使用此工具对计算器程序进行导入表注入后的示例图:
这样一段描述估计大家还是一头雾水吧,还是直接上代码说明好了:
下面这个函数是
void CPECryptor::CryptFile(int(__cdecl *progress) (unsigned int, unsigned int))
{
PCHAR ch_temp;
PIMAGE_SECTION_HEADER pimage_section_header;
DWORD dwNewSectionSize;
DWORD dwCodeSize;
DWORD dwCodeOffset;
//----------------------------------------
progress(0,0);
if((image_nt_headers->FileHeader.Characteristics&IMAGE_FILE_DLL)==IMAGE_FILE_DLL)
{
ImportTableMaker = new CITMaker(IMPORT_TABLE_OCX);
}
else
{
//对EXE文件进行一些初始化的操作:包括将假导入表需要包含的DLL和导入函数字符串信息存入一个全局链表。计算假导入表所需占用的内存,并分配相应内存空间。
ImportTableMaker = new CITMaker(IMPORT_TABLE_EXE);
}
//----------------------------------------
//ch_temp指向一段汇编代码,这段汇编代码是作者构造的(后面会给出源码),作用就是代替系统PE加载器,加载并初始化原始导入表的所有导入函数,并搜寻并替换shell32.dll中的ShellAbout函数地址。作者在代码中将目标PE文件的入口点指向了这段汇编代码,而此段汇编代码的最后,会跳转到PE文件的原始入口点执行正常的程序流程。DYN_LOADER_START_MAGIC是一个标记,指向了这段汇编代码的开头,此标记长4字节,加上4之后就是跳过标记后的实际功能代码了。--------这段汇编代码,我在后文中会称之为“启动汇编代码”
ch_temp=(PCHAR)DWORD(ReturnToBytePtr(DynLoader, DYN_LOADER_START_MAGIC))+4;
//dwCodeSize是此段汇编代码的大小,同样地DYN_LOADER_END_MAGIC是标记了此段汇编代码的结尾之处。
dwCodeSize=DWORD(ReturnToBytePtr(DynLoader, DYN_LOADER_END_MAGIC))-DWORD(ch_temp);
//dwCodeOffset是伪造的导入表的大小
dwCodeOffset = ImportTableMaker->dwSize;
//dwNewSectonSize是包含了假导入表和前面提到的汇编代码的总大小,这两部分内容作者将其合在一起,都写入了一个新添加的段中(.xxx段,详见后文)
dwNewSectionSize = dwCodeSize + ImportTableMaker->dwSize;
pNewSection=new TCHAR[dwNewSectionSize];
//这里先将“启动汇编代码”段写入指定位置
memcpy(pNewSection+dwCodeOffset, ch_temp, dwCodeSize);
//这里就是构造一个新段,段的名字叫.xxx,里面含有两部分内容,首先是构造的假导入表,之后紧接着就是“启动汇编代码”
pimage_section_header=AddNewSection(".xxx",dwNewSectionSize);
//CopyData1的作用是将原始PE文件的入口点和原始导入表的偏移地址等信息做一份备份存储到“启动汇编代码”中的尾部,此外,还会保存一些注入程序使用到的api函数的字符串信息(之后会通过LoadLibrary和GetProcAddress手动加载后使用)
CopyData1(pimage_section_header->VirtualAddress);
//将存放有伪造导入表dll名称和函数名称的全局链表中的数据按照导入表描述符的结构进行假导入表的构造(实际上就是填充相应的数据结构)
ImportTableMaker->Build(pimage_section_header->VirtualAddress);// build import table by the current virtual address
//将在内存中构造的导入表数据复制到新建的.xxx节的内存区域
memcpy(pNewSection, ImportTableMaker->pMem, ImportTableMaker->dwSize);
//将内存中的新建.xxx节数据复制到目标PE文件中相应位置(最后一个节处)
memcpy(image_section[image_nt_headers->FileHeader.NumberOfSections-1],
pNewSection,
dwNewSectionSize);
//下面就是修正目标PE文件头的相关内容,将入口点设置为新建节中跳过伪造导入表后的“启动汇编代码“段的开头,将数据目录表中的导入表相对虚拟地址设置为新建节的开头(记得吗?新建节有两部分组成,先是伪造的导入表,后跟“启动汇编代码段”)
image_nt_headers->OptionalHeader.AddressOfEntryPoint=pimage_section_header->VirtualAddress + dwCodeOffset;
image_nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress=pimage_section_header->VirtualAddress;
image_nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size=ImportTableMaker->dwSize;
image_nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress=0;
image_nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size=0;
SetSectionsWritePermission();
//----------------------------------------
delete []pNewSection;
delete ImportTableMaker;
//----------------------------------------
progress(100,0);
}
以上便是整体的框架结构,结合代码,大家应该基本可以理解一个大致思路,不过细节的话,还需大家结合源代码看看(最好下载源代码后结合我的说明看看,应该就比较懂了)
下面再详解一下“启动汇编代码”段,这段代码应该算是本程序的精华所在,希望大家慢慢体会。
前面也一再说明,启动汇编代码段是本程序在目标PE中首先执行的,也就是说目标PE文件的代码入口点已经被修改为指向“启动汇编代码”段了。启动汇编代码段主要完成1.原始导入表的手动加载。2. 替换shell32.dll中得shellabout函数在导入表中的绝对地址。 3.将代码跳转回目标PE文件原始入口点继续执行。
void __stdcall DynLoader()
{
_asm
{
//----------------------------------
dword_type(DYN_LOADER_START_MAGIC) //这是一个用来标示位置的标志位,前面也说过
//----------------------------------
_main_0:
pushad // save the registers context in stack
call _main_1
_main_1:
pop ebp
sub ebp,offset _main_1 // get base ebp
//----------------------------------------------------
//====================================================
//---------------- support dll, ocx -----------------
//下面判断目标文件类型,是否是EXE文件
_support_dll_0:
jmp _support_dll_1 // nop; nop; // in the secon time OEP
jmp _support_dll_2
_support_dll_1:
test [ebp+_p_dwFileType],IMPORT_TABLE_OCX
jz _no_dll_pe_file_0
mov eax,[esp+24h]// imagebase
mov ebx,[esp+30h]// oep
cmp eax,ebx
ja _no_dll_pe_file_0
cmp word ptr [eax],IMAGE_DOS_SIGNATURE
jne _no_dll_pe_file_0
mov [ebp+_p_dwImageBase],eax
//如果是EXE文件的话,就从这里开始执行
_no_dll_pe_file_0:
//----------------------------------------------------
//====================================================
mov eax,[ebp+_p_dwImageBase]
add eax,[eax+03Ch]
add eax,080h
mov ecx,[eax] // image_nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress
add ecx,[ebp+_p_dwImageBase]
add ecx,010h // image_import_descriptor.FirstThunk
mov eax,[ecx]
add eax,[ebp+_p_dwImageBase]
mov ebx,[eax]
mov [ebp+_p_LoadLibrary],ebx
add eax,04h
mov ebx,[eax]
mov [ebp+_p_GetProcAddress],ebx
//----------------------------------------------------
//------- load library and build api call-jmp --------
call _api_load
//上面是加载一些我们需要的API函数。因为我们的导入表已经经过伪造了,所以PE加载器无法对原始导入表中得函数进行加载,那么我们自己的api也是无法执行的,所以需要首先手动加载几个我们需要的api进来。(加载过程是这样的,我们自己伪造的导入表中含有LoadLibrary和GetProcAddress两个函数信息,这样PE加载器会加载这两个函数的绝对地址,而我们就可以使用这两个函数加载其他的api函数了)
//----------------------------------------------------
//====================================================
//----------- get write access for headers -----------
mov edi,[ebp+_p_dwImageBase]
add edi,[edi+03Ch]// edi -> IMAGE_NT_HEADERS
// get write permission by VirtualProtect()
lea eax,[ebp+_p_ptempbuffer]
push eax
push PAGE_READWRITE
push [edi+0x54]//IMAGE_NT_HEADERS.OptionalHeader.SizeOfHeaders
push [ebp+_p_dwImageBase]
call _jmp_VirtualProtect
//VirtualProtect(dwImageBase,image_nt_header.OptionalHeader.SizeOfHeaders,PAGE_READWRITE,ptempbuffer);
//这上面是对需要修改的内存位置赋予读写属性
//----------------------------------------------------
//----------------------------------------------------
//--------------- import table fix up ----------------
lea eax,[ebp+_ShellAbout_NewCode]
push eax
lea eax,[ebp+_p_szShellAbout]
push eax
lea eax,[ebp+_p_szSHELL32_r]
push eax
push [ebp+_p_dwImportVirtualAddress]
push [ebp+_p_dwImageBase]
call _it_fixup_1
//_it_fixup_1函数就是执行的手动加载原始导入表函数的过程了,在加载过程中它会搜寻shell32.dll的shellabout函数的绝对地址并进行替换
//--------------- import table fix up ----------------
//call _it_fixup
//----------------------------------------------------
//====================================================
//---------------- support dll, ocx 1 ---------------
//by set second way to oep and relocation table effect
mov edi,[ebp+_p_dwImageBase]
add edi,[edi+03Ch]// edi -> IMAGE_NT_HEADERS
mov ax,word ptr [edi+016h]// edi -> image_nt_headers->FileHeader.Characteristics
// .IF ((image_nt_headers->FileHeader.Characteristics&IMAGE_FILE_DLL) ==IMAGE_FILE_DLL)
test ax,IMAGE_FILE_DLL
jz _no_dll_pe_file_1
//--------------- relocation fix up --------------
call _reloc_fixup
//------------------------------------------------
mov ax,9090h
mov word ptr [ebp+_support_dll_0],ax
// .ENDIF
_no_dll_pe_file_1:
//----------------------------------------------------
//====================================================
_support_dll_2:
//----------------------------------------------------
//====================================================
//--------- Prepare the SEH for OEP approach ---------
mov eax,[ebp+_p_dwImageBase]
add eax,[ebp+_p_dwOrgEntryPoint]
mov [esp+10h],eax // pStack.Ebx <- eax
lea eax,[ebp+_except_handler1_OEP_Jump]
mov [esp+1Ch],eax // pStack.Eax <- eax
popad // restore the first registers context from stack
//----------------------------------------------------
// the structured exception handler (SEH) installation
push eax
xor eax, eax
push dword ptr fs:[0] // NT_TIB32.ExceptionList
mov fs:[0],esp // NT_TIB32.ExceptionList <-ESP
dword_type(0xCCCCCCCC)// raise a (int 3) exception
//----------------------------------------------------
//====================================================
//--------------------------------------------------------
//========================================================
//------------- t_size strlen(LPCTSTR lpStr) -------------
//下面是作者自己写的strlen函数,和c语言的函数功能一致,没啥好说的
__strlen:
push ebp
mov ebp,esp
push ecx
push esi
push ebx
mov esi,[ebp+08h]// -> destination
mov ecx,255// -> length
xor ebx,ebx
_strlenloop:
lods byte ptr ds:[esi]
cmp al,00h
jz _endbufstrlen
inc ebx
loop _strlenloop
_endbufstrlen:
mov eax,ebx
inc eax
pop ebx
pop esi
pop ecx
mov esp,ebp
pop ebp
ret
//--------------------------------------------------------
//========================================================
//------------------- fix up relocation ------------------
// I have stolen this reloc fix-up code from Morphine 2.7 !!
// so Thanks Holy_Father && Ratter/29A for it. (www.hxdef.org)
//下面是重定位表的修正过程,实际上就是当PE文件加载到非PE头指定的位置时,会导致PE文件中一些函数的位置发生变化,需要修正。
_reloc_fixup:
mov eax,[ebp+_p_dwImageBase]
mov edx,eax
mov ebx,eax
add ebx,[ebx+3Ch] // edi -> IMAGE_NT_HEADERS
mov ebx,[ebx+034h]// edx ->image_nt_headers->OptionalHeader.ImageBase
sub edx,ebx // edx -> reloc_correction
je _reloc_fixup_end
mov ebx,[ebp+_p_dwRelocationVirtualAddress]
test ebx,ebx
jz _reloc_fixup_end
add ebx,eax
_reloc_fixup_block:
mov eax,[ebx+004h] //ImageBaseRelocation.SizeOfBlock
test eax,eax
jz _reloc_fixup_end
lea ecx,[eax-008h] //ImageBaseRelocation.SizeOfBlock - sizeof(TImageBaseRelocation)
shr ecx,001h //WORD((ImageBaseRelocation.SizeOfBlock - sizeof(TImageBaseRelocation)) / sizeof(Word))
lea edi,[ebx+008h] //PImageBaseRelocation + sizeof(TImageBaseRelocation)
_reloc_fixup_do_entry:
movzx eax,word ptr [edi]//Entry
push edx
mov edx,eax
shr eax,00Ch //Type = Entry >> 12
mov esi,[ebp+_p_dwImageBase]//ImageBase
and dx,00FFFh
add esi,[ebx] //ImageBase + ImageBaseRelocation.VirtualAddress
add esi,edx //ImageBase + ImageBaseRelocation.VirtualAddress+Entry & 0x0FFF
pop edx
//------------------------------
//---- IMAGE_REL_BASED_HIGH ----
_reloc_fixup_HIGH:
dec eax
jnz _reloc_fixup_LOW
mov eax,edx
shr eax,010h //HIWORD(Delta)
jmp _reloc_fixup_LOW_fixup
//------------------------------
//---- IMAGE_REL_BASED_LOW -----
_reloc_fixup_LOW:
dec eax
jnz _reloc_fixup_HIGHLOW
movzx eax,dx //LOWORD(Delta)
_reloc_fixup_LOW_fixup:
add word ptr [esi],ax
jmp _reloc_fixup_next_entry
//------------------------------
//-- IMAGE_REL_BASED_HIGHLOW ---
_reloc_fixup_HIGHLOW:
dec eax
jnz _reloc_fixup_next_entry
add [esi],edx
//------------------------------
_reloc_fixup_next_entry:
inc edi
inc edi //Entry++
loop _reloc_fixup_do_entry
_reloc_fixup_next_base:
add ebx,[ebx+004h] //ImageBaseRelocation + ImageBaseRelocation.SizeOfBlock
jmp _reloc_fixup_block
_reloc_fixup_end:
// clean relocation table
/* mov eax,[ebp+_p_dwImageBase]
add eax,[ebp+_p_dwRelocationVirtualAddress]
lea edi,[eax]
xor eax,eax
add ecx,[ebp+_p_dwRelocationSize]
rep stos byte ptr [edi]*/
ret
//--------------------------------------------------------
//下面就是手动加载原始导入表的过程了,它会模拟PE加载器的操作,遍历导入表信息,查找dll名称和api函数名称,然后利用LoadLibrary和GetProcAddress进行加载
//--------------- fix up the import table ----------------
/*
ebp+18h [ebp+_ShellAbout_NewCode]
ebp+14h [ebp+_p_szShellAbout]
ebp+10h [ebp+_p_szSHELL32_r]
ebp+0ch _p_dwImportVirtualAddress
ebp+08h _p_dwImageBase
---------------------------------
ebp-04h x
ebp-08h _p_dwThunk
ebp-0ch _p_dwHintName
ebp-10h _p_dwLibraryName
ebp-14h _p_dwAPIaddress
ebp-18h _p_dwFunctionName
*/
_it_fixup_1:
push ebp
mov ebp,esp
add esp,-18h
mov ebx,[ebp+0ch] //ebx = _p_dwImportVirtualAddress
test ebx,ebx
jz _it_fixup_1_end
mov esi,[ebp+08h] //esi = _p_dwImageBase
add ebx,esi // dwImageBase + dwImportVirtualAddress
_it_fixup_1_get_lib_address_loop:
mov eax,[ebx+0ch] // image_import_descriptor.Name eax=dll名称
test eax,eax
jz _it_fixup_1_end
mov ecx,[ebx+10h] // ecx = image_import_descriptor.FirstThunk
add ecx,esi
mov [ebp-08h],ecx // dwThunk (FirstThunk的绝对地址)
mov ecx,[ebx] // ecx = image_import_descriptor.Characteristics
test ecx,ecx
jnz _it_fixup_1_table
mov ecx,[ebx+10h]
_it_fixup_1_table:
add ecx,esi
mov [ebp-0ch],ecx // dwHintName
add eax,esi // image_import_descriptor.Name + dwImageBase = ModuleName
push eax // lpLibFileName
mov [ebp-10h],eax
call _jmp_LoadLibrary // LoadLibrary(lpLibFileName);
test eax,eax
jz _it_fixup_1_end
mov edi,eax //edi指向dll加载基址
_it_fixup_1_get_proc_address_loop:
mov ecx,[ebp-0ch] // dwHintName
mov edx,[ecx] // image_thunk_data.Ordinal
test edx,edx
jz _it_fixup_1_next_module
test edx,080000000h // .IF( import by ordinal )
jz _it_fixup_1_by_name
and edx,07FFFFFFFh // get ordinal
jmp _it_fixup_1_get_addr
_it_fixup_1_by_name:
add edx,esi // image_thunk_data.Ordinal + dwImageBase = OrdinalName
inc edx
inc edx // OrdinalName.Name
_it_fixup_1_get_addr:
push edx // lpProcName
mov [ebp-18h],edx
push edi // hModule
call _jmp_GetProcAddress // GetProcAddress(hModule, lpProcName);
mov [ebp-14h],eax //_p_dwAPIaddress
//上面的代码段就是加载api函数的过程,然后获取的绝对地址暂存在[ebp-14h]中
//================================================================
//mov [ecx],eax//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
push edi
push esi
push ebx
mov ebx,[ebp-10h]
push ebx
push ebx
call _char_upper
//上面是把当前正在加载的dll名称转化为全大写,然后和下面的SHELL32.DLL进行字符串匹配,如果匹配的话,就再和shellabout函数进行字符串匹配,全部匹配的话就进行导入表的地址替换操作
mov esi,[ebp-10h]
mov edi,[ebp+010h] // [ebp+_p_szShell32]
_it_fixup_1_check_dll_redirected:
push edi
call __strlen
add esp, 4
mov ebx,eax
mov ecx,eax
push edi
push esi
repe cmps //byte ptr [edi], byte ptr [esi]
jz _it_fixup_1_check_func_name
jmp _it_fixup_1_no_check_func_name
_it_fixup_1_check_func_name:
mov edi,[ebp+014h] // [ebp+_p_szShellAbout]
push edi
call __strlen
add esp, 4
mov ecx,eax
mov esi,[ebp-18h]
mov edi,[ebp+014h] // [ebp+_p_szShellAbout]
repe cmps //byte ptr [edi], byte ptr [esi]
jz _it_fixup_1_do_normal_it_0
//如果不匹配的话就执行下面的代码,直接将当前加载的api函数绝对地址写入原始导入表的FirstThunk域(和系统PE加载的做法一致)
_it_fixup_1_no_check_func_name:
pop esi
pop edi
add edi,ebx
cmp byte ptr [edi],0
jnz _it_fixup_1_check_dll_redirected
mov ecx,[ebp-08h]
mov eax,[ebp-014h]
mov [ecx],eax
jmp _it_fixup_1_do_normal_it_1
//如果匹配的话就执行下面这段,就是将我们自己的代码段替换实际ShellAbout函数的绝对地址,实现导入表的注入。
_it_fixup_1_do_normal_it_0:
pop esi
pop edi
mov ecx,[ebp-08h]
mov edi,[ebp+18h]
mov [ecx],edi
_it_fixup_1_do_normal_it_1:
pop ebx
pop esi
pop edi
//================================================================
add dword ptr [ebp-08h],004h // dwThunk => next dwThunk
add dword ptr [ebp-0ch],004h// dwHintName => next dwHintName
jmp _it_fixup_1_get_proc_address_loop
_it_fixup_1_next_module:
add ebx,014h // sizeof(IMAGE_IMPORT_DESCRIPTOR)
jmp _it_fixup_1_get_lib_address_loop
_it_fixup_1_end:
mov esp,ebp
pop ebp
ret 14h
//--------------------------------------------------------
//========================================================
//-- char_upper(LPCTSTR lpDestination, LPCTSTR lpSource )
_char_upper:
push ebp
mov ebp,esp
push ecx
push eax
push esi
push edi
mov edi,dword ptr [ebp+08h]// -> Destination
mov esi,dword ptr [ebp+0Ch]// -> Source
mov ecx,255// -> Length
xor eax,eax
__makeupperloop:
lods byte ptr [esi]//ESI
cmp al,00h
jz endofbuffer
cmp al,60h
jc notinlowerfield
cmp al,7bh
jnc notinlowerfield
sub al,20h
notinlowerfield:
stos byte ptr [edi]//EDI
loop __makeupperloop
endofbuffer:
pop edi
pop esi
pop eax
pop ecx
pop ebp
retn 08h
//--------------------------------------------------------
//========================================================
//------------- load necessary api functions -------------
_api_load:
lea edi,[ebp+_p_szKernel32]
lea ebx,[ebp+_p_GetModuleHandle]
lea ecx,[ebp+_jmp_GetModuleHandle]
add ecx,02h
_api_get_lib_address_loop:
push ecx
//-------------------
push edi
mov eax,offset _p_LoadLibrary
call [ebp+eax]// LoadLibrary(lpLibFileName);
//-------------------
pop ecx
mov esi,eax // esi -> hModule
push edi
call __strlen
add esp,04h
add edi,eax
_api_get_proc_address_loop:
push ecx
//-------------------
push edi
push esi
mov eax,offset _p_GetProcAddress
call [ebp+eax]// GetModuleHandle=GetProcAddress(hModule, lpProcName);
//--------------------
pop ecx
mov [ebx],eax
mov [ecx],ebx
add ebx,04h
add ecx,06h
push edi
call __strlen
add esp,04h
add edi,eax
mov al,byte ptr [edi]
test al,al
jnz _api_get_proc_address_loop
inc edi
mov al,byte ptr [edi]
test al,al
jnz _api_get_lib_address_loop
ret
//--------------------------------------------------------
//========================================================
//这里就是我们自己的函数了,用来替换ShellAbout函数的,功能就是弹出一个对话框
_ShellAbout_NewCode:
_local_0:
pushad // save the registers context in stack
call _local_1
_local_1:
pop ebp
sub ebp,offset _local_1 // get base ebp
push MB_OK | MB_ICONINFORMATION
lea eax,[ebp+_p_szCaption]
push eax
lea eax,[ebp+_p_szText]
push eax
push NULL
call _jmp_MessageBox
// MessageBox(NULL, szText, szCaption, MB_OK | MB_ICONINFORMATION) ;
//...
//...
//...
// Place your code here ...
//...
//...
//...
popad // restore the first registers context from stack
ret 10h
//----------------------------------------------------------
//--------------------------------------------------------
//========================================================
// -------- exception handler expression filter ----------
//最后跳转到目标PE文件的原始入口点,继续执行
_except_handler1_OEP_Jump:
push ebp
mov ebp,esp
mov eax,[ebp+010h] // PCONTEXT: pContext <- eax
//---------------
push edi
// restore original SEH
mov edi,[eax+0C4h] // pContext.Esp
push dword ptr ds:[edi]
pop dword ptr fs:[0]
add dword ptr [eax+0C4h],8 // pContext.Esp
// set the Eip to the OEP
mov edi,[eax+0A4h] // edi <- pContext.Ebx
mov [eax+0B8h],edi // pContext.Eip <- edi
//
pop edi
//---------------
mov eax, EXCEPTION_CONTINUE_SEARCH
leave
ret
//--------------------------------------------------------
//========================================================
//下面是一些“启动汇编代码”段需要用到的常量数据等内容
dword_type(DYN_LOADER_START_DATA1)
//----------------------------------
_p_dwFileType: dword_type(0xCCCCCCCC)
_p_dwImageBase: dword_type(0xCCCCCCCC)
_p_dwOrgEntryPoint: dword_type(0xCCCCCCCC)
_p_dwImportVirtualAddress: dword_type(0xCCCCCCCC)
_p_dwRelocationVirtualAddress: dword_type(0xCCCCCCCC)
_p_dwRelocationSize: dword_type(0xCCCCCCCC)
//----------------------------------
_tls_dwStartAddressOfRawData: dword_type(0xCCCCCCCC)
_tls_dwEndAddressOfRawData: dword_type(0xCCCCCCCC)
_tls_dwAddressOfIndex: dword_type(0xCCCCCCCC)
_tls_dwAddressOfCallBacks: dword_type(0xCCCCCCCC)
_tls_dwSizeOfZeroFill: dword_type(0xCCCCCCCC)
_tls_dwCharacteristics: dword_type(0xCCCCCCCC)
//----------------------------------
_p_szKernel32: //db "Kernel32.dll",0,13
db db db db db db db db db db db db db
_p_szGetModuleHandle: //db "GetModuleHandleA",0,17
db db db db db db db db db db db db db db db db db
_p_szVirtualProtect: //db "VirtualProtect",0,15
db db db db db db db db db db db db db db db
_p_szGetModuleFileName: //db "GetModuleFileNameA",0,19
db db db db db db db db db db db db db db db db db db db
_p_szCreateFile: //db "CreateFileA",0,12
db db db db db db db db db db db db
_p_szGlobalAlloc: //db "GlobalAlloc",0,12
db db db db db db db db db db db db
_p_szVirtualAlloc: //db "VirtualAlloc",0,13
db db db db db db db db db db db db db
_p_szLoadLibrary: //db "LoadLibraryA",0,13
db db db db db db db db db db db db db
_p_szGetProcAddress: //db "GetProcAddress",0,15
db db db db db db db db db db db db db db db
byte_type(0)
_p_szUser32: //db "User32.dll",0,11
db db db db db db db db db db db
_p_szMessageBox: //db "MessageBoxA",0,12
db db db db db db db db db db db db
byte_type(0)
byte_type(0)
//----------------------------------
_p_LoadLibrary: dword_type(0xCCCCCCCC)
_p_GetProcAddress: dword_type(0xCCCCCCCC)
_p_GetModuleHandle:
dword_type(0xCCCCCCCC)
dword_type(0xCCCCCCCC)
dword_type(0xCCCCCCCC)
dword_type(0xCCCCCCCC)
dword_type(0xCCCCCCCC)
dword_type(0xCCCCCCCC)
dword_type(0xCCCCCCCC)
dword_type(0xCCCCCCCC)
dword_type(0xCCCCCCCC)
_jmp_GetModuleHandle: __jmp_api dword_type(0xCCCCCCCC)
_jmp_VirtualProtect: __jmp_api dword_type(0xCCCCCCCC)
_jmp_GetModuleFileName: __jmp_api dword_type(0xCCCCCCCC)
_jmp_CreateFile: __jmp_api dword_type(0xCCCCCCCC)
_jmp_GlobalAlloc: __jmp_api dword_type(0xCCCCCCCC)
_jmp_VirtualAlloc: __jmp_api dword_type(0xCCCCCCCC)
_jmp_LoadLibrary: __jmp_api dword_type(0xCCCCCCCC)
_jmp_GetProcAddress: __jmp_api dword_type(0xCCCCCCCC)
_jmp_MessageBox: __jmp_api dword_type(0xCCCCCCCC)
_p_szSHELL32_r:
//db "SHELL32.DLL",0,12
bb('S') bb('H') bb('E') bb('L') bb('L') bb('3') bb('2') bb('.') bb('D') bb('L') bb('L') bb(0)
byte_type(0)
_p_szShellAbout:
//db "ShellAboutW",0,12
bb('S') bb('h') bb('e') bb('l') bb('l') bb('A') bb('b') bb('o') bb('u') bb('t') bb('W') bb(0)
_p_szCaption:
bb('A') bb('s') bb('h') bb('k') bb('b') bb('i') bb('z') bb(0)
_p_szText:
bb('T') bb('h') bb('i') bb('s') bb(' ') bb('E') bb('x') bb('a') bb('m') bb('p') bb('l')
bb('e') bb(' ') bb('s') bb('h') bb('o') bb('w') bb('s') bb(' ') bb('h') bb('o') bb('w')
bb(' ') bb('A') bb('P') bb('I') bb(' ') bb('r') bb('e') bb('d') bb('i') bb('r') bb('e')
bb('c') bb('t') bb('i') bb('o') bb('n') bb(' ') bb('t') bb('e') bb('c') bb('h') bb('n')
bb('i') bb('q') bb('u') bb('e') bb(' ') bb('w') bb('o') bb('r') bb('k') bb('s') bb('.')
bb(0)
_p_dwThunk: dword_type(0xCCCCCCCC)
_p_dwHintName: dword_type(0xCCCCCCCC)
_p_ptempbuffer: dword_type(0xCCCCCCCC)
//----------------------------------
dword_type(DYN_LOADER_END_MAGIC)
//----------------------------------
}
}
我会有选择地翻译并扩充部分内容,由于本人很少翻译国外文章,翻译不到位的地方还望大家海涵。
原文出处:http://www.codeproject.com/Articles/14360/Injective-Code-inside-Import-Table
其中源代码也可以从上面的网站下载到,我这里就不添加附件了~
设想一下,我们通过修改导入表结构使得程序的导入函数指向特殊的函数,以完成我们指定的功能。此外,PE文件保护器会使用此技术植入一些保护函数,而一些后门程序也会利用此技术通过木马植入恶意代码。
在逆向工程中,我们描述此技术为“API重定位技术”。不过我不打算在本文中详述此技术的方方面面,我会重点描述本文所提供源代码之外的思想(译者注:这里我会添加对于源代码中一些核心技术的阐释)。我不会发布有恶意代码倾向的商用源代码,然而,我认为此文实际上也可以用来作为此方面的一个介绍。
1. 了解导入表
PE文件结构包含MS-DOS头,NT头,节表和节的映像,参见图1。MS-DOS自DOS时代起一直沿用至Windows时代。NT头是来自Unix系统下的ELF文件格式(Executable and Linkable Format),PE格式实际上可以算作Linux下ELF文件格式的姊妹。PE文件头包含“PE”标记, COFF(Common Object File Format)头,可选头以及各个节表。
图1,PE文件结构
NT头的定义可以在Virtual C++头文件目录下的<winnt.h>头文件中找到。此信息可以通过DbgHelp.dll动态链接库中得ImageNtHeader()函数获取。当然,你也可以通过DOS头来获取NT头,DOS中得e_lfanew字段代表了NT头相对于文件基址的偏移地址。
在PE文件的可选头中,有一些数据目录表,数据目录表划定了当前进程的虚拟内存中的一些基本的信息表的相对位置。这些表记录的信息包括,资源表,导入表,导出表,重定位表,调试表,线程局部存储表,和COM运行时表等,而对于一个PE文件而言,导入表的地位尤其重要,几乎找不到一个没有导入表的PE文件。导入表包含了DLL的名字和函数名,这些信息会在程序调用外部函数的时候通过虚拟地址找到。资源表在Console程序中是没有的,然而在GUI程序中,资源文件就显得比较重要了。导出表在动态链接库和OLE Active-X容器中应用较多。图2中是PE文件中对于数据目录表的一个展示:
只需要几行代码,我们就可以获取导入表的位置和大小等信息。知道了导入表的位置之后,我们就可以检索出DLL的名称和函数信息了,获取导入表详细信息的内容将在后文中详述
2. 深入导入表结构
导入表入口地址引导我们找到位于文件中的导入表位置。对于每一个动态链接库而言,都会有一个称之为“导入表描述符”的结构,此结构包含了FirstThunk地址和OriginalFirstThunk地址以及指向DLL名称的指针。其中FirstThunk所指向的内容会在程序加载时有系统内核的PE加载器初始化。而OriginalFirstThunk所指向的结构提供了Hint数据域和每一个导入函数的名称。
导入表描述符结构可以参见下面的结构体定义:
结构说明:
OriginalFirstThunk
此字段指向一个IMAGE_THUNK_DATA结构体,其中包含了Hint字段和导入函数的名字。
TimeDateStamp
如果绑定了的话,此字段含有时间/日期戳。如果此字段为0则表示导入的DLL不含绑定信息,将此值设为0xFFFFFFFF表示绑定发生。(译者注:何为绑定,俺也不懂)
ForwarderChain
在旧版本的绑定过程中,此字段作为API的前向链表指针,可以设置为0xFFFFFFFF表示不含有前向指针。
Name
指向DLL名字的相对虚拟地址
FirstThunk
指向IMAGE_THUNK_DATA的虚拟地址,此结构会被系统加载器重新初始化。
图3. 导入表一览
图4. 带有OriginalFirstThunk的导入表
图5. 被PE加载器修正后的导入表
译者注:图3,图4,图5应该连起来一起看,图3和图4是我们的PE文件在磁盘上保存着的时候的样子,而图5则是用户双击了某个PE文件之后,此PE文件被加载到内存后,由系统的PE加载器修改后的样子。具体的原理,我觉得此文介绍比较浅略,如果不太懂的话,建议看看罗云彬的《Win 32汇编教程》。
接下来就是本文的重点,即导入表的注入技术:
实际上本文算法非常简单,首先在当前进程中建立一块虚拟内存空间,构造一个假的导入表,这个假的导入表只含有几个基本函数如LoadLibrary和GetProcAddress,然后修改PE文件中指向原始导入表的数据目录表指针,使其指向我们的假导入表,这样系统就会将我们的假导入表初始化。然后我们就利用被系统初始化的假导入表中的导入函数地址,初始化原始导入表,在手动初始化的同时进行比对,一旦发现我们要替换的函数地址,就用我们自定义函数地址替换真正的API函数地址,以实现导入表注入。
本文的程序对用户选择的PE文件的导入表进行修改,替换Shell32.dll这个动态链接库中得ShellAbout函数,此函数在用户点击了”About”按钮时被弹出。而经过本文作者写的工具对导入表修正后,当用户点击“About”按钮时会执行另外一个函数(这里是弹出一个提示对话框)
下图是使用此工具对计算器程序进行导入表注入后的示例图:
这样一段描述估计大家还是一头雾水吧,还是直接上代码说明好了:
下面这个函数是
void CPECryptor::CryptFile(int(__cdecl *progress) (unsigned int, unsigned int))
{
PCHAR ch_temp;
PIMAGE_SECTION_HEADER pimage_section_header;
DWORD dwNewSectionSize;
DWORD dwCodeSize;
DWORD dwCodeOffset;
//----------------------------------------
progress(0,0);
if((image_nt_headers->FileHeader.Characteristics&IMAGE_FILE_DLL)==IMAGE_FILE_DLL)
{
ImportTableMaker = new CITMaker(IMPORT_TABLE_OCX);
}
else
{
//对EXE文件进行一些初始化的操作:包括将假导入表需要包含的DLL和导入函数字符串信息存入一个全局链表。计算假导入表所需占用的内存,并分配相应内存空间。
ImportTableMaker = new CITMaker(IMPORT_TABLE_EXE);
}
//----------------------------------------
//ch_temp指向一段汇编代码,这段汇编代码是作者构造的(后面会给出源码),作用就是代替系统PE加载器,加载并初始化原始导入表的所有导入函数,并搜寻并替换shell32.dll中的ShellAbout函数地址。作者在代码中将目标PE文件的入口点指向了这段汇编代码,而此段汇编代码的最后,会跳转到PE文件的原始入口点执行正常的程序流程。DYN_LOADER_START_MAGIC是一个标记,指向了这段汇编代码的开头,此标记长4字节,加上4之后就是跳过标记后的实际功能代码了。--------这段汇编代码,我在后文中会称之为“启动汇编代码”
ch_temp=(PCHAR)DWORD(ReturnToBytePtr(DynLoader, DYN_LOADER_START_MAGIC))+4;
//dwCodeSize是此段汇编代码的大小,同样地DYN_LOADER_END_MAGIC是标记了此段汇编代码的结尾之处。
dwCodeSize=DWORD(ReturnToBytePtr(DynLoader, DYN_LOADER_END_MAGIC))-DWORD(ch_temp);
//dwCodeOffset是伪造的导入表的大小
dwCodeOffset = ImportTableMaker->dwSize;
//dwNewSectonSize是包含了假导入表和前面提到的汇编代码的总大小,这两部分内容作者将其合在一起,都写入了一个新添加的段中(.xxx段,详见后文)
dwNewSectionSize = dwCodeSize + ImportTableMaker->dwSize;
pNewSection=new TCHAR[dwNewSectionSize];
//这里先将“启动汇编代码”段写入指定位置
memcpy(pNewSection+dwCodeOffset, ch_temp, dwCodeSize);
//这里就是构造一个新段,段的名字叫.xxx,里面含有两部分内容,首先是构造的假导入表,之后紧接着就是“启动汇编代码”
pimage_section_header=AddNewSection(".xxx",dwNewSectionSize);
//CopyData1的作用是将原始PE文件的入口点和原始导入表的偏移地址等信息做一份备份存储到“启动汇编代码”中的尾部,此外,还会保存一些注入程序使用到的api函数的字符串信息(之后会通过LoadLibrary和GetProcAddress手动加载后使用)
CopyData1(pimage_section_header->VirtualAddress);
//将存放有伪造导入表dll名称和函数名称的全局链表中的数据按照导入表描述符的结构进行假导入表的构造(实际上就是填充相应的数据结构)
ImportTableMaker->Build(pimage_section_header->VirtualAddress);// build import table by the current virtual address
//将在内存中构造的导入表数据复制到新建的.xxx节的内存区域
memcpy(pNewSection, ImportTableMaker->pMem, ImportTableMaker->dwSize);
//将内存中的新建.xxx节数据复制到目标PE文件中相应位置(最后一个节处)
memcpy(image_section[image_nt_headers->FileHeader.NumberOfSections-1],
pNewSection,
dwNewSectionSize);
//下面就是修正目标PE文件头的相关内容,将入口点设置为新建节中跳过伪造导入表后的“启动汇编代码“段的开头,将数据目录表中的导入表相对虚拟地址设置为新建节的开头(记得吗?新建节有两部分组成,先是伪造的导入表,后跟“启动汇编代码段”)
image_nt_headers->OptionalHeader.AddressOfEntryPoint=pimage_section_header->VirtualAddress + dwCodeOffset;
image_nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress=pimage_section_header->VirtualAddress;
image_nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size=ImportTableMaker->dwSize;
image_nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress=0;
image_nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size=0;
SetSectionsWritePermission();
//----------------------------------------
delete []pNewSection;
delete ImportTableMaker;
//----------------------------------------
progress(100,0);
}
以上便是整体的框架结构,结合代码,大家应该基本可以理解一个大致思路,不过细节的话,还需大家结合源代码看看(最好下载源代码后结合我的说明看看,应该就比较懂了)
下面再详解一下“启动汇编代码”段,这段代码应该算是本程序的精华所在,希望大家慢慢体会。
前面也一再说明,启动汇编代码段是本程序在目标PE中首先执行的,也就是说目标PE文件的代码入口点已经被修改为指向“启动汇编代码”段了。启动汇编代码段主要完成1.原始导入表的手动加载。2. 替换shell32.dll中得shellabout函数在导入表中的绝对地址。 3.将代码跳转回目标PE文件原始入口点继续执行。
void __stdcall DynLoader()
{
_asm
{
//----------------------------------
dword_type(DYN_LOADER_START_MAGIC) //这是一个用来标示位置的标志位,前面也说过
//----------------------------------
_main_0:
pushad // save the registers context in stack
call _main_1
_main_1:
pop ebp
sub ebp,offset _main_1 // get base ebp
//----------------------------------------------------
//====================================================
//---------------- support dll, ocx -----------------
//下面判断目标文件类型,是否是EXE文件
_support_dll_0:
jmp _support_dll_1 // nop; nop; // in the secon time OEP
jmp _support_dll_2
_support_dll_1:
test [ebp+_p_dwFileType],IMPORT_TABLE_OCX
jz _no_dll_pe_file_0
mov eax,[esp+24h]// imagebase
mov ebx,[esp+30h]// oep
cmp eax,ebx
ja _no_dll_pe_file_0
cmp word ptr [eax],IMAGE_DOS_SIGNATURE
jne _no_dll_pe_file_0
mov [ebp+_p_dwImageBase],eax
//如果是EXE文件的话,就从这里开始执行
_no_dll_pe_file_0:
//----------------------------------------------------
//====================================================
mov eax,[ebp+_p_dwImageBase]
add eax,[eax+03Ch]
add eax,080h
mov ecx,[eax] // image_nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress
add ecx,[ebp+_p_dwImageBase]
add ecx,010h // image_import_descriptor.FirstThunk
mov eax,[ecx]
add eax,[ebp+_p_dwImageBase]
mov ebx,[eax]
mov [ebp+_p_LoadLibrary],ebx
add eax,04h
mov ebx,[eax]
mov [ebp+_p_GetProcAddress],ebx
//----------------------------------------------------
//------- load library and build api call-jmp --------
call _api_load
//上面是加载一些我们需要的API函数。因为我们的导入表已经经过伪造了,所以PE加载器无法对原始导入表中得函数进行加载,那么我们自己的api也是无法执行的,所以需要首先手动加载几个我们需要的api进来。(加载过程是这样的,我们自己伪造的导入表中含有LoadLibrary和GetProcAddress两个函数信息,这样PE加载器会加载这两个函数的绝对地址,而我们就可以使用这两个函数加载其他的api函数了)
//----------------------------------------------------
//====================================================
//----------- get write access for headers -----------
mov edi,[ebp+_p_dwImageBase]
add edi,[edi+03Ch]// edi -> IMAGE_NT_HEADERS
// get write permission by VirtualProtect()
lea eax,[ebp+_p_ptempbuffer]
push eax
push PAGE_READWRITE
push [edi+0x54]//IMAGE_NT_HEADERS.OptionalHeader.SizeOfHeaders
push [ebp+_p_dwImageBase]
call _jmp_VirtualProtect
//VirtualProtect(dwImageBase,image_nt_header.OptionalHeader.SizeOfHeaders,PAGE_READWRITE,ptempbuffer);
//这上面是对需要修改的内存位置赋予读写属性
//----------------------------------------------------
//----------------------------------------------------
//--------------- import table fix up ----------------
lea eax,[ebp+_ShellAbout_NewCode]
push eax
lea eax,[ebp+_p_szShellAbout]
push eax
lea eax,[ebp+_p_szSHELL32_r]
push eax
push [ebp+_p_dwImportVirtualAddress]
push [ebp+_p_dwImageBase]
call _it_fixup_1
//_it_fixup_1函数就是执行的手动加载原始导入表函数的过程了,在加载过程中它会搜寻shell32.dll的shellabout函数的绝对地址并进行替换
//--------------- import table fix up ----------------
//call _it_fixup
//----------------------------------------------------
//====================================================
//---------------- support dll, ocx 1 ---------------
//by set second way to oep and relocation table effect
mov edi,[ebp+_p_dwImageBase]
add edi,[edi+03Ch]// edi -> IMAGE_NT_HEADERS
mov ax,word ptr [edi+016h]// edi -> image_nt_headers->FileHeader.Characteristics
// .IF ((image_nt_headers->FileHeader.Characteristics&IMAGE_FILE_DLL) ==IMAGE_FILE_DLL)
test ax,IMAGE_FILE_DLL
jz _no_dll_pe_file_1
//--------------- relocation fix up --------------
call _reloc_fixup
//------------------------------------------------
mov ax,9090h
mov word ptr [ebp+_support_dll_0],ax
// .ENDIF
_no_dll_pe_file_1:
//----------------------------------------------------
//====================================================
_support_dll_2:
//----------------------------------------------------
//====================================================
//--------- Prepare the SEH for OEP approach ---------
mov eax,[ebp+_p_dwImageBase]
add eax,[ebp+_p_dwOrgEntryPoint]
mov [esp+10h],eax // pStack.Ebx <- eax
lea eax,[ebp+_except_handler1_OEP_Jump]
mov [esp+1Ch],eax // pStack.Eax <- eax
popad // restore the first registers context from stack
//----------------------------------------------------
// the structured exception handler (SEH) installation
push eax
xor eax, eax
push dword ptr fs:[0] // NT_TIB32.ExceptionList
mov fs:[0],esp // NT_TIB32.ExceptionList <-ESP
dword_type(0xCCCCCCCC)// raise a (int 3) exception
//----------------------------------------------------
//====================================================
//--------------------------------------------------------
//========================================================
//------------- t_size strlen(LPCTSTR lpStr) -------------
//下面是作者自己写的strlen函数,和c语言的函数功能一致,没啥好说的
__strlen:
push ebp
mov ebp,esp
push ecx
push esi
push ebx
mov esi,[ebp+08h]// -> destination
mov ecx,255// -> length
xor ebx,ebx
_strlenloop:
lods byte ptr ds:[esi]
cmp al,00h
jz _endbufstrlen
inc ebx
loop _strlenloop
_endbufstrlen:
mov eax,ebx
inc eax
pop ebx
pop esi
pop ecx
mov esp,ebp
pop ebp
ret
//--------------------------------------------------------
//========================================================
//------------------- fix up relocation ------------------
// I have stolen this reloc fix-up code from Morphine 2.7 !!
// so Thanks Holy_Father && Ratter/29A for it. (www.hxdef.org)
//下面是重定位表的修正过程,实际上就是当PE文件加载到非PE头指定的位置时,会导致PE文件中一些函数的位置发生变化,需要修正。
_reloc_fixup:
mov eax,[ebp+_p_dwImageBase]
mov edx,eax
mov ebx,eax
add ebx,[ebx+3Ch] // edi -> IMAGE_NT_HEADERS
mov ebx,[ebx+034h]// edx ->image_nt_headers->OptionalHeader.ImageBase
sub edx,ebx // edx -> reloc_correction
je _reloc_fixup_end
mov ebx,[ebp+_p_dwRelocationVirtualAddress]
test ebx,ebx
jz _reloc_fixup_end
add ebx,eax
_reloc_fixup_block:
mov eax,[ebx+004h] //ImageBaseRelocation.SizeOfBlock
test eax,eax
jz _reloc_fixup_end
lea ecx,[eax-008h] //ImageBaseRelocation.SizeOfBlock - sizeof(TImageBaseRelocation)
shr ecx,001h //WORD((ImageBaseRelocation.SizeOfBlock - sizeof(TImageBaseRelocation)) / sizeof(Word))
lea edi,[ebx+008h] //PImageBaseRelocation + sizeof(TImageBaseRelocation)
_reloc_fixup_do_entry:
movzx eax,word ptr [edi]//Entry
push edx
mov edx,eax
shr eax,00Ch //Type = Entry >> 12
mov esi,[ebp+_p_dwImageBase]//ImageBase
and dx,00FFFh
add esi,[ebx] //ImageBase + ImageBaseRelocation.VirtualAddress
add esi,edx //ImageBase + ImageBaseRelocation.VirtualAddress+Entry & 0x0FFF
pop edx
//------------------------------
//---- IMAGE_REL_BASED_HIGH ----
_reloc_fixup_HIGH:
dec eax
jnz _reloc_fixup_LOW
mov eax,edx
shr eax,010h //HIWORD(Delta)
jmp _reloc_fixup_LOW_fixup
//------------------------------
//---- IMAGE_REL_BASED_LOW -----
_reloc_fixup_LOW:
dec eax
jnz _reloc_fixup_HIGHLOW
movzx eax,dx //LOWORD(Delta)
_reloc_fixup_LOW_fixup:
add word ptr [esi],ax
jmp _reloc_fixup_next_entry
//------------------------------
//-- IMAGE_REL_BASED_HIGHLOW ---
_reloc_fixup_HIGHLOW:
dec eax
jnz _reloc_fixup_next_entry
add [esi],edx
//------------------------------
_reloc_fixup_next_entry:
inc edi
inc edi //Entry++
loop _reloc_fixup_do_entry
_reloc_fixup_next_base:
add ebx,[ebx+004h] //ImageBaseRelocation + ImageBaseRelocation.SizeOfBlock
jmp _reloc_fixup_block
_reloc_fixup_end:
// clean relocation table
/* mov eax,[ebp+_p_dwImageBase]
add eax,[ebp+_p_dwRelocationVirtualAddress]
lea edi,[eax]
xor eax,eax
add ecx,[ebp+_p_dwRelocationSize]
rep stos byte ptr [edi]*/
ret
//--------------------------------------------------------
//下面就是手动加载原始导入表的过程了,它会模拟PE加载器的操作,遍历导入表信息,查找dll名称和api函数名称,然后利用LoadLibrary和GetProcAddress进行加载
//--------------- fix up the import table ----------------
/*
ebp+18h [ebp+_ShellAbout_NewCode]
ebp+14h [ebp+_p_szShellAbout]
ebp+10h [ebp+_p_szSHELL32_r]
ebp+0ch _p_dwImportVirtualAddress
ebp+08h _p_dwImageBase
---------------------------------
ebp-04h x
ebp-08h _p_dwThunk
ebp-0ch _p_dwHintName
ebp-10h _p_dwLibraryName
ebp-14h _p_dwAPIaddress
ebp-18h _p_dwFunctionName
*/
_it_fixup_1:
push ebp
mov ebp,esp
add esp,-18h
mov ebx,[ebp+0ch] //ebx = _p_dwImportVirtualAddress
test ebx,ebx
jz _it_fixup_1_end
mov esi,[ebp+08h] //esi = _p_dwImageBase
add ebx,esi // dwImageBase + dwImportVirtualAddress
_it_fixup_1_get_lib_address_loop:
mov eax,[ebx+0ch] // image_import_descriptor.Name eax=dll名称
test eax,eax
jz _it_fixup_1_end
mov ecx,[ebx+10h] // ecx = image_import_descriptor.FirstThunk
add ecx,esi
mov [ebp-08h],ecx // dwThunk (FirstThunk的绝对地址)
mov ecx,[ebx] // ecx = image_import_descriptor.Characteristics
test ecx,ecx
jnz _it_fixup_1_table
mov ecx,[ebx+10h]
_it_fixup_1_table:
add ecx,esi
mov [ebp-0ch],ecx // dwHintName
add eax,esi // image_import_descriptor.Name + dwImageBase = ModuleName
push eax // lpLibFileName
mov [ebp-10h],eax
call _jmp_LoadLibrary // LoadLibrary(lpLibFileName);
test eax,eax
jz _it_fixup_1_end
mov edi,eax //edi指向dll加载基址
_it_fixup_1_get_proc_address_loop:
mov ecx,[ebp-0ch] // dwHintName
mov edx,[ecx] // image_thunk_data.Ordinal
test edx,edx
jz _it_fixup_1_next_module
test edx,080000000h // .IF( import by ordinal )
jz _it_fixup_1_by_name
and edx,07FFFFFFFh // get ordinal
jmp _it_fixup_1_get_addr
_it_fixup_1_by_name:
add edx,esi // image_thunk_data.Ordinal + dwImageBase = OrdinalName
inc edx
inc edx // OrdinalName.Name
_it_fixup_1_get_addr:
push edx // lpProcName
mov [ebp-18h],edx
push edi // hModule
call _jmp_GetProcAddress // GetProcAddress(hModule, lpProcName);
mov [ebp-14h],eax //_p_dwAPIaddress
//上面的代码段就是加载api函数的过程,然后获取的绝对地址暂存在[ebp-14h]中
//================================================================
//mov [ecx],eax//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
push edi
push esi
push ebx
mov ebx,[ebp-10h]
push ebx
push ebx
call _char_upper
//上面是把当前正在加载的dll名称转化为全大写,然后和下面的SHELL32.DLL进行字符串匹配,如果匹配的话,就再和shellabout函数进行字符串匹配,全部匹配的话就进行导入表的地址替换操作
mov esi,[ebp-10h]
mov edi,[ebp+010h] // [ebp+_p_szShell32]
_it_fixup_1_check_dll_redirected:
push edi
call __strlen
add esp, 4
mov ebx,eax
mov ecx,eax
push edi
push esi
repe cmps //byte ptr [edi], byte ptr [esi]
jz _it_fixup_1_check_func_name
jmp _it_fixup_1_no_check_func_name
_it_fixup_1_check_func_name:
mov edi,[ebp+014h] // [ebp+_p_szShellAbout]
push edi
call __strlen
add esp, 4
mov ecx,eax
mov esi,[ebp-18h]
mov edi,[ebp+014h] // [ebp+_p_szShellAbout]
repe cmps //byte ptr [edi], byte ptr [esi]
jz _it_fixup_1_do_normal_it_0
//如果不匹配的话就执行下面的代码,直接将当前加载的api函数绝对地址写入原始导入表的FirstThunk域(和系统PE加载的做法一致)
_it_fixup_1_no_check_func_name:
pop esi
pop edi
add edi,ebx
cmp byte ptr [edi],0
jnz _it_fixup_1_check_dll_redirected
mov ecx,[ebp-08h]
mov eax,[ebp-014h]
mov [ecx],eax
jmp _it_fixup_1_do_normal_it_1
//如果匹配的话就执行下面这段,就是将我们自己的代码段替换实际ShellAbout函数的绝对地址,实现导入表的注入。
_it_fixup_1_do_normal_it_0:
pop esi
pop edi
mov ecx,[ebp-08h]
mov edi,[ebp+18h]
mov [ecx],edi
_it_fixup_1_do_normal_it_1:
pop ebx
pop esi
pop edi
//================================================================
add dword ptr [ebp-08h],004h // dwThunk => next dwThunk
add dword ptr [ebp-0ch],004h// dwHintName => next dwHintName
jmp _it_fixup_1_get_proc_address_loop
_it_fixup_1_next_module:
add ebx,014h // sizeof(IMAGE_IMPORT_DESCRIPTOR)
jmp _it_fixup_1_get_lib_address_loop
_it_fixup_1_end:
mov esp,ebp
pop ebp
ret 14h
//--------------------------------------------------------
//========================================================
//-- char_upper(LPCTSTR lpDestination, LPCTSTR lpSource )
_char_upper:
push ebp
mov ebp,esp
push ecx
push eax
push esi
push edi
mov edi,dword ptr [ebp+08h]// -> Destination
mov esi,dword ptr [ebp+0Ch]// -> Source
mov ecx,255// -> Length
xor eax,eax
__makeupperloop:
lods byte ptr [esi]//ESI
cmp al,00h
jz endofbuffer
cmp al,60h
jc notinlowerfield
cmp al,7bh
jnc notinlowerfield
sub al,20h
notinlowerfield:
stos byte ptr [edi]//EDI
loop __makeupperloop
endofbuffer:
pop edi
pop esi
pop eax
pop ecx
pop ebp
retn 08h
//--------------------------------------------------------
//========================================================
//------------- load necessary api functions -------------
_api_load:
lea edi,[ebp+_p_szKernel32]
lea ebx,[ebp+_p_GetModuleHandle]
lea ecx,[ebp+_jmp_GetModuleHandle]
add ecx,02h
_api_get_lib_address_loop:
push ecx
//-------------------
push edi
mov eax,offset _p_LoadLibrary
call [ebp+eax]// LoadLibrary(lpLibFileName);
//-------------------
pop ecx
mov esi,eax // esi -> hModule
push edi
call __strlen
add esp,04h
add edi,eax
_api_get_proc_address_loop:
push ecx
//-------------------
push edi
push esi
mov eax,offset _p_GetProcAddress
call [ebp+eax]// GetModuleHandle=GetProcAddress(hModule, lpProcName);
//--------------------
pop ecx
mov [ebx],eax
mov [ecx],ebx
add ebx,04h
add ecx,06h
push edi
call __strlen
add esp,04h
add edi,eax
mov al,byte ptr [edi]
test al,al
jnz _api_get_proc_address_loop
inc edi
mov al,byte ptr [edi]
test al,al
jnz _api_get_lib_address_loop
ret
//--------------------------------------------------------
//========================================================
//这里就是我们自己的函数了,用来替换ShellAbout函数的,功能就是弹出一个对话框
_ShellAbout_NewCode:
_local_0:
pushad // save the registers context in stack
call _local_1
_local_1:
pop ebp
sub ebp,offset _local_1 // get base ebp
push MB_OK | MB_ICONINFORMATION
lea eax,[ebp+_p_szCaption]
push eax
lea eax,[ebp+_p_szText]
push eax
push NULL
call _jmp_MessageBox
// MessageBox(NULL, szText, szCaption, MB_OK | MB_ICONINFORMATION) ;
//...
//...
//...
// Place your code here ...
//...
//...
//...
popad // restore the first registers context from stack
ret 10h
//----------------------------------------------------------
//--------------------------------------------------------
//========================================================
// -------- exception handler expression filter ----------
//最后跳转到目标PE文件的原始入口点,继续执行
_except_handler1_OEP_Jump:
push ebp
mov ebp,esp
mov eax,[ebp+010h] // PCONTEXT: pContext <- eax
//---------------
push edi
// restore original SEH
mov edi,[eax+0C4h] // pContext.Esp
push dword ptr ds:[edi]
pop dword ptr fs:[0]
add dword ptr [eax+0C4h],8 // pContext.Esp
// set the Eip to the OEP
mov edi,[eax+0A4h] // edi <- pContext.Ebx
mov [eax+0B8h],edi // pContext.Eip <- edi
//
pop edi
//---------------
mov eax, EXCEPTION_CONTINUE_SEARCH
leave
ret
//--------------------------------------------------------
//========================================================
//下面是一些“启动汇编代码”段需要用到的常量数据等内容
dword_type(DYN_LOADER_START_DATA1)
//----------------------------------
_p_dwFileType: dword_type(0xCCCCCCCC)
_p_dwImageBase: dword_type(0xCCCCCCCC)
_p_dwOrgEntryPoint: dword_type(0xCCCCCCCC)
_p_dwImportVirtualAddress: dword_type(0xCCCCCCCC)
_p_dwRelocationVirtualAddress: dword_type(0xCCCCCCCC)
_p_dwRelocationSize: dword_type(0xCCCCCCCC)
//----------------------------------
_tls_dwStartAddressOfRawData: dword_type(0xCCCCCCCC)
_tls_dwEndAddressOfRawData: dword_type(0xCCCCCCCC)
_tls_dwAddressOfIndex: dword_type(0xCCCCCCCC)
_tls_dwAddressOfCallBacks: dword_type(0xCCCCCCCC)
_tls_dwSizeOfZeroFill: dword_type(0xCCCCCCCC)
_tls_dwCharacteristics: dword_type(0xCCCCCCCC)
//----------------------------------
_p_szKernel32: //db "Kernel32.dll",0,13
db db db db db db db db db db db db db
_p_szGetModuleHandle: //db "GetModuleHandleA",0,17
db db db db db db db db db db db db db db db db db
_p_szVirtualProtect: //db "VirtualProtect",0,15
db db db db db db db db db db db db db db db
_p_szGetModuleFileName: //db "GetModuleFileNameA",0,19
db db db db db db db db db db db db db db db db db db db
_p_szCreateFile: //db "CreateFileA",0,12
db db db db db db db db db db db db
_p_szGlobalAlloc: //db "GlobalAlloc",0,12
db db db db db db db db db db db db
_p_szVirtualAlloc: //db "VirtualAlloc",0,13
db db db db db db db db db db db db db
_p_szLoadLibrary: //db "LoadLibraryA",0,13
db db db db db db db db db db db db db
_p_szGetProcAddress: //db "GetProcAddress",0,15
db db db db db db db db db db db db db db db
byte_type(0)
_p_szUser32: //db "User32.dll",0,11
db db db db db db db db db db db
_p_szMessageBox: //db "MessageBoxA",0,12
db db db db db db db db db db db db
byte_type(0)
byte_type(0)
//----------------------------------
_p_LoadLibrary: dword_type(0xCCCCCCCC)
_p_GetProcAddress: dword_type(0xCCCCCCCC)
_p_GetModuleHandle:
dword_type(0xCCCCCCCC)
dword_type(0xCCCCCCCC)
dword_type(0xCCCCCCCC)
dword_type(0xCCCCCCCC)
dword_type(0xCCCCCCCC)
dword_type(0xCCCCCCCC)
dword_type(0xCCCCCCCC)
dword_type(0xCCCCCCCC)
dword_type(0xCCCCCCCC)
_jmp_GetModuleHandle: __jmp_api dword_type(0xCCCCCCCC)
_jmp_VirtualProtect: __jmp_api dword_type(0xCCCCCCCC)
_jmp_GetModuleFileName: __jmp_api dword_type(0xCCCCCCCC)
_jmp_CreateFile: __jmp_api dword_type(0xCCCCCCCC)
_jmp_GlobalAlloc: __jmp_api dword_type(0xCCCCCCCC)
_jmp_VirtualAlloc: __jmp_api dword_type(0xCCCCCCCC)
_jmp_LoadLibrary: __jmp_api dword_type(0xCCCCCCCC)
_jmp_GetProcAddress: __jmp_api dword_type(0xCCCCCCCC)
_jmp_MessageBox: __jmp_api dword_type(0xCCCCCCCC)
_p_szSHELL32_r:
//db "SHELL32.DLL",0,12
bb('S') bb('H') bb('E') bb('L') bb('L') bb('3') bb('2') bb('.') bb('D') bb('L') bb('L') bb(0)
byte_type(0)
_p_szShellAbout:
//db "ShellAboutW",0,12
bb('S') bb('h') bb('e') bb('l') bb('l') bb('A') bb('b') bb('o') bb('u') bb('t') bb('W') bb(0)
_p_szCaption:
bb('A') bb('s') bb('h') bb('k') bb('b') bb('i') bb('z') bb(0)
_p_szText:
bb('T') bb('h') bb('i') bb('s') bb(' ') bb('E') bb('x') bb('a') bb('m') bb('p') bb('l')
bb('e') bb(' ') bb('s') bb('h') bb('o') bb('w') bb('s') bb(' ') bb('h') bb('o') bb('w')
bb(' ') bb('A') bb('P') bb('I') bb(' ') bb('r') bb('e') bb('d') bb('i') bb('r') bb('e')
bb('c') bb('t') bb('i') bb('o') bb('n') bb(' ') bb('t') bb('e') bb('c') bb('h') bb('n')
bb('i') bb('q') bb('u') bb('e') bb(' ') bb('w') bb('o') bb('r') bb('k') bb('s') bb('.')
bb(0)
_p_dwThunk: dword_type(0xCCCCCCCC)
_p_dwHintName: dword_type(0xCCCCCCCC)
_p_ptempbuffer: dword_type(0xCCCCCCCC)
//----------------------------------
dword_type(DYN_LOADER_END_MAGIC)
//----------------------------------
}
}
[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法
赞赏
他的文章
- [求助]科摩多的病毒分析师职位怎么样? 11916
- [求助]病毒分析师和安全开发工程师哪个靠谱? 12339
- [求助]信息安全专业毕业生找什么单位比较靠谱? 1623
- [求助]有人能在装有比特梵德的机子上写一个后台程序吗 1373
- [原创]自主设计贪吃蛇游戏源码 2735
谁下载
zxc
blue_devil_bomb
sbd
whip
twofishboy
qibinhao
小龙人
qulb
chinester
野狼
jasonnbfan
xfeii
shaozhen
heweidj
三秋叶
nekaxi
惜u雪
Cyane
longee
Callppsb
stonevx
FJX
kangcin
残梦飘雪
cscript
天行客
bestshow
cailiaock
刘德威
dumppe
月下孤儿
lovesuae
nomaster
叁毛
KingerWei
木叶ss
tokiii
chenjc
sahikaru
wxfengyun
ludecai
mengwuji
sdlingfeng
cosmoer
BoyXiao
loongzyd
fbgch
zadley
ybhdgggset
shuax
心情yl
kidult
SinCoder
chencibal
lucieniorl
zhxfl
lizonghan
为学而学
giantson
gloomyle
windowsa
丫丫journey
xuedongzhi
jackandsun
forlovefor
信潮
工藤零七
Charlesccz
revfish
cnnmop
fortitan
Vanilia
imbox
谁下载
zxc
blue_devil_bomb
sbd
whip
twofishboy
qibinhao
小龙人
qulb
chinester
野狼
jasonnbfan
xfeii
shaozhen
三秋叶
nekaxi
惜u雪
longee
Callppsb
stonevx
FJX
kangcin
残梦飘雪
cscript
天行客
bestshow
cailiaock
刘德威
dumppe
月下孤儿
lovesuae
nomaster
叁毛
KingerWei
木叶ss
tokiii
chenjc
wxfengyun
ludecai
mengwuji
sdlingfeng
cosmoer
BoyXiao
loongzyd
fbgch
zadley
ybhdgggset
shuax
心情yl
kidult
SinCoder
chencibal
lucieniorl
zhxfl
lizonghan
为学而学
giantson
gloomyle
windowsa
丫丫journey
xuedongzhi
jackandsun
forlovefor
信潮
工藤零七
Charlesccz
revfish
cnnmop
fortitan
Vanilia
imbox
谁下载
zxc
blue_devil_bomb
sbd
whip
twofishboy
qibinhao
小龙人
qulb
chinester
野狼
jasonnbfan
xfeii
shaozhen
三秋叶
nekaxi
惜u雪
longee
Callppsb
stonevx
FJX
kangcin
cscript
天行客
bestshow
cailiaock
刘德威
dumppe
月下孤儿
lovesuae
nomaster
叁毛
KingerWei
木叶ss
tokiii
chenjc
wxfengyun
ludecai
mengwuji
sdlingfeng
cosmoer
BoyXiao
loongzyd
fbgch
zadley
ybhdgggset
shuax
心情yl
kidult
SinCoder
chencibal
lucieniorl
zhxfl
lizonghan
为学而学
giantson
gloomyle
windowsa
丫丫journey
xuedongzhi
jackandsun
forlovefor
信潮
工藤零七
Charlesccz
revfish
cnnmop
fortitan
Vanilia
imbox
谁下载
zxc
blue_devil_bomb
sbd
whip
twofishboy
qibinhao
小龙人
qulb
野狼
jasonnbfan
xfeii
shaozhen
heweidj
三秋叶
nekaxi
惜u雪
longee
Callppsb
stonevx
FJX
kangcin
cscript
天行客
bestshow
cailiaock
刘德威
dumppe
月下孤儿
lovesuae
nomaster
叁毛
KingerWei
木叶ss
tokiii
chenjc
wxfengyun
ludecai
mengwuji
sdlingfeng
cosmoer
BoyXiao
loongzyd
fbgch
zadley
ybhdgggset
shuax
心情yl
kidult
SinCoder
chencibal
lucieniorl
zhxfl
lizonghan
为学而学
giantson
gloomyle
windowsa
丫丫journey
xuedongzhi
jackandsun
forlovefor
信潮
工藤零七
Charlesccz
revfish
cnnmop
fortitan
Vanilia
imbox
谁下载
blue_devil_bomb
sbd
whip
twofishboy
qibinhao
小龙人
qulb
chinester
野狼
jasonnbfan
xfeii
shaozhen
heweidj
三秋叶
nekaxi
惜u雪
longee
Callppsb
stonevx
FJX
kangcin
cscript
天行客
bestshow
cailiaock
刘德威
dumppe
月下孤儿
lovesuae
nomaster
叁毛
KingerWei
木叶ss
tokiii
chenjc
wxfengyun
ludecai
mengwuji
sdlingfeng
cosmoer
BoyXiao
loongzyd
fbgch
zadley
ybhdgggset
shuax
心情yl
kidult
SinCoder
chencibal
lucieniorl
zhxfl
lizonghan
为学而学
giantson
gloomyle
windowsa
丫丫journey
xuedongzhi
jackandsun
forlovefor
信潮
工藤零七
Charlesccz
revfish
cnnmop
灰色专属
fortitan
Vanilia
imbox
feiniaojp
看原图
赞赏
雪币:
留言: