通用ShellCode深入剖析(6)

发布时间:2021-06-05

r:");
scanf("%d %d",&x,&y);
z=x/y;
printf("%d DIV %d = %d",x,y,z);
getch();
return 0;
}
编译,运行:输入4 2,程序输出"4 DIV 2 = 2",结果很正确.再运行输入 4 0,问题出来了,Visual Studio弹出了一个信息框:
"Unhandled exception in seh.exe:0xC0000094:Integer Divide by Zero",出现了未处理的
"除0异常",传统的方法是我们在z=x/y之前加上判断:
#include <stdio.h>
#include <conio.h>
int main(void)
{
int x,y,z=y=x=0;
printf("Input two integer number:");
scanf("%d %d",&x,&y);
if(!y)
{
printf("Can not Divide by Zero!");
goto LQUIT;
}
z=x/y;
printf("%d DIV %d = %d",x,y,z);
LQUIT:
getch();
return 0;
}
出错处理在这个小程序里这的确很容易看懂,可是想想如果在数千甚至上万行的程序里,这样的错误捕获处理会让程序变的十分凌乱难懂,而且传统方法处理的是我们可以想像(猜测)到的错误,但是某些导到程序出错的情况是很随机的,这样就不能保证程序的健壮性了,而SEH正是为了让正常的处理代码和出错处理代码分开,以使程序结构清淅,并使程序更加
健壮.让我们再把这个小程序改一下:
#include <stdio.h>
#include <conio.h>
#include <windows.h>

int main(void)
{
int x,y,z=y=x=0;
printf("Input Two Integer Number:");
scanf("%d %d",&x,&y);
__try
{//把可能出错的程序段封装起来
z=x/y;
//......
}
__except(EXCEPTION_EXECUTE_HANDLER)
{//在这里找出出现异常的原因,并进行处理
switch(GetExceptionCode())
{
case EXCEPTION_INT_DIVIDE_BY_ZERO://如果除0异常
{
printf("Can not Divide by Zero!");
goto LQUIT;
}
case EXCEPTION_ACCESS_VIOLATION://内存访问违例
{
//.....
break;
}
//do other......
default:
break;
}
}
printf("%d DIV %d = %d\n",x,y,z);
LQUIT:
getch();
return 0;
}
这样我们就使终都可以捕获到异常了,编译,选择"Disassembly",可以看到这样的代码:
push offset __except_handler3 (00401330)
mov eax,fs:[00000000]
push eax
mov dword ptr fs:[0],esp
这是实际上是标准的SEH异常处理函数的注册方法,我们的__except(){}实际在编译时被当成一个线程相关的异常处理函数,实际上这段代码的作用是将我们的异常处理函数加入异常处理结构链表EXCEPTION_REGISTRATION_RECORD,fs:[0]是这个异常处理函数链表的首指针,它的最后一条记录的节点指针指向0xffffffff.它的结构描述是这样的:

typedef struct _EXCEPTION_REGISTRATION_RECORD
{
struct _EXCEPTION_REGISTRATION_RECORD * pNext; //指向后面的节点
FARPROC pfnHandler;//指向异常处理函数
} EXCEPTION_REGISTRATION_RECORD, *P
EXCEPTION_REGISTRATION_RECORD;

你可能会问"你怎么知道fs:[0]是该结构的首指针呢?",当然我没有那么天才,从Windows 95系统程序设计一书中可以得知每当创建一个线程,系统均会为每个线程分配TEB(Thread Envir

精彩图片

热门精选

大家正在看