New Microsoft 通用ShellCode深入剖析.docx

上传人:b****5 文档编号:7362488 上传时间:2023-05-11 格式:DOCX 页数:37 大小:31.95KB
下载 相关 举报
New Microsoft 通用ShellCode深入剖析.docx_第1页
第1页 / 共37页
New Microsoft 通用ShellCode深入剖析.docx_第2页
第2页 / 共37页
New Microsoft 通用ShellCode深入剖析.docx_第3页
第3页 / 共37页
New Microsoft 通用ShellCode深入剖析.docx_第4页
第4页 / 共37页
New Microsoft 通用ShellCode深入剖析.docx_第5页
第5页 / 共37页
New Microsoft 通用ShellCode深入剖析.docx_第6页
第6页 / 共37页
New Microsoft 通用ShellCode深入剖析.docx_第7页
第7页 / 共37页
New Microsoft 通用ShellCode深入剖析.docx_第8页
第8页 / 共37页
New Microsoft 通用ShellCode深入剖析.docx_第9页
第9页 / 共37页
New Microsoft 通用ShellCode深入剖析.docx_第10页
第10页 / 共37页
New Microsoft 通用ShellCode深入剖析.docx_第11页
第11页 / 共37页
New Microsoft 通用ShellCode深入剖析.docx_第12页
第12页 / 共37页
New Microsoft 通用ShellCode深入剖析.docx_第13页
第13页 / 共37页
New Microsoft 通用ShellCode深入剖析.docx_第14页
第14页 / 共37页
New Microsoft 通用ShellCode深入剖析.docx_第15页
第15页 / 共37页
New Microsoft 通用ShellCode深入剖析.docx_第16页
第16页 / 共37页
New Microsoft 通用ShellCode深入剖析.docx_第17页
第17页 / 共37页
New Microsoft 通用ShellCode深入剖析.docx_第18页
第18页 / 共37页
New Microsoft 通用ShellCode深入剖析.docx_第19页
第19页 / 共37页
New Microsoft 通用ShellCode深入剖析.docx_第20页
第20页 / 共37页
亲,该文档总共37页,到这儿已超出免费预览范围,如果喜欢就下载吧!
下载资源
资源描述

New Microsoft 通用ShellCode深入剖析.docx

《New Microsoft 通用ShellCode深入剖析.docx》由会员分享,可在线阅读,更多相关《New Microsoft 通用ShellCode深入剖析.docx(37页珍藏版)》请在冰点文库上搜索。

New Microsoft 通用ShellCode深入剖析.docx

NewMicrosoft通用ShellCode深入剖析

通用ShellCode深入剖析

 来源:

 类别:

安全文献 日期:

2004-1-913:

35:

08 今日/总浏览:

1/646  

[SafeChina推荐]通用ShellCode深入剖析

通用ShellCode深入剖析

作者:

yellow

Email:

yellow@

HomePage:

Date:

2003-12-19

前言:

在网上关于ShellCode编写技术的文章已经非常之多,什么理由让我再写这种技术文

章呢?

本文是我上一篇溢出技术文章的姊妹篇,同样

的在网上我们经常可以看到一些关于ShelCode编写技术的文章,似乎没有为初学者准备的

在这里我将站在初学者的角度对通用ShellCode进行比较详细的分析,有了上一篇的溢出

理论和本篇的通用ShellCode理论,基本上我们就可以根据一些公布的Window溢出漏洞或

是自己对一些软件系统进行反汇编分析出的溢出漏洞试着编写一些溢出攻击测试程序.

文章首先简单分析了PE文件格式及PE引出表,并给出了一个例程,演示了如何根据PE

相关技术查找引出函数及其地址,随后分析了一种比较通用的获得Kernel32基址的方法,

最后结合理论进行简单的应用,给出了一个通用ShellCode.

本文同样结合我学习时的理解以比较容易理解的方式进行描述,但由于ShellCode的

复杂性,文章主要使用C和Asm来讲解,作者假设你已具有一定的C/Asm混合编程基础以及上

