好友
阅读权限30
听众
最后登录1970-1-1
|
【前言】:
只是介绍一些调试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
-------------------------------------------------------------------------------------
第三种: ===> 这三条指令之间同样可以穿插 push ret 或者 jmp 指令...
Call 地址
xchg dword ptr [esp], 寄存器
pop 上面使用的寄存器...
eg.
0053A18F E8 0E1E0000 call 0053BFA2
0053BFA2 871C24 xchg dword ptr [esp], ebx
005448F9 pop ebx
----------------------------------------------------------------------------------------
第四种: ==> SEH , 这种方法既可以 检测硬件断点,同时又可以打乱程序流程,使得前后真正有用的指令无法衔接起来看。。达到反跟踪的目的
注意:这些指令任何两条指令之间都可以穿插前面的3种 乱序方法....
eg.
大概长的样子如下面:
call push fs:[0] mov fs:[0], esp
然后就是 int1 int3 异常指令
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA ---> 真正有用的指令
0062880B E8 530E0000 call 00629663
00629663 67:64:FF36 0000 push dword ptr fs:[0]
00629669 67:64:8926 0000 mov dword ptr fs:[0], 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 [ecx]
00629492 C701 13000100 mov dword ptr [ecx], 10013 // ecx --> CONTEXT
00628F42 81C1 18000000 add ecx, 18 // ecx + 18h --> Dr7
00628F48 8A01 mov al, byte ptr [ecx] // 如果有硬件断点的话,Dr7 不等于0
00628F4A 81C1 9C000000 add ecx, 9C // context.ebp
00628F50 0001 add byte ptr [ecx], al // context.ebp + context.dr7 (dr7不为0造成堆栈错误)
00629649 81C1 04000000 add ecx, 4 // context.eip
// 这里要看如何分析得到 SEH处理完后的返回地址...
0062964F C701 F5906200 mov dword ptr [ecx], 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:[0] // 去掉 fs:[0]
006290FB 870424 xchg dword ptr [esp], 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 [esp], ecx ; 将返回地址保存到寄存器中
005479CB 50 push eax
005479CC 33C0 xor eax, eax
005479CE 8A01 mov al, byte ptr [ecx] ; 本意是要检测 Int3断点
005479D0 push ecx ;
0051EA56 mov ecx, 99 ; ecx = 99
004F19EE sub eax, ecx ; VM Code
004F19F7 59 pop ecx
004F19F8 imul byte ptr [ecx] ; 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 [ebx], ecx ; VM Code
004F1A13 5B pop ebx
004F1A14 59 pop ecx ; 这个对应 00520E4C 和 0054F859 这两行.. (前面介绍的第三种乱序方法)
004F1A15 pop esi ; 对应 push 0D4CCF65 (真正有用的)
004F1A1E FF35 C0EA4E00 push dword ptr [4EEAC0] ; 用这种方式返回..
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 [esp], ebx
//
// --> push ebp
//---------------------------------------------
// xchg dword ptr [esp], ebp
// mov ecx, ebp
// pop ebp
//
// ===> pop ecx
//---------------------------------------------
// push ebx ( 8 REGISTERS -- EACH FOR ONE REGISTER )
// push 9EC3461A
// pop ebx
// sub ebx, FFB54740
// and ebx, DEE9A4C4
// add ebx, 6258f045
// xchg dword ptr [esp], ebx
//
// ---> push XXXXXX
//------------------------------------------------
// push eax
// push 5B7761EA
// pop eax
// sub eax, CF129CAB
// jb 0053B479
// add eax, 87ABB20D
// add edx, eax ---> 这里可以是其他的算术指令 ( xor or add sub .... )
// pop eax
//
//----> add edx, XXXXXXXX
//--------------------------------------------------
// push 3A20A71
// pop eax
// or eax, DAD460A2
// add eax, 1FDEF9BB
// xor eax, E16E3DA0
// add eax, D353C036
//
//===> mov eax, XXXXXXX
//--------------------------------------
// push eax
// pop edx
//
// --> 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[regIdx] );
}
else if( byteCode == 1 )
{
// pop Reg32
int regIdx = (decryptData >> 8 ) & 7;
sprintf( disasm, "pop %s", gRegisterNames32[regIdx] );
}
else if( byteCode == 2 )
{
// mov Reg32, Reg32
int srcReg = (decryptData >> 8) & 7;
int destReg = (decryptData >> 11) & 7;
sprintf( disasm, "mov %s, %s", gRegisterNames32[destReg], gRegisterNames32[srcReg] );
}
else if( byteCode == 3 )
{
// mov dword ptr [Reg32], reg32
int srcReg = (decryptData >> 8) & 7;
int destReg = (decryptData >> 11) & 7;
sprintf( disasm, "mov dword ptr [%s], %s", gRegisterNames32[destReg], gRegisterNames32[srcReg] );
}
else if( byteCode == 4 )
{
// mov Reg32, dword ptr [Reg32]
int srcReg = (decryptData >> 8) & 7;
int destReg = (decryptData >> 11) & 7;
sprintf( disasm, "mov %s, dword ptr [%s]", gRegisterNames32[destReg], gRegisterNames32[srcReg] );
}
else if( byteCode == 5 )
{
// xchg dword ptr [esp], Reg32
int regIdx = (decryptData >> 8) & 7;
sprintf( disasm, "xchg dword ptr [esp], %s", gRegisterNames32[regIdx] );
}
else if( byteCode == 6 )
{
// retn
sprintf( disasm, "retn");
}
else if( byteCode == 7 )
{
// not Reg32
int regIdx = (decryptData >> 8) & 7;
sprintf( disasm, "not %s", gRegisterNames32[regIdx] );
}
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[destReg], gRegisterNames32[srcReg] );
}
else if( byteCode == 0x13 )
{
}
else if( byteCode == 0x14 )
{
}
else if( byteCode == 0x15 )
{
// mov byte ptr [Reg32], 00
int regIdx = (decryptData>>8) & 7;
BYTE byteVal = (decryptData>>11) & 0xFF;
sprintf( disasm, "mov byte ptr [%s], %02lX", gRegisterNames32[regIdx], byteVal);
}
else if( byteCode == 0x16 )
{
// ror Reg32, xx
int regIdx = (decryptData >> 8) & 7;
BYTE bitCount = (decryptData >> 11) & 0xFF;
sprintf( disasm, "ror %s, %02lX", gRegisterNames32[regIdx], bitCount );
}
else if( byteCode == 0x17 )
{
// imul byte ptr [Reg32]
int regIdx = (decryptData >> 8) & 7;
sprintf( disasm, "imul byte ptr [%s]", gRegisterNames32[regIdx] );
}
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[type], gRegistersName8[destReg], gRegistersName8[srcReg] );
}
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[type], gRegisterNames32[destReg], gRegisterNames32[srcReg] );
}
else if( ((byteCode + 0x40) & 0x87) == 0x80 )
{
// add Reg32, Imm32
int regIdx = (byteCode >> 3) & 0x7;
sprintf( disasm, "add %s, %08lX", gRegisterNames32[regIdx], Immediate );
}
else if( ((byteCode + 0x40) & 0x87) == 0x81 )
{
// xor Reg32, Imm32
int regIdx = (byteCode >> 3) & 0x7;
sprintf( disasm, "xor %s, %08lX", gRegisterNames32[regIdx], Immediate );
}
else if( byteCode == 0x20 )
{
// test Reg8, Reg8
int srcReg = (decryptData >> 8) & 7;
int destReg = (decryptData >> 11) & 7;
sprintf( disasm, "test %s, %s", gRegistersName8[destReg], gRegistersName8[srcReg] );
}
else if( byteCode > 0x20 )
{
sprintf( disasm, "nop" );
}
|
|