栈溢出笔记1.6 地址问题(1)

前面的Shellcode中,我使用的都是自己XP机器上的硬编码地址。不论什么时候在Shellcode中使用硬编码地址都不是个好主意,这一点与动态库的重定位相似,一旦系统环境和程序编译设置发生变化。Shellcode差点儿肯定会失效。因此。我们要找到更好一点的方法。

前面的Shellcode中,我用到了例如以下几个硬编码地址。它们的含义例如以下:

当中。LoadLibraryA的作用比較特殊,我们用它来载入user32.dll库。

如今我们要换掉这些硬编码地址。那么,怎样得到这些API函数的地址呢?在动态链接库中获取函数地址有一个专门的函数——GetProcAddress,这个函数的原型例如以下:

/*****************************************************************************/
FARPROC WINAPI GetProcAddress(
  _In_ HMODULE hModule,
  _In_ LPCSTR  lpProcName
);
/*****************************************************************************/

这个函数第一个參数为模块的句柄,能够调用LoadLibraryA获得,第二个參数为函数名称。

这种话。MessageBoxA和ExitProcess都能够使用这种方式来获取。可是这依赖于两个函数:LoadLibraryA和GetProcAddress,那这两个函数的地址又怎么得到呢?

这两个函数都位于kernel32.dll。kernel32.dll肯定会载入。不须要我们自己载入。因此,如今的问题就是怎样从kernel32.dll中找到LoadLibraryA和GetProcAddress的地址?

接下来就须要一点Windows内核和PE文件格式的知识了。我们知道,kernel32.dll为PE格式的动态链接库,要导出的函数放在PE文件的导出表中,因此,为了获取LoadLibraryA和GetProcAddress的地址,我们须要手动解析kernel32.dll的导出表。但在这之前,我们须要知道kernel32.dll在内存中的位置,也就是kernel32.dll的基地址。

因此。问题总结为下面:

(1)怎样获取kernel32.dll的基地址?

(2)怎样在kernel32.dll的导出表中找到LoadLibraryA和GetProcAddress的地址?

先来解决第一个问题。我们知道。一个进程执行的时候。除了载入exe文件外,所依赖的.dll也会映射到进程的虚拟地址空间中。那么。这些载入的dll也是进程的財产。因此,进程会保存它们的信息。进程的信息都保存在PEB结构中。当中的Ldr(偏移为0xc0)指向一个PEB_LDR_DATA结构,这个结构保存的进程已载入模块的信息。

这个结构例如以下:

/*****************************************************************************/
typedef struct _PEB_LDR_DATA
{
 ULONG Length;          // +0x00
 BOOLEAN Initialized;   // +0x04
 PVOID SsHandle;            // +0x08
 LIST_ENTRY InLoadOrderModuleList;      // +0x0c
 LIST_ENTRY InMemoryOrderModuleList;    // +0x14
 LIST_ENTRY InInitializationOrderModuleList;    // +0x1c
} PEB_LDR_DATA,*PPEB_LDR_DATA;      // +0x24
/*****************************************************************************/

Windows并未全然公开(文档化)此结构。这是网上的版本号,我们也能够通过Windbg来得到,这是我XP SP3机器上的该结构:

图37

这个结构的重点在于后面三个链表:InLoadOrderModuleList、InMemoryOrderModuleList和InInitializationOrderModuleList。从名称上看,这三个队列都是模块链表,第一个是按载入的先后顺序。第二个是按在虚拟空间中的位置,第三个是按初始化的顺序。

第二个easy理解,可是第一个和第三个有什么差别呢?载入是先于初始化的,载入就是完毕虚拟空间的映射和与exe的链接。载入完毕后的DLL会挂入InInitializationOrderModuleList。进行初始化,初始化就是调用其DLLMain。能够看到的一点是InLoadOrderModuleList中有exe本身的模块,而InInitializationOrderModuleList中仅仅有exe依赖的DLL。(当然。这随着系统版本号在发生变化)

我们要使用的是InInitializationOrderModuleList,挂入该链表中的为LDR_DATA_TABLE_ENTRY。也就是说。每一个载入的模块相应于一个LDR_DATA_TABLE_ENTRY,该结构例如以下:

