安全矩阵

 找回密码
 立即注册
搜索
查看: 2268|回复: 0

Ring3注入学习:导入表注入

[复制链接]

221

主题

233

帖子

792

积分

高级会员

Rank: 4

积分
792
发表于 2021-8-29 16:43:33 | 显示全部楼层 |阅读模式
Ring3注入学习:导入表注入 (qq.com)
Ring3注入学习:导入表注入原创 Ddjsq_2333 [url=]看雪学院[/url] 昨天

本文为看雪论坛优秀文章
看雪论坛作者ID:Ddjsq_2333


1

原理
导入表的结构
在编程中常常用到“导入函数”(Import functions),导入函数就是被程序调用但其执行代码又不在程序中的函数,这些函数的代码位于一个或者多个DLL中,在调用者程序中只保留一些函数信息,包括函数名及其驻留的DLL名等。

于磁盘上的PE 文件来说,它无法得知这些输入函数在内存中的地址,只有当PE 文件被装入内存后,Windows 加载器才将相关DLL 装入,并将调用输入函数的指令和函数实际所处的地址联系起来。这就是“动态链接”的概念。动态链接是通过PE 文件中定义的“导入表”来完成的,导入表中保存的正是函数名和其驻留的DLL 名等。

导入表由一系列IMAGE_IMPORT_DESCRIPTOR结构体组成:



结构的数量取决于程序要使用的DLL文件的数量,每一个结构对应一个DLL文件。

该结构体的定义如下:
  1. struct _IMAGE_IMPORT_DESCRIPTOR {
  2.     union {
  3.         DWORD   Characteristics;
  4.         DWORD   OriginalFirstThunk;        
  5.     } DUMMYUNIONNAME;
  6.     DWORD   TimeDateStamp;
  7.     DWORD   ForwarderChain;        
  8.     DWORD   Name;//导入模块名的RVA
  9.     DWORD   FirstThunk;            
  10. } IMAGE_IMPORT_DESCRIPTOR;
复制代码
​具体在PE文件中,就像这个样子:



在IMAGE_IMPORT_DESCRIPTOR的结构体的最后面,附带着许多小结构体,里面记录的信息是该导入表的DLL要导入使用的API。
  1. typedef struct _IMAGE_IMPORT_BY_NAME {
  2.     WORD    Hint;
  3.     BYTE    Name[1];
  4. } IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
复制代码




以上便是导入表的基础结构。

导入表的注入


由《加密与解密》中得知,Windows装载PE文件时,会检查导入表,将导入表所包含要使用的DLL加载进程序中,这也就产生了一个DLL注入的点,如果将恶意DLL写入导入表中,就可以让程序在运行时调用恶意DLL中的代码,达到DLL注入的效果。


2

实现
要实现导入表注入,需要把要注入的DLL构造的结构体塞进导入表里,但是由于导入表默认不是在PE文件的最后,所以导入表原来所在的位置不一定有足够大的空间塞入一个新的结构体。

这时我们就需要把导入表移动到新的足够大的位置,有两种办法,扩大最后一个节和直接新增一个节,比较方便的方法是选择新增一个节,在新增节后将原导入表放进去,如何在最后写入自己的导入表,再新增8字节的INT和8字节IAT,至于为什么要增加8字节的INT表和8字节的IAT表,可以这样解释:



如图,当我们新写入一个IMAGE_IMPORT_DESCRIPTOR结构,我们就需要建立INT和IAT对于该结构的映射,此时就需要扩充IAT和INT表。

具体实现步骤如下:

           
  •         找到原导入表

           
  •         在程序最后开辟一个新节(也可扩大最后一个节)

           
  •         拷贝原来的导入表到新节中

           
  •         在新节拷贝的导入表后新增一个导入表

           
  •         增加8字节的INT表和8字节的IAT表

           
  •         存储要注入的dll的名称

           
  •         增加一个_IMAGE_IMPORT_BYNAME结构,并将函数名称存进结构体第一个变量后的内存中

           
  •         将_IMAGE_IMPORT_BY_NAME结构的地址的RVA赋值给INT表和IAT表第一项

           
  •         将dll名称所在位置的首地址的RVA赋值给新增导入表的Name

           
  •         修改IMAGE_DATA_DIRECTORY结构的VirtualAddress和Size


