长度反汇编引擎(Length-Disassembler Engine)的打造
忙,好忙。利用晚上下班的时间给大家赶出篇文章来。貌似好长时间没有睡过好觉了。这个星期还要回趟家乡去.... **** 总是时间太少........
这个星期先给大家来一篇Virus编写中经常要用到的长度反汇编引擎Length-Disassembler Engine的编写思路文章。因为它在Virus编写中也是十分重要。我们病毒变形、感染很多时候都要用到它。
长度反汇编引擎(Length-Disassembler Engine)是用于获取目标地址所在代码的长度。举个例子push ebp | mov ebp, esp 。我想大家对这两句代码并不陌生了,一般被用于我们的子程序中建
立堆栈框架。那么这两句指令的长度是不同的,有时候我们要替换某内存地址中的指令的话,如果我们茫然的替换,只能把稳定性给大大的抛弃了。所以这个时候就需要我们的长度反汇编引擎来获取我
们要替换地址所在指令的长度,如果小于或者等于替换指令的长度,我们则可以进行替换。假设我们通过Length-Disassembler Engine获取push ebp所在的地址的代码长度,那么返回的长度肯定是1。
那么目前我所见到过非常短小精炼的长度反汇编引擎就是rgblde,300 bytes+ 啊?有点扯远了我们继续主题。
比如把
Oep:
push ebp ;1 bytes $55
mov ebp, esp ;2 bytes $8B, $EC
换成
Oep:
push ebp ;1 bytes $55
push esi ;1 bytes $56
mov ebp, esp ;2 bytes $8B, $EC
假如我们在替换指令的时候, 我们的程序按照之前的替换方式。从mov ebp, esp地址处开始替换。因为此时这个地址是我们的指令push esi,(假设替换指令是 jmp shor Oep(2 bytes) - $EB $FD)。那
么替换后形成
Oep:
push ebp ;1 bytes $55
jmp short Oep ;2 bytes $EB $FD
;1 bytes $EC
那么此时如果替换后的话,并执行的话肯定会出现异常的。还有平常我们的inline Hook 例如那常用的5字节 替换(jmp long xxxx),如果我们不通过Length-Disassembler Engine长度取下要替换目标地
址的指令长度是否等于5字节就茫然替换的话这个程序的稳定性就太欠缺了。以上就是我们Length-Disassembler Engine的用处。它在病毒中也常常要用,例如用到EPO,例如搜索代码段的5字节指令进行
替换,或者用到变形等。
OK。了解了用处我们就来实现吧。
首先要编写长度反汇编引擎
intel指令格式
+-------------+--------+----------+---------+--------------+------------+
; instruction ; opcode ; ModR/M ; SIB ; Displacement ; Immediate ;
; prefixe ; ; ; ; ; ;
+-------------+--------+----------+---------+--------------+------------+
前缀(可选) 操作码 (可选) (可选) 地址偏移(可选) 立即数(可选)
这里opcode成员是必需的,其他5个成员是可选的。但是千万切记它们的顺序是不能颠倒的。sib我们可以认为是mod/rm的扩展。只要有sib成员那么mod/rm也是必需的。
在这里我不想太多的去介绍Intel指令格式,因为要介绍它我估计得另立一个专题的讲。幸好论坛也有朋友做过此类的系列课程。所以与其我给大家简要讲解,大家还不如去先学习下这些相关的专题。
如我们论坛
egogg 的打造自己的反汇编引擎——Intel指令编码学习报告专题。
我这里主要将部分的重点内容给大家标记下。
前缀:
1. 前缀是唯一的一个可能出现在Code之前的域
2. 所有的Prefixes都只有一个字节
3. 在一个opcode中可能会有多个Prefixes
4. 如果Prefixes不能对随它之后的opcode起作用,那么处理器将忽略它。
5. 一条指令可能只有一个CODE域,一个mod r/m域,或者一个 offset域等,但是可以有多个Prefixes.
Prefixes被分为:
1. 切换默认操作数大小(66h)
2. 切换默认地址大小(67h)
3. 重复(Rep)(F3h,F2h)
4. 切换默认段(2eh,36h,3eh,26h,64h,65h)
5. 总线锁定(Buslock)(F0)
ModR/M
涉及内存操作数的指令都有一个紧挨着主操作码的寻址格式说明字节也就是ModR/M。你应该知道ModR/m什么时候该用,什么时候不该用了吧。
SIB
ModR/M字节编码需要第二寻址字节(SIB).基址+索引或者比例+索引形式的32位寻址则需要SIB字节成员。
Displacement Immediate
这两个成员我就不说了吧。
OK。了解了结构,我们来说说如何编写。
不知道大家是否用过opcode表。
我们每一个汇编助记符就相当于opcode表的索引。举个例子
pushad - 060h
那么我们假设我们将如pushad, dec reg等单字节指令自己定义一个数值,例如1 来表示它是一个单字节指令。
那么我们的反汇编引擎读取目标地址的机器码后,通过opcode表偏移地址+机器码来索引我们机器码在表中的对应成员,例如我们的pushad如果取出的对应成员值是1的话,我们则知道我们的指令是一
个单字节指令。那么我们就可以直接将size +1,然后返回过程,这样我们的长度反汇编过程返回的就是1.
这就是我们长度反汇编引擎的过程。
当然如果都是单字节指令的话我们也就不用去学上面的intel格式了。
其实清楚了上面的pushad的长度反汇编引擎的工作原理,那么接下来的解码工作也是很简单。
例如通过opcode表偏移地址+机器码来索引我们机器码在表中的对应成员,然后通过对成员进行判断。如果是前缀的话,则跳转到我们的前缀处理过程处去执行。如果是存在ModR/m的指令则跳转到我们
的ModR/m过程去执行,其他的亦然。由于我们的指令成员字节数是可以确定,所以只要通过对指令结构成员进行解码确定我们的目标地址代码到底存在哪些成员,就可以确定目标地址代码的长度。
其实说到底还是要求你对intel指令格式的掌握。所以看不懂的朋友还是尽量先把基础补补。
我这里主要说说引擎的大小优化。
其实之前看到很多作者在设计表的时候居然用dword类型来初始化,这大大增加我们opcode表的字节大小。后期有的人采用了byte 来初始化。NONO,这都不是我们想要的。我们可以采用4bit位的方式来
定义,这样大大简化我们的opcode表字节大小。恩,没错。这个方式最早被29a的sars所使用。我们今天也是学习的29a - sars的Catchy32代码思路。那么我们在通过4bit位来定义成员,我们还是要讲究
点技巧,这样后面为我们提供方便。
1h 2h 4h 8h我们通过每次*2的形式来定义(还是否记得逻辑左移指令)。没错也就是每次shl 1位。为什么要这样?
我们这样定义的话,我们就省略了cmp判断了。直接通过btr来测试我们二进制位并影响CF标志位,然后通过jc分支跳转 来完成我们的判断,是不是很GOOD。
完成后的Opcode表如下。
;--------------------Opcode Table--------------------
;++
;Description:
;Size of table element is 4 bits.
;0h-one byte instruction
;1h-ModRM byte
;2h-imm8,rel8 etc
;4h-ptr16 etc
;8h-imm16/32,rel16/32 etc
;0Fh-prefix
;0Eh-unsupported opcodes
;--
pref66h equ 1
pref67h equ 2
Table:
; 01 23 45 67 89 AB CD EF
db 011h,011h,028h,000h,011h,011h,028h,000h;0Fh
db 011h,011h,028h,000h,011h,011h,028h,000h;1Fh
db 011h,011h,028h,0F0h,011h,011h,028h,0F0h;2Fh
db 011h,011h,028h,0F0h,011h,011h,028h,0F0h;3Fh
db 000h,000h,000h,000h,000h,000h,000h,000h;4Fh
db 000h,000h,000h,000h,000h,000h,000h,000h;5Fh
db 000h,011h,0FFh,0FFh,089h,023h,000h,000h;6Fh
db 022h,022h,022h,022h,022h,022h,022h,022h;7Fh
db 039h,033h,011h,011h,011h,011h,011h,011h;8Fh
db 000h,000h,000h,000h,000h,0C0h,000h,000h;9Fh
db 088h,088h,000h,000h,028h,000h,000h,000h;AFh
db 022h,022h,022h,022h,088h,088h,088h,088h;BFh
db 033h,040h,011h,039h,060h,040h,002h,000h;CFh
db 011h,011h,022h,000h,011h,011h,011h,011h;DFh
db 022h,022h,022h,022h,088h,0C2h,000h,000h;EFh
db 0F0h,0FFh,000h,011h,000h,000h,000h,011h;FFh
;==============================================
Lentable equ $-Table
;===============EXTENDED OPCODES===============
TableExt:
; 01 23 45 67 89 AB CD EF
db 011h,011h,0E0h,000h,000h,0EEh,0E1h,003h;0Fh
db 011h,011h,011h,011h,01Eh,0EEh,0EEh,0EEh;1Fh
db 011h,011h,01Eh,01Eh,011h,011h,011h,011h;2Fh
db 000h,000h,000h,0EEh,0EEh,0EEh,0EEh,0EEh;3Fh
db 011h,011h,011h,011h,011h,011h,011h,011h;4Fh
db 011h,011h,011h,011h,011h,011h,011h,011h;5Fh
db 011h,011h,011h,011h,011h,011h,011h,011h;6Fh
db 033h,033h,011h,010h,011h,011h,011h,011h;7Fh
db 088h,088h,088h,088h,088h,088h,088h,088h;8Fh
db 011h,011h,011h,011h,011h,011h,011h,011h;9Fh
db 000h,001h,031h,011h,000h,001h,031h,011h;AFh
db 011h,011h,011h,011h,0EEh,031h,011h,011h;BFh
db 011h,031h,033h,031h,000h,000h,000h,000h;CFh
db 0E1h,011h,011h,011h,011h,011h,011h,011h;DFh
db 011h,011h,011h,011h,011h,011h,011h,011h;EFh
db 0E1h,011h,011h,011h,011h,011h,011h,01Eh;FFh
;==============================================
OK,这里给大家贴一段我参考29a - sars的Catchy32代码思路写的一段长度反汇编引擎,我去掉了些不必要的代码, 并且以上我已经说了这个符号表以及这个引擎的判断方式,相信理解了以上,你看代码应该没有问题了。。它处理完重定位 大概应该在500 byte - 600 byte之间。抱歉原谅我不喜欢静态库的方式来移植到高级语言编译器中,我还是喜欢shellcode方式。。
format PE GUI 4.0
include 'win32ax.inc'
.text
entry $
mov esi, Lde32
push esi
call Lde32
add esi, eax
jmp $-13
ret
;++
;
; Int
; Lde32(
; IN byte *pDestAddress
; )
;
; Routine Description:
;
; 获取目标地址代码的长度
;
; Thanks sars .
;
; Arguments:
;
; (esp) - return address
;
; Data (esp+4*8+4) - pDestAddress
;
; Return Value:
;
; eax = TRUE, initialization succeeds . = OpCode Length
; eax = -1, Faild
;--
include 'optable.inc'
Lde32:
pushad
xor ecx, ecx
mov esi, [esp+4*8+4]
Lde_ExtFlags:
xor eax, eax
xor ebx, ebx
cdq
lodsb
mov cl, al
cmp al, 0fh
je Lde_ExtdTable
jmp Lde_NormTable
Lde_ExtdTable: ;Load flags from extended table
lodsb
inc ah ;EAX=al+100h (100h/2 - lenght first table)
Lde_NormTable: ;Load flags from normal table
shr eax, 1 ;Div 2
mov al, byte [Table+eax]
jc Lde_CheckC1 ;如果是奇数则取低4位
shr eax, 4 ;Get Hight high 4-bits
Lde_CheckC1:
and eax, 0Fh ;...low
xchg eax, ebx
;--------------Opcode type checking---------------
Lde_CheckFlags:
cmp bl, 0Eh ;unsupported opcodes
je Lde_Error
cmp bl, 0Fh ;test prefix
je Lde_Prefix
or ebx, ebx ;Test One byte command
jz Lde_GetLen
btr ebx, 0 ;Test ModRM byte
jc Lde_ModRM
btr ebx, 1 ;Test imm8,rel8 etc
jc Lde_incr1
btr ebx, 2 ;Test ptr16 etc
jc Lde_incr2
;-----imm16/32,rel16/32, etc types processing-----
Lde_16_32:
and bl, 11110111b ;Reset 16/32 sign
cmp cl, 0A0h ;Processing group 0A0h-0A3h
jb Lde_Check66h
cmp cl, 0A3h
ja Lde_Check66h
test ch, pref67h
jnz Lde_incr2
jmp Lde_incr4
Lde_Check66h: ;Processing other groups
test ch, pref66h ;pref66h
jz Lde_incr4
jmp Lde_incr2
;---------------Prefixes processing---------------
Lde_Prefix:
cmp cl, 66h
je Lde_SetFlag66h
cmp cl, 67h
jne Lde_ExtFlags
Lde_SetFlag67h:
or ch, pref67h
jmp Lde_ExtFlags
Lde_SetFlag66h:
or ch, pref66h
jmp Lde_ExtFlags
;--------------ModR/M byte processing-------------
Lde_ModRM:
lodsb
cmp cl, 0F7h
je Lde_GroupF6F7
cmp cl, 0F6h
jnz Lde_Modxx
Lde_GroupF6F7:
test al, 00111000b
jnz Lde_Modxx
test cl, 00000001b
jz Lde_incbt1
test ch, pref66h
jnz Lde_incbt2
inc esi
inc esi
Lde_incbt2: inc esi
Lde_incbt1: inc esi
Lde_Modxx:
mov edx, eax
and al, 00000111b ;al <- only R/M bits
test dl, 11000000b ;Check MOD bits
jz Lde_Mod00
jp Lde_CheckFlags ;Or Lde_Mod11
js Lde_Mod10
Lde_Mod01:
test ch, pref67h
jnz Lde_incr1 ;16-bit addressing
cmp al, 4 ;Check SIB
je Lde_incr2
jmp Lde_incr1
Lde_Mod00:
test ch, pref67h ;pref67h
jz Lde_Mod00_32 ;32-bit addressing
cmp al, 6
je Lde_incr2
jmp Lde_CheckFlags
Lde_Mod00_32:
cmp al, 4 ;Check SIB
jne Lde_disp32
Lde_SIB: ;Processing SIB byte
lodsb
and al, 00000111b
cmp al, 5
je Lde_incr4
jmp Lde_CheckFlags
Lde_disp32:
cmp al, 5
je Lde_incr4
jmp Lde_CheckFlags
Lde_Mod10:
test ch, pref67h
jnz Lde_incr2 ;16-bit addressing
cmp al, 4 ;Check SIB
je Lde_incr5
jmp Lde_incr4
Lde_incr5: inc esi
Lde_incr4: inc esi
inc esi
Lde_incr2: inc esi
Lde_incr1: inc esi
jmp Lde_CheckFlags
;-----------Command length calculation------------
Lde_GetLen:
sub esi, [esp+4*8+4]
mov [esp+pushad_eax], esi
jmp Lde_Ret
;-----------Setting the error-----------------------
Lde_Error:
xor eax, eax
dec eax
mov [esp+pushad_eax], eax
Lde_Ret:
popad
retn 4*1
4h入门PHP代码审计之反序列化
上传的附件: