如何恶心CTF逆向选手 第二季

本文介绍了一种将虚拟机(VM)的opcode与汇编指令混杂执行的方法,通过在Windows环境下利用调试API,创建子进程并在遇到非法指令时触发异常,从而实现在逆向工程中增加难度的技术。
没想到吧,我又回来了,这次带来的不是源码级混淆了,而是VM相关。

也许你会觉得很奇怪,一个VM能有啥新鲜的,对,单纯来说VM保护源代码已经非常的成熟了,所以在这里只做最基本的介绍,而且这次的重点不在于VM。
VM就是Virtual Machine(虚拟机器),这里和vmware不是一样的,这里指的是自己写的引擎和opcode类型,比如Java就是基于JVM的语言,通过将代码编译成字节码,再运行在JVM上。
所以我们对于一个加密算法可以先翻译成opcode,再运行在我们自己写的引擎上,这样就大大增加逆向成本。
上图就是一个典型的VM执行引擎结构图(有点像控制流平坦化 )…
再来个源码样式:

那么介绍完了VM,我们了解到VM保护的逆向一般都得先找到opcode,再分析引擎,再写parser,再人肉看,我们从后三个方向都容易让人恶心,这里介绍一种从Opcode这里恶心人的操作。
我们知道CTF的VM一般将opcode单独放到一个字节数组里,有一个指针慢慢的扫描过去来进行解释执行,完全是虚拟化的环境,但是有没有想过,这里就导致了opcode极其容易dump出来,要是能够让opcode和汇编指令混在一起就好了
所以这里说的就是如何将Opcode和汇编指令放在一起,混杂着执行就很恶心了。
首先,你的静态分析会炸掉,因为混杂了不可反汇编的指令,可能会因为上下文使得这一块的代码和数据类型区分不开甚至指令分析错误,F5铁定是废了,再者,Opcode提取比较困难,你可能得手工提取那些穿插着Opcode。
其实还有一种好处,涉及到原理才能知道为什么了。
这里我们使用windows下的环境,使用的是windows 强大的调试API,具体的实现逻辑是:运行时碰到非法指令,会触发异常,通过调试器调试子进程截获异常,获取上下文进行操作
所以我们得先创造子进程,子进程被调试,本身则相当于系统处理不了的字节码的解释器(有点类似linux下基于信号的VM,大佬敏锐的见解)

	STARTUPINFO si;
	memset(&si,0,sizeof(si));
	si.cb=sizeof(si);
	PROCESS_INFORMATION pi;
	HMODULE h=GetModuleHandleW(0);
	CHAR Filename[260];
	GetModuleFileNameA(h,(LPSTR)&Filename,260);
	BOOL result=CreateProcessA(NULL,(LPSTR)&Filename,NULL,NULL,FALSE,DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS,NULL,NULL,(LPSTARTUPINFOA)&si,&pi);

这里就是创建子进程的代码,而且是以调试模式启动创建,所以会拥有许多权限。
这里实现VM最重要的API就是SetThreadContext,因为可以实现寄存器等的获取与修改,还有一个就是ReadProcessMemory和WriteProcessMemory,因为需要对内存进行读写。
最后再来个while(true)里面随时截获异常消息进行处理,处理完之后再SetThreadContext将目前执行完的寄存器的值设置。
还有一点细节,就是写个IsDebuggerPresent来判断自己是被调试的还是父进程,切勿陷入死循环,疯狂CreateProcess,那CPU直接暴毙。

void CreateSubProcess()
{
	STARTUPINFO si;
	memset(&si,0,sizeof(si));
	si.cb=sizeof(si);
	PROCESS_INFORMATION pi;
	HMODULE h=GetModuleHandleW(0);
	CHAR Filename[260];
	GetModuleFileNameA(h,(LPSTR)&Filename,260);
	BOOL result=CreateProcessA(NULL,(LPSTR)&Filename,NULL,NULL,FALSE,DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS,NULL,NULL,(LPSTARTUPINFOA)&si,&pi);
	VMState vms;
	if(result)
	{
		//ShowWindow(GetConsoleWindow(),SW_HIDE);
		DEBUG_EVENT de;
		while(WaitForDebugEvent(&de,INFINITE)!=0)
		{
			if(de.dwDebugEventCode==EXCEPTION_DEBUG_EVENT)
			{
				EXCEPTION_DEBUG_INFO di=de.u.Exception;
				if(di.ExceptionRecord.ExceptionCode==EXCEPTION_ILLEGAL_INSTRUCTION)
				{
					CONTEXT context;
					memset(&context,0,sizeof(context));
					context.ContextFlags=CONTEXT_FULL;
					GetThreadContext(pi.hThread,&context);
					if(ReadRemoteByte(pi.hProcess,context.Eip)==0xC7)
					{
						vms.context=&context;
						vms.pi=π
						RunVM(vms);
						SetThreadContext(pi.hThread,&context);
					}
					else
					{
						TerminateProcess(pi.hProcess,0);
						exit(0);
					}

				}
			}
			if(de.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT)
				exit(0);
			ContinueDebugEvent(de.dwProcessId,de.dwThreadId,DBG_CONTINUE);
		}
	}
	return;
}