3

代码
DLL代码
  1. // dllmain.cpp : 定义 DLL 应用程序的入口点。
  2. #include "pch.h"


  3. extern "C" __declspec(dllexport) void puts()
  4. {
  5.     MessageBoxA(0, "hi", "hello", 0);
  6.     return;
  7. }

  8. BOOL APIENTRY DllMain( HMODULE hModule,
  9.                        DWORD  ul_reason_for_call,
  10.                        LPVOID lpReserved
  11.                      )
  12. {
  13.     switch (ul_reason_for_call)
  14.     {
  15.     case DLL_PROCESS_ATTACH:
  16.         puts();
  17.         break;
  18.     case DLL_THREAD_ATTACH:
  19.     case DLL_THREAD_DETACH:
  20.     case DLL_PROCESS_DETACH:
  21.         break;
  22.     }
  23.     return TRUE;
  24. }
复制代码
注入程序代码
  1. #include<Windows.h>
  2. #include<stdio.h>
  3. #define DLLNAMELENGTH 0xE
  4. #define FUNCTIONNAMELENGTH 0xF
  5. #define FUNCTIONNAME "puts"
  6. #define DLLNAME "Dll1.dll"

  7. //获取DOS头
  8. PIMAGE_DOS_HEADER GetDosHeader(_In_ char* pBase) {
  9.     return PIMAGE_DOS_HEADER(pBase);
  10. }

  11. //获取NT头
  12. PIMAGE_NT_HEADERS GetNtHeader(_In_ char* pBase) {
  13.     return PIMAGE_NT_HEADERS(GetDosHeader(pBase)->e_lfanew + (SIZE_T)pBase);
  14. }

  15. //获取文件头
  16. PIMAGE_FILE_HEADER GetFileHeader(_In_ char* pBase) {
  17.     return &(GetNtHeader(pBase)->FileHeader);
  18. }

  19. //获取OPT头
  20. PIMAGE_OPTIONAL_HEADER32 GetOptHeader(_In_ char* pBase) {
  21.     return &(GetNtHeader(pBase)->OptionalHeader);
  22. }

  23. PIMAGE_SECTION_HEADER GetSecByName(_In_ char* pBase, _In_ const char* name) {
  24.     DWORD Secnum = GetFileHeader(pBase)->NumberOfSections;
  25.     PIMAGE_SECTION_HEADER Section = IMAGE_FIRST_SECTION(GetNtHeader(pBase));
  26.     char buf[10] = { 0 };
  27.     for (DWORD i = 0; i < Secnum; i++) {
  28.         memcpy_s(buf, 8, (char*)Section[i].Name, 8);
  29.         if (!strcmp(buf, name)) {
  30.             return Section + i;
  31.         }
  32.     }
  33.     return nullptr;
  34. }

  35. //获取最后一个区段
  36. PIMAGE_SECTION_HEADER GetLastSec(_In_ char* PeBase) {
  37.     DWORD SecNum = GetFileHeader(PeBase)->NumberOfSections;
  38.     PIMAGE_SECTION_HEADER FirstSec = IMAGE_FIRST_SECTION(GetNtHeader(PeBase));
  39.     PIMAGE_SECTION_HEADER LastSec = FirstSec + SecNum - 1;
  40.     return LastSec;
  41. }

  42. char* OpenPeFiles(_In_ const char* Path, _Out_opt_ DWORD* nFileSize)
  43. {
  44.     //读文件
  45.     HANDLE hFile = CreateFileA(Path,
  46.         GENERIC_READ,
  47.         FILE_SHARE_READ,
  48.         NULL,
  49.         OPEN_ALWAYS,
  50.         FILE_ATTRIBUTE_NORMAL,
  51.         NULL);
  52.     if (hFile == INVALID_HANDLE_VALUE)
  53.     {
  54.         //printf("打开文件失败");
  55.         return NULL;
  56.     }
  57.     DWORD PeSize = GetFileSize(hFile, NULL);
  58.     if (nFileSize)
  59.         *nFileSize = PeSize;
  60.     DWORD ReadSize = 0;
  61.     char* PeBase = new CHAR[PeSize]{ 0 };
  62.     ReadFile(hFile, PeBase, PeSize, &ReadSize, NULL);

  63.     PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)PeBase;
  64.     //检测DOS头和NT头
  65.     if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE)
  66.     {
  67.         //printf("不是PE文件\n");
  68.         //system("pause");
  69.         return NULL;
  70.     }
  71.     PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)(PeBase + pDosHeader->e_lfanew);
  72.     if (pNtHeader->Signature != IMAGE_NT_SIGNATURE)
  73.     {
  74.         //printf("不是PE文件\n");
  75.         //system("pause");
  76.         return NULL;
  77.     }

  78.     CloseHandle(hFile);
  79.     return PeBase;
  80. }

  81. //粒度对齐处理
  82. int AlignMent(_In_ int size, _In_ int alignment) {
  83.     return (size) % (alignment) == 0 ? (size) : ((size) / alignment + 1) * (alignment);
  84. }

  85. //新增节
  86. char* AddSection(_In_ char*& PeBase, _In_ DWORD& PeSize, _In_ const char* Section_name, _In_ const int Section_size)
  87. {
  88.     GetFileHeader(PeBase)->NumberOfSections++;
  89.     PIMAGE_SECTION_HEADER LastPeSection = GetLastSec(PeBase);

  90.     memcpy(LastPeSection->Name, Section_name, 8);
  91.     LastPeSection->Misc.VirtualSize = Section_size;
  92.     LastPeSection->VirtualAddress = (LastPeSection - 1)->VirtualAddress + AlignMent((LastPeSection - 1)->SizeOfRawData, GetOptHeader(PeBase)->SectionAlignment);
  93.     LastPeSection->SizeOfRawData = AlignMent(Section_size, GetOptHeader(PeBase)->FileAlignment);
  94.     LastPeSection->PointerToRawData = AlignMent(PeSize, GetOptHeader(PeBase)->FileAlignment);
  95.     LastPeSection->Characteristics = 0xc0000040;//节表属性设为该值,意为该节表可读可写且包含已初始化的数据

  96.     GetOptHeader(PeBase)->SizeOfImage = LastPeSection->VirtualAddress + LastPeSection->SizeOfRawData;

  97.     int NewSize = LastPeSection->PointerToRawData + LastPeSection->SizeOfRawData;

  98.     char* NewPeBase = new char [NewSize] {0};
  99.     //向新缓冲区录入数据
  100.     memcpy(NewPeBase, PeBase, PeSize);
  101.     //缓存区更替
  102.     delete PeBase;
  103.     PeSize = NewSize;
  104.     return NewPeBase;
  105. }

  106. //保存文件
  107. void SaveFile(_In_ const char* path, _In_ const char* data, _In_ int FileSize) {
  108.     HANDLE hFile = CreateFileA(
  109.         path,
  110.         GENERIC_WRITE,
  111.         FILE_SHARE_READ,
  112.         NULL,
  113.         CREATE_ALWAYS,
  114.         FILE_ATTRIBUTE_NORMAL,
  115.         NULL
  116.     );
  117.     DWORD Buf = 0;
  118.     WriteFile(hFile, data, FileSize, &Buf, NULL);
  119.     CloseHandle(hFile);
  120. }

  121. //将RVA的值转换成FOA
  122. LPVOID RvaToFoa(LPVOID pFileBuffer, LPSTR virtualAddress) {
  123.     LPSTR sectionAddress = NULL;//记录距离节头的距离
  124.     LPSTR fileAddress = NULL;//记录文件中的偏移
  125.     PIMAGE_DOS_HEADER pDosHeader = NULL;
  126.     PIMAGE_NT_HEADERS pNTHeader = NULL;
  127.     PIMAGE_FILE_HEADER pPEHeader = NULL;
  128.     PIMAGE_OPTIONAL_HEADER32 pOptionHeader = NULL;
  129.     PIMAGE_SECTION_HEADER pSectionHeader = NULL;

  130.     if (pFileBuffer == NULL) {
  131.         printf("文件写入内存失败!\n");
  132.         return NULL;
  133.     }

  134.     pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
  135.     pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer + pDosHeader->e_lfanew);
  136.     pPEHeader = (PIMAGE_FILE_HEADER)((DWORD)pNTHeader + 4);
  137.     pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + 20);
  138.     pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);

  139.     if ((DWORD)virtualAddress <= pOptionHeader->SizeOfHeaders) {
  140.         return virtualAddress;
  141.     }

  142.     for (DWORD i = 1; i <= pPEHeader->NumberOfSections; i++) {
  143.         if ((DWORD)virtualAddress < pSectionHeader->VirtualAddress) {
  144.             pSectionHeader--;
  145.             break;
  146.         }
  147.         else if (i == pPEHeader->NumberOfSections) {
  148.             break;
  149.         }
  150.         else {
  151.             pSectionHeader++;
  152.         }

  153.     }

  154.     //距离该节头的距离
  155.     sectionAddress = virtualAddress - pSectionHeader->VirtualAddress;
  156.     fileAddress = pSectionHeader->PointerToRawData + sectionAddress;

  157.     return (LPVOID)fileAddress;
  158. }

  159. //将FOA的值转换成RVA
  160. LPVOID FoaToRva(LPVOID pFileBuffer, LPSTR fileaddress) {
  161.     LPSTR sectionAddress = NULL;//记录距离节头的距离
  162.     LPSTR virtualaddress = NULL;//记录内存中的偏移
  163.     PIMAGE_DOS_HEADER pDosHeader = NULL;
  164.     PIMAGE_NT_HEADERS pNTHeader = NULL;
  165.     PIMAGE_FILE_HEADER pPEHeader = NULL;
  166.     PIMAGE_OPTIONAL_HEADER32 pOptionHeader = NULL;
  167.     PIMAGE_SECTION_HEADER pSectionHeader = NULL;

  168.     if (pFileBuffer == NULL) {
  169.         printf("文件写入内存失败!\n");
  170.         return NULL;
  171.     }

  172.     pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
  173.     pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer + pDosHeader->e_lfanew);
  174.     pPEHeader = (PIMAGE_FILE_HEADER)((DWORD)pNTHeader + 4);
  175.     pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + 20);
  176.     pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);

  177.     if ((DWORD)fileaddress <= pOptionHeader->SizeOfHeaders) {
  178.         return fileaddress;
  179.     }

  180.     for (DWORD i = 1; i <= pPEHeader->NumberOfSections; i++) {
  181.         if ((DWORD)fileaddress < pSectionHeader->PointerToRawData) {
  182.             pSectionHeader--;
  183.             break;
  184.         }
  185.         else if (i == pPEHeader->NumberOfSections) {
  186.             break;
  187.         }
  188.         else {
  189.             pSectionHeader++;
  190.         }

  191.     }

  192.     //距离该节头的距离
  193.     sectionAddress = fileaddress - pSectionHeader->PointerToRawData;
  194.     virtualaddress = pSectionHeader->VirtualAddress + sectionAddress;

  195.     return (LPVOID)virtualaddress;
  196. }

  197. char* inject_dll(_In_ char*& PeBase, _In_ DWORD& PeSize)
  198. {
  199.     PIMAGE_DATA_DIRECTORY pDataDirectory = NULL;//定位表目录
  200.     PIMAGE_IMPORT_DESCRIPTOR importTableAddress = NULL;//定位导入表的真正位置
  201.     LPVOID returnAddress = NULL;//记录RVAtoFOA的返回值

  202.     //定位到新节的位置和导入表的位置
  203.     pDataDirectory = (PIMAGE_DATA_DIRECTORY)GetOptHeader(PeBase)->DataDirectory;
  204.     pDataDirectory += 0x1;

  205.     DWORD sectionLength = pDataDirectory->Size + 0x28 + +0x10 + DLLNAMELENGTH + FUNCTIONNAMELENGTH + 0x2;
  206.     sectionLength = AlignMent(sectionLength, GetOptHeader(PeBase)->FileAlignment);

  207.     char SecName[] = ".ddjsq";
  208.     char* NewPeBase = AddSection(PeBase, PeSize, SecName, sectionLength);

  209.     pDataDirectory = (PIMAGE_DATA_DIRECTORY)GetOptHeader(NewPeBase)->DataDirectory;
  210.     pDataDirectory += 0x1;

  211.     PDWORD pNewSection = (PDWORD)(GetLastSec(NewPeBase)->PointerToRawData + (DWORD)NewPeBase);
  212.     returnAddress = RvaToFoa(NewPeBase, (LPSTR)pDataDirectory->VirtualAddress);
  213.     importTableAddress = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)returnAddress + (DWORD)NewPeBase);

  214.     //复制原导入表,在原导入表后新增一个导入表
  215.     memcpy(pNewSection, importTableAddress, pDataDirectory->Size);
  216.     importTableAddress = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)pNewSection + pDataDirectory->Size - 0x14);

  217.     //增加8字节INT表
  218.     PIMAGE_THUNK_DATA32 pIntTable = (PIMAGE_THUNK_DATA32)((DWORD)importTableAddress + 0x28);//保留20字节的0
  219.     PIMAGE_THUNK_DATA32 repairIntTable = pIntTable;
  220.     pIntTable++;
  221.     pIntTable->u1.Ordinal = 0x0;
  222.     pIntTable++;

  223.     //增加8字节IAT表
  224.     PIMAGE_THUNK_DATA32 pIatTable = (PIMAGE_THUNK_DATA32)(pIntTable);
  225.     PIMAGE_THUNK_DATA32 repairIatTable = pIatTable;
  226.     pIatTable++;
  227.     pIatTable->u1.Ordinal = 0x0;
  228.     pIatTable++;

  229.     //分配空间存储DLL名称字符串
  230.     PDWORD dllNameAddress = (PDWORD)pIatTable;
  231.     memcpy(dllNameAddress, DLLNAME, DLLNAMELENGTH);

  232.     //增加IMAGE_IMPORT_BY_NAME 结构
  233.     PIMAGE_IMPORT_BY_NAME functionNameAddress = (PIMAGE_IMPORT_BY_NAME)((DWORD)dllNameAddress + DLLNAMELENGTH);
  234.     PDWORD pFunctionName = (PDWORD)((DWORD)functionNameAddress + 0x2);
  235.     memcpy(pFunctionName, FUNCTIONNAME, FUNCTIONNAMELENGTH);

  236.     //将IMAGE_IMPORT_BY_NAME结构的RVA赋值给INT和IAT表中的第一项
  237.     repairIntTable->u1.AddressOfData = (DWORD)FoaToRva(NewPeBase, (LPSTR)((DWORD)functionNameAddress - (DWORD)NewPeBase));
  238.     repairIatTable->u1.AddressOfData = repairIntTable->u1.Ordinal;

  239.     //修正导入表Name、OriginalFirstThunk、FirstThunk
  240.     importTableAddress->Name = (DWORD)FoaToRva(NewPeBase, (LPSTR)((DWORD)dllNameAddress - (DWORD)NewPeBase));
  241.     importTableAddress->OriginalFirstThunk = (DWORD)FoaToRva(NewPeBase, (LPSTR)((DWORD)repairIntTable - (DWORD)NewPeBase));
  242.     importTableAddress->FirstThunk = (DWORD)FoaToRva(NewPeBase, (LPSTR)((DWORD)repairIatTable - (DWORD)NewPeBase));

  243.     //修正IMAGE_DATA_DIRECTORY结构的VirtualAddress和Size
  244.     pDataDirectory->VirtualAddress = (DWORD)FoaToRva(NewPeBase, (LPSTR)((DWORD)pNewSection - (DWORD)NewPeBase));
  245.     pDataDirectory->Size += 0x14;

  246.     return NewPeBase;
  247. }

  248. int main()
  249. {
  250.     char path[] = "路径";
  251.     DWORD pesize;
  252.     char* PeBase = OpenPeFiles(path, &pesize);
  253.     if (!PeBase)
  254.     {
  255.         printf("wrong");
  256.         return 0;
  257.     }
  258.     char* NewPeBase = inject_dll(PeBase, pesize);
  259.     SaveFile("路径", NewPeBase, pesize);
  260. }
复制代码
运行结果
注入前:



注入后:



运行时:



防范手段
由于是静态注入,直接修改了exe文件,所以可使用校验的方式去检测有无被注入。
参考链接
《32位PE解析、PE修改、导入表注入》(https://bbs.pediy.com/thread-258246.htm
PE基础之导入表注入(https://bbs.pediy.com/thread-262420.htm
Ring3注入总结及编程实现(https://bbs.pediy.com/thread-217722.htm


回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2024-11-29 18:49 , Processed in 0.013129 second(s), 18 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表