前言:
在网上关于ShellCode编写技巧的文章已经非常之多,什么理由让我再写这种技巧文章呢?本文是我上一篇溢出技巧文章
文章首先简略分析了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
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相干的部分,假如你对其它部分也比拟感爱好可以看看台港侯俊杰先生译的
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{
DWORDVirtualAddress;
DWORDisize;
}IMAGE_DATA_DIRECTORY,*PIMAGE_DATA_DIRECTORY;
该结构数组共包含16成员,第一个成员的VirtualAddress存储了一个相对偏移量,它指向一个IMAGE_EXPORT_DIRECTORY结构,它的定义是这样的:
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics;//0x00
DWORD TimeDateStamp;//0x04
WORDMajorVersion;//0x08
WORDMinorVersion;//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".