一篇的溢出理论基础,希望本文能让和我一样初学溢出技术的朋友有所提高.

[目录]

1,PE文件结构的简介,及PE引出表的分析.

1.1PE文件简介

1.2引出表分析

1.3使用内联汇编写一个通用的根据DLL基址获得引出函数地址的实用函数

GetFunctionByName

2,通用Kernel32.DLL地址的获得方法.

2.1结构化异常处理和TEB简介

2.2使用内联汇编写一个通用的获得Kernel32.DLL函数基址的实用函数

GetKernel32

3,综合运用(一个简单的通用ShellCode)

3.1综合前面所讲解的技术编写一个添加帐号及开启Telnet的简单ShellCode:

根据第2节所述技术使用我们自己实现的GetFunctionByName获得LoadLibraryA和

GetProcAddress函数地址,再使用这两个函数引入所有我们需要的函数实现期望的

功能.

4,参考资料.

5,关键字.

--------------------------------------------------------------------------------

一,PE文件结构及引出表基础

1,PE文件结构简介

PE(PortableExecutable,移植的执行体),是微软Win32环境可执行文件的标准格式

(所谓可执行文件不光是.EXE文件,还包?

DLL/.VXD/.SYS/.VDM等)

PE文件结构(简化):

-----------------

│1,DOSMZheader│

-----------------

│2,DOSstub│

-----------------

│3,PEheader│

-----------------

│4,Sectiontable│

-----------------

│5,Section1│

-----------------

│6,Section2│

-----------------

│Section...│

-----------------

│n,Sectionn│

-----------------

记得在我还没有接确Win32编程时,我曾在Dos下运行过一个Win32可执行文件,程序只输出

了一行"ThisprogramcannotberuninDOSmode.",我觉得很有意思,它是怎么识别自

己不在Win32平台下的呢?

其实它并没有进行识别,它可能简单到只输入这一行文字就退出

了,可能源码就像下面的C程序这么简单:

#include

voidmain(void)

{

printf("ThisprogramcannotberuninDOSmode.\n");

}

你可能会问"我在写Win32程序时并没有写过这样的语句啊?

",其实这是由连接器(linker)

为你构建的一个16位DOS程序,当在16位系统(DOS/Windows3.x)下运行Win32程序时它才会

被执行用来输出一串字符提示用户"这个程序不能在DOS模式下运行".

我们先来看看DOSMZheader到底是什么东西,下面是它在Winnt.h中的结构描述:

typedefstruct_IMAGE_DOS_HEADER{//DOS.EXEheader

WORDe_magic;//0x00Magicnumber

WORDe_cblp;//0x02Bytesonlastpageoffile

WORDe_cp;//0x04Pagesinfile

WORDe_crlc;//0x06Relocations

WORDe_cparhdr;//0x08Sizeofheaderinparagraphs

WORDe_minalloc;//0x0aMinimumextraparagraphsneeded

WORDe_maxalloc;//0x0cMaximumextraparagraphsneeded

WORDe_ss;//0x0eInitial(relative)SSvalue

WORDe_sp;//0x10InitialSPvalue

WORDe_csum;//0x12Checksum

WORDe_ip;//0x14InitialIPvalue

WORDe_cs;//0x16Initial(relative)CSvalue

WORDe_lfarlc;//0x18Fileaddressofrelocationtable

WORDe_ovno;//0x1aOverlaynumber

WORDe_res[4];//0x1cReservedwords

WORDe_oemid;//0x24OEMidentifier(fore_oeminfo)

WORDe_oeminfo;//0x26OEMinformation;e_oemidspecific

WORDe_res2[10];//0x28Reservedwords

LONGe_lfanew;//0x3cFileaddressofnewexeheader

}IMAGE_DOS_HEADER,*PIMAGE_DOS_HEADER;

DOSMZheader中包括了一些16位DOS程序的初使化值如果IP(指令指针),cs(代码段寄存

器),需要分配的内存大小,checksum(校验和)等,当DOS准备为可执行文件建立进程时会读取其

中的值来完成初使化工作.

留意到最后一个结构成员了吗?