/*****************************************************************************/
typedef struct _LDR_DATA_TABLE_ENTRY
{
    LIST_ENTRY InLoadOrderLinks;
    LIST_ENTRY InMemoryOrderLinks;
    LIST_ENTRY InInitializationOrderLinks;
    PVOID DllBase;
    PVOID EntryPoint;
    DWORD SizeOfImage;
    UNICODE_STRING FullDllName;
    UNICODE_STRING BaseDllName;
    DWORD Flags;
    WORD LoadCount;
    WORD TlsIndex;
    LIST_ENTRY HashLinks;
    PVOID SectionPointer;
    DWORD CheckSum;
    DWORD TimeDateStamp;
    PVOID LoadedImports;
    PVOID EntryPointActivationContext;
    PVOID PatchInformation;
}LDR_DATA_TABLE_ENTRY,*PLDR_DATA_TABLE_ENTRY;
/*****************************************************************************/

相同。在我的机器上为:

前面三个链表相应于该结构挂入的三个链表,重点在于第四个成员DllBase,这是载入模块的基地址。因此,我们仅仅须要顺着InInitializationOrderModuleList链表找到kernel32.dll的PLDR_DATA_TABLE_ENTRY。然后通过其DllBase成员。就知道了kernel32.dll载入的地址。

那么InInitializationOrderModuleList链表中哪一个kernel32.dll呢?最保险的方法是解析FullDllName成员,这样代码会比較复杂。实际上在特定版本号的系统中,动态库初始化的顺序是一定的。第一个为ntdll.dll,第二个就是kernel32.dll。Vista 以后第二个是kernelbase.dll。第三个是kernel32.dll。因此,能够避免解析FullDllName成员。

如今。我们要找到进程的PEB结构地址,PEB结构保存于线程的TEB结构中的peb成员,而Windows系统中,寄存器fs总是指向当前线程的TEB。因此。获取kernel32.dll基地址的整个流程例如以下:

图38

写代码之前。先通过调试器顺着该顺序看一看,我用windbg载入的是example_1。输入:

d fs:[0x30]查看当前peb的地址:

图39

地址为0x7ffda000。输入:!peb。验证一下:

图40

是相同的。

输入: d 7ffda000+0x0c,查看PEB_LDR_DATA结构的地址:

图41

输入: d 00251ea0+0x1c,查看InInitializationOrderModuleList链表:

图42

先列一下LIST_ENTRY结构。Flink 指向下一个节点:

/*****************************************************************************/
typedef struct _LIST_ENTRY
{
     struct _LIST_ENTRY *Flink;
     struct _LIST_ENTRY *Blink;
} LIST_ENTRY, *PLIST_ENTRY;
/*****************************************************************************/

因此。0x00251f58是第一个元素。输入:d 0x00251f58查看下面其内容:

基地址为0x7c920000,可是右边显示为kernel32.dll???第一个元素不是ntdll.dll吗?

来看看下一个元素:

基地址为0x7c800000。显示为uer32.dll,看来是出了一些问题,输入!peb来看看:

这里的基地址和名称相应才是对的。

下面来写获取kernel32.dll基地址的代码:

/*****************************************************************************/
// example_8 获取kernel32.dll的基地址
#include <stdio.h>

// 获取kernel32.dll的基地址
int get_kernel32_base()
{
    __asm
    {
        mov eax, fs:[0x30]  // PEB
        mov eax, [eax+0x0c] // PEB->Ldr
        mov eax, [eax+0x1c] // PEB->Ldr.InInitializationOrderModuleList.Flink(指向第一个元素)
        mov eax, [eax]      // 指向第二个元素
        mov eax, [eax+0x08] // kernel32.dll基地址
    }
}

int main()
{
    printf("0x%x\n", get_kernel32_base());

    return 0;
}
/*****************************************************************************/

结果例如以下:

图43

这里是一种方法,其他方法(包含Windows 7)请看:

http://blog.harmonysecurity.com/2009_06_01_archive.html

时间: 2024-08-02 13:27:00

栈溢出笔记1.6 地址问题(1)的相关文章

CCNA学习笔记五——ip地址子网划分

ip地址作用:用来标识一个节点的网络地址 ip地址的分类: A类:0NNNNNNN(1-126)  私有地址:10.0.0.0-10.255.255.255 B类:10NNNNNN(128-191)  私有地址:172.16.0.0-172.31.255.255 C类:110NNNNN(192-223)  私有地址:192.168.0.0-192.168.255.255 D类:1110NNNN(224-239) 子网掩码中1对应的为ip地址位为网络位,0对应的为主机位 划分子网:通过将子网掩码变

