这里有两个主题,不过都是关于LDE的,所以放在一起了。
主题一:一个32位长度反汇编引擎
和xfish的那个不同,这里的解析过程是模仿CPU解码。这个代码实际上是ASM Community窥来的,只是稍作修改。
#include "lde.h"
int LDE(const unsigned char *func)
{
int operandSize = 4;
int FPU = 0;
const unsigned char* pOrigin = func;
//跳过F0h,F2h,F3h,66h,67h,2Eh,26h,36h,3Eh,64h,65h等前缀,
//以及D8h-DFh等ESC(转移操作码)
while (*func == 0xF0 ||
*func == 0xF2 ||
*func == 0xF3 ||
*func == 0x66 ||
*func == 0x67 ||
*func == 0x2E ||
*func == 0x3E ||
*func == 0x26 ||
*func == 0x36 ||
*func == 0x64 ||
*func == 0x65 ||
(*func & 0xF8) == 0xD8 //D8-DF
)
{
if (*func == 0x66)
{
operandSize = 2;
}
else if ( (*func & 0xF8)==0xD8 )
{
FPU = *func++;
break;
}
func++;
}
//跳过双字节操作码转义字节0Fh
bool twoByte = false;
if (*func == 0x0F)
{
twoByte = true;
func++;
}
//跳过主操作码
unsigned char opcode = *func++;
//跳过ModR/M字节
unsigned char modRM = 0xFF;
if (FPU)
{
if ( (opcode & 0xC0) != 0xC0 )
{
modRM = opcode;
}
}
else if (!twoByte)
{
if ((opcode & 0xC4) == 0x00 ||
(opcode & 0xF4) == 0x60 && ((opcode & 0x0A) == 0x02 || (opcode & 0x09) == 0x9) ||
(opcode & 0xF0) == 0x80 ||
(opcode & 0xF8) == 0xC0 && (opcode & 0x0E) != 0x02 ||
(opcode & 0xFC) == 0xD0 ||
(opcode & 0xF6) == 0xF6
)
{
modRM = *func++;
}
}
else
{
if ((opcode & 0xF0) == 0x00 && (opcode & 0x0F) >= 0x04 && (opcode & 0x0D) != 0x0D ||
(opcode & 0xF0) == 0x30 ||
opcode == 0x77 ||
(opcode & 0xF0) == 0x80 ||
(opcode & 0xF0) == 0xA0 && (opcode & 0x07) <= 0x02 ||
(opcode & 0xF8) == 0xC8
)
{
// No mod R/M byte
}
else
{
modRM = *func++;
}
}
//跳过SIB字节
if ( (modRM & 0x07) == 0x04 && (modRM>>6 & 3) != 3 )
{
unsigned char SIB = *func;
func += 1;
if ((SIB & 0x7) == 5)
{
func += 4; // disp32
}
}
if ( (modRM & 0xC5) == 0x05 )
{
func += 4; // disp32, no base
}
if ( (modRM & 0xC0) == 0x40 )
{
func += 1; // disp8
}
if ( (modRM & 0xC0) == 0x80 )
{
func += 4; // disp32
}
//跳过立即数
if (FPU)
{
// Can't have immediate operand
}
else if (!twoByte)
{
if ((opcode & 0xC7) == 0x04 ||
(opcode & 0xFE) == 0x6A || // PUSH/POP/IMUL
(opcode & 0xF0) == 0x70 || // Jcc
opcode == 0x80 ||
opcode == 0x83 ||
(opcode & 0xFD) == 0xA0 || // MOV
opcode == 0xA8 || // TEST
opcode == 0xB0 || // MOV
(opcode & 0xFE) == 0xC0 || // RCL
opcode == 0xC6 || // MOV
opcode == 0xCD || // INT
(opcode & 0xFE) == 0xD4 || // AAD/AAM
(opcode & 0xF8) == 0xE0 || // LOOP/JCXZ
opcode == 0xEB ||
opcode == 0xF6 && (modRM & 0x30) == 0x00 // TEST
)
{
func += 1;
}
else if( (opcode & 0xF7) == 0xC2 )
{
func += 2; // RET
}
else if( (opcode & 0xFC) == 0x80 ||
(opcode & 0xC7) == 0x05 ||
(opcode & 0xFE) == 0xE8 || // CALL/Jcc
(opcode & 0xFE) == 0x68 ||
(opcode & 0xFC) == 0xA0 ||
(opcode & 0xEE) == 0xA8 ||
opcode == 0xC7 ||
opcode == 0xF7 && (modRM & 0x30) == 0x00
)
{
func += operandSize;
}
}
else
{
if ( opcode == 0xBA || // BT
opcode == 0x0F || // 3DNow!
(opcode & 0xFC) == 0x70 || // PSLLW
(opcode & 0xF7) == 0xA4 || // SHLD
opcode == 0xC2 ||
opcode == 0xC4 ||
opcode == 0xC5 ||
opcode == 0xC6
)
{
func += 1;
}
else if((opcode & 0xF0) == 0x80)
{
func += operandSize; // Jcc -i
}
}
return func-pOrigin;
}
主题二:对xfish LDE的进一步优化
为了进一步阅读,请先参考
【Anti Virus专题】长度反汇编引擎的打造 。这里的优化是针对字节大小而言的,即追求更少的字节数。过程其实很简单,主要是对表格进行压缩,这里采用的是行程编码。行程编码一般适用于位图等压缩,压缩的单位一般与文件中的处理单位一致,如对于位图为一个像素的字节数,这里自然是半个字节即4位。
编码部分:
行程编码的过程非常简单:
如字节序列00 00 00 00 00 01 00 00,以字节为单位进行压缩。压缩后为00 04 01 00 00 01,第一字节为序列中的一个元素00,第二字节为该元素重复的次数04,解码后为00 00 00 00 00。够简单吧!~
#include <stdio.h>
#include <fstream>
#include <iostream>
using namespace std;
void main()
{
ifstream fin;
fin.open("in.txt");
char szIn[600];
fin>>szIn;
fin.close();
int i=0;
int dwLen = strlen(szIn);
int dwCount = 0;
while (i<dwLen)
{
if (dwCount%16==0)
{
cout<<"\""<<endl;
cout<<"\"";
}
dwCount++;
char chTemp = szIn[i];
cout<<"\\x"<<chTemp;
i++;
int dwRunlen = 0;
while (i<dwLen && dwRunlen<0xF && szIn[i]==chTemp)
{
dwRunlen++;
i++;
}
printf("%01X", dwRunlen);
}
cout<<"\"";
cout<<endl;
cout<<"after being compressed it's "<<dwCount<<" bytes"<<endl;
}
原表为8*16*2=256字节,被压缩后为134字节。
解码部分:
这部分是采用汇编编写的。现在的内存寻址一般是以字节为单位的,而这里的压缩单位为4位,故使用了一个位索引或位偏移的概念,看看代码就知道了,这样省去了很多判断。另外为了方便的处理位,这里利用rol,rcl对CF位处理的特性。
unsigned char szDecrypted[256];
unsigned char szCrypted[] =
"\x13\x20\x80\x01\x13\x20\x80\x01\x13\x20\x80\x01\x13\x20\x80\x01"
"\x13\x20\x80\xF0\x00\x13\x20\x80\xF0\x00\x13\x20\x80\xF0\x00\x13"
"\x20\x80\xF0\x0F\x0F\x02\x11\xF3\x80\x90\x20\x30\x03\x2F\x30\x90"
"\x31\x1B\x09\xC0\x04\x83\x03\x20\x80\x05\x27\x87\x31\x40\x00\x11"
"\x30\x90\x60\x00\x40\x01\x20\x01\x13\x21\x01\x17\x27\x81\xC0\x20"
"\x03\xF0\x00\xF1\x01\x11\x05\x15\xE0\x04\xE2\x10\x00\x30\x18\xE6"
"\x14\xE0\x10\xE0\x17\x05\xE9\x1F\x1F\x1F\x33\x12\x00\x17\x8F\x1F"
"\x02\x10\x30\x12\x02\x10\x30\x1A\xE1\x30\x16\x30\x10\x32\x10\x07"
"\xE0\x1F\x1E\xE0\x1D\xE0";
__declspec(naked) void main(void)
{
/*
约定:
al: lodsb
cl: 个数计数,即行程
ebx: 字节游标
edx: 位游标
esi: 压缩后的内存
edi: 解压后的内存
rol:
cf <---- [...] <-
|_________|
rcl:
<- cf <- [...] <-
|_______________|
*/
__asm
{
pushad //60
pushfd //9C
lea edi, szDecrypted
lea esi, szCrypted
xor edx, edx //33D2 //位索引
_Loop:
xor ecx, ecx //33C9
lodsb //AC
mov cl, al //8AC8
and cl, 0x0F //80E10F //得到字符个数
mov ah, al //8AE0
shr ah, 4 //C0EC04
and al, 0xF0 //24F0
or al, ah //0AC4 //复制高4位到低4位,方便循环
inc cl //FEC1 //真正的字符个数
shl ecx, 2 //C1E102 //ecx *= 4
_SetBitLoop:
mov ebx, edx //8BDA
shr ebx, 3 //C1EB03 //ebx /= 8
cmp bx, 255 //6681FBFF00 替换cmp ebx, 255 81FBFF000000,解压后的表长为256Bytes
jg _Exit //7F0A
rol al, 1 //D0C0 //cf = al最高位
rcl byte ptr [edi+ebx],1 //D0141F //cf移进edi
inc edx //42
loop _SetBitLoop //E2EB
jmp _Loop //EB03
_Exit:
popfd //9D
popad //61
}
}
解压的代码大致为0X3E=62字节。
由于Xfish的代码是Fasm的(我一般熟悉Masm和Nasm),而我又没有太多时间去修改,所以只能有时间再把这个优化添加进去。稍微估算了一下还是能节省数十字节。
附件:
32位长度反汇编引擎
Lde vc.rar
[培训]内核驱动高级班,冲击BAT一流互联网大厂工
作,每周日13:00-18:00直播授课