导入表内注入代码(一)

 

导入表内注入代码(一)

作者: Ashkbiz Danehkar

翻译:小刀人

原文出处

下载源代码(包括:itview.zip (87.1 KB) pemaker6.zip (96.6 KB) pemaker7.zip (193 KB)zimport.zip (130 KB)。译注:本文代码可在VS2003及WINDOWSXP+sp2下正常运行,Windows2000下ITview功能有异常。)


     本文介绍注入代码到PE(Portable Executable可移植的执行体)文件格式的Import Table(导入表,也有译为“引入表”)技术,其也被称为API重定向技术(API redirection technique)。

让我们想像一下:如果我们可以通过操作导入表thunks将导入函数的入口点(thoroughfare)重定向到我们的指定的例程,用我们的例程过滤导入(消息)就成为可能。此外,我们可以通过这个功能实现安排给我们适当的例程,专业的Portable Executable (PE) Protectors正是这么来做的,另外一些种类的rootkits使用这个方法通过一个特洛伊木马嵌入其恶意代码到受害者。在反向工程世界里,我们称之为:API重定向技术,然而我不准备通过源代码描述这个领域的所有观点,本文只是通过一个简单代码介绍一下这个技术的概况。我将描述这个源代码中没有的其他一些问题;我不能公开这些代码,原因是其关系到一些商业项目或可能会被怀有恶意者利用,然而我想本文可以被用来作为一个关于该主题的入门。

1.进入导入表

PE文件格式包括:MS-DOS header、NT headers、Sections headers和Section images(译注:正如很多技术读物上一样,其实header可以译为“头”,image可译为“映像”,但本文正文在不影响理解的前提下尽量保留原文术语,以免误解。)正如你在图 1中所看到的。MS-DOS header是自DOS时代到Windows时代在所有微软可执行文件格式(executable file format)公有的。NT headers的思想来源于UNIX系统的Executable and Linkable Format (ELF),当然Portable Executable (PE)格式是Linux Executable and Linkable Format (ELF)的姐妹。PE 格式包括"PE" Signature、Common Object File Format (COFF) header、Portable Executable Optimal header和Section headers。

1 - Portable Executable 文件格式结构

NT headers的定义可以在Virtual C++ included 目录下 <winnt.h>头文件中找到。该信息可以非常容易地通过使用DbgHelp.dll的ImageNtHeader()函数获得。你也可以使用DOS header来获取NT headers,因为DOS header的末尾位置:e_lfanew,代表NT headers的偏移。(译注:将这个偏移加到内存映射文件的基址上就得到了PE header 的地址:pNTHeader=dosHeader+ dosHeader->e_lfanew;)

 

typedef struct _IMAGE_NT_HEADERS {

 

DWORD Signature;

IMAGE_FILE_HEADER FileHeader;

IMAGE_OPTIONAL_HEADER OptionalHeader;

} IMAGE_NT_HEADERS, *PIMAGE_NT_HEADERS;

在Portable Executable Optional header,有一些数据目录(data directories)描述了当前进程在虚拟内存中主信息表(the principal information tables)的相对位置和大小。这些表可以有关于资源的信息、import(导入)、export(导出)、relocation(重定位)、Debug(调试)、thread local storage(线程本地存储)和COM运行时。没有导入表想要找到一个PE可执行文件是不可能的;该表包含DLL的名称和Functions(函数)名称,这些是当程序意图通过它们的虚地址来请求(调用)它们时所必需的。在Console executable files(控制台可执行文件)中没有发现资源表(resource table);然而它是拥有Graphic User Interface (GUI)的Windows可执行文件的至关重要的部分。导出表(export table)在一个动态链接库想要导出它的函数到外界时是必需的,并且它也在OLE Active-X容器中。Dot NET虚拟机在没有COM+ runtime header下时不能被执行。正如你看到的,在PE格式中每个表都有特定的委派任务,图 2

2 - Data Directories(数据目录)

Data
Directories

0 Export Table

1Import Table

2 Resource Table

3 Exception Table

4 Certificate File

5 Relocation Table

6 Debug Data

7 Architecture Data

8 Global Ptr

9 Thread Local Storage Table

10 Load Config Table

11 Bound Import Table

12 Import Address Table

13 Delay Import Descriptor

14 COM+ Runtime Header

15 Reserved

 

// <winnt.h>

 

#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES16

// Optional header format.