所以主要代码就在这里了,然后完善下RunVM函数就可以了。如下就是完整代码,有个Opcode.h没给出不过问题不大,我们不在于写VM而在于怎么实现opcode和asm混杂
还有一个重要细节,就是每个VM的opcode之前必须要有个前缀,而且这个前缀必须不能对应任何的汇编指令,不然就不会触发非法指令的异常,最后的EIP设置也记得要跳过最开头这个字节,在代码中我选取的是0xC7

// SEHVM.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include<cstdio>
#include<cstdlib>
#include<windows.h>
#include"Opcodes.h"
using namespace std;
struct VMState
{
	PCONTEXT context;
	PPROCESS_INFORMATION pi;
};
BYTE ReadRemoteByte(HANDLE hProcess,int addr)
{
	BYTE data;
	ReadProcessMemory(hProcess,(LPVOID)addr,&data,1,NULL);
	return data;
}
DWORD ReadRemoteDword(HANDLE hProcess,int addr)
{
	int data;
	ReadProcessMemory(hProcess,(LPVOID)addr,&data,4,NULL);
	return data;
}
void WriteRemoteDword(HANDLE hProcess,int addr,DWORD data)
{
	WriteProcessMemory(hProcess,(LPVOID)addr,(LPVOID)&data,4,NULL);
}
void pop(VMState vms,DWORD *val)
{
	*val=ReadRemoteDword(vms.pi->hProcess,vms.context->Esp);
	vms.context->Esp+=4;
}
void push(VMState vms,int val)
{
	vms.context->Esp-=4;
	WriteRemoteDword(vms.pi->hProcess,vms.context->Esp,val);
}
DWORD *getReg(VMState vms,BYTE x)
{
	if(x==Reg1)
		return &vms.context->Eax;
	if(x==Reg2)
		return &vms.context->Ebx;
	if(x==Flag)
		return &vms.context->Ecx;
	if(x==LoopReg)
		return &vms.context->Edx;
	printf("Unknow reg id: 0x%X\n",x);
	exit(0);
}
void RunVM(VMState vms)
{
	DWORD *eip=&(vms.context->Eip);
	BYTE op=ReadRemoteByte(vms.pi->hProcess,*eip+1);
	int arg1;
	BYTE arg2;
	switch(op)
	{
		case PushImm:
			arg1=ReadRemoteDword(vms.pi->hProcess,*eip+2);
			push(vms,arg1);
			vms.context->Eip+=6;
			break;
		case PopReg:
			arg2=ReadRemoteByte(vms.pi->hProcess,*eip+2);
			pop(vms,getReg(vms,arg2));
			vms.context->Eip+=3;
			break;
		default:
			TerminateProcess(vms.pi->hProcess,0);
			exit(0);
			break;
	}
}
void CreateSubProcess()
{
	STARTUPINFO si;
	memset(&si,0,sizeof(si));
	si.cb=sizeof(si);
	PROCESS_INFORMATION pi;
	HMODULE h=GetModuleHandleW(0);
	CHAR Filename[260];
	GetModuleFileNameA(h,(LPSTR)&Filename,260);
	BOOL result=CreateProcessA(NULL,(LPSTR)&Filename,NULL,NULL,FALSE,DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS,NULL,NULL,(LPSTARTUPINFOA)&si,&pi);
	VMState vms;
	if(result)
	{
		//ShowWindow(GetConsoleWindow(),SW_HIDE);
		DEBUG_EVENT de;
		while(WaitForDebugEvent(&de,INFINITE)!=0)
		{
			if(de.dwDebugEventCode==EXCEPTION_DEBUG_EVENT)
			{
				EXCEPTION_DEBUG_INFO di=de.u.Exception;
				if(di.ExceptionRecord.ExceptionCode==EXCEPTION_ILLEGAL_INSTRUCTION)
				{
					CONTEXT context;
					memset(&context,0,sizeof(context));
					context.ContextFlags=CONTEXT_FULL;
					GetThreadContext(pi.hThread,&context);
					if(ReadRemoteByte(pi.hProcess,context.Eip)==0xC7)
					{
						vms.context=&context;
						vms.pi=&pi;
						RunVM(vms);
						SetThreadContext(pi.hThread,&context);
					}
					else
					{
						TerminateProcess(pi.hProcess,0);
						exit(0);
					}

				}
			}
			if(de.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT)
				exit(0);
			ContinueDebugEvent(de.dwProcessId,de.dwThreadId,DBG_CONTINUE);
		}
	}
	return;
}
int main()
{
	if(!IsDebuggerPresent())
	{
		CreateSubProcess();
		return 0;
	}
	printf("I am sub process\n");
	int t=0;
	_asm
	{
		_emit 0xC7 //pushImm 0xDDCCBBAA
		_emit 0x17
		_emit 0xAA
		_emit 0xBB
		_emit 0xCC
		_emit 0xDD
		_emit 0xC7 //pushImm 0xEECCBBAA
		_emit 0x17
		_emit 0xAA
		_emit 0xBB
		_emit 0xCC
		_emit 0xEE
		pop eax
		pop ebx
		mov t,eax
	}
	printf("the value of eax: %X\n",t); //eax=0xEECCBBAA
	MessageBoxA(NULL,"GOT","YES",MB_OK);
	return 0;
}

这里的VM只实现了最简单的两条指令,所以等着大佬们完善,这里主要是阐述一种操作,只是个雏形罢了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值