hkfans007 发表于 2010-3-17 22:41

菜鸟见识到的一些指令猥琐方法

【前言】:

只是介绍一些调试execryptor外壳遇到的一些指令猥琐的处理方法....专业术语我也不懂怎么叫,只知道长那个样。。
总的来说。。这些方法拼在一起。。除非有强大的耐力和精力。要不用F7我看是会吐血。。。程序的方法生成下面这些猥琐

我想同样是要用程序的方法来除掉他们。。只是这个程序的编写也是相当不简单(个人感觉)...想要越简单越容易扩展,就越难编写....

期待大牛写出一个强大的工具对付这些猥琐...


【阅读对象】
和我一样的菜鸟。。也许大牛都见多不怪。。他们都有对付这个的法宝了。哈哈!!!


==============================================================================================================
正题:


主要包括以下几种:

1. 代码乱序
2. 代码变形
3. 垃圾代码         ===========> 我暂时没遇到.. 不过可想而知,也是非常恶心的一种方法.. (久闻其大名)..
4. VM           ===========> 这个可以用在大部分的指令... 所以下面的介绍乱序还是变形都可以再使用VM,导致代码变形无法匹配一个变形模板..


【代码乱序方法】

第一种:jmp
不断的E9远跳.. 跳来跳去的。。就够你爽了。。如果说用F7一步步的跟踪。。估计要吐血。。这个方法对付F7跟踪的就够有效了。。当然这个可以被程序或者脚本处理简简单单的处理掉

eg.
0053CC7C                        jmp   00512830

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

第二种: push ret   --> 同时这两条指令之间可以加入 jmp .或者其他的 变形跳转指令
push 返回地址
ret

eg.
00628387   68 D5886200         push    006288D5
006296AC   C3                  retn

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

第三种:    ===> 这三条指令之间同样可以穿插 pushret 或者 jmp 指令...

Call 地址
xchg dword ptr , 寄存器
pop 上面使用的寄存器...

eg.
0053A18F    E8 0E1E0000      call    0053BFA2                        
0053BFA2    871C24             xchg    dword ptr , ebx            
005448F9                     pop   ebx                           

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

第四种:==> SEH , 这种方法既可以 检测硬件断点,同时又可以打乱程序流程,使得前后真正有用的指令无法衔接起来看。。达到反跟踪的目的

注意:这些指令任何两条指令之间都可以穿插前面的3种 乱序方法....


eg.


大概长的样子如下面:
callpush fs:mov fs:, esp
然后就是 int1int3 异常指令

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA   ---> 真正有用的指令

0062880B   E8 530E0000         call    00629663               
00629663   67:64:FF36 0000       push    dword ptr fs:
00629669   67:64:8926 0000       mov   dword ptr fs:, esp
006294CC    64:F1                int1                                        // 造成异常。。



// SEH 异常处理函数的样子如下

00628871   51                  push    ecx
00629488   8BCC                  mov   ecx, esp
0062948A   81C1 10000000         add   ecx, 10
00629490   8B09                  mov   ecx, dword ptr
00629492   C701 13000100         mov   dword ptr , 10013                // ecx --> CONTEXT
00628F42   81C1 18000000         add   ecx, 18                        //ecx + 18h--> Dr7

00628F48   8A01                  mov   al, byte ptr                 // 如果有硬件断点的话,Dr7 不等于0
00628F4A   81C1 9C000000         add   ecx, 9C                        // context.ebp
00628F50   0001                  add   byte ptr , al                // context.ebp + context.dr7 (dr7不为0造成堆栈错误)

00629649   81C1 04000000         add   ecx, 4                                // context.eip

// 这里要看如何分析得到 SEH处理完后的返回地址...

0062964F   C701 F5906200         mov   dword ptr , 006290F5        // context.eip = 006290F5
00629655   33C0                  xor   eax, eax
00629657   59                  pop   ecx
00629658   C3                  retn

// 平衡堆栈,去掉 SEH--> 和前面的对应..

006290F5    67:64:8F06 0000 pop   dword ptr fs:                        // 去掉 fs:
006290FB    870424          xchg    dword ptr , eax                // 下面两句去掉 SEH处理函数地址
006290FE    58            pop   eax


// 从这里往后才是接有用的代码...

BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB   --> 真正有用的指令...


