上边只摘录相比较关键的字段:
;
; data section…
;
section ‘.data’ data readable
pszText db ‘Hello, FASM world!’,0
pszCaption db ‘Flat Assembler’,0
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics; // 0 for terminating null import descriptor
DWORD OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA)
} DUMMYUNIONNAME;
DWORD TimeDateStamp; // 0 if not bound,
// -1 if bound, and real datetime stamp
// in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)
// O.W. date/time stamp of DLL bound to (Old BIND)
DWORD ForwarderChain; // -1 if no forwarders
DWORD Name;
DWORD FirstThunk; // RVA to IAT (if bound this IAT has actual addresses)
} IMAGE_IMPORT_DESCRIPTOR;
typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;
5.2、导出表
———–
因此导入表的就学,小编想各位读者已经对PE文件的求学进度有了投机认识和情势,所以下边
至于导出表的豆蔻梢头节自己将加速局地速度。“朋友们注意啊!!! @#$%$%&#^” 😀
在导出表的原初地方是二个IMAGE_EXPORT_DIRECTOLANDY结构,但与引进表分裂的是在导出表中
唯有一个以此组织。上面大家来看一下那几个布局的定义:
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;
Characteristics、MajorVersion和MinorVersion不使用,一般为0。
提姆eDataStamp是岁月戳。
Name字段是三个汉兰达VA值,它指向了那几个模块的原来名称。这些称号与编译后的文件名无关。
Base字段钦定了导出函数序号的开始序号。如若Base的值为n,那么导出函数入口地址表中
的第二个函数的序号正是n,第1个正是n+1…
NumberOfFunctions钦赐了导出函数的总的数量。
NumberOfNames钦赐了按名称导出的函数的总额。按序号导出的函数总量正是以此值与内地
总数NumberOfFunctions的差。
AddressOfFunctions字段是三个WranglerVA值,指向一个EvoqueVA数组,数组中的种种昂科拉VA均指向二个导
出函数的输入地址。数组的项数等于NumberOfFuntions。
AddressOfNames字段是三个EnclaveVA值,同样指向三个安德拉VA数组,数组中的种种双字是三个针对性函
数名字符串的CR-VVA。数组的项数等于NumberOfNames。
AddressOfNameOrdinals字段是二个法拉利488VA值,它指向二个篇幅组,注意这里不再是双字了!!
本条数组起着相当的重大的效劳,它的项数等于NumberOfNames,并与AddressOfNames指向的数组
依次对应。其各样类别的值代表了这一个函数在进口地址表中索引。以往大家来看贰个例证,
假设贰个导出函数Foo在导出入口地址表中居于第m个职务,大家搜求Ordinal数组的第m项,
即使这么些值为x,大家把那个值与导出序号的先导值Base的值n相加获得的值就是函数在入口
地方表中索引。
下图表示了导出表的组织和上述进程:
+———————–+ +—————–+
| Characteristics | +—-> | ‘dlltest.dll’,0 |
+———————–+ | +—————–+
| TimeDataStamp | |
+———————–+ | +-> +—————–+
| MajorVersion | | | 0 | 函数进口地址ENCOREVA | ==> 函数Foo,序号n+0 <–+
+———————–+ | | +—————–+ |
| MinorVersion | | | | … | |
+———————–+ | | +—————–+ |
| Name | -+ | x | 函数输入地址CRUISERVA | ==> 按序号导出,序号为n+x |
+———————–+ | +—————–+ |
| Base(若是值为n卡塔尔 | | | … | |
+———————–+ | +—————–+ |
| NumberOfFunctions | | |
+———————–+ | +-> +—–+ +———-+ +—–+ <-+ |
| NumberOfNames | | | | RVA | –> | ‘_foo’,0 | <==> | 0 | –+—+
+———————–+ | | +—–+ +———-+ +—–+ |
| AddressOfFunctions | —-+ | | … | | … | |
+———————–+ | +—–+ +—–+ |
| AddressOfNames | ——-+ |
+———————–+ |
| AddressOfNameOrdinals | —————————————————+
+———————–+
好了,上边大家来看构键我们的导出表。假如咱们按名称导出一个函数_foo。大家以如下代
码开始:
section ‘.edata’ export data readable
接着是IMAGE_EXPORT_DIRECTORY结构:
dd 0 ; Characteristics
dd 0 ; TimeDataStamp
dw 0 ; MajorVersion
dw 0 ; MinorVersion
dd RVA dll_name ; RVA,指向DLL名称
dd 0 ; 早先序号为0
dd 1 ; 只导出三个函数
dd 1 ; 这几个函数是按名称方式导出的
dd RVA addr_tab ; RubiconVA,指向导出函数入口地址表
dd RVA name_tab ; 本田UR-VVA,指向函数名称地址表
dd RVA ordinal_tab ; WranglerVA,指向函数索引表
下边大家定义DLL名称:
dll_name db ‘foo.dll’,0 ; DLL名称,编写翻译的文书名能够与它分裂
接下去是导出函数入口地址表和函数名称地址表,大家要导出二个叫_foo的函数:
addr_tab dd RVA _foo ; 函数输入地址
name_tab dd RVA func_name
func_name db ‘_foo’,0 ; 函数名称
终极是函数索引表:
ordinal_tab dw 0 ; 独有贰个按名称导出函数,序号为0
上面大家看三个安然无恙的次序:
format PE GUI 4.0 DLL at 76000000h
entry _dll_entry
FirstThunk
它饱含由IMAGE_THUNK_DATA定义的 first
thunk数组的虚地址,通过loader用函数虚地址开头化thunk。
在Orignal First Thunk缺席下,它指向first thunk:Hints和The Function
names的thunks。
上面来讲明下OriginalFirstThunk和FirstThunk。就个人了然来说:
1.
在文书中时,他们都分别针对三个WranglerVA地址。那些地址调换来文件中,分别对应三个以
IMAGE_THUNK_DATA 为成分的的数组,那四个数组是以多少个填写为 0
的IMAGE_THUNK_DATA作为完毕标志符。固然他们那三个表地点差别,但事实上内容是千篇一律的。当时,各种IMAGE_THUNK_DATA 成分指向的是多个笔录了函数名和相呼应的DLL文件名的
IMAGE_IMPORT_BY_NAME结构体。
- 干什么会有五个大器晚成律的数组呢?是有原因的:
OriginalFirstThunk 指向的数组平日称得上 hint-name table,即 HNT ,他在 PE
加载到内部存款和储蓄器中时被保留了下去且永世不会被改善。不过在 Windows 加载过 PE
到内部存款和储蓄器之后,Windows 会重写 FirstThunk
所指向的数组成分中的内容,使得数组中各个 IMAGE_THUNK_DATA
不再代表针对带有函数描述的 IMAGE_THUNK_DATA
成分,而是平昔针对了函数地址。那时,FirstThunk
所指向的数组就称为输入地址表(Import Address Table ,即平常说的
IAT卡塔尔国。
重写前:
重写后:
(以上两张图纸源于:)
typedef struct _IMAGE_THUNK_DATA32 {
union {
DWORD ForwarderString; // PBYTE 指向一个转向者字符串的RVA
DWORD Function; // PDWORD 被输入的函数的内存地址
DWORD Ordinal; // 被输入的 API 的序数值
DWORD AddressOfData; // PIMAGE_IMPORT_BY_NAME 指向 IMAGE_IMPORT_BY_NAME
} u1;
} IMAGE_THUNK_DATA32;
typedef IMAGE_THUNK_DATA32 * PIMAGE_THUNK_DATA32;
根据 _IMAGE_THUNK_DATA32 所指虚构地址转到文件地方能够获得实际的
_IMAGE_IMPORT_BY_NAME 数据
typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint; // 序号
CHAR Name[1]; // 实际上是一个可变长的以0为结尾的字符串
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
譬如有程序:
文字版:
#include <windows.h>
int WINAPI WinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPSTR lpCmdLine,
_In_ int nShowCmd)
{
MessageBoxA(0, "hello", "my message", MB_OK);
SetWindowTextA(0, "Si Wang");
return 0;
}
此程序接纳了三个 Windows API : Message博克斯A 和 SetWindowTextA
编写翻译得到程序(为简化表达,区段位置由软件总括出卡塔尔:
我们试着寻找 MessageBoxA。首先分析 PE 头文件,找到导出表在文书中的地点:
输入表地方在 .rdata 区段内, 0x2264 – 0x2001 = 0x0264
获得偏移量。加上文件地方 0x0E00 拿到实在文件偏移量(0x0E00 + 0x264 =
0x1064卡塔尔国:0x1064。
接下去查看 0x1064 处:
能够获取四个 DLL 的叙说,最后叁个_IMAGE_IMPORT_DESC帕JeroIPTO福睿斯以0填充表示停止:
那便是说只要二个个查看各样DLL对应的数据就会找到,可是早先小编把具备的数量都看了下,在率先个DLL中
依附第叁个DLL描述的 OriginalFirstThunk 的 0x2350
转换能够了解,_IMAGE_THUNK_DATA32 在文书的 0x1150处,FirstThunk
指向的数码意气风发致:
于是就获取了文本中的 MessageBoxA 的音信。
末尾,在内部存款和储蓄器中 FirstThunk 所指地点上的_IMAGE_THUNK_DATA32 数组被
Windows 加载后被重写后就成了遗闻中的 IAT ,Import Address
Table,输入地址表。使用 OllyDbg 查看运营时情状:
;
; code section…
;
section ‘.text’ code readable executable
_foo:
push 0
push pszCaption
push pszText
push 0
call [MessageBox]
ret
_dll_entry:
xor eax,eax
inc eax
ret 0ch
在此个
IID数组中,并不曾建议有个别许个项(便是从未刚毅指明有多少个链接文件),但它最后是以三个全为NULL(0)
的 IID 作为达成的申明。
;
; code section…
;
section ‘.text’ code readable executable
__start:
push 0
push pszCaption
push pszText
push 0
call [MessageBox]
push 0
call [ExitProcess]
在 PE文件头的 IMAGE_OPTIONAL_HEADETiggo 结构中的 DataDirectory(数据目录表)
的第叁个成员就是指向输入表的。每一个被链接进来的 DLL文件都各自对应一个IMAGE_IMPORT_DESC君越IPTO奇骏 (简单的称呼IID) 数组结构。
;
; import section…
;
section ‘.idata’ import data readable
; image import descriptor
dd 0,0,0,RVA usr_dll,RVA usr_公海710登录网址 ,thunk
dd 0,0,0,RVA krnl_dll,RVA krnl_thunk
dd 0,0,0,0,0
; dll name
usr_dll db ‘user32.dll’,0
krnl_dll db ‘kernel32.dll’,0
; image thunk data
usr_thunk:
MessageBox dd RVA __imp_MessageBox
dd 0
krnl_thunk:
ExitProcess dd RVA __imp_ExitProcess
dd 0
; image import by name
__imp_MessageBox dw 0
db ‘MessageBoxA’,0
__imp_ExitProcess dw 0
db ‘ExitProcess’,0
看来这里自个儿信赖大家都对FASM那几个编写翻译器有了叁个始发的认知,也终将有过多读者会说:“
如此那般费劲啊,干吧要用那几个编写翻译器呢?”。是的,或许上边的代码看起来很复杂,编写起来
也很费力,但FASM的三个功利在于大家能够更积南北极垄断大家转移的PE文件结构,同期能对
PE文件有更理性的认知。可是每一个人的口味不生龙活虎,嘿嘿,大概下边的说辞还非常不足说服各位读
者,没提到,选拔朝气蓬勃款符合您的编写翻译器吧,它们都平等雅观 😛
Name
它表示DLL
名称的相对虚地址(译注:相对一个用null作为完毕符的ASCII字符串的叁在那之中华VVA,该字符串是该导入DLL文件的称呼。如:KERubiconNEL32.DLL卡塔尔国。
;
; import section…
;
section ‘.idata’ import data readable
; image import descriptor
dd 0,0,0,RVA usr_dll,RVA usr_thunk
dd 0,0,0,RVA krnl_dll,RVA krnl_thunk
dd 0,0,0,0,0
; dll name
usr_dll db ‘user32.dll’,0
krnl_dll db ‘kernel32.dll’,0
; image thunk data
usr_thunk:
MessageBox dd RVA __imp_MessageBox
dd 0
krnl_thunk:
ExitProcess dd RVA __imp_ExitProcess
dd 0
; image import by name
__imp_MessageBox dw 0
db ‘MessageBoxA’,0
__imp_ExitProcess dw 0
db ‘ExitProcess’,0
OriginalFirstThunk
它指向first thunk,IMAGE_THUNK_DATA,该 thunk 拥有 Hint 和 Function
name 的地址。
;
; data section…
;
section ‘.data’ data readable
pszText db ‘Hello, FASM world!’,0
pszCaption db ‘Flat Assembler’,0
【pker / CVC.GB】
5、关于FASM
———–
下边我们用FASM来编排我们的首先个程序。我们得以编写如下代码:
format PE GUI 4.0
entry __start
section ‘.text’ code readable executable
__start:
ret
我们把那么些文件存为test.asm并编写翻译它:
fasm test.asm test.exe
还未有任何烦人的参数,相当的低价,不是么? 😛
小编们先来看一下那么些顺序的布局。第一句是format提醒字,它钦赐了程序的花色,PE表示本人
们编写的是三个PE文件,后边的GUI提醒编写翻译器我们将动用Windows图形分界面。假诺要编制风流倜傥
个调整台应用程序则足以钦定为CONSOLE。纵然要写三个根基驱动,能够钦点为NATIVE,表示
没有必要子系统辅助。最终的4.0内定了子系统的版本号(还记得前边的MajorSubsystemVersion
和MinorSubsystemVersion么?)。
下面生龙活虎行钦命了前后相继的入口为__start。
section提醒字表示大家要从头二个新节。我们的顺序独有八个节,即代码节,大家将其命名
为.text,并钦点节属性为只读(readable卡塔 尔(英语:State of Qatar)和可进行(executable卡塔尔。
其后便是大家的代码了,大家无非用一条ret指令回到系统,此时饭店里的回来地址为Exit-
Thread,所以程序直接退出。
下边运维它,程序只是简短地退出了,大家成功地用FASM编写了二个顺序!我们曾经迈出了
率先步,下边要让我们的次第能够做点什么。大家想要调用二个API,大家要如何是好啊?让
我们再来充充电吧 😀
5.1、导入表
———–
我们编辑如下代码并用TASM编写翻译:
;
; tasm32 /ml /m5 test.asm
; tlink32 -Tpe -aa test.obj ,,, import32.lib
;
ideal
p586
model use32 flat
extrn MessageBoxA:near
dataseg
str_hello db ‘Hello’,0
codeseg
__start:
push 0
push offset str_hello
push offset str_hello
push 0
call MessageBoxA
ret
end __start
上边大家用w32dasm反汇编,获得:
:00401000 6A00 push 00000000
:00401002 6800204000 push 00402000
:00401007 6800204000 push 00402000
:0040100C 6A00 push 00000000
:0040100E E801000000 call 00401014
:00401013 C3 ret
:00401014 FF2530304000 jmp dword ptr [00403030]
能够见到代码中的call MessageBoxA被翻译成了call 00401014,在此个地址处是三个跳转
指令jmp dword ptr [00403030],大家得以鲜明在地址00403030处寄放的是MessageBoxA的
当真地址。
实质上那几个地址是身处PE文件的导入表中的。上面我们世袭大家的PE文件的读书。大家先来看
不常间导入表的协会。导入表是由风姿罗曼蒂克多种的IMAGE_IMPORT_DESC昂CoraIPTO悍马H2结构构成的。结构的个
数由文件引用的DLL个数调节,文件援用了略微个DLL就有些许个IMAGE_IMPORT_DESCRIPTOR
协会,最后还应该有叁个全为零的IMAGE_IMPORT_DESC卡宴IPTOTiguan作为实现。
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics;
DWORD OriginalFirstThunk;
};
DWORD TimeDateStamp;
DWORD ForwarderChain;
DWORD Name;
DWORD FirstThunk;
} IMAGE_IMPORT_DESCRIPTOR;
typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;
Name字段是贰个ENVISIONVA,钦点了引进的DLL的名字。
OriginalFirstThunk和FirstThunk在二个PE没有加载到内部存款和储蓄器中的时候是同样的,都是指向大器晚成
个IMAGE_THUNK_DATA结构数组。最后以叁个剧情为0的布局结束。其实那些布局正是叁个双
字。那个布局很有趣,因为在差别的时候这么些结构意味着着分歧的意思。当以此双字的最高
位为1时,表示函数是以序号的办法导入的;当最高位为0时,表示函数是以名称情势导入的,
那是其一双字是二个福特ExplorerVA,指向一个IMAGE_IMPORT_BY_NAME结构,这几个布局用来内定导入函数
名称。
typedef struct _IMAGE_IMPORT_www.710.com ,BY_NAME {
WORD Hint;
BYTE Name[1];
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
Hint字段表示二个序号,不过因为是按名称导入,所以那么些序号平时为零。
Name字段是函数的名称。
下边我们用一张图来证明这么些纷纷的经过。假如叁个PE援用了kernel32.dll中的LoadLibraryA
和GetProcAddress,还应该有多少个按序号导入的函数80010002h。
IMAGE_IMPORT_DESCRIPTOR IMAGE_IMPORT_BY_NAME
+——————–+ +–> +——————+ +———————–+
| OriginalFirstThunk | –+ | IMAGE_THUNK_DATA | –> | 023B | ExitProcess | <–+
+——————–+ +——————+ +———————–+ |
| TimeDataStamp | | IMAGE_THUNK_DATA | –> | 0191 | GetProcAddress | <–+–+
+——————–+ +——————+ +———————–+ | |
| ForwarderChain | | 80010002h | | |
+——————–+ +——————+ +—> +——————+ | |
| Name | –+ | 0 | | | IMAGE_THUNK_DATA | —+ |
+——————–+ | +——————+ | +——————+ |
| FirstThunk |-+ | | | IMAGE_THUNK_DATA | ——+
+——————–+ | | +——————+ | +——————+
| +–> | kernel32.dll | | | 80010002h |
| +——————+ | +——————+
| | | 0 |
+——————————+ +——————+
还记得前边大家说过在三个PE未有被加载到内部存款和储蓄器中的时候IMAGE_IMPORT_DESCRIPTOR中的
OriginalFirstThunk和FirstThunk是大器晚成律的,那么为何Windows要占用三个字段呢?其实
是那样的,在PE文件被PE加载器加载到内部存款和储蓄器中的时候那些加载器会自动把FirstThunk的值替
换为API函数的着实入口,也正是极度前边jmp的确实地址,而OriginalFirstThunk只然则是
用来反向查找函数名而已。
好了,又讲了那样多是要做什么呢?你立时就能看出。上面大家就来组织我们的导入表。
大家用来下代码来开头大家的引进节:
section ‘.idata’ import data readable
section提醒字表示大家要从头三个新节。.idata是这些新节的名称。import data表示这是
叁个引进节。readable代表那么些节的节属性是只读的。
生机勃勃经大家的次第只必要引入user32.dll中的MessageBoxA函数,那么我们的引进节唯有三个
陈述那几个dll的IMAGE_IMPORT_DESC冠道IPTOCR-V和多个全0的结构。思量如下代码:
dd 0 ; 大家并不要求OriginalFirstThunk
dd 0 ; 我们也不必要管那一个时间戳
dd 0 ; 大家也不保护那些链
dd RVA usr_dll ; 指向大家的DLL名称的福特ExplorerVA
dd RVA usr_thunk ; 指向我们的IMAGE_IMPORT_BY_NAME数组的RVA
; 注意那几个数组也是以0聊起底的
dd 0,0,0,0,0 ; 结束标记
地点用到了贰个ENCOREVA伪指令,它钦赐的地点在编写翻译时被机关写为对应的RubiconVA值。下边定义大家
要引进的动态链接库的名字,那是三个以0最终的字符串:
usr_dll db ‘user32.dll’,0
再有大家的IMAGE_THUNK_DATA:
usr_thunk:
MessageBox dd RVA __imp_MessageBox
dd 0 ; 甘休标记
上面的__imp_MessageBox在编写翻译时由于前边有CR-VVA提醒,所以表示是IMAGE_IMPORT_BY_NAME的
ENCOREVA。下边大家定义那么些布局:
__imp_MessageBox dw 0 ; 大家不按序号导入,所以可以
; 轻巧地置0
db ‘MessageBoxA’,0 ; 导入的函数名
好了,大家完毕了导入表的创立。上边大家来看一个风度翩翩体化的程序,看看四个完整的FASM程序
是何其的美貌 😛
format PE GUI 4.0
entry __start
;
; export section…
;
section ‘.edata’ export data readable
; image export directory
dd 0,0,0,RVA dll_name,0,1,1
dd RVA addr_tab
dd RVA name_tab
dd RVA ordinal_tab
; dll name
dll_name db ‘foo.dll’,0
; function address table
addr_tab dd RVA _foo
; function name table
name_tab dd RVA ex_foo
; export name table
ex_foo db ‘_foo’,0
; ordinal table
ordinal_tab dw 0
5.3、强盛的宏
————-
有关FASM,还应该有二个强大的效应正是宏。大家对宏一定都不面生,上边大家来拜访在FASM中
如何定义宏。假如大家要编写制定二个复制字符串的宏,此中源、指标串由ESI和EDI钦点,大家
可以:
macro @copysz
{
local next_char
next_char:
lodsb
stosb
or al,al
jnz next_char
}
上边我们再来看一个带参数的宏定义:
macro @stosd _dword
{
mov eax,_dword
stosd
}
假定大家要再三存入几个不等的双字大家能够简简单单地在概念宏时把参数用中括号括起来,比
如:
macro @stosd [_dword]
{
mov eax,_dword
stosd
}
如此那般当大家调用@stosd 1,2,3的时候,大家的代码被编写翻译成:
mov eax,1
stosd
mov eax,2
stosd
mov eax,3
stosd
对于这种多参数的宏,FASM提供了八个伪指令common、forward和reverse。他们把宏代码分
成块并各自管理。上边我分别来介绍:
forward节制的块象征指令块对参数进行逐项管理,比如上边的宏,倘诺把下边包车型地铁代码定义在
forward块中,大家能够收获相仿的结果。对于forward块我们可以这么定义
macro @stosd [_dword]
{
forward
mov eax,_dword
stosd
}
reverse和forward适逢其时相反,表示指令块对参数实行反向管理。对于地点的吩咐块借使用
reverse限制,那么大家的参数将被根据相反的逐一存入内部存款和储蓄器。
macro @stosd [_dword]
{
reverse
mov eax,_dword
stosd
}
此时当大家调用@stosd 1,2,3的时候,大家的代码被编写翻译成:
mov eax,3
stosd
mov eax,2
stosd
mov eax,1
stosd
common约束的块将仅被拍卖管理一遍。大家今日编辑一个调用API的宏@invoke:
macro @invoke _api,[_argv]
{
reverse
push _argv
common
call [_api]
}
现今我们得以选用这么些宏来调用API了,比如:
@invoke MessageBox,0,pszText,pszCaption,0
对于宏的利用大家就介绍那一个,更加的多的代码能够参见小编的useful.inc(此中有过多29A的宏,
tnx 29a :P)
;
; relocation section…
;
section ‘.reloc’ fixups data discardable
前后相继的生机勃勃开端用format钦点了PE和GUI,在子系统版本号的前面大家采用了DLL提醒字,表示
那是贰个DLL文件。最后还恐怕有二个at关键字,提示了文本的image base。
次第的最终叁个节是重定位节,对于重定位表作者不做过多解释,有意思味的读者能够参照其余
书籍或随笔。我们能够把刚刚的先后编写翻译成叁个DLL:
fasm foo.asm foo.dll
下边大家编辑一个测量试验程序考验程序的正确性:
#include <windows.h>
int __stdcall WinMain (HINSTANCE,HINSTANCE,LPTSTR,int)
{
HMODULE hFoo=LoadLibrary (“foo.dll”);
FARPROC _foo=GetProcAddress (hFoo,”_foo”);
_foo ();
FreeLibrary (hFoo);
return 0;
}
作者们把编写翻译后的exe和刚刚的dll放在同叁个目录下并运维,看看程序运转是还是不是科学 😛