typedef struct _IMAGE_OPTIONAL_HEADER {

...

IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];

} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

// Directory Entries

#define IMAGE_DIRECTORY_ENTRY_EXPORT0 // Export Directory

#define IMAGE_DIRECTORY_ENTRY_IMPORT1 // Import Directory

#define IMAGE_DIRECTORY_ENTRY_RESOURCE2 // Resource Directory

#define IMAGE_DIRECTORY_ENTRY_BASERELOC5 // Base Relocation Table

#define IMAGE_DIRECTORY_ENTRY_DEBUG6 // Debug Directory

#define IMAGE_DIRECTORY_ENTRY_TLS9 // TLS Directory

我们只用三两行代码就可以获得导入表的位置和大小。通过知道导入表的位置,我们转入下一步获得DLL名称和Function名称,它将在下一节里讨论。

 

PIMAGE_NT_HEADERS pimage_nt_headers = ImageNtHeader(pImageBase);

 

DWORD it_voffset = pimage_nt_headers->OptionalHeader.

DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;

PIMAGE_DOS_HEADER pimage_dos_header = PIMAGE_DOS_HEADER(pImageBase);

PIMAGE_NT_HEADERS pimage_nt_headers = (PIMAGE_NT_HEADERS)

(pImageBase + pimage_dos_header->e_lfanew);

DWORD it_voffset = pimage_nt_headers->OptionalHeader.

DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;

2. 导入描述符(Import Descriptor)一瞥

导入表的导入目录入口(import directory entry)带我们来到文件image内部的导入表位置。

对于每个被导入的DLL,导入描述符是个容器,它包含了first thunk的地址和original first thunk的地址,DLL名称的指针。First Thunk引用first thunk的位置,thunks在运行该程序时将会被Windows的PE loader(装载器)初始化,图 5。Original First Thunk指向thunks第一个存储处,该存储处提供Hint 数据的地址和每个函数的Function Name数据,图 4。在此情况下,First Original Thunk没有出现;First Thunks引用Hint 数据和Function Name 数据被定位的位置,图 3。用IMAGE_IMPORT_DESCRIPTOR来表示导入描述符结构如下定义:

 

typedef struct _IMAGE_IMPORT_DESCRIPTOR {

 

DWORDOriginalFirstThunk;

DWORDTimeDateStamp;

DWORDForwarderChain;

DWORDName;

DWORDFirstThunk;

} IMAGE_IMPORT_DESCRIPTOR, *PIMAGE_IMPORT_DESCRIPTOR;

成员:

OriginalFirstThunk
它指向first thunk,IMAGE_THUNK_DATA,该thunk拥有Hint和Function name的地址。

TimeDateStamp
如果那里有绑定的话它包含时间/数据戳(time/data stamp)。如果它是0,就没有绑定在被导入的DLL中发生。在最近,它被设置为0xFFFFFFFF以表示绑定发生。

ForwarderChain
在老版的绑定中,它引用API的第一个forwarder chain(传递器链表)。它可被设置为0xFFFFFFFF以代表没有forwarder。

Name
它表示DLL 名称的相对虚地址(译注:相对一个用null作为结束符的ASCII字符串的一个RVA,该字符串是该导入DLL文件的名称,如:KERNEL32.DLL)。

FirstThunk
它包含由IMAGE_THUNK_DATA定义的 first thunk数组的虚地址,通过loader用函数虚地址初始化thunk。在Orignal First Thunk缺席下,它指向first thunk:Hints和The Function names的thunks。

 

typedef struct _IMAGE_IMPORT_BY_NAME {

 

WORDHint;

BYTEName[1];

} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;

typedef struct _IMAGE_THUNK_DATA {

union {

PDWORDFunction;

PIMAGE_IMPORT_BY_NAMEAddressOfData;

} u1;

} IMAGE_THUNK_DATA, *PIMAGE_THUNK_DATA;

3 - Import Table View