===> 从上面可以看到 AAA 和 BBB 这两种真正有用的指令被一个SEH隔开了,这样,我们思维就被SEH干扰了。。没办法将AAA 和 BBB 指令
===> 连起来看.. 就无法理解程序的真正是要做什么的...


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

第五种:

这一种我也不知道叫什么名字。。但是看起来蛮有用的。。和SEH反跟踪的方法很像。。只不过这个可以检测INT3软件断点...
两者刚好可以互补..

代码样式如下:

00520E47    68 65CF4C0D         push    0D4CCF65                        ; 这句是真正有用的代码...

********************************
00520E4C    E8 08EA0200         call    0054F859                        ;
0054F859    870C24                xchg    dword ptr , ecx            ; 将返回地址保存到寄存器中

005479CB    50                  push    eax                           
005479CC    33C0                  xor   eax, eax                        
005479CE    8A01                  mov   al, byte ptr             ; 本意是要检测 Int3断点

005479D0                        push    ecx                           ;
0051EA56                        mov   ecx, 99                         ; ecx = 99
004F19EE                        sub   eax, ecx                        ; VM Code
004F19F7    59                  pop   ecx      
                     
004F19F8                        imul    byte ptr                   ; VM Code
004F1A01    80C0 5C               add   al, 5C                        

00524988                        nop                                     ; 原本这里可以检测CC软件断点的... 不知道为何这个壳不做。。

00524995                        pop   eax                           ;

0052499F    53                  push    ebx                           
0054D6CE                        mov   ebx, 4EEAC0                     ; ebx = 4EEAC0 ==> 用这个保存前面CALL返回地址
004F37E1                        jmp   004F1A09                        ; VM Code
004F1A0A                        mov   dword ptr , ecx            ; VM Code
004F1A13    5B                  pop   ebx                           

004F1A14    59                  pop   ecx                           ; 这个对应 00520E4C 和 0054F859 这两行.. (前面介绍的第三种乱序方法)


004F1A15                        pop   esi                           ; 对应 push    0D4CCF65(真正有用的)

004F1A1E    FF35 C0EA4E00         push    dword ptr             ; 用这种方式返回..
0054300A                        retn                                    ;
**********************************


最终有用的代码就只有下面两句:


00520E47    68 65CF4C0D         push    0D4CCF65                        ; 这句是真正有用的代码...
004F1A15                        pop   esi                           ; 对应 push    0D4CCF65(真正有用的)


可见: 这种方法也起到了反跟踪的作用。。真正有用的代码无法连在一起看。。被分隔开了。。。同样也可以检测CC断点...

============================================================================================================================================

【代码变形】

下面列出几种见到的样式.... 通过这些变形。。一条指令变成5条甚至 10条。。中间又不断穿插那些代码乱序指令,还可以对任何指令践行VM... 所以你要先除掉VM,
然后在除掉乱序 再整理出那些变形指令。。然后在将变形还原回来。。....吐血!!!

还是这些变形可以不断的扩展。。

        //------------------------------------------------------
        // push ebx( 8 registers-- each for one register )
        // mov ebx, ebp
        // xchg dword ptr , ebx       
        //
        //--> push ebp




        //---------------------------------------------
        // xchg dword ptr , ebp
        // movecx, ebp
        // popebp
        //
        // ===> pop ecx




        //---------------------------------------------
        // push ebx      ( 8 REGISTERS-- EACH FOR ONE REGISTER )
        // push 9EC3461A
        // popebx
        // subebx, FFB54740
        // andebx, DEE9A4C4
        // addebx, 6258f045
        // xchg dword ptr , ebx
        //
        // ---> push XXXXXX


        //------------------------------------------------
        // push eax
        // push 5B7761EA
        // popeax
        // subeax, CF129CAB
        // jb   0053B479
        // addeax, 87ABB20D
        // addedx, eax       ---> 这里可以是其他的算术指令 ( xor or add sub .... )
        // pop   eax
        //
        //----> add edx, XXXXXXXX



        //--------------------------------------------------
        // push 3A20A71
        // popeax
        // or   eax, DAD460A2
        // addeax, 1FDEF9BB
        // xoreax, E16E3DA0
        // addeax, D353C036
        //
        //===> mov eax, XXXXXXX



        //--------------------------------------
        // push eax
        // popedx
        //
        // --> mov edx, eax

=========================================================================================================

【VM】

这个可以将上面的变形 乱序的指令 VM掉。。或者其他有用的指令VM掉。。。 结合起来用的。。可以想象,处理起来是挺费劲的