微软的人对它的描述是Fileaddressofnewexeheader

意义是"新的exe文件头部地址",它是一个相对偏移值,我想文件偏移量你一定知道是什么吧!

e_lfanew就是一个文件偏移值,它指向PEheader,它对我们来说非常重要.紧跟着DOSMZheader

的是DOSstub它是linker为我们建立的这个16位DOS程序的代码实体部分,就是它输出了

"ThisprogramcannotberuninDOSmode.".再后面就是PEheader了,有人曾问过我PE头部

相对于.exe文件的偏移是不是固定的?

这个可不好说,不同的编译器生成的stub长度可能不一样

(比如:

它可能存储了这样一个字串来提示用户"TheCurrnetOSisnotWin32,Iwanttorun

inWin32Mode.",那么这个stub的长度将比前面的那个长),所以用一个固定值来定位PEheader

是不科学的,这个时候我们就用到了e_lfanew,它指向真正的PEheader,它总是正确吗?

那是当然

的!

linker总是会它赋予一个正确的值.所以我们要它精确定位PEheader,同样的Win32PELoader

也根据e_lfanew来定位真正的PEheader,并使用PEheader中的不同的成员值进行初使化,PE还

包涵了很多个"节"(Section),有用来存储数据的,有用来存可执行代码的,还有的是用来存资源

的(如:

程序图标,位图,声音,对话框模板等)

下面我只简单分析一下PE结构与编写ShellCode相关的部分,如果你对其它部分也比较感兴趣

可以看看台港侯俊杰先生译的中的相关内容以及Iczelion的经

典PE教程,我个人觉得将两者结合起来看要好一点.

2,引出表分析

在PEheader结构(你可以Winnt.h中找到它)中包括一个DataDirectory结构成员数组,可以通

过这样的方法来找到它的位置:

PE头部偏移=可执行文件内存映象基址+0x3c(e_lfanew)

PE基址=可执行文件内存映象基址+PE头部偏移

引出表目录指针(IMAGE_EXPORT_DIRECTORY*)=PE基址+0x78<=---DataDirectory

引出函数名称表首指针(char**)=引出表目录基址+0x20

引出函数地址表首指针(DWORD**)=引出表目录指针+0x1c

它的结构定义是这样的:

typedefstruct_Image_Data_Directory{

DWORDVirtualAddress;

DWORDisize;

}IMAGE_DATA_DIRECTORY,*PIMAGE_DATA_DIRECTORY;

该结构数组共包括16成员,第一个成员的VirtualAddress存储了一个相对偏移量,它指向一个

IMAGE_EXPORT_DIRECTORY结构,它的定义是这样的:

typedefstruct_IMAGE_EXPORT_DIRECTORY{

DWORDCharacteristics;//0x00

DWORDTimeDateStamp;//0x04

WORDMajorVersion;//0x08

WORDMinorVersion;//0x0a

DWORDName;//0x0c

DWORDBase;//0x10

DWORDNumberOfFunctions;//0x14

DWORDNumberOfNames;//0x18

DWORDAddressOfFunctions;//0x1cRVAfrombaseofimage

DWORDAddressOfNames;//0x20RVAfrombaseofimage

DWORDAddressOfNameOrdinals;//0x24RVAfrombaseofimage

}IMAGE_EXPORT_DIRECTORY,*PIMAGE_EXPORT_DIRECTORY;

其中AddressOfFunctions里又存储了一个二级指针,它指向一个DWORD型指针数组该数

组成员所指就是函数地址值,但其中的值是函数相对于可执行文件在内存映象中基地址的一

个相对偏移值,真正的函数地址等于这个相对偏移值+可执行文件在内存映象中的基地址,我

们可以Call这个计算后的真实地址来调用函数.AddressOfNames是一个二级字符指针,该数组

成员所指就是函数名称字符串相对于可执行文件在内存映象中的基地址的一个偏移值,同样

可以通过相对偏移值+可执行文件在内存映象中的基地址来引用函数名称字串.Name也是一个

字符指针,它也只存储了相对偏移值,如果是kernel32的IMAGE_EXPORT_DIRECTORY那么它指向

的字串就为"KERNEL32.dll".

3,本节应用实例

关于PE和引出表我们已经分析了与编写ShellCode密切相关的部分,这一部分的确有点难,

但一定要把它搞清楚,只有把它搞懂我们才能进行下一节的学习,在本节的最后附上一个小程序,

在内联汇编代码中大量使用了"间接引用",如果你对指针很熟悉基本上它很好理解,在程序里我

们实现了WindowsAPIGetProcAddress的功能,这种技术对于想使用一些未公开的系统函数也是

非常之有用的.

-----------------------------------------------------

GetFunctionByName函数可以从一个PE执行文件中以函数名查找引出表并返回引出函数地址,只

需要知道KERNEL32.DLL的基地址值,使用它在本程序中我们不包括头文件也可以使用任何一个

WindowsAPI.在我的机器上它是0x77e60000程序如下:

//GetFunctionByName.c

//原型:

DWORDGetFunctionByName(DWORDImageBase,constchar*FuncName,intflen);

//参数:

//ImageBase:

可执行文件的内存映象基址

//FuncName:

函数名称指针

//flen:

函数名称长度

//返回值:

//函数成功时返回有效的函数地址,失败时返回0.

//最终在写ShellCode时,应该给该函数加上__inline声明,因为它要与ShellCode融为一体.

//注意,在本例中我们没有包括任何一个.h文件

unsignedintGetFunctionByName(unsignedintImageBase,constchar*FuncName,intflen)

{

unsignedintFunNameArray,PE,Count=0,*IED;

__asm

{

moveax,ImageBase

addeax,0x3c//指向PE头部偏移值e_lfanew

moveax,[eax]//取得e_lfanew值

addeax,ImageBase//指向PEheader

cmp[eax],0x00004550

jneNotFound//如果ImageBase句柄有错

movPE,eax

moveax,[eax+0x78]

addeax,ImageBase

mov[IED],eax//指向IMAGE_EXPORT_DIRECTORY

//moveax,[eax+0x0c]

//addeax,ImageBase//指向引出模块名,如果在查找KERNEL32.DLL的引出函数那么它将指向"KERNEL32.dll"

//moveax,[IED]

moveax,[eax+0x20]

addeax,ImageBase

movFunNameArray,eax//保存函数名称指针数组的指针值

movecx,[IED]

movecx,[ecx+0x14]//根据引出函数个数NumberOfFunctions设置最大查找次数

FindLoop:

pushecx//使用一个小技巧,使用程序循环更简单

moveax,[eax]

addeax,ImageBase

movesi,FuncName

movedi,eax

movecx,flen//逐个字符比较,如果相同则为找到函数,注意这里的ecx值

cld

repcmpsb

jneFindNext//如果当前函数不是指定的函数则查找下一个

addesp,4//如果查找成功,则清除用于控制外层循环而压入的Ecx,准备返回

moveax,[IED]

moveax,[eax+0x1c]

addeax,ImageBase//获得函数地址表

shlCount,2//根据函数索引计算函数地址指针=函数地址表基址+(函数索引*4)

addeax,Count

moveax,[eax]//获得函数地址相对偏移量

addeax,ImageBase//计算函数真实地址,并通过Eax返回给调用者

jmpFound

FindNext:

incCount//记录函数索引

add[FunNameArray],4//下一个函数名指针

moveax,FunNameArray

popecx//恢复压入的ecx(NumberOfFunctions),进行计数循环

loopFindLoop//如果ecx不为0则递减并回到FindLoop,往后查找

NotFound:

xoreax,eax//如果没有找到,则返回0

Found:

}

}

/*

让我们来测试一下,先用GetFunctionByName获得kernel32.dll中LoadLibraryA

的地址,再用它装载user32.dll,再用GetFunctionByName获得MessageBoxA的地址,call

它一下

*/

intmain(void)

{

chartitle[]="test",user32[]="user32",msgf[]="MessageBoxA";

unsignedintloadlibfun;

loadlibfun=GetFunctionByName(0x77e60000,"LoadLibraryA",12);

//0x77e60000是我机器上的kernel32.dll的基址,不同机器上的值可能不同

__asm

{

leaeax,user32

pusheax

calldwordptrloadlibfun//相当于执行LoadLibrary("user32");

leaebx,msgf

push0x0b//"MessageBoxA"的长度

pushebx

pusheax

callGetFunctionByName

movebx,eax

addesp,0x0c//GetFunctionByName使用C调用约定,由调用者调整堆栈

push0

leaeax,title

pusheax

pusheax

push0

callebx//相当于执行MessageBox(NULL,"test","test",MB_OK)

}

return1;

}

函数的内联汇编代码有很多这样的语句:

moveax,[somewhere]

moveax,[eax+0x?

?

]

addeax,ImageBase

我试过使用moveax,[ImageBase+eax+0x?

?

]之类的语法,因为用到很多多级指针,而它们指向

的又是相对偏移量所以要不断的"获取和计算",否则很容易导致"访问违例".编译运行,弹出了

一个MessageBox标题和内容都是"test"看到了吗?

你可能会问这个程序拿到其它机器上也可能

运行吗?

在整个程序里我们唯一依赖的就是0x77e60000这个kernel32.dll基址,其它机器上的

可能不是这个值,如果这个地址值可以在程序运行时动态的计算出来,那么这个程序将非常通

用,它可以动态计算出来吗?

答案是肯定的!

下一节我们将来分析一种并不很流行但很通用的动

态计算获得kernel32.dll基址的方法.

---------------------------------------------------------------------------------

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

1,简析结构化异常处理(SEH,StructredExceptionHandling)

SEH已经不是很什么新技术了,但是对于我将要讲了非常重要,所以在这里对它做一个简单的

分析.Ok,打开VC,让我们来分析一个简单的"除"运算程序,看看它哪里有问题:

#include

#include

intmain(void)

{

intx,y,z=y=x=0;

printf("Inputtwointegernumber:

");

scanf("%d%d",&x,&y);

z=x/y;

printf("%dDIV%d=%d",x,y,z);

getch();

return0;

}

编译,运行:

输入42,程序输出"4DIV2=2",结果很正确.再运行输入40,问题出来了,

VisualStudio弹出了一个信息框:

"Unhandledexceptioninseh.exe:

0xC0000094:

IntegerDividebyZero",出现了未处理的

"除0异常",传统的方法是我们在z=x/y之前加上判断:

#include

#include

intmain(void)

{

intx,y,z=y=x=0;

printf("Inputtwointegernumber:

");

scanf("%d%d",&x,&y);

if(!

y)

{

printf("CannotDividebyZero!

");

gotoLQUIT;

}

z=x/y;

printf("%dDIV%d=%d",x,y,z);

LQUIT:

getch();

return0;

}

出错处理在这个小程序里这的确很容易看懂,可是想想如果在数千甚至上万行的程序里,这样的

错误捕获处理会让程序变的十分凌乱难懂,而且传统方法处理的是我们可以想像(猜测)到的错误,

但是某些导到程序出错的情况是很随机的,这样就不能保证程序的健壮性了,而SEH正是为了让正

常的处理代码和出错处理代码分开,以使程序结构清淅,并使程序更加

健壮.让我们再把这个小程序改一下:

#include

#include

#include

intmain(void)

{

intx,y,z=y=x=0;

printf("InputTwoIntegerNumber:

");

scanf("%d%d",&x,&y);

__try

{//把可能出错的程序段封装起来

z=x/y;

//......

}

__except(EXCEPTION_EXECUTE_HANDLER)

{//在这里找出出现异常的原因,并进行处理

switch(GetExceptionCode())

{

caseEXCEPTION_INT_DIVIDE_BY_ZERO:

//如果除0异常

{

printf("CannotDividebyZero!

");

gotoLQUIT;

}

caseEXCEPT

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 自然科学 > 物理

copyright@ 2008-2023 冰点文库 网站版权所有

经营许可证编号:鄂ICP备19020893号-2