首页
论坛
课程
招聘
雪    币: 509
活跃值: 活跃值 (10)
能力值: ( LV12,RANK:220 )
在线值:
发帖
回帖
粉丝

[系统底层] [原创]一个32位长度反汇编引擎以及对xfish LDE的进一步优化

2009-8-10 23:08 6239

[系统底层] [原创]一个32位长度反汇编引擎以及对xfish LDE的进一步优化

2009-8-10 23:08
6239
这里有两个主题,不过都是关于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

HWS计划·2020安全精英夏令营来了!我们在华为松山湖欧洲小镇等你

上传的附件:
最新回复 (2)
雪    币: 204
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
nicoster 活跃值 2009-8-17 11:02
2
0
有技术含量啊. 怎么没有人关注?

不过第一种方法的代码编译出来 (vc6, O1) 的尺寸是 747 字节. 可能在尺寸上没有太多优势.
雪    币: 509
活跃值: 活跃值 (10)
能力值: ( LV12,RANK:220 )
在线值:
发帖
回帖
粉丝
xiep 活跃值 5 2009-8-17 12:06
3
0
谢谢关注!
第一个是模拟CPU解码,不是一般的表查询,因为不附带表,可以用在一般的场合。

另一个问题是早几天才看了mlde32,才发现原来已经有了一个index-compressed优化的版本,不过和上面写的这个是不一样的。

附mlde32 mlde32.zip
上传的附件:
游客
登录 | 注册 方可回帖
返回