这个壳的VM很早softworm就分析了。。 呵呵,我等到现在才来学!!差距好远啊,努力学习!!。。。。


参考:http://bbs.pediy.com/showthread.php?p=117830


贴一段自己分析并且编写处理这个简单VM的代码。。。 从代码可以看出它VM的指令类型有哪些:



// get decrypt data first (it is a DWORD type data) --> opcode is the last byte of decryptData
        decryptData = decryptInstructionData( originalData, eflags );
       

        if( decryptData & 0xE0000000 )
        {
                decryptData = 0x3F;                // NOP.. but 'vmDataSize = 8'
                vmDataSize = 8;
        }
        else
        {
                if( decryptData & 0xC0 )
                {
                        // get 32 bit immediate number
                        Readmemory(&originalData2, address + 4, 4, MM_SILENT);
                        DWORD high5 = originalData2 & 0xFFFFF;
                        DWORD low3= (decryptData >> 8) & 0xFFF;

                        high5 <<= 0xC;
                        Immediate = high5 | low3;

                        // vmcode size
                        vmDataSize = 8;
                }
        }

        byteCode    = decryptData & 0xFF;
       
        //=======================================================================

        if( byteCode == 0 )
        {
                // push Reg32
                int regIdx = (decryptData >> 8) & 7;
                sprintf( disasm, "push    %s", gRegisterNames32 );

        }
        else if( byteCode == 1 )
        {
                // pop Reg32
                int regIdx = (decryptData >> 8 ) & 7;
                sprintf( disasm, "pop   %s", gRegisterNames32 );
        }
        else if( byteCode == 2 )
        {
                // mov Reg32, Reg32
                int srcReg        = (decryptData >> 8) & 7;
                int destReg        = (decryptData >> 11) & 7;

                sprintf( disasm, "mov   %s, %s", gRegisterNames32, gRegisterNames32 );

        }
        else if( byteCode == 3 )
        {
                // mov dword ptr , reg32
                int srcReg        = (decryptData >> 8) & 7;
                int destReg        = (decryptData >> 11) & 7;

                sprintf( disasm, "mov   dword ptr [%s], %s", gRegisterNames32, gRegisterNames32 );
        }
        else if( byteCode == 4 )
        {
                // mov Reg32, dword ptr
                int srcReg        = (decryptData >> 8) & 7;
                int destReg        = (decryptData >> 11) & 7;

                sprintf( disasm, "mov   %s, dword ptr [%s]", gRegisterNames32, gRegisterNames32 );
        }
        else if( byteCode == 5 )
        {
                // xchg dword ptr , Reg32
                int regIdx = (decryptData >> 8) & 7;
                sprintf( disasm, "xchg    dword ptr , %s", gRegisterNames32 );

        }
        else if( byteCode == 6 )
        {
                // retn
                sprintf( disasm, "retn");
        }
        else if( byteCode == 7 )
        {
                // not Reg32
                int regIdx = (decryptData >> 8) & 7;
                sprintf( disasm, "not   %s", gRegisterNames32 );
        }
        else if( byteCode == 0x10 )
        {
                // pushfd
                sprintf( disasm, "pushfd");
        }
        else if( byteCode == 0x11 )
        {
                // popfd
                sprintf( disasm, "popfd");       
        }
        else if( byteCode == 0x12 )
        {
                // test reg32, reg32
                int srcReg        = (decryptData>>8) & 7;
                int destReg        = (decryptData>>11) & 7;

                sprintf( disasm, "test   %s, %s", gRegisterNames32, gRegisterNames32 );

        }
        else if( byteCode == 0x13 )
        {

        }
        else if( byteCode == 0x14 )
        {

        }
        else if( byteCode == 0x15 )
        {
                // mov byte ptr , 00
                int                regIdx        = (decryptData>>8) & 7;
                BYTE        byteVal        = (decryptData>>11) & 0xFF;
       
                sprintf( disasm, "mov   byte ptr [%s], %02lX", gRegisterNames32, byteVal);
        }
        else if( byteCode == 0x16 )
        {
                // ror Reg32, xx
                int regIdx                = (decryptData >> 8) & 7;
                BYTE bitCount        = (decryptData >> 11) & 0xFF;
               
                sprintf( disasm, "ror   %s, %02lX", gRegisterNames32, bitCount );
        }
        else if( byteCode == 0x17 )
        {
                // imul byte ptr
                int regIdx = (decryptData >> 8) & 7;
                sprintf( disasm, "imul    byte ptr [%s]", gRegisterNames32 );

        }
        else if( byteCode == 0x18 )
        {
                // 注意:这里有 ah al 的区别.. 但是还没有看懂它是怎么区分的...
                //
                // arithemic type? for one byte==> "add al, dl"
                //
                // 8 types of arithmetic instruction between two Reg8
                // 0: add 1: or 2: adc 3: sbb 4: and 5: sub 6: xor 7: cmp
                int type = decryptData & 0x7;

                int srcReg= (decryptData >> 8) & 7;
                int destReg = (decryptData >> 11) & 7;
       
                sprintf( disasm, "%s   %s, %s", gArithmetic, gRegistersName8,         gRegistersName8 );

        }
        else if( byteCode == 0x44 )
        {
                // push Imm32--> Imm32: normal 32 bit instant number
                sprintf( disasm, "push    %08lX", Immediate );

        }
        else if( byteCode == 0xC0 )
        {
                // jmp XXXXXXXX
                sprintf( disasm, "jmp   %08lX", Immediate );
        }
        else if( byteCode == 0x82 )
        {
                // call XXXXXXXX
                sprintf( disasm, "call    %08lX", Immediate );
        }
        else if( byteCode == 0x83 )
        {
               
        }
        else if( byteCode == 0x84 )
        {
                // push Imm32--> Imm32: instruction address
                sprintf( disasm, "push    %08lX", Immediate );

        }
        else if( (byteCode & 0xF8) == 0x8 )
        {
                // 8 types of arithmetic instruction between two registers
                // 0: add 1: or 2: adc 3: sbb 4: and 5: sub 6: xor 7: cmp
                int type                        = decryptData & 0x7;
                int srcReg                        = (decryptData>>8) & 7;
                int destReg                        = (decryptData>>11) & 7;

                sprintf( disasm, "%s   %s, %s", gArithmetic, gRegisterNames32, gRegisterNames32 );
       
        }
        else if( ((byteCode + 0x40) & 0x87) == 0x80 )
        {
                // add Reg32, Imm32
                int regIdx = (byteCode >> 3) & 0x7;
                sprintf( disasm, "add   %s, %08lX", gRegisterNames32, Immediate );

        }
        else if( ((byteCode + 0x40) & 0x87) == 0x81 )
        {
                // xor Reg32, Imm32
                int regIdx = (byteCode >> 3) & 0x7;
                sprintf( disasm, "xor   %s, %08lX", gRegisterNames32, Immediate );

        }
        else if( byteCode == 0x20 )
        {
                // test Reg8, Reg8
                int srcReg= (decryptData >> 8) & 7;
                int destReg = (decryptData >> 11) & 7;

                sprintf( disasm, "test    %s, %s", gRegistersName8, gRegistersName8 );
        }
        else if( byteCode > 0x20 )
        {
                sprintf( disasm, "nop" );
        }

