这里有两个主题,不过都是关于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; }
为了进一步阅读,请先参考【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; }
解码部分:
这部分是采用汇编编写的。现在的内存寻址一般是以字节为单位的,而这里的压缩单位为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 } }
由于Xfish的代码是Fasm的(我一般熟悉Masm和Nasm),而我又没有太多时间去修改,所以只能有时间再把这个优化添加进去。稍微估算了一下还是能节省数十字节。
附件:
32位长度反汇编引擎Lde vc.rar