您的位置:首页 > 其它

用16进制编辑器编写一个DLL文件

2010-02-08 22:29 225 查看
对于.text区块,节内包含可执行代码,bit 5,bit 29都置1;对于可执行文件,bit 30总是置1。设置为 “60 00 00 20”, 在16进制编辑器里输入“20 00 00 60”。
对于.data区块,节内包含的数据在执行前是确定的,bit 6置1;因为是数据区,所以bit 30,bit 31都置1。设置为 “C0 00 00 40”, 在16进制编辑器里输入“40 00 00 C0”。
对于.idata区块,和.data区块的设置一样。设置为 “C0 00 00 40”, 在16进制编辑器里输入“40 00 00 C0”。
对于.edata区块,节内包含的数据在执行前是确定的,bit 6置1;但输出表区块是不用写数据的,所以只有bit 30置1。设置为 “40 00 00 40”, 在16进制编辑器里输入“40 00 00 40”。
对于.reloc区块,和.edata区块设置一样。设置为 “40 00 00 40”, 在16进制编辑器里输入“40 00 00 40”。
所以区块表的16进制源码如下:

00000180 2E 74 65 78 74 00 00 00 .text...
00000190 00 10 00 00 00 10 00 00 00 02 00 00 00 06 00 00 ................
000001A0 00 00 00 00 00 00 00 00 00 00 00 00 20 00 00 60 ............ ..`
000001B0 2E 64 61 74 61 00 00 00 00 10 00 00 00 20 00 00 .data........ ..
000001C0 00 02 00 00 00 08 00 00 00 00 00 00 00 00 00 00 ................
000001D0 00 00 00 00 40 00 00 C0 2E 69 64 61 74 61 00 00 ....@..?idata..
000001E0 00 10 00 00 00 30 00 00 00 02 00 00 00 0A 00 00 .....0..........
000001F0 00 00 00 00 00 00 00 00 00 00 00 00 40 00 00 C0 ............@..?
00000200 2E 65 64 61 74 61 00 00 00 10 00 00 00 40 00 00 .edata.......@..
00000210 00 02 00 00 00 0C 00 00 00 00 00 00 00 00 00 00 ................
00000220 00 00 00 00 40 00 00 40 2E 72 65 6C 6F 63 00 00 ....@..@.reloc..
00000230 00 10 00 00 00 50 00 00 00 02 00 00 00 0E 00 00 .....P..........
00000240 00 00 00 00 00 00 00 00 00 00 00 00 40 00 00 40 ............@..@

下面是开始编写区块部分,但手写区块的顺序是很讲究的,因为我们的目标是编写一个可以弹出对话框的函数,所以在写代码前必须要有对话框标题和内容等基本数据,而且还需要MessageBoxA的地址,因为是DLL文件,写完代码后还要处理输出表和重定位表。

先来个最简单的,对话框标题是“hello”,内容是“hello,pediy!!!”,我们在磁盘文件偏移800处开始写入数据,完成后内容如下:

00000800 68 65 6C 6C 6F 00 00 00 00 00 00 00 00 00 00 00 hello...........
00000810 68 65 6C 6C 6F 2C 70 65 64 69 79 21 21 21 00 00 hello,pediy!!!..
下面是输入表部分,也就是.idata区块。
输入表是以IMAGE_IMPORT_DESCRIPTOR( IID )数组开始的,每个被调用的DLL文件都对应一个IID,做后一个全0数组结束。IMAGE_IMPORT_DESCRIPTOR的结构如下所示:

typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics; // 0 for terminating null import descriptor
DWORD OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA)
};
DWORD TimeDateStamp; IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)
DWORD ForwarderChain; // -1 if no forwarders
DWORD Name;
DWORD FirstThunk; // RVA to IAT (if bound this IAT has 、、//actual addresses)
} IMAGE_IMPORT_DESCRIPTOR;

(1) OriginalFirstThunk:四个字节,包含指向输入名称表(INT)的RVA,INT是一个IMAGE_THUNK_DATA结构的数组,数组的每个元素指向IMAGE_IMPORT_BY_NAME结构,数组最后以0结束。
(2) TimeDataStamp:四个字节,32位的时间标志,设置为 “00 00 00 00”, 在16进制编辑器里输入“00 00 00 00”。
(3) ForwardChain:四个字节,第一个被转向的API索引。设置为 “00 00 00 00”, 在16进制编辑器里输入“00 00 00 00”。
(4) Name:四个字节,DLL文件的名称。是个以00结束的ASCⅡ码的RVA。
(5) FirstThunk:包含指向输入地址表( IAT )的RVA,IAT是个IMAGE_THUNK_DATA结构的数组。

现在我们编写的DLL文件只是弹出一个对话框,所以只是需要用到一个函数MessageBoxA,在USER32.dll动态链接库里。观察输入表区块如下:

00000A00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000A10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000A20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000A30 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000A40 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000A50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000A60 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000A70 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000A80 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000A90 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000AA0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000AB0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................

必须要先构造一个IID 元素指向USER32.dll的信息,然后以一个全0的IID 元素结束。这里就占用了2*(4*5)=40字节,就是占用了A00到A27之间的空间。
由表5可知,.idata区块的虚拟偏移是3000h,物理偏移是A00h,所以两者之间的差值为3000-A00=2600h,这个数值以后算RVA时会经常用到。
现在先处理IID中Name的数值,由于A00到A27的空间应经被用了,所以我们在A30开始存放USER32.dll文件名的ASCⅡ码,以00结束。A30转化为RVA的方法是加上前面计算出来的2600h,所以Name的数值为2600+A30=3030h,在16进制编辑器里输入“30 30 00 00”。
接下来在A40h存放INT,由于只有一个函数MessageBoxA,所以整个INT只有两项(以一个全0结束),在A50h存放MessageBoxA的ASCⅡ码,以00结束。
所以OriginalFirstThunk存放的是INT的RVA,为2600+A40=3040h,在16进制编辑器里输入“40 30 00 00”。A40h存放的是MessageBoxA的ASCⅡ码的RVA,为2600+A50=3050h,在16进制编辑器里输入“50 30 00 00”。 A50h存放的是MessageBoxA的ASCⅡ码,在16进制编辑器里输入“50 30 00 00”。但输入MessageBoxA的ASCⅡ码时就要注意,ASCⅡ字符串前有两个字节的空缺,是作为函数名的引用,可以为0,所以在16进制编辑器里输入“50 30 00 00”。
最后要处理的是输入地址表( IAT ),我们在A60处存放IAT,由于只有一个函数MessageBoxA,所以整个IAT只有两项(以一个全0结束),IAT的第一项也是指向MessageBoxA的ASCⅡ码的RVA,在A60处输入“50 30 00 00”。而FirstThunk字段填充A60的RVA,为2600+A60=3060h,在16进制编辑器里输入“60 30 00 00”。
整个.idata区块完成后如下所示:

00000A00 40 30 00 00 00 00 00 00 00 00 00 00 30 30 00 00 @0..........00..
00000A10 60 30 00 00 00 00 00 00 00 00 00 00 00 00 00 00 `0..............
00000A20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000A30 55 53 45 52 33 32 2E 64 6C 6C 00 00 00 00 00 00 USER32.dll......
00000A40 50 30 00 00 00 00 00 00 00 00 00 00 00 00 00 00 P0..............
00000A50 00 00 4D 65 73 73 61 67 65 42 6F 78 41 00 00 00 ..MessageBoxA...
00000A60 50 30 00 00 00 00 00 00 00 00 00 00 00 00 00 00 P0..............
00000A70 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000A80 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000A90 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................