4 - Import Table View (有 Orignal First Thunk

这里有两个导入表(图3和图4)表示有和没有original first thunk下导入表的不同。

5-PE loader重写后的导入表

我们可以用Dependency Walker,图 6,来察看导入表的所有信息。顺便说一下,我已提供另一个工具,Import Table viewer,图 7,它简单且与前面工具的操作相似。通过这类工具我肯定它的资源将帮助你更好地理解它做的主要(功能)表现。

Figure 6 - Dependency Walker, Steve P. Miller

这里我们看到一个简单的可以用一个控制台模式程序来显示导入DLLs和导入Functions的资源。然而,我想我的Import Table viewer,图 7,因为它的图形用户界面更适于来理解主题。

 

PCHARpThunk;

 

PCHARpHintName;

DWORDdwAPIaddress;

PCHARpDllName;

PCHARpAPIName;

//----------------------------------------

DWORD dwImportDirectory= RVA2Offset(pImageBase, pimage_nt_headers->OptionalHeader.

DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);

//----------------------------------------

PIMAGE_IMPORT_DESCRIPTOR pimage_import_descriptor= (PIMAGE_IMPORT_DESCRIPTOR)

(pImageBase+dwImportDirectory);

//----------------------------------------

while(pimage_import_descriptor->Name!=0)

{

pThunk= pImageBase+pimage_import_descriptor->FirstThunk;

pHintName= pImageBase;

if(pimage_import_descriptor->OriginalFirstThunk!=0)

{

pHintName+= RVA2Offset(pImageBase, pimage_import_descriptor->OriginalFirstThunk);

}

else

{

pHintName+= RVA2Offset(pImageBase, pimage_import_descriptor->FirstThunk);

}

pDllName= pImageBase + RVA2Offset(pImageBase, pimage_import_descriptor->Name);

printf(" DLL Name: %s First Thunk: 0x%x", pDllName,

pimage_import_descriptor->FirstThunk);

PIMAGE_THUNK_DATA pimage_thunk_data= (PIMAGE_THUNK_DATA) pHintName;

while(pimage_thunk_data->u1.AddressOfData!=0)

{

dwAPIaddress= pimage_thunk_data->u1.AddressOfData;

if((dwAPIaddress&0x80000000)==0x80000000)

{

dwAPIaddress&= 0x7FFFFFFF;

printf("Proccess: 0x%x", dwAPIaddress);

}

else

{

pAPIName= pImageBase+RVA2Offset(pImageBase, dwAPIaddress)+2;

printf("Proccess: %s", pAPIName);

}

pThunk+= 4;

pHintName+= 4;

pimage_thunk_data++;

}

pimage_import_descriptor++;

}

7 - Import Table viewer

3. API重定向技术

 

我们了解了所有关于导入表的基本知识,是时候来建立我们重定向方法的时候了。算法是很简单的,在当前进程的虚拟内存中创建一个额外虚拟空间,并生成指令用JMP到原始函数位置来重定向。我们可以用绝对jump(跳)或相对jump来实现之。你应该注意绝对jump这种情况,你不能像图8那样简单地实现,你应该首先移动虚地址到EAX然后用JMP EAX做一个jump。在pemaker6.zip中,我已经用相对jump做了一个重定向。

8 -一个用绝对jump指令的简单API重定向概览

这个PE maker是在我先前的文章 [1]的成果上创建的,如果你有兴趣知道它是如何工作的话,我建议你去看这篇文章。在这个版本中,我已经改进了导入表修改例程,正如你下面看到的这些代码,我写了几行的代码来生成相对JMP指令到函数的实际位置。你一定要知道,你不能实现所有DLL模块的API重定向。比如在CALC.EXE中,在运行时初始化时MSVCRT.DLL的一些thunks将会被CALC.EXE内部的一些code 节访问,因此在重定向的情况下它将不会起作用。

 

_it_fixup_1:

 

push ebp

mov ebp,esp

add esp,-14h

push PAGE_READWRITE

push MEM_COMMIT

push 01D000h

push 0

call _jmp_VirtualAlloc

//NewITaddress=VirtualAlloc(NULL, 0x01D000, MEM_COMMIT, PAGE_READWRITE);

mov [ebp-04h],eax

mov ebx,[ebp+0ch]

test ebx,ebx

jz _it_fixup_1_end

mov esi,[ebp+08h]

add ebx,esi// dwImageBase + dwImportVirtualAddress

_it_fixup_1_get_lib_address_loop:

mov eax,[ebx+0ch]// image_import_descriptor.Name

test eax,eax

jz _it_fixup_1_end

mov ecx,[ebx+10h]// image_import_descriptor.FirstThunk

add ecx,esi

mov [ebp-08h],ecx// dwThunk

mov ecx,[ebx]// 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

_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

push edi// hModule

call _jmp_GetProcAddress// GetProcAddress(hModule, lpProcName);

mov [ebp-14h],eax//_p_dwAPIaddress

//================================================================

//RedirectionEngine

push edi

push esi

push ebx

 

mov ebx,[ebp-10h]

push ebx

push ebx

call _char_upper

mov esi,[ebp-10h]

mov edi,[ebp+010h]

_it_fixup_1_check_dll_redirected:

push edi

call __strlen

addesp, 4

mov ebx,eax

mov ecx,eax

push edi

push esi

repe cmps

jz_it_fixup_1_do_normal_it_0

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

_it_fixup_1_do_normal_it_0:

pop esi

pop edi

mov edi,[ebp-04h]

mov byte ptr [edi], 0e9h// JMP Instruction

mov eax,[ebp-14h]

sub eax, edi

sub eax, 05h

mov [edi+1],eax// Relative JMP value

mov word ptr [edi+05], 0c08bh

mov ecx,[ebp-08h]

mov [ecx],edi// -> Thunk

add dword ptr [ebp-04h],07h

_it_fixup_1_do_normal_it_1:

pop ebx

pop esi

pop edi

//================================================================

add dword ptr [ebp-08h],004h// dwThunk => next dwThunk

add dwordptr [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 0ch

不要认为API重定向是用这个简单方法在专业EXE protectors(保护器)中被搞定的;他们有一个x86指令生成器引擎,它被用来生成重定向意图的代码。有时这个引擎是和metamorphism(混淆或扰乱)引擎一同使用的,这可以使得他们极为复杂难于分析。

它是如何工作的?

前面的代码是依照下面的算法工作的:

1.用VirtualAlloc()创建一块独立空间来存放生成的指令。

2.用LoadLibrary()和GerProcAddress()找到函数虚地址

3.检查DLL名称是否在有效DLL表单中。在这个例子中,我们认为KERNEL32.DLLUSER32.DLLGDI32.DLLADVAPI32.DLLSHELL32.DLL是可以重定向的有效DLL名称。

4.如果DLL名称有效,转入重定向例程,另外用original function虚地址初始化这个thunk。

5.为了重定向API,生成JMP (0xE9)指令,计算function position的相对位置来建立一个相对jump。

6.存储生成指令到独立的空间,并引用thunk到这些指令的首位置。

7.继续对其他Functions 和DLLs运行这个例程。

如果你在CALC.EXE上实现这个技术,并用OllyDbg或一个相似的用户模式调试器跟踪它,你会觉得这个代码生成了和下面视图相似的一个视图:

 

 

008E0000- E9 E6F8177CJMP SHELL32.ShellAboutW

 

008E00058BC0MOV EAX,EAX

008E0007- E9 0F764F77JMP ADVAPI32.RegOpenKeyExA

008E000C8BC0MOV EAX,EAX

008E000E- E9 70784F77JMP ADVAPI32.RegQueryValueExA

008E00138BC0MOV EAX,EAX

008E0015- E9 D66B4F77JMP ADVAPI32.RegCloseKey

008E001A8BC0MOV EAX,EAX

008E001C- E9 08B5F27BJMP kernel32.GetModuleHandleA

008E00218BC0MOV EAX,EAX

008E0023- E9 4F1DF27BJMP kernel32.LoadLibraryA

008E00288BC0MOV EAX,EAX

008E002A- E9 F9ABF27BJMP kernel32.GetProcAddress

008E002F8BC0MOV EAX,EAX

008E0031- E9 1AE4F77BJMP kernel32.LocalCompact

008E00368BC0MOV EAX,EAX

008E0038- E9 F0FEF27BJMP kernel32.GlobalAlloc

008E003D8BC0MOV EAX,EAX

008E003F- E9 EBFDF27BJMP kernel32.GlobalFree

008E00448BC0MOV EAX,EAX

008E0046- E9 7E25F37BJMP kernel32.GlobalReAlloc

008E004B8BC0MOV EAX,EAX

008E004D - E9 07A8F27BJMP kernel32.lstrcmpW

008E00528BC0MOV EAX,EAX

留给你一个家庭作业:你可以用下面这个代码实践一下以绝对jump指令改变PE Maker source。

 

 

008E0000- B8 EBF8A57CMOV EAX,7CA5F8EBh // address of SHELL32.ShellAboutW

 

008E0005FFE0JMP EAX

 

导入表内注入代码(二)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值