Windows?NT/2000/XP下不用驅動的Ring0代碼實現?????
??????????? WebCrazy(http://webcrazy.yeah.net/)
????大家知道,Windows?NT/2000為實現其可靠性,嚴格將系統劃分為內核模式與用戶模式,在i386系統中分別對應CPU的Ring0與Ring3級別。Ring0下,可以執行特權級指令,對任何I/O設備都有訪問權等等。要實現從用戶態進入核心態,即從Ring?3進入Ring?0必須借助CPU的某種門機制,如中斷門、調用門等。而Windows?NT/2000提供用戶態執行系統服務(Ring?0例程)的此類機制即System?Service的int?2eh中斷服務等,嚴格的參數檢查,只能嚴格的執行Windows?NT/2000提供的服務,而如果想執行用戶提供的Ring?0代碼(指運行在Ring?0權限的代碼),常規方法似乎只有編寫設備驅動程序。本文將介紹一種在用戶態不借助任何驅動程序執行Ring0代碼的方法。
????Windows?NT/2000將設備驅動程序調入內核區域(常見的位于地址0x80000000上),由DPL為0的GDT項8,即cs為8時實現Ring?0權限。本文通過在系統中構造一個指向我們的代碼的調用門(CallGate),實現Ring0代碼。基于這個思路,為實現這個目的主要是構造自己的CallGate。CallGate由系統中叫Global?Descriptor?Table(GDT)的全局表指定。GDT地址可由i386指令sgdt獲得(sgdt不是特權級指令,普通Ring 3程序均可執行)。GDT地址在Windows?NT/2000保存于KPCR(Processor?Control?Region)結構中(見《再談Windows?NT/2000環境切換》)。GDT中的CallGate是如下的格式:
????typedef?struct

????
{?
????????unsigned?short??offset_0_15;
????????unsigned?short??selector;

????????unsigned?char????param_count?:?4;
????????unsigned?char????some_bits???:?4;

????????unsigned?char????type????????:?4;
????????unsigned?char????app_system??:?1;
????????unsigned?char????dpl?????????:?2;
????????unsigned?char????present?????:?1;
????
????????unsigned?short??offset_16_31;
?????}?CALLGATE_DESCRIPTOR;????GDT位于內核區域,一般用戶態的程序是不可能對這段內存區域有直接的訪問權。幸運的是Windows?NT/2000提供了一個叫PhysicalMemory的Section內核對象位于\Device的路徑下。顧名思義,通過這個Section對象可以對物理內存進行操作。用objdir.exe對這個對象分析如下:
????C:\NTDDK\bin>objdir?/D?\Device

????PhysicalMemory???????????????????
????????Section
????????DACL?-?
???????????Ace[?0]?-?Grant?-?0xf001f?-?NT?AUTHORITY\SYSTEM
?????????????????????????????Inherit:?
?????????????????????????????Access:?0x001F??and??(?D?RCtl?WOwn?WDacl?)

???????????Ace[?1]?-?Grant?-?0x2000d?-?BUILTIN\Administrators
?????????????????????????????Inherit:?
?????????????????????????????Access:?0x000D??and??(?RCtl?)???從dump出的這個對象DACL的Ace可以看出默認情況下只有SYSTEM用戶才有對這個對象的讀寫權限,即對物理內存有讀寫能力,而Administrator只有讀權限,普通用戶根本就沒有權限。不過如果我們有Administrator權限就可以通過GetSecurityInfo、SetEntriesInAcl與SetSecurityInfo這些API來修改這個對象的ACE。這也是我提供的代碼需要Administrator的原因。實現的代碼如下:
???VOID?SetPhyscialMemorySectionCanBeWrited(HANDLE?hSection)

????
{?

???????PACL?pDacl=NULL;
???????PACL?pNewDacl=NULL;
???????PSECURITY_DESCRIPTOR?pSD=NULL;
???????DWORD?dwRes;
???????EXPLICIT_ACCESS?ea;

???????if(dwRes=GetSecurityInfo(hSection,SE_KERNEL_OBJECT,DACL_SECURITY_INFORMATION,
??????????????????NULL,NULL,&pDacl,NULL,&pSD)!=ERROR_SUCCESS)

??????????
{?
?????????????printf(?"GetSecurityInfo?Error?%u\n",?dwRes?);
?????????????goto?CleanUp;
???????????}

???????ZeroMemory(&ea,?sizeof(EXPLICIT_ACCESS));
???????ea.grfAccessPermissions?=?SECTION_MAP_WRITE;
???????ea.grfAccessMode?=?GRANT_ACCESS;
???????ea.grfInheritance=?NO_INHERITANCE;
???????ea.Trustee.TrusteeForm?=?TRUSTEE_IS_NAME;
???????ea.Trustee.TrusteeType?=?TRUSTEE_IS_USER;
???????ea.Trustee.ptstrName?=?"CURRENT_USER";


???????if(dwRes=SetEntriesInAcl(1,&ea,pDacl,&pNewDacl)!=ERROR_SUCCESS)

??????????
{?
?????????????printf(?"SetEntriesInAcl?%u\n",?dwRes?);
?????????????goto?CleanUp;
???????????}

???????if(dwRes=SetSecurityInfo(hSection,SE_KERNEL_OBJECT,DACL_SECURITY_INFORMATION,NULL,NULL,pNewDacl,NULL)!=ERROR_SUCCESS)

??????????
{?
?????????????printf("SetSecurityInfo?%u\n",dwRes);
?????????????goto?CleanUp;
???????????}

????CleanUp:

???????if(pSD)
??????????LocalFree(pSD);
???????if(pNewDacl)
??????????LocalFree(pSD);
?????}

????這段代碼對給定HANDLE的對象增加了如下的ACE:?

????PhysicalMemory???????????????????
????????Section
????????DACL?-?
???????????Ace[?0]?-?Grant?-?0x2?-?WEBCRAZY\Administrator
?????????????????????????????Inherit:?
?????????????????????????????Access:?0x0002????//SECTION_MAP_WRITE

???這樣我們在有Administrator權限的條件下就有了對物理內存的讀寫能力。但若要修改GDT表實現Ring?0代碼。我們將面臨著另一個難題,因為sgdt指令獲得的GDT地址是虛擬地址(線性地址),我們只有知道GDT表的物理地址后才能通過\Device\PhysicalMemory對象修改GDT表,這就牽涉到了線性地址轉化成物理地址的問題。我們先來看一看Windows?NT/2000是如何實現這個的:
????kd>?u?nt!MmGetPhysicalAddress?l?30
????ntoskrnl!MmGetPhysicalAddress:
????801374e0?56???????????????push????esi
????801374e1?8b742408?????????mov?????esi,[esp+0x8]
????801374e5?33d2?????????????xor?????edx,edx
????801374e7?81fe00000080?????cmp?????esi,0x80000000
????801374ed?722c?????????????jb????ntoskrnl!MmGetPhysicalAddress+0x2b?(8013751b)
????801374ef?81fe000000a0?????cmp?????esi,0xa0000000
????801374f5?7324?????????????jnb???ntoskrnl!MmGetPhysicalAddress+0x2b?(8013751b)
????801374f7?39153ce71780?????cmp?????[ntoskrnl!MmKseg2Frame?(8017e73c)],edx
????801374fd?741c?????????????jz????ntoskrnl!MmGetPhysicalAddress+0x2b?(8013751b)
????801374ff?8bc6?????????????mov?????eax,esi
????80137501?c1e80c???????????shr?????eax,0xc
????80137504?25ffff0100???????and?????eax,0x1ffff
????80137509?6a0c?????????????push????0xc
????8013750b?59???????????????pop?????ecx
????8013750c?e8d3a7fcff???????call????ntoskrnl!_allshl?(80101ce4)
????80137511?81e6ff0f0000?????and?????esi,0xfff
????80137517?03c6?????????????add?????eax,esi
????80137519?eb17?????????????jmp???ntoskrnl!MmGetPhysicalAddress+0x57?(80137532)
????8013751b?8bc6?????????????mov?????eax,esi
????8013751d?c1e80a???????????shr?????eax,0xa
????80137520?25fcff3f00???????and?????eax,0x3ffffc
????80137525?2d00000040???????sub?????eax,0x40000000
????8013752a?8b00?????????????mov?????eax,[eax]
????8013752c?a801?????????????test????al,0x1
????8013752e?7506?????????????jnz???ntoskrnl!MmGetPhysicalAddress+0x44?(80137536)
????80137530?33c0?????????????xor?????eax,eax
????80137532?5e???????????????pop?????esi
????80137533?c20400???????????ret?????0x4

????從這段匯編代碼可看出如果線性地址在0x80000000與0xa0000000范圍內,只是簡單的進行移位操作(位于801374ff-80137519指令間),并未查頁表。我想Microsoft這樣安排肯定是出于執行效率的考慮。這也為我們指明了一線曙光,因為GDT表在Windows?NT/2000中一般情況下均位于這個區域(我不知道/3GB開關的Windows?NT/2000是不是這種情況)。
????經過這樣的分析,我們就可以只通過用戶態程序修改GDT表了。而增加一個CallGate就不是我可以介紹的了,找本Intel手冊自己看一看了。具體實現代碼如下:

????typedef?struct?gdtr?
{?
????????short?Limit;
????????short?BaseLow;
????????short?BaseHigh;
?????}?Gdtr_t,?*PGdtr_t;

????ULONG?MiniMmGetPhysicalAddress(ULONG?virtualaddress)

????
{?
????????if(virtualaddress<0x80000000||virtualaddress>=0xA0000000)
???????????return?0;
????????return?virtualaddress&0x1FFFF000;
?????}

????BOOL?ExecRing0Proc(ULONG?Entry,ULONG?seglen)

????
{?
???????Gdtr_t?gdt;
???????__asm?sgdt?gdt;
?????
???????ULONG?mapAddr=MiniMmGetPhysicalAddress(gdt.BaseHigh<<16U|gdt.BaseLow);
???????if(!mapAddr)?return?0;

???????HANDLE???hSection=NULL;
???????NTSTATUS?status;
???????OBJECT_ATTRIBUTES????????objectAttributes;
???????UNICODE_STRING?objName;
???????CALLGATE_DESCRIPTOR?*cg;

???????status?=?STATUS_SUCCESS;
???
???????RtlInitUnicodeString(&objName,L"\\Device\\PhysicalMemory");

???????InitializeObjectAttributes(&objectAttributes,
??????????????????????????????????&objName,
??????????????????????????????????OBJ_CASE_INSENSITIVE?|?OBJ_KERNEL_HANDLE,
??????????????????????????????????NULL,
?????????????????????????????????(PSECURITY_DESCRIPTOR)?NULL);

???????status?=?ZwOpenSection(&hSection,SECTION_MAP_READ|SECTION_MAP_WRITE,&objectAttributes);


???????if(status?==?STATUS_ACCESS_DENIED)
{?
??????????status?=?ZwOpenSection(&hSection,READ_CONTROL|WRITE_DAC,&objectAttributes);
??????????SetPhyscialMemorySectionCanBeWrited(hSection);
??????????ZwClose(hSection);
??????????status?=ZwOpenSection(&hSection,SECTION_MAP_WRITE|SECTION_MAP_WRITE,&objectAttributes);
????????}

???????if(status?!=?STATUS_SUCCESS)

?????????
{?
????????????printf("Error?Open?PhysicalMemory?Section?Object,Status:%08X\n",status);
????????????return?0;
??????????}
??????
???????PVOID?BaseAddress;

???????BaseAddress=MapViewOfFile(hSection,
?????????????????????FILE_MAP_READ|FILE_MAP_WRITE,
?????????????????????0,
?????????????????????mapAddr,????//low?part
?????????????????????(gdt.Limit+1));

???????if(!BaseAddress)

??????????
{?
?????????????printf("Error?MapViewOfFile:");
?????????????PrintWin32Error(GetLastError());
?????????????return?0;
???????????}

???????BOOL?setcg=FALSE;

???????for(cg=(CALLGATE_DESCRIPTOR?*)((ULONG)BaseAddress+(gdt.Limit&0xFFF8));(ULONG)cg>(ULONG)BaseAddress;cg--)

???????????if(cg->type?==?0)
{?
?????????????cg->offset_0_15?=?LOWORD(Entry);
?????????????cg->selector?=?8;
?????????????cg->param_count?=?0;
?????????????cg->some_bits?=?0;
?????????????cg->type?=?0xC;??????????//?386?call?gate
?????????????cg->app_system?=?0;??????//?A?system?descriptor
?????????????cg->dpl?=?3;?????????????//?Ring?3?code?can?call
?????????????cg->present?=?1;
?????????????cg->offset_16_31?=?HIWORD(Entry);
?????????????setcg=TRUE;
?????????????break;
???????????}


???????if(!setcg)
{?
????????????ZwClose(hSection);
????????????return?0;
????????}

???????short?farcall[3];

???????farcall[2]=((short)((ULONG)cg-(ULONG)BaseAddress))|3;??//Ring?3?callgate;

???????if(!VirtualLock((PVOID)Entry,seglen))

??????????
{?
?????????????printf("Error?VirtualLock:");
?????????????PrintWin32Error(GetLastError());
?????????????return?0;
???????????}

???????SetThreadPriority(GetCurrentThread(),THREAD_PRIORITY_TIME_CRITICAL);

???????Sleep(0);

???????_asm?call?fword?ptr?[farcall]

???????SetThreadPriority(GetCurrentThread(),THREAD_PRIORITY_NORMAL);

???????VirtualUnlock((PVOID)Entry,seglen);

???????//Clear?callgate
???????*(ULONG?*)cg=0;
???????*((ULONG?*)cg+1)=0;

???????ZwClose(hSection);
???????return?TRUE;

?????}


????我在提供的代碼中演示了對Control?Register與I/O端口的操作。CIH病毒在Windows?9X中就是因為獲得Ring?0權限才有了一定的危害,但Windows?NT/2000畢竟不是Windows?9X,她已經有了比較多的安全審核機制,本文提供的代碼也要求具有Administrator權限,但如果系統存在某種漏洞,如緩沖區溢出等等,還是有可能獲得這種權限的,所以我不對本文提供的方法負有任何的責任,所有討論只是一個技術熱愛者在討論技術而已。謝謝!?
????參考資料:
??????1.Intel Corp<<Intel Architecture Software Developer's Manual,Volume 3>>?