现在我们来处理代码区块(.text)的编写。
首先,DLL文件都有一个入口点,放在600处,内容如下:
mov eax, 1 B8 01000000
retn 0C C2 0C00 ;由于DLL入口函数压了3个DWORD型参数大小为4*3=12=0Ch,所以retn 0c

紧接着编写弹出对话框的函数,但在编写代码前先要弄清楚对话框的标题“hello”和内容“hello,pediy!!!”的内存地址。由于对话框的标题和内容数据都处于.data区块中,由表5可知,该区块在内存中与基址的偏移为2000h,与磁盘文件头的偏移为800h,两者的差值为2000-800=1800h。
现在计算对话框的标题“hello”的内存地址。“hello”在磁盘文件的800h处,加上1800为800+1800=2000h,再加上ImageBase的值400000h,则标题“hello”的内存地址为400000+2000=402000h。内容“hello,pediy!!!”的内存地址如上,最后得到内存地址为401010h。
另外还需要MessageBoxA的函数地址,由输入表部分可知,文件偏移A60处存放的是MessageBoxA的函数地址,转化成内存地址为400000+((3000-A00)+A60)=403060h。
编写完成的代码如下:

55 push ebp
8BEC mov ebp, esp
6A 00 push 0
68 00204000 push 00402000 ;title “hello”
68 10204000 push 00402010 ; text “hello,pediy!!!”
6A 00 push 0
FF15 60304000 call dword ptr [403060] ;调用MessageBoxA函数
8BE5 mov esp, ebp
5D pop ebp
C3 retn