【安全牛学习笔记】Mac地址绑定攻击

MAC地址绑定攻击 MAC绑定                           管理员误以为MAC绑定是一种安全机制 限制可以关联的客户端MAC地址                                                                   准备AP                                AP基本配置                        Open认证                          开启无线过滤   

栈溢出笔记1.10 基于SEH的栈溢出

上节中简单地讲述了SEH的原理及逻辑结构.本节,要继续讲述SEH的物理结构及如何利用它进行栈溢出. 先来看SEH的物理结构.先回想上节中的图51,我们在程序停在gets函数输入的时候查看SEH链,看到了一大堆异常处理器,而当我们把断点设置在gets函数下一条语句的时候,其中很多没有了,这给我们一个直观的感觉:SEH链保存在栈上. 下面,我们就来看看栈上的SEH链.我们使用的是example_10,即添加了一个自己的异常处理块的程序(编译时继续采用前面教程中的设置,即关闭缓冲区安全检查).依然把断

栈溢出笔记1.9 认识SEH

从本节开始,我们就要研究一些稍微高级点的话题了,如同在1.2节中看到的,Windows中为抵抗栈溢出做了很多保护性的检查工作,编译的程序默认开启了这些保护.如果我们不能绕过这些保护,那么我们的Shellcode也就是一个玩具而已,什么都做不了. 我们从SEH(结构化异常处理)开始. 这篇文章讲SEH简洁易懂:http://www.securitysift.com/windows-exploit-development-part-6-seh-exploits/ 因此,本文的前面部分就直接对其进行翻

栈溢出笔记1.4 黑掉example_2

在1.2节中我们编写了一个有漏洞的程序,通过输入可以控制其EIP,本节,我们要让example_2运行我们的MessageBox.再看看example_2: /*****************************************************************************/ // example_2: 演示栈溢出 #include <stdio.h> void get_print() { char str[11]; gets(str); printf(

栈溢出笔记1.1 函数调用过程

选择从栈溢出开始学习Shellcode的编写,是因为在没有保护机制(栈Cookie,ASLR,DEP,SafeSEH)的系统中使用栈溢出是一件很简单的事情.栈区随着函数调用动态变化,每个函数调用时在栈上占用的空间称为栈帧.用一个示例来说明栈上保存的内容及动态变化的过程. 下面是一个程序,生成一个对话框显示一条"Hello World!"消息.下面是该程序的C代码: 在VS2008中用Debug版编译之后,拖入Immunity Debugger中: 图1 example_1.exe入口点

栈溢出笔记1.2 覆盖EIP

1.1节中我们说到可以利用栈溢出来破坏栈中原有的内容,这一节中,我们就来看看如何争夺到返回地址(EIP),使得我们可以随意控制它的值,这样我们就可以控制程序.来看一个经典的程序: 这个程序的get_print函数中定义了一个大小为11个字节的数组,正常情况下我们的输入应该最多为10个字符(还有一个\0结束符),而gets函数没有明确定义输入的大小,因此,我们可以输入超过10个字符,从而造成栈溢出.如下,输入10个'A',一切正常: 图8 当我输入11个'A'时,虽然顺利打印出来11个'A',但是

栈溢出笔记1.3 准备Shellcode

经过1.1和1.2节的讲述,我们已经知道了怎样更改EIP的值. 程序运行函数之后将跳转到我们设定的位置開始运行,因此,我们须要准备一个自己的程序,接手后面的工作.这是一个什么样的程序?是一个C语言编写的代码?是一个可直接调用的exe?肯定不是,由于EIP所指的地址保存的内容为指令的操作码,CPU读取该操作码运行相应的操作. 所以我们要准备的程序也应该是一段"操作码". 继续写1.1中的Hello World.这次我们要把一个C语言编写的MessageBox换成一个仅仅有"操作

RHCA442学习笔记-Unit10内存地址及分配

Unit 10 Memory Addressing and Allocation 内存地址及分配 学习目标: A. 虚拟地址与物理地 B. 调整内存地址分配 C. 解析内存溢出 10.1 Overview of memory addressing 内存地址概述 A. 虚拟地址空间 a. 每个进程都有自己线性连续的地址空间 b. 地址空间范围从0到最大地址空间值: X86: 2^32=4G X86_64: 2^64=1TiB (redhat 最大支持256GiB) B. 物理地址空间 a. 虚拟地