Hmily 发表于 2010-3-17 23:01

楼主好强的毅力啊,支持下,加精鼓励.

hkfans007 发表于 2010-3-17 23:26

本帖最后由 hkfans007 于 2010-3-18 20:50 编辑

楼主好强的毅力啊,支持下,加精鼓励.
Hmily 发表于 2010-3-17 23:01 http://www.52pojie.cn/images/common/back.gif


    哈哈,其实我毅力一般,如果说我一路F7的话。。那确实毅力很强。。其实我正在写一个OD插件。。可以把这些东西全部过滤掉的。。只是现在很乱。。局限性很大。。暂时只能针对正在研究的那个壳。。

等研究到TMD,能够处理掉TMD的那些东西的话,再修修改改一下发出来一起玩 。。。。

如下图:

Hmily 发表于 2010-3-17 23:33

回复 3# hkfans007


这样看起来爽多了,等你完善后发布!期待~

czjh2008 发表于 2010-3-17 23:37

呵呵!高手来着……

qaz003 发表于 2010-3-17 23:59

服了。。。够耐心的

热火朝天 发表于 2010-3-18 00:29

期待楼主的大作,我看着都累

李东国 发表于 2010-3-18 01:02

我们只有期待了

zjhfh0098 发表于 2010-3-18 07:00

就等你出作品了

root8384 发表于 2010-3-18 10:06

我一个小菜鸟,代码看的头晕啊
页: [1] 2
查看完整版本: 菜鸟见识到的一些指令猥琐方法