通用ShellCode深入剖析(3)

2021-09-24 19:47


的地址,再用它装载user32.dll,再用GetFunctionByName获得MessageBoxA的地址,call
它一下
*/
int main(void)
{

char title[]="test",user32[]="user32",msgf[]="MessageBoxA";
unsigned int loadlibfun;
loadlibfun=GetFunctionByName(0x77e60000,"LoadLibraryA",12);
//0x77e60000是我机器上的kernel32.dll的基址,不同机器上的值可能不同
__asm
{
lea eax,user32
push eax
call dword ptr loadlibfun //相当于执行LoadLibrary("user32");
lea ebx,msgf
push 0x0b//"MessageBoxA"的长度
push ebx
push eax
call GetFunctionByName
mov ebx,eax
add esp,0x0c//GetFunctionByName使用C调用约定,由调用者调整堆栈
push 0
lea eax,title
push eax
push eax
push 0
call ebx//相当于执行MessageBox(NULL,"test","test",MB_OK)
}
return 1;
}
函数的内联汇编代码有很多这样的语句:
mov eax,[somewhere]
mov eax,[eax+0x??]
add eax,ImageBase
我试过使用mov eax,[ImageBase+eax+0x??]之类的语法,因为用到很多多级指针,而它们指向的又是相对偏移量所以要不断的"获取和计算",否则很容易导致"访问违例".编译运行,弹出了一个MessageBox标题和内容都是"test"看到了吗?你可能会问这个程序拿到其它机器上也可能运行吗?在整个程序里我们唯一依赖的就是0x77e60000这个kernel32.dll基址,其它机器上的可能不是这个值,如果这个地址值可以在程序运行时动态的计算出来,那么这个程序将非常通用,它可以动态计算出来吗?答案是肯定的!下一节我们将来分析一种并不很流行但很通用的动态计算获得kernel32.dll基址的方法.


二,在动态获得Kernel32.DLL地址方法的分析

1,简析结构化异常处理(SEH,Structred Exception Handling)
SEH已经不是很什么新技术了,但是对于我将要讲了非常重要,所以在这里对它做一个简单的分析.Ok,打开VC,让我们来分析一个简单的"除"
;运算程序,看看它哪里有问题:

#include <stdio.h>
#include <conio.h>
int main(void)
{
int x,y,z=y=x=0;
printf("Input two integer numbe

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

onment Block)在Windows 9x中被称为TIB(Thread Information Block),而且TEB永远放在fs段选择器指定的数据段的0偏移处.

通用ShellCode深入剖析(3).doc 将本文的Word文档下载到电脑 下载失败或者文档不完整,请联系客服人员解决!

下一篇:51+HD7279+ADS1110+PT100的测温系统(程序)

相关阅读
本类排行
× 注册会员免费下载(下载后可以自由复制和排版)

马上注册会员

注:下载文档有可能“只有目录或者内容不全”等情况,请下载之前注意辨别,如果您已付费且无法下载或内容有问题,请联系我们协助你处理。
微信: QQ: