没有合适的资源?快使用搜索试试~ 我知道了~
通用ShellCode深入剖析
需积分: 3 22 下载量 120 浏览量
2009-06-04
08:56:12
上传
评论
收藏 162KB DOC 举报
温馨提示
试读
27页
通用ShellCode理论,基本上我们就可以根据一些公布的Window溢出漏洞或 是自己对一些软件系统进行反汇编分析出的溢出漏洞试着编写一些溢出攻击测试程序.
资源推荐
资源详情
资源评论
通用 ShellCode 深入剖析
文章出处:http://www.diybl.com/course/3_program/c++/cppsl/2008311/104207.html
通用 ShellCode 深入剖析
前言:
在网上关于 ShellCode 编写技术的文章已经非常之多,什么理由让我再写这种技术文
章呢?本文是我上一篇溢出技术文章<Windows 2000 缓冲区溢出技术原理>的姊妹篇,同样
的在网上我们经常可以看到一些关于 ShelCode 编写技术的文章,似乎没有为初学者准备的
,在这里我将站在初学者的角度对通用 ShellCode 进行比较详细的分析,有了上一篇的溢出
理论和本篇的通用 ShellCode 理论,基本上我们就可以根据一些公布的 Window 溢出漏洞或
是自己对一些软件系统进行反汇编分析出的溢出漏洞试着编写一些溢出攻击测试程序.
文章首先简单分析了 PE 文件格式及 PE 引出表,并给出了一个例程,演示了如何根据 PE
相关技术查找引出函数及其地址,随后分析了一种比较通用的获得 Kernel32 基址的方法,
最后结合理论进行简单的应用,给出了一个通用 ShellCode.
本文同样结合我学习时的理解以比较容易理解的方式进行描述,但由于 ShellCode 的
复杂性,文章主要使用 C 和 Asm 来讲解,作者假设你已具有一定的 C/Asm 混合编程基础以及
上
一篇的溢出理论基础,希望本文能让和我一样初学溢出技术的朋友有所提高.
[目录]
1,PE 文件结构的简介,及 PE 引出表的分析.
1.1 PE 文件简介
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(Portable Executable,移植的执行体),是微软 Win32 环境可执行文件的标准格式
(所谓可执行文件不光是.EXE 文件,还包括.DLL/.VXD/.SYS/.VDM 等)
PE 文件结构(简化):
-----------------
│1,DOS MZ header│
-----------------
│2,DOS stub │
-----------------
│3,PE header │
-----------------
│4,Section table│
-----------------
│5,Section 1 │
-----------------
│6,Section 2 │
-----------------
│ Section ... │
-----------------
│n,Section n │
-----------------
记得在我还没有接确 Win32 编程时,我曾在 Dos 下运行过一个 Win32 可执行文件,程序只输
出
了一行"This program cannot be run in DOS mode.",我觉得很有意思,它是怎么识别自
己不在 Win32 平台下的呢?其实它并没有进行识别,它可能简单到只输入这一行文字就退出
了,可能源码就像下面的 C 程序这么简单:
#include <stdio.h>
void main(void)
{
printf("This program cannot be run in DOS mode.\n");
}
你可能会问"我在写 Win32 程序时并没有写过这样的语句啊?",其实这是由连接器(linker)
为你构建的一个 16 位 DOS 程序,当在 16 位系统(DOS/Windows 3.x)下运行 Win32 程序时它
才会
被执行用来输出一串字符提示用户"这个程序不能在 DOS 模式下运行".
我们先来看看 DOS MZ header 到底是什么东西,下面是它在 Winnt.h 中的结构描述:
typedef struct _IMAGE_DOS_HEADER { //DOS .EXE header
WORD e_magic; //0x00 Magic number
WORD e_cblp; //0x02 Bytes on last page of file
WORD e_cp; //0x04 Pages in file
WORD e_crlc; //0x06 Relocations
WORD e_cparhdr; //0x08 Size of header in paragraphs
WORD e_minalloc; //0x0a Minimum extra paragraphs needed
WORD e_maxalloc; //0x0c Maximum extra paragraphs needed
WORD e_ss; //0x0e Initial (relative) SS value
WORD e_sp; //0x10 Initial SP value
WORD e_csum; //0x12 Checksum
WORD e_ip; //0x14 Initial IP value
WORD e_cs; //0x16 Initial (relative) CS value
WORD e_lfarlc; //0x18 File address of relocation table
WORD e_ovno; //0x1a Overlay number
WORD e_res[4]; //0x1c Reserved words
WORD e_oemid; //0x24 OEM identifier (for e_oeminfo)
WORD e_oeminfo; //0x26 OEM information; e_oemid specific
WORD e_res2[10]; //0x28 Reserved words
LONG e_lfanew; //0x3c File address of new exe header
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
DOS MZ header 中包括了一些 16 位 DOS 程序的初使化值如果 IP(指令指针),cs(代码段寄存
器),需要分配的内存大小,checksum(校验和)等,当 DOS 准备为可执行文件建立进程时会读取
其
中的值来完成初使化工作.
留意到最后一个结构成员了吗?微软的人对它的描述是 File address of new exe header
意义是"新的 exe 文件头部地址",它是一个相对偏移值,我想文件偏移量你一定知道是什么吧!
e_lfanew 就是一个文件偏移值,它指向 PE header,它对我们来说非常重要.紧跟着 DOS MZ
header
的是 DOS stub 它是 linker 为我们建立的这个 16 位 DOS 程序的代码实体部分,就是它输出了
"This program cannot be run in DOS mode.".再后面就是 PE header 了,有人曾问过我 PE 头部
相对于.exe 文件的偏移是不是固定的?这个可不好说,不同的编译器生成的 stub 长度可能不
一样
(比如:它可能存储了这样一个字串来提示用户"The Currnet OS is not Win32,I want to run
in Win32 Mode.",那么这个 stub 的长度将比前面的那个长),所以用一个固定值来定位 PE
header
是不科学的,这个时候我们就用到了 e_lfanew,它指向真正的 PE header,它总是正确吗?那是当
然
的!linker 总是会它赋予一个正确的 值 .所以我们要它 精确定位 PE header,同样的 Win32
PELoader
也根据 e_lfanew 来定位真正的 PE header,并使用 PE header 中的不同的成员值进行初使化,PE
还
包涵了很多个"节"(Section),有用来存储数据的,有用来存可执行代码的,还有的是用来存资源
的(如:程序图标,位图,声音,对话框模板等)
下面我只简单分析一下 PE 结构与编写 ShellCode 相关的部分,如果你对其它部分也比较感兴
趣
可以看看台港侯俊杰先生译的 <Windows 95 系统程序设计大奥秘 >中的相关内容以及
Iczelion 的经
典 PE 教程,我个人觉得将两者结合起来看要好一点.
2,引出表分析
在 PE header 结构(你可以 Winnt.h 中找到它)中包括一个 DataDirectory 结构成员数组,可以通
过这样的方法来找到它的位置:
PE 头部偏移=可执行文件内存映象基址+0x3c(e_lfanew)
PE 基址=可执行文件内存映象基址+PE 头部偏移
引出表目录指针(IMAGE_EXPORT_DIRECTORY*)=PE 基址+0x78<=---DataDirectory
引出函数名称表首指针(char**)=引出表目录基址+0x20
引出函数地址表首指针(DWORD **)=引出表目录指针+0x1c
它的结构定义是这样的:
typedef struct _Image_Data_Directory{
DWORD VirtualAddress;
DWORD isize;
}IMAGE_DATA_DIRECTORY,*PIMAGE_DATA_DIRECTORY;
该结构数组共包括 16 成员,第一个成员的 VirtualAddress 存储了一个相对偏移量,它指向一个
IMAGE_EXPORT_DIRECTORY 结构,它的定义是这样的:
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics;//0x00
DWORD TimeDateStamp;//0x04
WORD MajorVersion;//0x08
WORD MinorVersion;//0x0a
DWORD Name;//0x0c
DWORD Base;//0x10
DWORD NumberOfFunctions;//0x14
DWORD NumberOfNames;//0x18
DWORD AddressOfFunctions;//0x1c RVA from base of image
DWORD AddressOfNames;//0x20 RVA from base of image
DWORD AddressOfNameOrdinals;//0x24 RVA from base of image
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
其中 AddressOfFunctions 里又存储了一个二级指针,它指向一个 DWORD 型指针数组该数
组成员所指就是函数地址值,但其中的值是函数相对于可执行文件在内存映象中基地址的一
个相对偏移值,真正的函数地址等于这个相对偏移值+可执行文件在内存映象中的基地址,我
们可以 Call 这个计算后的真实地址来调用函数.AddressOfNames 是一个二级字符指针,该数
组
成员所指就是函数名称字符串相对于可执行文件在内存映象中的基地址的一个偏移值,同样
可以通过相对偏移值+可执行文件在内存映象中的基地址来引用函数名称字串.Name 也是一
个
字符指针,它也只存储了相对偏移值,如果是 kernel32 的 IMAGE_EXPORT_DIRECTORY 那
么它指向
的字串就为"KERNEL32.dll".
3,本节应用实例
关于 PE 和引出表我们已经分析了与编写 ShellCode 密切相关的部分,这一部分的确有点难,
但一定要把它搞清楚,只有把它搞懂我们才能进行下一节的学习,在本节的最后附上一个小程
序,
在内联汇编代码中大量使用了"间接引用",如果你对指针很熟悉基本上它很好理解,在程序里
我
们实现了 Windows API GetProcAddress 的功能,这种技术对于想使用一些未公开的系统函数
也是
非常之有用的.
------------ -----------------------------------------
GetFunctionByName 函数可以从一个 PE 执行文件中以函数名查找引出表并返回引出函数地
址,只
需要知道 KERNEL32.DLL 的基地址值,使用它在本程序中我们不包括头文件也可以使用任
何一个
Windows API.在我的机器上它是 0x77e60000 程序如下:
//GetFunctionByName.c
//原型:DWORD GetFunctionByName(DWORD ImageBase,const char*FuncName,int flen);
//参数:
// ImageBase: 可执行文件的内存映象基址
// FuncName: 函数名称指针
// flen: 函数名称长度
//返回值:
// 函数成功时返回有效的函数地址,失败时返回 0.
//最终在写 ShellCode 时,应该给该函数加上__inline 声明,因为它要与 ShellCode 融为一体.
//注意,在本例中我们没有包括任何一个.h 文件
unsigned int GetFunctionByName(unsigned int ImageBase,const char*FuncName,int flen)
{
unsigned int FunNameArray,PE,Count=0,*IED;
__asm
剩余26页未读,继续阅读
资源评论
scumatlab
- 粉丝: 0
- 资源: 5
上传资源 快速赚钱
- 我的内容管理 展开
- 我的资源 快来上传第一个资源
- 我的收益 登录查看自己的收益
- 我的积分 登录查看自己的积分
- 我的C币 登录后查看C币余额
- 我的收藏
- 我的下载
- 下载帮助
安全验证
文档复制为VIP权益,开通VIP直接复制
信息提交成功