博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
PE文件导入表的代码注入http://blog.csdn.net/xieqidong/archive/2008/05/05/2391338.aspx
阅读量:2400 次
发布时间:2019-05-10

本文共 10059 字,大约阅读时间需要 33 分钟。

PE
文件导入表的代码注入
 
 
         试想一下,如果通过修改导入表,能把PE格式文件中的函数入口点,重定向到自己的程序中来,是不是很酷!这样,在自己在程序中,可以过滤掉对某些函数的调用,或者,设置自己的处理程序,Professional Portable Executable (PE) Protector也就是这样做的。另外,某些rootkit也使用了此方法把恶意代码嵌入到正常程序中。在逆向工程的概念里,均称为API重定向技术,让我们一起进入这个神奇的世界吧。
 
 
         1
、导入表简介
         PE文件由MS-DOS头、NT头、节头、节映像组成,如图1所示。MS-DOS头在从DOS至现今Windows的所有微软可执行文件中都存在;NT头的概念抽象自Unix系统的可执行与链接文件格式(ELF)。实际上,PE格式可以说是Linux可执行与链接格式(ELF)的兄弟,PE格式头由PE签名、通用对象文件格式(COFF)头、PE最优化头、节头组成。
 
 
图1:PE文件格式结构
 
 
         NT头的定义可在Visual C++的<winnt.h>头文件中找到,也可使用DbgHelp.dll中的ImageNtHeader()函数得到相关信息,另外,还可以使用DOS头来获取NT头,因为DOS头的最后位置e_lfanew,代表了NT头的偏移量。
 
 
typedef struct _IMAGE_NT_HEADERS {
   DWORD Signature;
   IMAGE_FILE_HEADER FileHeader;
   IMAGE_OPTIONAL_HEADER OptionalHeader;
} IMAGE_NT_HEADERS, *PIMAGE_NT_HEADERS;
 
 
         在PE最优化头中,有一些数据目录指明了当前进程虚拟内存中,主信息表的相对位置及大小,这些表可保存资源、导入、导出、重定位、调试、线程本地存储及COM运行时的有关信息。没有导入表的PE可执行文件是不可能存在的,这张表包含了DLL名及函数名,这些都是程序通过虚拟地址调用它们时所必不可少的;控制台可执行文件中没有资源表,然而,对图形用户界面的Windows可执行文件来说,资源表却是至关重要的部分;当某个动态链接库导出了它的函数,此时就需要导出表了,在OLE Active-X容器中也同样;而.NET虚拟机缺少了COM+运行时头则不能被执行。PE格式的详细说明见表1:
 
 
数据目录
0 Export Table(导出表)
1 Import 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(COM+运行时头)
15 Reserved(保留)
    表1:数据目录
 
 
// <winnt.h>
 
#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES     16
 
//可选头格式
 
typedef struct _IMAGE_OPTIONAL_HEADER {
 
   ...
 
   IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
 
 
//目录项
#define IMAGE_DIRECTORY_ENTRY_EXPORT      0 //导出目录
#define IMAGE_DIRECTORY_ENTRY_IMPORT      1 //导入目录
#define IMAGE_DIRECTORY_ENTRY_RESOURCE    2 //资源目录
#define IMAGE_DIRECTORY_ENTRY_BASERELOC 5 //基重定位表
#define IMAGE_DIRECTORY_ENTRY_DEBUG       6 //调试目录
#define IMAGE_DIRECTORY_ENTRY_TLS         9 //TLS目录
 
 
         可通过简单的几行代码获得导入表的位置及大小,知道了导入表的位置后,就可知道DLL名及函数名了,这将在后面进行讨论。
 
 
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
、导入描述符概览
         通过导入表的导入目录项,可获知文件映像内部导入表的位置,且对每个导入的DLL、导入描述符,都有一个相应的容器,其包含了首个thunk(转换程序)地址、原始首个thunk的地址,还有指向DLL名的指针。FirstThunk指向首个thunk的位置,这些thunk在程序运行时,由Windows的PE加载器初始化,如图5所示。OriginalFirstThunk指向这些thunk的第一个存储位置,对每个函数而言,这也是提供Hint(提示)数据地址及函数名数据之处,见图4。在本例中,OriginalFirstThunk不存在,而FirstThunk则指向了提示数据及函数名数据位置之处,见图3。
         IMAGE_IMPORT_DESCRIPTOR结构代表了导入描述符,以下是其定义:
 
 
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
  DWORD    OriginalFirstThunk;
   DWORD   TimeDateStamp;
   DWORD   ForwarderChain;
   DWORD   Name;
   DWORD   FirstThunk;
} IMAGE_IMPORT_DESCRIPTOR, *PIMAGE_IMPORT_DESCRIPTOR;
 
 
         OriginalFirstThunk指向第一个thunk(IMAGE_THUNK_DATA),thunk中保存了提示数据地址及函数名。
         TimeDateStamp包含了绑定的时间日期戳,如果它为0,表示在导入的DLL没有任何绑定。在将来,会设为0xFFFFFFFF以表明有绑定。
         ForwarderChain指向API的第一个转发链,设为0xFFFFFFFF表示没有转发。
         Name指明了DLL名的相对虚拟地址。
         FirstThunk包含了由IMAGE_THUNK_DATA定义的首个thunk数组的虚拟地址,而thunk由加载器用函数虚拟地址初始化。如果OrignalFirstThunk不存在,它指向了第一个thunk、提示(Hint)thunk及函数名。
 
 
