利用SEH进行代码混淆

63 篇文章 4 订阅
7 篇文章 1 订阅

    这几天在重看SEH机制,收获颇丰。随手写了一个用SEH进行跳转的代码贴于此处以作纪念。

    当发生异常,并捕捉了异常,在OS的异常处理机制下,会进入异常过滤函数。过滤函数可以返回EXCEPTION_EXECUTE_HANDLER/EXCEPTION_CONTINUE_SEARCH/EXCEPTION_CONTINUE_EXECUTION三者之一,以此决定OS的后续操作。如果返回EXCEPTION_CONTINUE_EXECUTION,OS认为异常已解决,可以从发生异常的指令处继续运行。一般这是用于解决访问内存出错的情况,比如:

int* ptr=NULL;

int ex(EXCEPTION_POINTERS* ExceptionInfo)
{
	ExceptionInfo->ContextRecord->Eax=(DWORD)malloc(sizeof(int));
	return EXCEPTION_CONTINUE_EXECUTION;
}

int main()
{
	__try
	{
		*ptr = 0x00;
//004010AC   mov         eax,[ptr (00422780)]
//004010B1   mov         dword ptr [eax],0
                MessageBox(NULL,"","",MB_OK);
	}
	__except(ex(GetExceptionInformation()))
	{

	}
}
        当执行*ptr=0x00时,由于ptr指向空,因此mov dword ptr [eax],0这句会引起异常。为了让代码继续往下执行弹出对话框,只要让eax指向有效内存即可。那如何让eax指向有效地址?只要给eax创建堆内存即可,因此可以在异常过滤函数中开辟空间。按windows的设计,异常发生时OS会把压入在堆栈中寄存器值保存到线程环境中(通过KiTrapFrameToContext);当从异常返回时,用保存的线程环境恢复寄存器继续执行(通过KiContextToTrapFrame)。那么所谓的线程环境在哪?代码中ExceptionInfo->ContextRecord即为所求。因此,我在过滤函数中修改Context->Eax,使之指向有效内存,实现了修复异常继续指令流的目的。其实,这整个流程类似于SetThreadContext/GetThreadContext。

        另外,值得一提的是,当异常发生时,如果异常类型是访存失败,错误码是0xC0000005的情况下,

    ExceptionInfo->ExceptionRecord->NumberParameters;

    ExceptionInfo->ExceptionRecord->ExceptionAddress;
    ExceptionInfo->ExceptionRecord->ExceptionInformation[1];

    这几个域包含了重要信息。

    来看个常见而又蛋痛的对话框:

   

    当你happy的玩着游戏时,跳出这个,是不是都毁了?如果仔细看出错信息上面给出了如下信息:出错的指令的地址-0x6F001080,出错的原因-0xC0000005(访问无效内存),无效内存的地址0xE2AF524C。这些信息,进入内核态以后可以轻松获得,但是用户态有没有办法获得?比如想做一个用户态的调试器?当然有,上面提到的3个域依次对应:异常时ExceptionInformation数组的元素数量、出错的指令的地址、访问无效内存的地址。

    有了这些辅助信息后,开始讨论正题。其实,这里已经呼之欲出了:代码混淆一般就是通过各种手段混乱代码执行流程,放在这篇文章中,只要修改EXCEPTION_CONTINUE_EXECUTION返回时的地址即可(注意我的用词,是EXCEPTION_CONTINUE_EXECUTION的返回地址,不是异常的返回地址,如果没记错对于vc++6.0异常的返回地址应该是_except_handle3),进一步说,就是触发异常并保存线程环境时Eip的值。

    罗列一下代码:代码执行流程为1)-5)

#include <windows.h>
#include <stdio.h>

int* ptr;
//4)Msg被调用
void Msg(int a,int b)
{
	int c = a+b;
	printf("%d\n",c);
	MessageBox(NULL,"","",MB_OK);
	__asm
	{
		mov eax,c
	}
}

int ex(EXCEPTION_POINTERS* ExceptionInfo)
{
	//3)准备跳转到Msg中
	//Msg是带参数函数,参数保存在异常发生前的堆栈上,即Esp指向
	//此时ExceptionInfo->ContextRecord->Eip[0]指向Lab1代表的地址
	//ExceptionInfo->ContextRecord->Eip[4]=0x01 ExceptionInfo->ContextRecord->Eip[8]=0x02
	ExceptionInfo->ContextRecord->Eip = (DWORD)Msg;
	return EXCEPTION_CONTINUE_EXECUTION;
}

int main()
{
	int i=0;
	ptr = (int*)&main;
	__try
	{
		/*
		原本向jmp Lab2,结果提示"illegal jump into __try scope",就这么代替一下
		*/
		if(1)
			goto Lab2;
Lab1:
		//5)从Msg返回后 指令流进入Lab1
		i++;
		printf("%d\n",i);
		ExitProcess(0);
Lab2:
		__asm
		{
			//1)制造Msg函数的参数及返回地址.模拟c调用函数的过程.异常触发后要跳入Msg执行
			//对于Msg函数,他根本不知道是谁调用他的,他只关心参数是否正确
			push 0x02;
			push 0x01;
			lea eax,Lab1;
			push eax;
		}
		//2)触发异常,给与Lab1中真正需要执行的代码于机会
		(*ptr) = 0x01;
		
	}
	__except(ex(GetExceptionInformation()))
	{}
	return 0;
}


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值