Posted on 2009-10-24 22:20
S.l.e!ep.¢% 閱讀(698)
評論(0) 編輯 收藏 引用 所屬分類:
RootKit
注明:本文是轉帖。希望中國計算機技術越來越好。
論技術,我還差得遠,而且網上關于SSDT的文章也多不勝數。但是還是想自己寫一下,因為我想試試我能不能用最簡單的語言來描述SSDT——這個對一般來人來說比較神秘的屬于內核的地帶。引用EVA說的一句話,“以為寫個驅動就是內核,還遠著了”——大概是這么個意思,記得不是很清楚。
關于SSDT,描述得最清楚的應該算《SSDT Hook的妙用-對抗ring0 inline hook》一文了,作者是墮落天才。這里引用一下他寫的開頭部分,略有個別字符的修改:
內核中有兩個系統服務描述符表,一個是KeServiceDescriptorTable,由ntoskrnl.exe導出,一個是KeServieDescriptorTableShadow,沒有導出。這兩者都是一個結構體,結構下面會給出。他們的區別是,KeServiceDescriptorTable僅有 ntoskrnel一項,而KeServieDescriptorTableShadow則包含了ntoskrnel和win32k。一般的Native API的服務地址由KeServiceDescriptorTable分派,而gdi.dll和 user.dll的內核API調用服務地址,由 KeServieDescriptorTableShadow分派。還有要清楚一點的是win32k.sys只有在GUI線程中才加載,一般情況下是不加載的。
他們的結構如下: 代碼:
typedef struct _SYSTEM_SERVICE_TABLE { ???? PVOID ServiceTableBase;???? //這個指向系統服務函數地址表 ???? PULONG ServiceCounterTableBase; ???? ULONG NumberOfService; ???? //服務函數的個數,NumberOfService*4 就是整個地址表的大小 ???? ULONG ParamTableBase; }SYSTEM_SERVICE_TABLE,*PSYSTEM_SERVICE_TABLE;
typedef struct _SERVICE_DESCRIPTOR_TABLE { ???? SYSTEM_SERVICE_TABLE ntoskrnel;???? //ntoskrnl.exe的服務函數 ???? SYSTEM_SERVICE_TABLE win32k;???? //win32k.sys的服務函數,(gdi.dll/user.dll的內核支持) ???? SYSTEM_SERVICE_TABLE NotUsed1; ???? SYSTEM_SERVICE_TABLE NotUsed2; }SYSTEM_DESCRIPTOR_TABLE,*PSYSTEM_DESCRIPTOR_TABLE;
|
當系統需要使用一個本機API的時候,就會去查找SYSTEM_DESCRIPTOR_TABLE這個表,也就是由ntoskrnl.exe導出的KeServiceDescriptorTable:
代碼: nt!RtlpBreakWithStatusInstruction: 80527fc8 cc?????????????? int???? 3 kd> dd KeServiceDescriptorTable 80553380?? 805021fc 00000000 0000011c 80502670 80553390?? 00000000 00000000 00000000 00000000 805533a0?? 00000000 00000000 00000000 00000000 805533b0?? 00000000 00000000 00000000 00000000 805533c0?? 00002710 bf80c227 00000000 00000000 805533d0?? f9e6da80 f963a9e0 816850f0 806e0f40 805533e0?? 00000000 00000000 00000000 00000000 805533f0?? 97c5ac40 01c7abf5 00000000 00000000
|
可以看到,KeServiceDescriptorTable的地址是80553380。現在看看這個地址保存的是什么,因為這個地址的值就是SYSTEM_SERVICE_TABLE的起始地址。好了,我們看到這個地址保存的是805021fc,那么也就是說,系統服務的地址表起始地址為805021fc了。看看這個表是些什么鬼東西: 代碼: kd> dd 805021fc 805021fc?? 80599746 805e6914 805ea15a 805e6946 8050220c?? 805ea194 805e697c 805ea1d8 805ea21c 8050221c?? 8060b880 8060c5d2 805e1cac 805e1904 8050222c?? 805ca928 805ca8d8 8060bea6 805ab334 8050223c?? 8060b4be 8059dbbc 805a5786 805cc406 8050224c?? 804ffed0 8060c5c4 8056be64 805353f2 8050225c?? 80604b90 805b19c0 805ea694 80619a56 8050226c?? 805eeb86 80599e34 80619caa 805996e6
|
這個過程是這樣的,最開始是SYSTEM_DESCRIPTOR_TABLE(80553380)保存了SYSTEM_SERVICE_TABLE的地址(805021fc),SYSTEM_SERVICE_TABLE的地址(805021fc)又保存了很多地址,這個地址就是系統服務的地址了,類似NtOpenProcess這樣的ring0的函數地址。這樣,系統就可以方便的找到每一個ring0函數去調用。
我們先看看第一個地址80599746是個什么函數,反匯編一下: 代碼: kd> u 80599746 nt!NtAcceptConnectPort: 80599746 689c000000?????? push???? 9Ch 8059974b 6820a14d80?????? push???? offset nt!_real+0x128 (804da120) 80599750 e8abebf9ff?????? call???? nt!_SEH_prolog (80538300) 80599755 64a124010000???? mov???? eax,dword ptr fs:[00000124h] 8059975b 8a8040010000???? mov???? al,byte ptr [eax+140h] 80599761 884590?????????? mov???? byte ptr [ebp-70h],al 80599764 84c0???????????? test???? al,al 80599766 0f84b9010000???? je?????? nt!NtAcceptConnectPort+0x1df (80599925)
|
原來是NtAcceptConnectPort函數,第二個805e6914呢?我們也看一下, 代碼: kd> u 805e6914 nt!NtAccessCheck: 805e6914 8bff???????????? mov???? edi,edi 805e6916 55?????????????? push???? ebp 805e6917 8bec???????????? mov???? ebp,esp 805e6919 33c0???????????? xor???? eax,eax 805e691b 50?????????????? push???? eax 805e691c ff7524?????????? push???? dword ptr [ebp+24h] 805e691f ff7520?????????? push???? dword ptr [ebp+20h] 805e6922 ff751c?????????? push???? dword ptr [ebp+1Ch]
|
原來是NtAccessCheck函數。
這樣我們可以清楚的看到,在這個起始地址為0x805021fc的表中,保存了各個ring0函數的地址。下面我來做個簡單的比喻。
從前有一個很大的幫派,名字叫做Windows,功能很多并且很強大。因為這些各方面的能力由各個專人負責,他們一個人做一件事情。隨著人員增多,幫主發現聯系起來越來越困了。有一天幫主要找竟然NtOpenProcess來調查一下他的一個手下是不是別的幫派派來的間諜,但是他發現NtOpenProcess跑不見了。
于是軍師就想出了一個好辦法來解決這個問題:先建立一個封閉的密室,這個密室只有八袋長老以上的人才能進去。密室中間有一張紙條,上面寫著一個地址——溫家堡,還有這個地址放著多少人的聯系信息等內容。這個密室就是Ntdll.dll,這個紙條就是SYSTEM_DESCRIPTOR_TABLE,上寫的地址就是SYSTEM_SERVICE_TABLE,也就是溫家堡了。這個溫家堡是一個有很多大房間的地方,每個房子有個房間號 ,房間里面又放著一張紙條,上面寫著各個手下的住所。比如說編號為7A的房間,里面放的是NtOpenProcess的家庭住址。
這樣一來,幫主要找人就容易了。先去密室找到紙條,看看上面寫的是溫家堡還是白云城,那個地方有多少個人的聯系信息等。如果是溫家堡就跑到那里去,看看要找誰,找NtOpenProcess就去7A房間。在這個房間里一看,啊,里面寫著NtOpenProcess現在就住在密室的旁邊……搞定。
這里就有一個新的問題,幫主假設這個里面寫的東西都是正確的,沒有被人改過。于是就有了別派的間諜發現了,偷偷溜進密室,然后根據紙條的內容,又跑到溫家堡。進到7A房間,神不知鬼不覺的把里面記錄的NtOpenProcess的地址改成了自己的家。于是,幫主再找人,發現找到對頭家里去了。這個就是傳說中的SSDT Hook了。
攻擊者進入ring0之后,找到KeServiceDescriptorTable地址的值,即SYSTEM_SERVICE_TABLE的地址(進入密室,找到紙條寫的地址——溫家堡)。然后改寫SYSTEM_SERVICE_TABLE中一個特定函數的地址為自己定義的函數入口處,截獲了系統調用(來到溫家堡,改掉7A房間里面寫的住所,改成自己家)。一次HOOK就完成了。
下面我給一段簡單的代碼,演示怎么樣讓一個特定的PID不會被殺死。這段代碼基本和《SSDT Hook的妙用-對抗ring0 inline hook》一文一樣,我只是注釋了一下而已,另外在MyNtOpenProcess處加了個判斷是不是某個特定PID的功能。 代碼:
/* 演示HOOK系統服務調用表中的NtOpenProcess函數,保護需要保護的進程被,防止被殺掉 */
#include<ntddk.h>
/* KeServiceDescriptorTable僅有ntoskrnel一項,沒有包含win32k,而且后面的兩個字段都沒有使用,所
以為了簡便直接把SystemServiceDescriptorTable定義成SYSTEM_SERVICE_TABLE,免得訪問多個結構體的
字段,麻煩。這里明白就行了。 */ typedef struct _SystemServiceDescriptorTable { ???? PVOID???? ServiceTableBase; ???? PULONG???? ServiceCounterTableBase; ???? ULONG???? NumberOfService; ???? ULONG???? ParamTableBase; }SystemServiceDescriptorTable,*PSystemServiceDescriptorTable;
// KeServiceDescriptorTable為ntoskrnl.exe導出 extern???? PSystemServiceDescriptorTable???? KeServiceDescriptorTable;
// 定義一下NtOpenProcess的原型,下面如果用匯編調用就不用定義了,但是我想盡量不用匯編 typedef???? NTSTATUS???? (__stdcall *NTOPENPROCESS)( OUT PHANDLE ProcessHandle, ????????????????????????????????????????????????
IN ACCESS_MASK AccessMask, ????????????????????????????????????????????????
IN POBJECT_ATTRIBUTES ObjectAttributes, ????????????????????????????????????????????????
IN PCLIENT_ID ClientId ????????????????????????????????????????????????
);
NTOPENPROCESS???? RealNtOpenProcess;
// 定義函數原型 VOID Hook(); VOID Unhook(); VOID OnUnload(IN PDRIVER_OBJECT DriverObject);
// 真實的函數地址,我們會在自定義的函數中調用 ULONG???? RealServiceAddress;
// 需要被驅動保護的進程ID HANDLE???? MyPID;
// 自定義的NtOpenProcess函數 NTSTATUS __stdcall MyNtOpenProcess( OUT???? PHANDLE ProcessHandle, ???????????????????? IN???? ACCESS_MASK DesiredAccess, ???????????????????? IN???? POBJECT_ATTRIBUTES ObjectAttributes, ???????????????????? IN???? PCLIENT_ID ClientId ) { ???? NTSTATUS???? rc; ???? ULONG???????? PID; ???? ???? //DbgPrint( "NtOpenProcess() called.\n" ); ???? ???? rc = (NTSTATUS)(NTOPENPROCESS)RealNtOpenProcess( ProcessHandle, DesiredAccess,
ObjectAttributes, ClientId ); ???? ???? if( (ClientId != NULL) ) ???? { ???????? PID = (ULONG)ClientId->UniqueProcess; ???????? //DbgPrint( "%d was opened,Handle is %d.\n", PID, (ULONG)ProcessHandle ); ???????? ???????? // 如果進程PID是1520,直接返回權限不足,并將句柄設置為空 ???????? if( PID == 1520 ) ???????? { ???????????? DbgPrint( "Some want to open pid 1520!\n" ); ???????????? ???????????? ProcessHandle = NULL; ???????????????????????? ???????????? rc = STATUS_ACCESS_DENIED; ???????? } ???? } ???? ???? return rc; }
// 驅動入口 NTSTATUS DriverEntry( IN PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath ) { ???? DriverObject->DriverUnload = OnUnload;
???? Hook(); ???? ???? return STATUS_SUCCESS; }
// 驅動卸載 VOID OnUnload(IN PDRIVER_OBJECT DriverObject) { ???? Unhook( ); }
//?? 此處修改SSDT中的NtOpenProcess服務地址 VOID Hook() { ???? ULONG???????????? Address; ???? ???? // 0x7A為Winxp+SP2下NtOpenProcess服務ID號 ???? // Adress是個地址A,這個地址的數據還是一個地址B,這個地址B就是NtOpenProcess的地址了 ???? // (ULONG)KeServiceDescriptorTable->ServiceTableBase就是溫家堡的第一個房間 ???? // Address是第7A個房間。 ???? Address = (ULONG)KeServiceDescriptorTable->ServiceTableBase + 0x7A * 4;
???? // 取得地址A的值,也就是NtOpenProcess服務的地址了,保存原來NtOpenProcess的地址以后恢
復用 ???? RealServiceAddress = *(ULONG*)Address; ???? ???? RealNtOpenProcess = (NTOPENPROCESS)RealServiceAddress; ???? ???? DbgPrint( "Address of Real NtOpenProcess: 0x%08X\n", RealServiceAddress );
???? DbgPrint(" Address of MyNtOpenProcess: 0x%08X\n", MyNtOpenProcess );
???? // 去掉內存保護 ???? __asm ???? { ???????? cli ???????? mov???? eax, cr0 ???????? and???? eax, not 10000h ???????? mov???? cr0, eax ???? } ???? ???? // 修改SSDT中NtOpenProcess服務的地址 ?? *((ULONG*)Address) = (ULONG)MyNtOpenProcess;
???? // 恢復內存保護 ???? __asm ???? { ???????? mov???? eax, cr0 ???????? or???? eax, 10000h ???????? mov???? cr0, eax ???????? sti ???? } }
////////////////////////////////////////////////////// VOID Unhook() { ?? ULONG?? Address; ?? Address = (ULONG)KeServiceDescriptorTable->ServiceTableBase + 0x7A * 4;
???? __asm ???? { ???????? cli ???????? mov???? eax, cr0 ???????? and???? eax, not 10000h ???????? mov???? cr0, eax ???? }
???? // 還原SSDT ???? *((ULONG*)Address) = (ULONG)RealServiceAddress; ???? ???? __asm ???? { ???????? mov???? eax, cr0 ???????? or???? eax, 10000h ???????? mov???? cr0, eax ???????? sti ???? }
???? DbgPrint("Unhook"); }
|
|
|
補充:雖然是轉貼,我還是補充兩句,各抒己見。
SSDT功能強大,猶如寶刀屠龍,號令天下,誰敢不從?那么SSDT必定是武林豪杰必爭之寶。引起江湖上的風風雨雨在所難免。
黑道門派有:木馬,病毒,流氓軟件,無聊程序,下賤程序等幫派,如果被如此等流氓幫派得到SSDT如此寶物,必定腥風血雨,天下不得安寧。
白道門派有:卡巴,瑞星,諾頓,金山,殺毒軟件,防毒軟件,木馬清道夫,尾巴清道夫,流氓軟件清道夫等等門派。如果如此門派得到此寶,那必定造福于天下,是武林之福啊。
中立門派有:外掛,反外掛,游戲防外掛等門派,如果他們得到此寶,影響較小,危害不大。
SSDT并非專屬誰所有,所以誰要得到他,那就得憑真本事,勝者為王,敗者為寇的道理就不用多說了。介于上面種種原因,我們從江湖行情可以看出。經常會出現病毒殺掉殺毒軟件,殺毒軟件干掉病毒的事情,刀劍無眼,這些都是不可避免的事情。但是經常會覺得奇怪,為什么大家都同樣的用機器,我的就被病毒感染,別人的就沒有被感染呢?而且經常會出現,重裝系統后仍然有病毒,而且仍然kill殺毒軟件,甚至安裝不上。
解釋就只有一個:先下手為強。重裝系統后任然有毒,并且安裝不上殺毒軟件只能說明一個問題,你先啟動了病毒,后安裝殺毒軟件。讓病毒先得到了SSDT的控制權,殺毒軟件沒了那東西,自然威力大減,甚至發生滅門慘案。病毒被隱藏在其他盤了,在重裝系統后,以為平安無私了,掉以輕心去打開其他盤,導致病毒重新啟動。。。。
現在的外掛制作也基本上涉及到了SSDT技術,系統內核層次也設計到 零環 和 3環 的對抗,為什么現在使用用戶態的鉤子,無法HOOK某些游戲做外掛,但是只要不HOOK游戲,在其他程序里跑又沒有問題呢?原因就是這個問題,你要HOOK某游戲,必定掛鉤到該進程,游戲可以在SSDT那里修改你要使用的HOOK函數地址,然后每當你調用鉤子函數去控制游戲的時候,都要經過該游戲的過濾層,非法操作則return什么都不做就可以了,但是其實這是個笨辦法。去HOOK游戲的時候,必須使用全局鉤子,必定注入到游戲進程,在外掛打開該游戲進程,調用OpenProccess或者NtOpenProccess時,直接HooK該函數,然后在里面檢測打開的進程是不是該游戲進程,如果不是則正確調用該函數。如果是該游戲進程,那你就死定了,程序就返回一個假的進程值,比如說游戲進程開啟一個子進程,里面什么也不做,只讓他休眠。你以為打開了該游戲進程,其實你打開了一個什么也不做的游戲子進程。所以,無論你怎么做,游戲內部都沒有反映。其實最強的游戲外掛是脫機外掛,不過這種外掛,哈哈···10有9都是游戲公司內部做出來的,不然游戲通訊協議分析哪有那么容易,我現在看來覺得會很難。如果不是內部人員做出來,或者透露出來的話,那么也就是說,協議分析很簡單咯,那么像網上銀行,支付寶早這些就被拿下了。或者說像QQ這種軟件,那些什么消息加密,早就拿下了吧,如果能破解通信協議,你破解的不僅僅是通信協議本身,而是破解了別人的心靈加密問題了,你已經不是人,是神了。當然不排除真正靠自己分析將協議分析出來的高手,我很佩服,我說過他們不是人,是神,哈哈···。
?