typedef struct _IMAGE_IMPORT_BY_NAME {
   WORD    Hint;
   BYTE    Name[1];
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
 
typedef struct _IMAGE_THUNK_DATA {
   union {
      PDWORD    Function;
      PIMAGE_IMPORT_BY_NAME AddressOfData;
   } u1;
} IMAGE_THUNK_DATA, *PIMAGE_THUNK_DATA;
 
 
图2:导入表一览
 
 
图3:带有Orignal First Thunk的导入表视图
 
 
         这两张导入表(图2、3)清楚地说明了带有及不带Orignal First Thunk时的区别。
 
 
图4:被PE加载器覆写后的导入表
 
 
         可用Dependency Walker,见图5,来查看导入表的所有信息,另外,还有一个小工具Import Table viewer,见图6,也可用来查看此类信息。
 
 
图5:Dependency Walker——Visual Studio自带的工具
 
 
图6:Import Table viewer
 
 
         另外,也可利用下面这段代码,在自己的程序中显示导入DLL及导入函数(只适用于控制台模式的程序)。
 
 
PCHAR        pThunk;
PCHAR        pHintName;
DWORD        dwAPIaddress;
PCHAR        pDllName;
PCHAR        pAPIName;
//----------------------------------------
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++;
}
 
 
         3
、API
重定向技术
         在了解了导入表的所有基础知识后,现在就要来看看重定向方法了,其算法非常简单,在当前进程的虚拟内存内部创建一个额外的虚拟空间,并生成相应指令通过JMP重定向到原始函数位置。在这里,可使用绝对跳转或相对跳转,在使用绝对跳转时要非常小心,不能像图7中那么简单地进行,应首先把虚拟地址存到EAX中,接着由JMP EAX跳转。
 
 
图7:绝对跳转指令的API重定向
 
 
         当然了,也可以使用相对跳转,下面的代码就是这样做的,但要清楚,不能对所有DLL模块都进行API重定向,比如,拿“计算器”CALC.EXE来说,MSVCRT.DLL的某些thunk在运行时初始化期间,是从CALC.EXE代码节内部访问的,因此,重定向的话则不能正常工作。
 
 
_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          //是否按顺序导入
         jz _it_fixup_1_by_name
         and edx,07FFFFFFFh          //取得顺序
         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
         //=========================================================
         //            重定向引擎
         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
            add esp, 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指令
            mov eax,[ebp-14h]
            sub eax, edi
            sub eax, 05h
            mov [edi+1],eax             //相对JMP值
            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 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 0ch
 
 
         说点题外话,千万不要认为这样就可以绕过Professional EXE Protector了,这个软件中有一套自己的x86指令生成引擎用于创建重定向代码,有时,这个引擎还带有一个扰乱变形引擎,以使它复杂到难以跟踪分析。
 
 
         在工作原理上,上述代码依照了下列算法:
 
