【pker / CVC.GB】
8、API函数地址的获得
--------------------
回忆一下刚才我们是如何调用API的:首先,引入表是由一系列的IMAGE_IMPORT_DESCRIPTOR
结构组成的,这个结构中有一个FirstThunk字段,它指向一个数组,这个数组中的值在文件
被pe ldr加载到内存后被改写成函数的真正入口。一些编译器在调用API时把后面的地址指向
一个跳转表,这个跳转表中的jmp后面的地址就是FirstThunk中函数的真正入口。对于FASM
编译器,由于PE文件的引入表是由我们自己建立的,所以我们可以直接使用FirstThunk数组
中的值。
无论是哪种情况,总之,call的地址在编译时就被确定了。而我们的病毒代码是要插入到宿
主的代码中去的,所以我们的call指令后面的地址必须是在运行时计算的。那么怎么找到API
函数的地址呢?我们可以到宿主的引入表中去搜索那个对应函数的FirstThunk,但是这样做
有一个问题,我们需要函数并不一定是宿主程序需要的。换句话说,就是可能我们需要的函
数在宿主的引入表中不存在。这使我们不得不考虑别的实现。我们可以直接从模块的导出表
中搜索API的地址。
8.1、暴力搜索kernel32.dll
-------------------------
在kernel32.dll中有两个API -- LoadLibraryA和GetProcAddress。前者用来加载一个动态
链接库,后者用来从一个已加载的动态链接库中找到API的地址。我们只要得到这两个函数
就可以调用任何库中的任意函数了。
在上一节中我们说过,程序被加载后[esp]的值是kernel32.dll中的ExitThread的地址,所以
我们可以肯定kernel32.dll是一定被加载的模块。所以我们第一步就是要找到kernel32.dll
在内存中的基地址。
那么我们从哪里入手呢?我们可以使用硬编码,比如Win2k下一般是77e60000h,WinXP SP1
是77e40000h,SP2是7c800000h等。但是这么做不具有通用性,所以这里我们介绍一个通用
也是现在最流行的方法:暴力搜索kernel32.dll。
大概的思想是这样的:我们只要找到得到任意一个位于kernel32.dll地址空间的地址,从这
个地址向下搜索就一定能得到kernel32.dll的基址。还记得刚才说的那个[esp]么,那个
ExitThread的地址就是位于kernel32.dll中的,我们可以从这里入手。考虑如下代码:
mov edi,[esp] ; get address of kernel32!ExitThread
and edi,0ffff0000h ; base address must be aligned by 1000h
krnl_search:
cmp word [edi],'MZ' ; 'MZ' signature?
jnz not_pe ; it's not a PE, continue searching
lea esi,[edi+3ch] ; point to e_lfanew
lodsd ; get e_lfanew
test eax,0fffff000h ; DOS header+DOS stub mustn't > 4k
jnz not_pe ; it's not a PE, continue searching
add eax,edi ; point to IMAGE_NT_HEADER
cmp word [eax],'PE' ; 'PE' signature?
jnz not_pe ; it's not a PE, continue searching
jmp krnl_found
not_pe:
dec edi
xor di,di ; decrease 4k bytes
cmp edi,70000000h ; the base cannot below 70000000h
jnb krnl_search
xor edi,edi ; base not found
krnl_found:
... ; now EDI contains the kernel base
; zero if not found
程序首先把ExitThread的地址和0ffff0000h相与,因为kernel32.dll在内存中一定是1000h字
节对齐的(什么?为什么?还记得IMAGE_OPTIONAL_HEADER中的SectionAlignment么 :P)。
然后我们比较EDI指向的字单元是不是MZ标识,如果不是那么一定不是一个PE文件的起始位
置;如果是,那么我们就得到e_lfanew。我们先检查这个偏移是不是小于4k,因为这个值一
般是不会大于4k的。如果仍然符合条件,我们把这个值与EDI相加,如果EDI就是kernel32的
基址那么这时相加的结果应该指向IMAGE_NT_HEADER,所以我们检查这个字单元,如果是PE
标识,那么我们可以肯定这就是我们要找的kernel32了;如果不是把EDI的值减少4k并继续
查找。一般kernel32.dll的基址不会低于70000000h的,所以我们可以把这个地址作为下界,
如果低于这个地址我们还没有找到kernel32那么我们可以认为我们找不到kernel32了 :P
但是上面的作为有一些缺陷,因为我们的代码是要插入到宿主体内的,所以我们不能保证在
我们的代码执行前堆栈没有被破坏。假如宿主在我们的代码执行前进行了堆栈操作那么我们
很可能就得不到kernel32.dll了。
还有一个方法,就是遍历SEH链。在SEH链中prev字段为0ffffffffh的ER结构的异常处理例程
是在kernel32.dll中的。所以我们可以找到这个ER结构,然后...
下面我给出一个完整的程序,演示了如何搜索kernel32.dll并显示:
format PE GUI 4.0
entry __start
;
; code section...
;
section '.text' code readable writeable executable
szText: times 20h db 0
;
; _get_krnl_base: get kernel32.dll's base address
;
; input:
; nothing
;
; output:
; edi: base address of kernel32.dll, 0 if not found
;
_get_krnl_base:
mov esi,[fs:0]
visit_seh:
lodsd
inc eax
jz in_krnl
dec eax
xchg esi,eax
jmp visit_seh
in_krnl:
lodsd
xchg eax,edi
and edi,0ffff0000h ; base address must be aligned by 1000h
krnl_search:
cmp word [edi],'MZ' ; 'MZ' signature?
jnz not_pe ; it's not a PE, continue searching
lea esi,[edi+3ch] ; point to e_lfanew
lodsd ; get e_lfanew
test eax,0fffff000h ; DOS header+DOS stub mustn't > 4k
jnz not_pe ; it's not a PE, continue searching
add eax,edi ; point to IMAGE_NT_HEADER
cmp word [eax],'PE' ; 'PE' signature?
jnz not_pe ; it's not a PE, continue searching
jmp krnl_found
not_pe:
dec edi
xor di,di ; decrease 4k bytes
cmp edi,70000000h ; the base cannot below 70000000h
jnb krnl_search
xor edi,edi ; base not found
krnl_found:
ret
;
; main entrance...
;
__start:
call _get_krnl_base
push edi ; now EDI contains the kernel base
call push_format ; zero if not found
db 'kernel32 base = 0x%X',0
push_format:
push szText
call [wsprintf]
add esp,0ch
xor eax,eax
push eax
call push_caption
db 'kernel',0
push_caption:
push szText
push eax
call [MessageBox]
ret
;
; import section...
;
section '.idata' import data readable
; image import descriptor
dd 0,0,0,RVA usr_dll,RVA usr_thunk
dd 0,0,0,0,0
; dll name
usr_dll db 'user32.dll',0
; image thunk data
usr_thunk:
MessageBox dd RVA __imp_MessageBox
wsprintf dd RVA __imp_wsprintf
dd 0
; image import by name
__imp_MessageBox dw 0
db 'MessageBoxA',0
__imp_wsprintf dw 0
db 'wsprintfA',0
8.2、搜索导出表,获取API地址
----------------------------
在开始之前,如果大家对前面导出表的知识还不熟悉,那么请务必再复习一遍,否则后边的
内容会显得很晦涩...
好了,我们继续吧 :P
整个搜索的过程说起来很简单,但做起来很麻烦,让我们一点一点来。首先我们要先导出函
数名称表中找到我们要得到的函数,并记下它在这个数组中的索引值。然后通过这个索引值
在序号数组中找到它对应的序号。最后通过这个序号在导出函数入口表中找到其入口。
下面我们慢慢来。先要匹配函数名。假设edx中存放着kernel32.dll的基址,esi中存放着API
的名称。考虑如下代码:
mov ebx,edx ; save module image base for
; later use
push esi ; save API name
xchg esi,edi
xor ecx,ecx
xor al,al
dec ecx
repnz scasb
neg ecx
dec ecx
push ecx ; save length of the API name
lea edi,[edx+3ch]
add edx,dword [edi] ; edx points to IMAGE_NT_HEADER
push edx ; save IMAGE_NT_HEADER
mov edi,dword [edx+78h] ; edi has the RVA of export table
add edi,ebx ; edi points to export table
lea esi,[edi+18h]
lodsd ; eax get NumberOfNames
push eax ; save NumberOfNames
mov esi,[edi+20h]
add esi,ebx ; now points to name RVA table
xor edx,edx
match_api_name:
lodsd
add eax,ebx
xchg eax,edi ; get a API name
xchg esi,ebp
mov ecx,dword [esp+08h] ; length of API name
mov esi,dword [esp+0ch] ; API name buffer
repz cmpsb
jz api_name_found
xchg esi,ebp
inc edx
cmp edx,dword [esp]
jz api_not_found
jmp match_api_name
上面的代码首先把kernel32.dll的基址复制到ebx中保存,然后计算了API名称的长度(包括
零)并进行匹配,如果匹配成功则edx包含了这个函数在函数名数组中的索引值。下面在序号
数组中通过这个索引值得到这个函数的序号。考虑如下代码:
shl edx,1
mov esi,[esp+04h] ; export table address
mov eax,[esi+24h]
add eax,ebx ; ordinal table
movzx edx,word [eax+edx]
shl edx,2
mov eax,[esi+1ch]
add eax,ebx ; function address table
mov eax,[eax+edx]
add eax,ebx ; found!!!
首先我们可以得到序号数组的RVA,然后把这个值与模块(这里是kernel32.dll)的基地址
相加,这样就得到了数组的内存地址。由于序号数组是WORD型的,所以我们的索引值必须要
乘以2。然后通过这个值在数组中索引到函数在导出函数入口表中的索引。由于这个数组是
DWORD型的,所以我们这个索引要乘以4。我们很容易得到导出函数入口表的内存地址。最后
我们通过刚才的索引得到函数的入口地址。
下面我们看一个完整的代码:
format PE GUI 4.0
entry __start
;
; code section...
;
section '.text' code readable writeable executable
;
; _get_krnl_base: get kernel32.dll's base address
;
; input:
; nothing
;
; output:
; edi: base address of kernel32.dll, zero if not found
;
_get_krnl_base:
mov esi,[fs:0]
visit_seh:
lodsd
inc eax
jz in_krnl
dec eax
xchg esi,eax
jmp visit_seh
in_krnl:
lodsd
xchg eax,edi
and edi,0ffff0000h ; base address must be aligned by 1000h
krnl_search:
cmp word [edi],'MZ' ; 'MZ' signature?
jnz not_pe ; it's not a PE, continue searching
lea esi,[edi+3ch] ; point to e_lfanew
lodsd ; get e_lfanew
test eax,0fffff000h ; DOS header+DOS stub mustn't > 4k
jnz not_pe ; it's not a PE, continue searching
add eax,edi ; point to IMAGE_NT_HEADER
cmp word [eax],'PE' ; 'PE' signature?
jnz not_pe ; it's not a PE, continue searching
jmp krnl_found
not_pe:
dec edi
xor di,di ; decrease 4k bytes
cmp edi,70000000h ; the base cannot below 70000000h
jnb krnl_search
xor edi,edi ; base not found
krnl_found:
ret
;
; _get_apiz: get apiz from a loaded module, something like GetProcAddress
;
; input:
; edx: module handle (module base address)
; esi: API name
;
; output:
; eax: API address, zero if fail
;
_get_apiz:
push ebp
mov ebp,esp
push ebx
push ecx
push edx
push esi
push edi
or edx,edx ; module image base valid?
jz return
mov ebx,edx ; save module image base for
; later use
push esi ; save API name
xchg esi,edi
xor ecx,ecx
xor al,al
dec ecx
repnz scasb
neg ecx
dec ecx
push ecx ; save length of the API name
lea edi,[edx+3ch]
add edx,dword [edi] ; edx points to IMAGE_NT_HEADER
push edx ; save IMAGE_NT_HEADER
mov edi,dword [edx+78h] ; edi has the RVA of export table
add edi,ebx ; edi points to export table
push edi ; save address of export table
lea esi,[edi+18h]
lodsd ; eax get NumberOfNames
push eax ; save NumberOfNames
mov esi,[edi+20h]
add esi,ebx ; now points to name RVA table
xor edx,edx
match_api_name:
lodsd
add eax,ebx
xchg eax,edi ; get a API name
xchg esi,eax
mov ecx,dword [esp+0ch] ; length of API name
mov esi,dword [esp+10h] ; API name buffer
repz cmpsb
jz api_name_found
xchg esi,eax
inc edx
cmp edx,dword [esp]
jz api_not_found
jmp match_api_name
api_not_found:
xor eax,eax
xor edi,edi
jmp return
api_name_found:
shl edx,1
mov esi,[esp+04h] ; export table address
mov eax,[esi+24h]
add eax,ebx ; ordinal table
movzx edx,word [eax+edx]
shl edx,2
mov eax,[esi+1ch]
add eax,ebx ; function address table
mov eax,[eax+edx]
add eax,ebx ; found!!!
return:
add esp,14h
pop edi
pop esi
pop edx
pop ecx
pop ebx
mov esp,ebp
pop ebp
ret
;
; main entrance...
;
__start:
call _get_krnl_base ; get kernel32.dll base address
or edi,edi
jz exit
xchg edi,edx ; edx <-- kernel32.dll's image base
call @f
db 'LoadLibraryA',0
@@:
pop esi ; esi <-- api name
call _get_apiz
or eax,eax
jz exit
mov [__addr_LoadLibrary],eax
call @f
db 'GetProcAddress',0
@@:
pop esi
call _get_apiz
or eax,eax
jz exit
mov [__addr_GetProcAddress],eax
call @f
db 'user32.dll',0
@@:
mov eax,12345678h
__addr_LoadLibrary = $-4
call eax
call @f
db 'MessageBoxA',0
@@:
push eax
mov eax,12345678h
__addr_GetProcAddress = $-4
call eax
xor ecx,ecx
push ecx
call @f
db 'get_apiz',0
@@:
call @f
db 'Can you find the import section from this app ^_^',0
@@:
push ecx
call eax
exit:
ret
8、API函数地址的获得
--------------------
回忆一下刚才我们是如何调用API的:首先,引入表是由一系列的IMAGE_IMPORT_DESCRIPTOR
结构组成的,这个结构中有一个FirstThunk字段,它指向一个数组,这个数组中的值在文件
被pe ldr加载到内存后被改写成函数的真正入口。一些编译器在调用API时把后面的地址指向
一个跳转表,这个跳转表中的jmp后面的地址就是FirstThunk中函数的真正入口。对于FASM
编译器,由于PE文件的引入表是由我们自己建立的,所以我们可以直接使用FirstThunk数组
中的值。
无论是哪种情况,总之,call的地址在编译时就被确定了。而我们的病毒代码是要插入到宿
主的代码中去的,所以我们的call指令后面的地址必须是在运行时计算的。那么怎么找到API
函数的地址呢?我们可以到宿主的引入表中去搜索那个对应函数的FirstThunk,但是这样做
有一个问题,我们需要函数并不一定是宿主程序需要的。换句话说,就是可能我们需要的函
数在宿主的引入表中不存在。这使我们不得不考虑别的实现。我们可以直接从模块的导出表
中搜索API的地址。
8.1、暴力搜索kernel32.dll
-------------------------
在kernel32.dll中有两个API -- LoadLibraryA和GetProcAddress。前者用来加载一个动态
链接库,后者用来从一个已加载的动态链接库中找到API的地址。我们只要得到这两个函数
就可以调用任何库中的任意函数了。
在上一节中我们说过,程序被加载后[esp]的值是kernel32.dll中的ExitThread的地址,所以
我们可以肯定kernel32.dll是一定被加载的模块。所以我们第一步就是要找到kernel32.dll
在内存中的基地址。
那么我们从哪里入手呢?我们可以使用硬编码,比如Win2k下一般是77e60000h,WinXP SP1
是77e40000h,SP2是7c800000h等。但是这么做不具有通用性,所以这里我们介绍一个通用
也是现在最流行的方法:暴力搜索kernel32.dll。
大概的思想是这样的:我们只要找到得到任意一个位于kernel32.dll地址空间的地址,从这
个地址向下搜索就一定能得到kernel32.dll的基址。还记得刚才说的那个[esp]么,那个
ExitThread的地址就是位于kernel32.dll中的,我们可以从这里入手。考虑如下代码:
mov edi,[esp] ; get address of kernel32!ExitThread
and edi,0ffff0000h ; base address must be aligned by 1000h
krnl_search:
cmp word [edi],'MZ' ; 'MZ' signature?
jnz not_pe ; it's not a PE, continue searching
lea esi,[edi+3ch] ; point to e_lfanew
lodsd ; get e_lfanew
test eax,0fffff000h ; DOS header+DOS stub mustn't > 4k
jnz not_pe ; it's not a PE, continue searching
add eax,edi ; point to IMAGE_NT_HEADER
cmp word [eax],'PE' ; 'PE' signature?
jnz not_pe ; it's not a PE, continue searching
jmp krnl_found
not_pe:
dec edi
xor di,di ; decrease 4k bytes
cmp edi,70000000h ; the base cannot below 70000000h
jnb krnl_search
xor edi,edi ; base not found
krnl_found:
... ; now EDI contains the kernel base
; zero if not found
程序首先把ExitThread的地址和0ffff0000h相与,因为kernel32.dll在内存中一定是1000h字
节对齐的(什么?为什么?还记得IMAGE_OPTIONAL_HEADER中的SectionAlignment么 :P)。
然后我们比较EDI指向的字单元是不是MZ标识,如果不是那么一定不是一个PE文件的起始位
置;如果是,那么我们就得到e_lfanew。我们先检查这个偏移是不是小于4k,因为这个值一
般是不会大于4k的。如果仍然符合条件,我们把这个值与EDI相加,如果EDI就是kernel32的
基址那么这时相加的结果应该指向IMAGE_NT_HEADER,所以我们检查这个字单元,如果是PE
标识,那么我们可以肯定这就是我们要找的kernel32了;如果不是把EDI的值减少4k并继续
查找。一般kernel32.dll的基址不会低于70000000h的,所以我们可以把这个地址作为下界,
如果低于这个地址我们还没有找到kernel32那么我们可以认为我们找不到kernel32了 :P
但是上面的作为有一些缺陷,因为我们的代码是要插入到宿主体内的,所以我们不能保证在
我们的代码执行前堆栈没有被破坏。假如宿主在我们的代码执行前进行了堆栈操作那么我们
很可能就得不到kernel32.dll了。
还有一个方法,就是遍历SEH链。在SEH链中prev字段为0ffffffffh的ER结构的异常处理例程
是在kernel32.dll中的。所以我们可以找到这个ER结构,然后...
下面我给出一个完整的程序,演示了如何搜索kernel32.dll并显示:
format PE GUI 4.0
entry __start
;
; code section...
;
section '.text' code readable writeable executable
szText: times 20h db 0
;
; _get_krnl_base: get kernel32.dll's base address
;
; input:
; nothing
;
; output:
; edi: base address of kernel32.dll, 0 if not found
;
_get_krnl_base:
mov esi,[fs:0]
visit_seh:
lodsd
inc eax
jz in_krnl
dec eax
xchg esi,eax
jmp visit_seh
in_krnl:
lodsd
xchg eax,edi
and edi,0ffff0000h ; base address must be aligned by 1000h
krnl_search:
cmp word [edi],'MZ' ; 'MZ' signature?
jnz not_pe ; it's not a PE, continue searching
lea esi,[edi+3ch] ; point to e_lfanew
lodsd ; get e_lfanew
test eax,0fffff000h ; DOS header+DOS stub mustn't > 4k
jnz not_pe ; it's not a PE, continue searching
add eax,edi ; point to IMAGE_NT_HEADER
cmp word [eax],'PE' ; 'PE' signature?
jnz not_pe ; it's not a PE, continue searching
jmp krnl_found
not_pe:
dec edi
xor di,di ; decrease 4k bytes
cmp edi,70000000h ; the base cannot below 70000000h
jnb krnl_search
xor edi,edi ; base not found
krnl_found:
ret
;
; main entrance...
;
__start:
call _get_krnl_base
push edi ; now EDI contains the kernel base
call push_format ; zero if not found
db 'kernel32 base = 0x%X',0
push_format:
push szText
call [wsprintf]
add esp,0ch
xor eax,eax
push eax
call push_caption
db 'kernel',0
push_caption:
push szText
push eax
call [MessageBox]
ret
;
; import section...
;
section '.idata' import data readable
; image import descriptor
dd 0,0,0,RVA usr_dll,RVA usr_thunk
dd 0,0,0,0,0
; dll name
usr_dll db 'user32.dll',0
; image thunk data
usr_thunk:
MessageBox dd RVA __imp_MessageBox
wsprintf dd RVA __imp_wsprintf
dd 0
; image import by name
__imp_MessageBox dw 0
db 'MessageBoxA',0
__imp_wsprintf dw 0
db 'wsprintfA',0
8.2、搜索导出表,获取API地址
----------------------------
在开始之前,如果大家对前面导出表的知识还不熟悉,那么请务必再复习一遍,否则后边的
内容会显得很晦涩...
好了,我们继续吧 :P
整个搜索的过程说起来很简单,但做起来很麻烦,让我们一点一点来。首先我们要先导出函
数名称表中找到我们要得到的函数,并记下它在这个数组中的索引值。然后通过这个索引值
在序号数组中找到它对应的序号。最后通过这个序号在导出函数入口表中找到其入口。
下面我们慢慢来。先要匹配函数名。假设edx中存放着kernel32.dll的基址,esi中存放着API
的名称。考虑如下代码:
mov ebx,edx ; save module image base for
; later use
push esi ; save API name
xchg esi,edi
xor ecx,ecx
xor al,al
dec ecx
repnz scasb
neg ecx
dec ecx
push ecx ; save length of the API name
lea edi,[edx+3ch]
add edx,dword [edi] ; edx points to IMAGE_NT_HEADER
push edx ; save IMAGE_NT_HEADER
mov edi,dword [edx+78h] ; edi has the RVA of export table
add edi,ebx ; edi points to export table
lea esi,[edi+18h]
lodsd ; eax get NumberOfNames
push eax ; save NumberOfNames
mov esi,[edi+20h]
add esi,ebx ; now points to name RVA table
xor edx,edx
match_api_name:
lodsd
add eax,ebx
xchg eax,edi ; get a API name
xchg esi,ebp
mov ecx,dword [esp+08h] ; length of API name
mov esi,dword [esp+0ch] ; API name buffer
repz cmpsb
jz api_name_found
xchg esi,ebp
inc edx
cmp edx,dword [esp]
jz api_not_found
jmp match_api_name
上面的代码首先把kernel32.dll的基址复制到ebx中保存,然后计算了API名称的长度(包括
零)并进行匹配,如果匹配成功则edx包含了这个函数在函数名数组中的索引值。下面在序号
数组中通过这个索引值得到这个函数的序号。考虑如下代码:
shl edx,1
mov esi,[esp+04h] ; export table address
mov eax,[esi+24h]
add eax,ebx ; ordinal table
movzx edx,word [eax+edx]
shl edx,2
mov eax,[esi+1ch]
add eax,ebx ; function address table
mov eax,[eax+edx]
add eax,ebx ; found!!!
首先我们可以得到序号数组的RVA,然后把这个值与模块(这里是kernel32.dll)的基地址
相加,这样就得到了数组的内存地址。由于序号数组是WORD型的,所以我们的索引值必须要
乘以2。然后通过这个值在数组中索引到函数在导出函数入口表中的索引。由于这个数组是
DWORD型的,所以我们这个索引要乘以4。我们很容易得到导出函数入口表的内存地址。最后
我们通过刚才的索引得到函数的入口地址。
下面我们看一个完整的代码:
format PE GUI 4.0
entry __start
;
; code section...
;
section '.text' code readable writeable executable
;
; _get_krnl_base: get kernel32.dll's base address
;
; input:
; nothing
;
; output:
; edi: base address of kernel32.dll, zero if not found
;
_get_krnl_base:
mov esi,[fs:0]
visit_seh:
lodsd
inc eax
jz in_krnl
dec eax
xchg esi,eax
jmp visit_seh
in_krnl:
lodsd
xchg eax,edi
and edi,0ffff0000h ; base address must be aligned by 1000h
krnl_search:
cmp word [edi],'MZ' ; 'MZ' signature?
jnz not_pe ; it's not a PE, continue searching
lea esi,[edi+3ch] ; point to e_lfanew
lodsd ; get e_lfanew
test eax,0fffff000h ; DOS header+DOS stub mustn't > 4k
jnz not_pe ; it's not a PE, continue searching
add eax,edi ; point to IMAGE_NT_HEADER
cmp word [eax],'PE' ; 'PE' signature?
jnz not_pe ; it's not a PE, continue searching
jmp krnl_found
not_pe:
dec edi
xor di,di ; decrease 4k bytes
cmp edi,70000000h ; the base cannot below 70000000h
jnb krnl_search
xor edi,edi ; base not found
krnl_found:
ret
;
; _get_apiz: get apiz from a loaded module, something like GetProcAddress
;
; input:
; edx: module handle (module base address)
; esi: API name
;
; output:
; eax: API address, zero if fail
;
_get_apiz:
push ebp
mov ebp,esp
push ebx
push ecx
push edx
push esi
push edi
or edx,edx ; module image base valid?
jz return
mov ebx,edx ; save module image base for
; later use
push esi ; save API name
xchg esi,edi
xor ecx,ecx
xor al,al
dec ecx
repnz scasb
neg ecx
dec ecx
push ecx ; save length of the API name
lea edi,[edx+3ch]
add edx,dword [edi] ; edx points to IMAGE_NT_HEADER
push edx ; save IMAGE_NT_HEADER
mov edi,dword [edx+78h] ; edi has the RVA of export table
add edi,ebx ; edi points to export table
push edi ; save address of export table
lea esi,[edi+18h]
lodsd ; eax get NumberOfNames
push eax ; save NumberOfNames
mov esi,[edi+20h]
add esi,ebx ; now points to name RVA table
xor edx,edx
match_api_name:
lodsd
add eax,ebx
xchg eax,edi ; get a API name
xchg esi,eax
mov ecx,dword [esp+0ch] ; length of API name
mov esi,dword [esp+10h] ; API name buffer
repz cmpsb
jz api_name_found
xchg esi,eax
inc edx
cmp edx,dword [esp]
jz api_not_found
jmp match_api_name
api_not_found:
xor eax,eax
xor edi,edi
jmp return
api_name_found:
shl edx,1
mov esi,[esp+04h] ; export table address
mov eax,[esi+24h]
add eax,ebx ; ordinal table
movzx edx,word [eax+edx]
shl edx,2
mov eax,[esi+1ch]
add eax,ebx ; function address table
mov eax,[eax+edx]
add eax,ebx ; found!!!
return:
add esp,14h
pop edi
pop esi
pop edx
pop ecx
pop ebx
mov esp,ebp
pop ebp
ret
;
; main entrance...
;
__start:
call _get_krnl_base ; get kernel32.dll base address
or edi,edi
jz exit
xchg edi,edx ; edx <-- kernel32.dll's image base
call @f
db 'LoadLibraryA',0
@@:
pop esi ; esi <-- api name
call _get_apiz
or eax,eax
jz exit
mov [__addr_LoadLibrary],eax
call @f
db 'GetProcAddress',0
@@:
pop esi
call _get_apiz
or eax,eax
jz exit
mov [__addr_GetProcAddress],eax
call @f
db 'user32.dll',0
@@:
mov eax,12345678h
__addr_LoadLibrary = $-4
call eax
call @f
db 'MessageBoxA',0
@@:
push eax
mov eax,12345678h
__addr_GetProcAddress = $-4
call eax
xor ecx,ecx
push ecx
call @f
db 'get_apiz',0
@@:
call @f
db 'Can you find the import section from this app ^_^',0
@@:
push ecx
call eax
exit:
ret