企业即时通讯编写教程
;------从这里开始剪切----------------------------------------------------------------- szUSER32 db "USER32.dll",0 ; USER32.DLL ASCIIz string a_User32 dd 00000000h ; Variables needed .code RNG_test: rdtsc lea eax,dword ptr [ebp+szUSER32] or eax,eax mov dword ptr [ebp+a_User32],eax push 32512 mov dword ptr [ebp+h_icon],eax .386 extrn LoadLibraryA:PROC ; All the APIs needed by the ---------------------------------------- CreateThread函数在调用进程的地址空间中创建一个线程执行。 HANDLE CreateThread( 参数 ?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程序调用的时候。 ---------------------------------------- mov ecx,00000100h ; Put 256 icons in the screen loop_payload: push eax exit_payload: ; RNG - This example is by GriYo/29A (see Win32.Marburg) random proc get_rnd_range proc end RNG_test .386 ; Blah .即时 shit: buffer db 00h .code Silly_I: lea edi,buffer ; Pointer to the buffer end Silly_I ;------到这里为止剪切----------------------------------------------------------------- 编译上面地代码,看看发生了什么。呵?我知道它不是什么事情也没做。但是你看到了,你产生了代码,不是直接编写的,而且我给你表明了你从0开始初始代码,并想想可能性,你可以从一个什么也没有的缓冲区里面初始一整个有用的代码。这是多态引擎代码(不是多态引擎产生的代码)怎样初始解密代码的基本概念。所以,想象一下我们要编写如下的指令: mov ecx,virus_size 那么,从上面的代码产生的解密程序将会这样: mov al,0B9h ; MOV ECX,imm32 opcode OK,然后你已经产生了它应该是什么模样的代码,但是你意识到了在真正的代码中加一些什么也不做的指令非常简单,通过使用同样的方法。你可以用一个字节的指令实验一下,例如,看看它的兼容能力。 ;------从这里开始剪切----------------------------------------------------------------- .386 ; Blah virus_size equ 12345678h ; Fake 即时 .即时 db 00h .code Silly_II: lea edi,buffer ; Pointer to the buffer mov al,0B9h ; MOV ECX,imm32 opcode call onebyte mov al,0BFh ; MOV EDI,offset32 opcode call onebyte mov al,0B8h ; MOV EAX,imm32 opcode call onebyte mov ax,0731h ; XOR [EDI],EAX opcode mov ax,0C783h ; ADD EDI,imm32 (>7F) opcode mov ax,0F9E2h ; LOOP @@1 opcode ret random: onebyte: one_table label byte ; One-byters table buffer db 100h dup (90h) ; A simple buffer end Silly_II ;------到这里为止剪切----------------------------------------------------------------- 呵呵,我建立了一个很弱的3级,比2级强一些的多态引擎:)寄存器交换将在后面解释,因为它随着操作码格式变。但是我在这个小子章节里的目标达到了:你现在应该知道了我们想要做什么。想象一下你使用两个字节而不是一个字节,如PUSH REG/POP REG, CLI/STI, 等等。 %“真正”代码产生% mov ecx,virus_size ; (1) 为了达到同样的目的,但是用不同的代码,许多事情可以做,而且这是我们的目标。例如,前3个指令可以以其它的顺序排列,而且结果不会改变,所以你可以创建一个使它们的顺序随机的函数。而且我们可以使用其它的寄存器,没有任何问题。而且我们可以使用一个dec/jnz来取代一个loop...等,等,等... - 你的代码应该能够产生,例如,如下的能够处理一个简单指令,让我们想象一下,第一个mov: mov ecx,virus_size 或者 push virus_size 或者 mov ecx,not (virus_size) 或者 mov ecx,(virus_size xor 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个寄存器来对解密程序产生的代码: --------------------------------------- @@1: mov eax,8 ; Get a random reg cmp eax,4 ; Is ESP? mov byte ptr [ebp+base],al ; Store it @@2: mov eax,8 ; Get a random reg cmp eax,4 ; Is ESP? cmp eax,ebx ; Is equal to base pointer? mov byte ptr [ebp+count],al ; Store it @@3: mov eax,8 ; Get random reg cmp eax,4 ; Is it ESP? cmp eax,ebx ; Is equal to base ptr reg? cmp eax,ecx ; Is equal to counter reg? mov byte ptr [ebp+key],al ; Store it ret InitPoly endp ------------------------------------ %垃圾的产生% - 产生现实代码,以合法的应用代码面目出现。例如,GriYo的引擎。 - 产生尽可能多的代码,以一个破坏的文件面目出现。例如,Mental Driller的 MeDriPoLen(看看 Squatter)。 Ok,让我们开始吧: ?两个的共同点: - 用很多不同方式调用(调用中嵌调用再嵌调用...) ?现实主义: 一些现实的东西是那些看起来真实的东西,虽然它并不是。对于这个我打算解释如下:如果你看到大量的没有CALL和JUMP的代码你会怎么想?如果在一个CMP后面没有一个条件跳转你会怎么想?它几乎是不可能的,正如你,我和反企业即时通讯者知道的。所以我们必须有能力产生所有这些类型的垃圾结构: - CMP/条件跳转 ?精神摧毁...恩...象破坏代码: 这个当解密程序充满了无意义的操作码看起来不像代码的时候发生,也就是说不符合以前列出来的规则的代码,而且使用协处理器的不做任何事情的指令,当然了,使用的操作码越多越好。 -=?-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?-=? 现在,我将试图解释代码产生的所有要点。首先,让我们以和它们相关的所有东西开始,CALL和无条件跳转。 ?首先一点,CALL,它非常简单。你可以做成调用子例程,通过许多方式: |Figure 1 -------| |Figure 2 -------| |Figure 3 -------| 当然你可以把所有的都混合起来,而且结果是,你有许多方式在一个解密程序内部编写一个子例程。而且,毫无疑问,你可以反过来(你将会听到我对它提更多的次数),而且可能在另外的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, 只要在企业即时通讯的开始放这两个指令来重置协处理器: fwait Mental Driller现在正偏向于现实主义了(据我所知)由他的最近的令人印象深刻的引擎(TUAREG),所以... % 指令建立 % 寄存器二进制形式 | 000 001 010 011 100 101 110 111 我认为在写我的《Virus Writing Guides for MS-DOS》时候,所犯的大错误是在我的解释OpCodes 结构部分,和所有那些东西。这里我想要描述的是许多"你自己做",就像我在写一个多态引擎时那样。只以一个XOR操作码为例... xor edx,12345678h -> 81 F2 78563412 你看到了不同了吗?我习惯利用一个调试器,然后写我想要用一些寄存器构造代码,看看有什么改变。OK,正如你能看到的(嗨!你没瞎吧?),改变的字节是第二个。现在是有趣的部分了:把值变成二进制形式。 F2 -> 11 110 010 OK,你看到了什么改变了吗?最后3个bit,对吗?好了,现在到我把寄存器以二进制表示的部分:)正如你已经发现的,这3个bit根据寄存器的改变而改变了。所以... 010 -> EDX 寄存器 只要试着把那3个比特赋其它的二进制值,你将会发现寄存器是怎么改变的。但是要小心...不要使用用这个操作码EAX值(000),因为,所有的算术指令,都对EAX优化了,因此要彻底地改变操作码。 所以,调试所有你想要的构造,看看它们之间的关系,并建立产生任何东西的可靠的代码。它非常简单! % Recursivity % PolyTable: 并想象一下你有在它们之中选择的如下例程: GenGarbage: 现在想象一下你的'GenerateCALL'指令从内部调用'GenGarbage'例程。呵呵'GenGarbage'可以再次调用'GenerateCALL',并再次,然后再次(取决于RNG),所以你将有CALL在CALL中在CALL中...我已经在那件事情之前提了一个限度仅仅是为了避免速度问题,但是它可以用这些新的 GenGarbage: mov eax,EndPolyTable-PolyTable GarbageExit: 所以,我们的引擎将能产生巨大数量的充满这种CALL的垃圾代码;)当然了,这个还可以在PUSH和POP间利用:) %最后的话% 【高级Win32 技术】 - Structured Exception Handler(SEH) % Structured Exception Handler % ;--------从这里开始剪切------------------------------------------------------- .386p extrn MessageBoxA:PROC ; Defined APIs .即时 szTitle db "Structured Exception Handler [SEH]",0 .code start: errorhandler: pop dword ptr fs:[0000h] ; Restore old SEH handler push 1010h ; Parameters for MessageBoxA push 00h setupSEH: end start ;--------到这里为止剪切------------------------------------------------------- 正如在"Win32反调试"那一章所看到的,除此之外SEH还有另外一个特色:)它愚弄了大多数应用级的调试器。为了使你的设置一个新的SEH handler更简单,这里你可以用一些宏来做做这个(hi,Jacky!): ; Put SEH - Sets a new SEH handler pseh macro what2do ; Restore SEH - Restore old SEH handler rseh macro 它的用法非常简单。例如: pseh <jmp SEH_handler> 下面的代码,如果执行了,将会在'rseh'宏之后继续,而不是终止进程。清楚了吗?:) %多线程% 一个多任务的过程的主要算法是: 1.创建你想要运行的相关线程的代码 这个看起来很难,但是有两个API可以救我们。它们的名字:CreateThread 和 WaitForSingleObject。让我们看看Win32 API列表关于这两个API是怎么说的... WaitForSingleObject函数当如下的情况发生的时候返回: ?指定的对象在signaled状态。 DWORD WaitForSingleObject( 参数 ?hHandle:识别对象。对一个对象类型的列表,它的句柄可以指定,看看接下来的评论。 Windows NT:句柄必须有SYNCHRONIZE访问。想知道更多的信息,看看Access Masks and Access Rights(访问标志和访问权限)。 ?dwMilliseconds: 指定过期间隔,以毫秒形式。如果间隔过了,甚至对象的状态是nonsignaled,这个函数就返回。如果dwMilliseconds是0,这个函数就测试对象的状态并立即返回。如果dwMilliseconds是INFINITE这个函数从不会过期。 返回值 ?如果函数成功了,返回值表明了导致函数返回的事件。 ?如果函数失败了,返回值是WAIT_FAILED。为了获得详细的错误信息,调用GetLastError。 ---------------------------------------- ;-------从这里开始剪切------------------------------------------------------ extrn CreateThread:PROC .即时 lpParameter dd 00000000h .code multitask: ; EAX = Thread handle push 00h ; 'Parent Process' blah blah push 0FFh ; Wait infinite seconds push 00h ; Exit program child_process: end multitask ;-------到这里为止剪切------------------------------------------------------ 如果你测试上述代码,你将会发现,如果你单击了在子进程中的'Accept'按钮,那么你将不得不去单击在父进程中的'Accept'按钮。是不是很有意思呀?如果父进程死了,所有相关的线程和它一起死了,但是不过子进程死了,父进程还仍然存活者。 所以看到你可以通过父进程和子进程通过WaitForSingleObject控制两个进程相当有趣。想象一下可能性:在目录里搜索一个特定文件(如MIRC.INI)同时你在产生一个多态解密程序,并解包余下的东西...哇! ;) 看看Benny的关于Threads 和 Fibers (29A#4)的教程。 % CRC32 (IT/ET) % |