1、 创建一个单独的空间以存储由VirtualAlloc()生成的指令。
2、 通过LoadLibrary()和GerProcAddress()找到函数的虚拟地址。
3、 检查DLL名是否匹配有效DLL列表。在本例中,识别出KERNEL32.DLL、USER32.DLL、GDI32.DLL、ADVAPI32.DLL、SHELL32.DLL为可重定向的有效DLL名。
4、 如果DLL名有效,转到重定向部分;否则,用原始函数虚拟地址初始化thunk。
5、 为重定向API,生成JMP(0xE9)指令并计算函数的相对地址以确定相对跳转。
6、 把生成的指令存储在单独的空间中,并把thunk引用为这些指令的首地址。
7、 对其他函数及DLL重复以上步骤。
 
 
如果对CALC.EXE执行了以上步骤,并以OllyDbg进行跟踪,将会看到类似以下的代码:
 
 
008E0000 - E9 E6F8177C     JMP SHELL32.ShellAboutW
008E0005     8BC0           MOV EAX,EAX
008E0007 - E9 0F764F77     JMP ADVAPI32.RegOpenKeyExA
008E000C     8BC0           MOV EAX,EAX
008E000E - E9 70784F77     JMP ADVAPI32.RegQueryValueExA
008E0013     8BC0           MOV EAX,EAX
008E0015 - E9 D66B4F77     JMP ADVAPI32.RegCloseKey
008E001A     8BC0           MOV EAX,EAX
008E001C - E9 08B5F27B     JMP kernel32.GetModuleHandleA
008E0021     8BC0           MOV EAX,EAX
008E0023 - E9 4F1DF27B     JMP kernel32.LoadLibraryA
008E0028     8BC0           MOV EAX,EAX
008E002A - E9 F9ABF27B     JMP kernel32.GetProcAddress
008E002F     8BC0           MOV EAX,EAX
008E0031 - E9 1AE4F77B     JMP kernel32.LocalCompact
008E0036     8BC0           MOV EAX,EAX
008E0038 - E9 F0FEF27B     JMP kernel32.GlobalAlloc
008E003D     8BC0           MOV EAX,EAX
008E003F - E9 EBFDF27B     JMP kernel32.GlobalFree
008E0044     8BC0           MOV EAX,EAX
008E0046 - E9 7E25F37B     JMP kernel32.GlobalReAlloc
008E004B     8BC0           MOV EAX,EAX
008E004D - E9 07A8F27B     JMP kernel32.lstrcmpW
008E0052     8BC0           MOV EAX,EAX
 
 
         而绝对跳转时的代码如下:
 
 
008E0000 - B8 EBF8A57C     MOV EAX,7CA5F8EBh
 //SHELL32.ShellAboutW的地址
008E0005     FFE0           JMP EAX
 
 
         下面,就要用重定向技术来改变某个API的功能了,在本例中,将把CALC.EXE的ShellAbout()对话框重定向到“Hello World!”消息框,且只需对前述代码作稍许改动:
 
 
...
   //==============================================================
   push edi
   push esi
   push ebx
 
   mov ebx,[ebp-10h]
   push ebx
你可能感兴趣的文章
14大管理方法工具(zt)
查看>>
职业生涯规划与管理实操(zt)
查看>>
国贸、货代常用词汇(zt)
查看>>
人力资源术语英汉对照(zt)
查看>>
传说中的100句英语可以帮你背7000单词(zt)
查看>>
网管IT服务管理五个心得 (zt)
查看>>
沟通管理 让你的团队动起来(zt)
查看>>
循序渐进学SAP系列(一):--SAP该如何入门
查看>>
PMP考试经验谈(转载)
查看>>
緣分是找到包容你的人(转载)
查看>>
关注人力资源管理的十大变化(转)
查看>>
知识管理与业务流程重组(转载)
查看>>
世界经典广告词欣赏
查看>>
35岁前应该做好的十件事
查看>>
创业从小做起的十条忠告(转)
查看>>
妙语连珠94句
查看>>
机械制造企业内外供应链的整合(转载)
查看>>
笔记:ISO/IEC 2000与ITIL的区别与联系
查看>>
7大最重要的管理方法(zt)
查看>>
物流系统管理课程(四)
查看>>