所以,代码区块(.text)的16源码如下:

00000600 B8 01 00 00 00 C2 0C 00 55 8B EC 6A 00 68 00 20 ?...?.U嬱j.h.
00000610 40 00 68 10 20 40 00 6A 00 FF 15 60 30 40 00 8B @.h. @.j. `0@.?
00000620 E5 5D C3 00 00 00 00 00 00 00 00 00 00 00 00 00 錧?............

由代码区块可知,代码中存在3个重定位数据,分别是title “hello”( 00402000h ), text “hello,pediy!!!”( 00402010 ), MessageBoxA函数地址( 00403060h )。这就需要重定为表的帮忙。重定位表是为与.reloc区块上,数据的组织方式是由许多重定位块串接而成。每个块是必须以4字节对齐,重定位块的结构如下:

typedef struct _IMAGE_BASE_RELOCATION {
DWORD VirtualAddress;
DWORD SizeOfBlock;
// WORD TypeOffset[1];
} IMAGE_BASE_RELOCATION;

(1) VirtualAddress:这组重定位数据的RVA地址。每个重定位数据加上这个值才是重定位项的RVA。
(2) SizeOfBlack:四个字节,当前重定位结构的大小。用这个项减去8,就是TypeOffset的大小。
(3) TypeOffset:是一个数组。数组每项大小是两个字节,其中高4位是重定位的类型,低12位是重定位地址,它与VirtualAddress相加就是PE映像需要修改的地址数据的指针。

常见的重定位类型如下表6:



表6
title “hello”( 00402000h )在文件中的偏移为60Eh,转化成RVA为60E+(1000-600)=100Eh,减去这组重定位数据开始的RVA地址为100E-1000=00Eh,于是得到TypeOffset低12位地址00Eh,TypeOffset的高4位类型为3,所以TypeOffset的值为300Eh,在16进制编辑器里输入“0E 30”。
text “hello,pediy!!!”( 00402010 )按title “hello”的方法计算,得到TypeOffset的值为3013h,在16进制编辑器里输入“13 30”。
MessageBoxA函数地址( 00403060h )按title “hello”的方法计算,得到TypeOffset的值为301Bh,在16进制编辑器里输入“1B 30”。
最后,我们得到VirtualAddress为00001000h,在16进制编辑器里输入“00 10 00 00”。
SizeOfBlock为00000010h(有四个重定位项,其中一个是为了数据对齐,(10-8)/2=4h),在16进制编辑器里输入“10 0 00 00”。
构造完成的重定位表区块16进制源码如下:

00000E00 00 10 00 00 10 00 00 00 0E 30 13 30 1B 30 00 00 .........0.0.0..

好,革命快要成功了,现在就差输出表区块(.edata)整个DLL文件就完成了。
输出表是个指向IMAGE_EXPORT_DIRECTORY( IED )结构,IED结构如下所示:

typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
DWORD Name;
DWORD Base;
DWORD NumberOfFunctions;
DWORD NumberOfNames;
DWORD AddressOfFunctions; // RVA from base of image
DWORD AddressOfNames; // RVA from base of image
DWORD AddressOfNameOrdinals; // RVA from base of image
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

(1) Characteristics:四个字节,表示输出表的属性,保留。设置为 “00 00 00 00”, 在16进制编辑器里输入“00 00 00 00”。
(2) TimeDateStamp:四个字节,表示输出表创建时间,设置为 “00 00 00 00”, 在16进制编辑器里输入“00 00 00 00”。
(3) MajorVersion:两个字节,输出表的版本号,设置为 “00 00”, 在16进制编辑器里输入“00 00”。
(4) MinorVersion:两个字节,输出表的次版本号,设置为 “00 00”, 在16进制编辑器里输入“00 00”。
(5) Name:四个字节,指向一个ASCⅡ码的RVA。这个字符串是本输出函数所指向的DLL名。这个值后面才论述。
(6) Base:四个字节,包含这个文件输出表的起始序数值。一般来说,这个值为1。设置为 “00 00 00 01”, 在16进制编辑器里输入“01 00 00 00”。
(7) NumberOfFunction:四个字节,输出地址表(EAT)中的条目数量。因为只有一个输出函数ShowMesBox,所以这个值为1,设置为 “00 00 00 01”, 在16进制编辑器里输入“01 00 00 00”。
(8) NumberOfName:四个字节,输出名称表(ENT)的条目数量。因为只有一个输出函数ShowMesBox,所以这个值为1,设置为 “00 00 00 01”, 在16进制编辑器里输入“01 00 00 00”。
(9) AddressOfFunction:四个字节,EAT的RVA。数组中的每一个非零的RVA都对应于一个被输出的符号。
(10) AddressOfName:四个字节,ENT的RVA。ENT是一个指向ASCⅡ码字符串的RVA数组,每个ASCⅡ字符串对应于一个被输出的符号。
(11) AddressOfNameOrdinals:四个字节,输出序数表的RVA。这个表将ENT中的数组索引映射到相应的输出地址表条目。

现在我们来看,这个IMAGE_EXPORT_DIRECTORY结构共用了40个字节,也就是C00到C27 。
那么,我们在C30构造Name属性的所指向的DLL名字的ASCⅡ码的。“BinaryDll.dll”转化成的ASCⅡ码是“42696E617279446C6C2E646C6C”,在16进制编辑器里输入“42 69 6E 61 72 79 44 6C 6C 2E 64 6C 6C”。
把C30转化成RVA就是C30+(4000-C00)=4030h,所以把属性Name设置为“00 00 40 30”, 在16进制编辑器里输入“30 40 00 00”。
在C40构造EAT表,C40转化为RVA为C40+(4000-C00)=4040h,所以把属性AddressOfFunction设置为“00 00 40 40”, 在16进制编辑器里输入“40 40 00 00”。
在C50构造ENT表,C50转化为RVA为C50+(4000-C00)=4050h,所以把属性AddressOfName设置为“00 00 40 50”, 在16进制编辑器里输入“50 40 00 00”。
在C60构造输出序数表,C60转化为RVA为C60+(4000-C00)=4060h,所以把属性AddressOfNameOrdinals设置为“00 00 40 60”, 在16进制编辑器里输入“60 40 00 00”。
在C40处输入输出函数” ShowMesBox”的RVA,有代码区可得DLL的入口函数用了8个字节的空间,则ShowMesBox的地址为1000+8=1008h,在16进制编辑器里输入“08 10 00 00”。
在C70处输入输出函数” ShowMesBox”的ASCⅡ码,在16进制编辑器里输入“53 68 6F 77 4D 65 73 42 6F 78”。
在C50处输入C70的RVA,为C70+(4000-C00)=4070,在16进制编辑器里输入“70 40 00 00”。
在C60处构造输出序数表,由于只有一个输出函数,所以设置为“00 00”, 在16进制编辑器里输入“00 00”。
完成后输出表16进制源码如下图所示:

00000C00 00 00 00 00 00 00 00 00 00 00 00 00 30 40 00 00 ............0@..
00000C10 01 00 00 00 01 00 00 00 01 00 00 00 40 40 00 00 ............@@..
00000C20 50 40 00 00 60 40 00 00 00 00 00 00 00 00 00 00 P@..`@..........
00000C30 42 69 6E 61 72 79 44 6C 6C 2E 64 6C 6C 00 00 00 BinaryDll.dll...
00000C40 08 10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000C50 70 40 00 00 00 00 00 00 00 00 00 00 00 00 00 00 p@..............
00000C60 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000C70 53 68 6F 77 4D 65 73 42 6F 78 00 00 00 00 00 00 ShowMesBox......

最后,我们来填写数据目录表的内容。
数据目录表中的输出表RVA是4000,大小是80h,我们在16进制编辑器输入“00 40 00 00 80 00 00 00”。
数据目录表中的输入表RVA是3000,大小是70h,我们在16进制编辑器输入“00 30 00 00 70 00 00 00”。
数据目录表中的重定位表RVA是5000,大小是10h,我们在16进制编辑器输入“00 50 00 00 10 00 00 00”。

最后写了个小测试程序,代码如下

#include <iostream>
#include <windows.h>
using namespace std;

int main()
{
typedef void(*pShowMsg)(void);
HINSTANCE hDLL;
pShowMsg ShowMeg;
hDLL=LoadLibrary("BinaryDll.dll");
ShowMeg=(pShowMsg)GetProcAddress(hDLL,"ShowMesBox");
ShowMeg();
::FreeLibrary(hDLL);
return 0;
}

运行后出现下图:

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