通用ShellCode深入剖析

时间:2006-10-12 来源: 作者: 点击:
前言: 在网上关于ShellCode编写技术的文章已经非常之多,什么理由让我再写这种技术文章呢?本文是我上一篇溢出技术文章Windows 2000缓冲区溢出技术原理>的姊妹篇,同样的在网上我们经常可以看到一些关于ShelCode编写技术的文章,似乎没有为初学者准备的,在这里我将站在初学
  
  前言:
  在网上关于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的功能,这种技术对于想使用一些未公开的系统函数也是非常之有用的.
  ------------ -----------------------------------------
  
  GetFunctionBy
------分隔线----------------------------
顶一下
(0)
0%
踩一下
(0)
0%
------分隔线----------------------------
最新评论 查看所有评论
发表评论 查看所有评论
请自觉遵守互联网相关的政策法规,严禁发布色情、暴力、反动的言论。
评价:
表情:
用户名: 密码: 验证码:
推荐内容