久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

城里城外看SSDT

 guoliyan1 2012-04-04

引子

2006年,中國互聯(lián)網(wǎng)上的斗爭硝煙彌漫,。這時的戰(zhàn)場上,,先前頗為流行的窗口掛鉤、API掛鉤,、進程注入等技術(shù)已然成為昨日黃花,,大有逐漸淡出之勢;取而代之的,,則是更狠毒,、更為赤裸裸的詞匯:驅(qū)動、隱藏進程,、Rootkit……
前不久,,我不經(jīng)意翻出自己2005年9月寫下的一篇文章《DLL的遠程注入技術(shù)》,在下面看到了一位名叫L4bm0s的網(wǎng)友說這種技術(shù)已經(jīng)過時了,。雖然我也曾想過擬出若干辯解之詞聊作應(yīng)對,,不過最終還是作罷了——畢竟,拿出些新的,、有技術(shù)含量的東西才是王道,。于是這一次,李馬首度從ring3(應(yīng)用層)的圍城跨出,,一躍而投身于ring0(內(nèi)核層)這一更廣闊的天地,,便有了這篇《城里城外看SSDT》?!櫭剂x,,城里和城外的這一墻之隔,就是ring3與ring0的分界,。
在這篇文章里,,我會用到太多雜七雜八的東西,比如匯編,,比如內(nèi)核調(diào)試器,,比如DDK。這誠然是一件令我瞻前顧后畏首畏尾的事情——一方面在ring0我不得不依靠這些東西,,另一方面我實在擔心它們會導致我這篇文章的閱讀門檻過高。所以,,我決定盡可能少地涉及驅(qū)動,、內(nèi)核與DDK,也不會對諸如如何使用內(nèi)核調(diào)試器等問題作任何講解——你只需要知道我大概在做些什么,,這就足夠了,。

什么是SSDT,?

什么是SSDT?自然,,這個是我必須回答的問題,。不過在此之前,請你打開命令行(cmd.exe)窗口,,并輸入“dir”并回車——好了,,列出了當前目錄下的所有文件和子目錄。
那么,,以程序員的視角來看,,整個過程應(yīng)該是這樣的:

由用戶輸入dir命令。 cmd.exe獲取用戶輸入的dir命令,,在內(nèi)部調(diào)用對應(yīng)的Win32 API函數(shù)FindFirstFile,、FindNextFile和FindClose,獲取當前目錄下的文件和子目錄,。 cmd.exe將文件名和子目錄輸出至控制臺窗口,,也就是返回給用戶。

到此為止我們可以看到,,cmd.exe扮演了一個非常至關(guān)重要的角色,,也就是用戶與Win32 API的交互?!愦蟾乓呀?jīng)可以猜到,,我下面要說到的SSDT亦必將扮演這個角色,這實在是一點新意都沒有,。
沒錯,,你猜對了。SSDT的全稱是System Services Descriptor Table,,系統(tǒng)服務(wù)描述符表,。這個表就是一個把ring3的Win32 API和ring0的內(nèi)核API聯(lián)系起來的角色,下面我將以API函數(shù)OpenProcess為例說明這個聯(lián)系的過程,。
你可以用任何反匯編工具來打開你的kernel32.dll,,然后你會發(fā)現(xiàn)在OpenProcess中有類似這樣的匯編代碼:

匯編代碼
call ds:NtOpenProcess  

這就是說,OpenProcess調(diào)用了ntdll.dll的NtOpenProcess函數(shù),。那么繼續(xù)反匯編之,,你會發(fā)現(xiàn)ntdll.dll中的這個函數(shù)很短:

匯編代碼
mov eax, 7Ah   mov edx, 7FFE0300h   call dword ptr [edx]   retn 10h  

另外,call的一句實質(zhì)是調(diào)用了KiFastSystemCall:

C++代碼
mov edx, esp   sysenter  

上面是我的XP Professional sp2中ntdll.dll的反匯編結(jié)果,,如果你用的是2000系統(tǒng),,那么可能是這個樣子:

C++代碼
mov eax, 6Ah   lea edx, [esp+4]   int 2Eh   retn 10h  

雖然它們存在著些許不同,但都可以這么來概括:

把一個數(shù)放入eax(XP是0x7A,,2000是0x6A),,這個數(shù)值稱作系統(tǒng)的服務(wù)號,。 把參數(shù)堆棧指針(esp+4)放入edx。 sysenter或int 2Eh,。

好了,,你在ring3能看到的東西就到此為止了。事實上,,在ntdll.dll中的這些函數(shù)可以稱作真正的NT系統(tǒng)服務(wù)的存根(Stub)函數(shù),。分隔ring3與ring0城里城外的這一道嘆息之墻,也正是由它們打通的,。接下來SSDT就要出場了,,come some music。

站在城墻看城外

插一句先,,貌似到現(xiàn)在為止我仍然沒有講出來SSDT是個什么東西,,真正可以算是“猶抱琵琶半遮面”了?!獣由衔?,在你調(diào)用sysenter或int 2Eh之后,Windows系統(tǒng)將會捕獲你的這個調(diào)用,,然后進入ring0層,,并調(diào)用內(nèi)核服務(wù)函數(shù)NtOpenProcess,這個過程如下圖所示,。

SSDT在這個過程中所扮演的角色是至關(guān)重要的,。讓我們先看一看它的結(jié)構(gòu),如下圖,。

當程序的處理流程進入ring0之后,,系統(tǒng)會根據(jù)服務(wù)號(eax)在SSDT這個系統(tǒng)服務(wù)描述符表中查找對應(yīng)的表項,這個找到的表項就是系統(tǒng)服務(wù)函數(shù)NtOpenProcess的真正地址,。之后,,系統(tǒng)會根據(jù)這個地址調(diào)用相應(yīng)的系統(tǒng)服務(wù)函數(shù),并把結(jié)果返回給ntdll.dll中的NtOpenProcess,。圖中的“SSDT”所示即為系統(tǒng)服務(wù)描述符表的各個表項,;右側(cè)的“ntoskrnl.exe”則為Windows系統(tǒng)內(nèi)核服務(wù)進程(ntoskrnl即為NT OS KerneL的縮寫),它提供了相對應(yīng)的各個系統(tǒng)服務(wù)函數(shù),。ntoskrnl.exe這個文件位于Windows的system32目錄下,,有興趣的朋友可以反匯編一下。
附帶說兩點,。根據(jù)你處理器的不同,,系統(tǒng)內(nèi)核服務(wù)進程可能也是不一樣的。真正運行于系統(tǒng)上的內(nèi)核服務(wù)進程可能還有ntkrnlmp.exe、ntkrnlpa.exe這樣的情況——不過為了統(tǒng)一起見,,下文仍統(tǒng)稱這個進程為ntoskrnl.exe。另外,,SSDT中的各個表項也未必會全部指向ntoskrnl.exe中的服務(wù)函數(shù),,因為你機器上的殺毒監(jiān)控或其它驅(qū)動程序可能會改寫SSDT中的某些表項——這也就是所謂的“掛鉤SSDT”——以達到它們的“主動防御”式殺毒方式或其它的特定目的。

KeServiceDescriptorTable

事實上,,SSDT并不僅僅只包含一個龐大的地址索引表,,它還包含著一些其它有用的信息,諸如地址索引的基地址,、服務(wù)函數(shù)個數(shù)等等,。ntoskrnl.exe中的一個導出項KeServiceDescriptorTable即是SSDT的真身,亦即它在內(nèi)核中的數(shù)據(jù)實體,。SSDT的數(shù)據(jù)結(jié)構(gòu)定義如下:

C++代碼
typedef struct _tagSSDT {       PVOID pvSSDTBase;       PVOID pvServiceCounterTable;       ULONG ulNumberOfServices;       PVOID pvParamTableBase;   } SSDT, *PSSDT;  

其中,,pvSSDTBase就是上面所說的“系統(tǒng)服務(wù)描述符表”的基地址。pvServiceCounterTable則指向另一個索引表,,該表包含了每個服務(wù)表項被調(diào)用的次數(shù),;不過這個值只在Checkd Build的內(nèi)核中有效,在Free Build的內(nèi)核中,,這個值總為NULL(注:Check/Free是DDK的Build模式,,如果你只使用SDK,可以簡單地把它們理解為Debug/Release),。ulNumberOfServices表示當前系統(tǒng)所支持的服務(wù)個數(shù),。pvParamTableBase指向SSPT(System Service Parameter Table,即系統(tǒng)服務(wù)參數(shù)表),,該表格包含了每個服務(wù)所需的參數(shù)字節(jié)數(shù),。
下面,讓我們開看看這個結(jié)構(gòu)里邊到底有什么,。打開內(nèi)核調(diào)試器(以kd為例),,輸入命令顯示KeServiceDescriptorTable,如下,。

WinDbg輸出
lkd> dd KeServiceDescriptorTable l4   8055ab80 804e3d20 00000000 0000011c 804d9f48  

接下來,,亦可根據(jù)基地址與服務(wù)總數(shù)來查看整個服務(wù)表的各項:

WinDbg輸出
lkd> dd 804e3d20 l11c   804e3d20 80587691 f84317aa f84317b4 f84317be   804e3d30 f84317c8 f84317d2 f84317dc f84317e6   804e3d40 8057741c f84317fa f8431804 f843180e   804e3d50 f8431818 f8431822 f843182c f8431836   ...  

你獲得的結(jié)果可能和我會有不同——我指的是那堆以十六進制f開頭的地址項,因為我的SSDT被System Safety Monitor接管了,,沒留下幾個原生的ntoskrnl.exe表項,。
現(xiàn)在是寫些代碼的時候了。KeServiceDescriptorTable及SSDT各個表項的讀取只能在ring0層完成,,于是這里我使用了內(nèi)核驅(qū)動并借助DeviceIoControl來完成,。其中DeviceIoControl的分發(fā)代碼實現(xiàn)如下面的代碼所示,沒有什么技術(shù)含量,所以不再解釋,。

switch ( IoControlCode ) 
{
case IOCTL_GETSSDT:
    {
        __try
        {
            ProbeForWrite( OutputBuffer, sizeof( SSDT ), sizeof( ULONG ) );
            RtlCopyMemory( OutputBuffer, KeServiceDescriptorTable, sizeof( SSDT ) );
        }
        __except ( EXCEPTION_EXECUTE_HANDLER )
        {
            IoStatus->Status = GetExceptionCode();
        }
    }
    break;
case IOCTL_GETPROC:
    {
        ULONG uIndex = 0;
        PULONG pBase = NULL;

        __try
        {
            ProbeForRead( InputBuffer, sizeof( ULONG ), sizeof( ULONG ) );
            ProbeForWrite( OutputBuffer, sizeof( ULONG ), sizeof( ULONG ) );
        }
        __except( EXCEPTION_EXECUTE_HANDLER )
        {
            IoStatus->Status = GetExceptionCode();
            break;
        }

        uIndex = *(PULONG)InputBuffer;
        if ( KeServiceDescriptorTable->ulNumberOfServices <= uIndex )
        {
            IoStatus->Status = STATUS_INVALID_PARAMETER;
            break;
        }
        pBase = KeServiceDescriptorTable->pvSSDTBase;
        *((PULONG)OutputBuffer) = *( pBase + uIndex );
    }
    break;
// ...



補充一下,,再。DDK的頭文件中有一件很遺憾的事情,,那就是其中并未聲明KeServiceDescriptorTable,,不過我們可以自己手動添加之:

extern PSSDT KeServiceDescriptorTable; 


——當然,如果你對DDK開發(fā)實在不感興趣的話,,亦可以直接使用配套代碼壓縮包中的SSDTDump.sys,,并使用DeviceIoControl發(fā)送IOCTL_GETSSDT和IOCTL_GETPROC控制碼即可;或者,,直接調(diào)用我為你準備好的兩個函數(shù):

BOOL GetSSDT( IN HANDLE hDriver, OUT PSSDT buf );
BOOL GetProc( IN HANDLE hDriver, IN ULONG ulIndex, OUT PULONG buf ); 


獲取詳細模塊信息

雖然我們現(xiàn)在可以獲取任意一個服務(wù)號所對應(yīng)的函數(shù)地址了已經(jīng),,但是你可能仍然不滿意,認為只有獲得了這個服務(wù)函數(shù)所在的模塊才是王道,。換句話說,,對于一個干凈的SSDT表來說,它里邊的表項應(yīng)該都是指向ntoskrnl.exe的,;如果SSDT之中有若干個表項被改寫(掛鉤),,那么我們應(yīng)該知道是哪一個或哪一些模塊替換了這些服務(wù)。

首先我們需要獲得當前在ring0層加載了那些模塊,。如我在本文開頭所說,,為了盡可能地少涉及ring0層的東西,于是在這里我使用了ntdll.dll的NtQuerySystemInformation函數(shù),。關(guān)鍵代碼如下:

typedef struct _SYSTEM_MODULE_INFORMATION { 
    ULONG Reserved[2]; 
    PVOID Base; 
    ULONG Size; 
    ULONG Flags; 
    USHORT Index; 
    USHORT Unknown; 
    USHORT LoadCount; 
    USHORT ModuleNameOffset; 
    CHAR ImageName[256]; 
} SYSTEM_MODULE_INFORMATION, *PSYSTEM_MODULE_INFORMATION; 

typedef struct _tagSysModuleList {
    ULONG ulCount;
    SYSTEM_MODULE_INFORMATION smi[1];
} SYSMODULELIST, *PSYSMODULELIST;

s = NtQuerySystemInformation( SystemModuleInformation, pRet,
    sizeof( SYSMODULELIST ), &nRetSize );
if ( STATUS_INFO_LENGTH_MISMATCH == s )
{
    // 緩沖區(qū)太小,,重新分配
    delete pRet;
    pRet = (PSYSMODULELIST)new BYTE[nRetSize];
    s = NtQuerySystemInformation( SystemModuleInformation, pRet,
        nRetSize, &nRetSize ); 



需要說明的是,這個函數(shù)是利用內(nèi)核的PsLoadedModuleList鏈表來枚舉系統(tǒng)模塊的,,因此如果你遇到了能夠隱藏驅(qū)動的Rootkit,,那么這種方法是無法找到被隱藏的模塊的。在這種情況下,,枚舉系統(tǒng)的“\Driver”目錄對象可能可以更好解決這個問題,,在此不再贅述了就。

接下來,,是根據(jù)SSDT中的地址表項查找模塊,。有了SYSTEM_MODULE_INFORMATION結(jié)構(gòu)中的模塊基地址與模塊大小,這個工作完成起來也很容易:

BOOL FindModuleByAddr( IN ULONG ulAddr, IN PSYSMODULELIST pList,
                      OUT LPSTR buf, IN DWORD dwSize )
{
    for ( ULONG i = 0; i < pList->ulCount; ++i )
    {
        ULONG ulBase = (ULONG)pList->smi[i].Base;
        ULONG ulMax  = ulBase + pList->smi[i].Size;
        if ( ulBase <= ulAddr && ulAddr < ulMax )
        {
            // 對于路徑信息,,截取之
            PCSTR pszModule = strrchr( pList->smi[i].ImageName, '\\' );
            if ( NULL != pszModule )
            {
                lstrcpynA( buf, pszModule + 1, dwSize );
            }
            else
            {
                lstrcpynA( buf, pList->smi[i].ImageName, dwSize );
            }
            return TRUE;
        }
    }
    return FALSE;



詳細枚舉系統(tǒng)服務(wù)項

到現(xiàn)在為止,,還遺留有一個問題,就是獲得服務(wù)號對應(yīng)的服務(wù)函數(shù)名,。比如XP下0x7A對應(yīng)著NtOpenProcess,,但是到2000下,,NtOpenProcess就改為0x6A了。

——有一個好消息一個壞消息,,你先聽哪個,?

——什么壞消息?

——Windows并沒有給我們開放這樣現(xiàn)成的函數(shù),,所有的工作都需要我們自己來做,。

——那好消息呢?

——牛糞有的是,。

壞了,串詞兒了,。好消息是我們可以通過枚舉ntdll.dll的導出函數(shù)來間接枚舉SSDT所有表項所對應(yīng)的函數(shù),,因為所有的內(nèi)核服務(wù)函數(shù)對應(yīng)于ntdll.dll的同名函數(shù)都是這樣開頭的:

mov eax, <ServiceIndex> 


對應(yīng)的機器碼為:

B8 <ServiceIndex> 


再說一遍:非常幸運,僅就我手頭上的2000 sp4,、XP,、XP sp1、XP sp2,、2003的ntdll.dll而言,,無一例外。不過Mark Russinovich的《深入解析Windows操作系統(tǒng)》一書中指出,,IA64的調(diào)用方式與此不同——由于手頭上沒有相應(yīng)的文件,,所以在這里不進行討論了就。

接著說,。我們可以把mov的一句用如下的一個結(jié)構(gòu)來表示:

#pragma pack( push, 1 )
typedef struct _tagSSDTEntry {
    BYTE  byMov;   // 0xb8
    DWORD dwIndex;
} SSDTENTRY;
#pragma pack( pop ) 


那么,,我們可以對ntdll.dll的所有導出函數(shù)進行枚舉,并篩選出“Nt”開頭者,,以SSDTENTRY的結(jié)構(gòu)取出其開頭5個字節(jié)進行比對——這就是整個的枚舉過程,。相關(guān)的PE文件格式解析我不再解釋,可參考注釋,。整個代碼如下:

#define MOV        0xb8

void EnumSSDT( IN HANDLE hDriver, IN HMODULE hNtDll )
{
    DWORD dwOffset                  = (DWORD)hNtDll;
    PIMAGE_EXPORT_DIRECTORY pExpDir = NULL;
    int nNameCnt                    = 0;
    LPDWORD pNameArray              = NULL;
    int i                           = 0;

    // 到PE頭部
    dwOffset += ((PIMAGE_DOS_HEADER)hNtDll)->e_lfanew + sizeof( DWORD );
    // 到第一個數(shù)據(jù)目錄
    dwOffset += sizeof( IMAGE_FILE_HEADER ) + sizeof( IMAGE_OPTIONAL_HEADER )
        - IMAGE_NUMBEROF_DIRECTORY_ENTRIES * sizeof( IMAGE_DATA_DIRECTORY );
    // 到導出表位置
    dwOffset = (DWORD)hNtDll
        + ((PIMAGE_DATA_DIRECTORY)dwOffset)->VirtualAddress;
    pExpDir = (PIMAGE_EXPORT_DIRECTORY)dwOffset;

    nNameCnt = pExpDir->NumberOfNames;
    // 到函數(shù)名RVA數(shù)組
    pNameArray = (LPDWORD)( (DWORD)hNtDll + pExpDir->AddressOfNames );

    // 初始化系統(tǒng)模塊鏈表
    PSYSMODULELIST pList = CreateModuleList( hNtDll );

    // 循環(huán)查找函數(shù)名
    for ( i = 0; i < nNameCnt; ++i )
    {
        PCSTR pszName = (PCSTR)( pNameArray[i] + (DWORD)hNtDll );
        if ( 'N' == pszName[0] && 't' == pszName[1] )
        {
            // 找到了函數(shù),,則定位至查找表
            LPWORD pOrdNameArray = (LPWORD)( (DWORD)hNtDll + pExpDir->AddressOfNameOrdinals );
            // 定位至總表
            LPDWORD pFuncArray   = (LPDWORD)( (DWORD)hNtDll + pExpDir->AddressOfFunctions );
            LPCVOID pFunc        = (LPCVOID)( (DWORD)hNtDll + pFuncArray[pOrdNameArray[i]] );
            
            // 解析函數(shù),獲取服務(wù)名
            SSDTENTRY entry;
            CopyMemory( &entry, pFunc, sizeof( SSDTENTRY ) );
            if ( MOV == entry.byMov )
            {
                ULONG ulAddr = 0;
                GetProc( hDriver, entry.dwIndex, &ulAddr );

                CHAR strModule[MAX_PATH] = "[Unknown Module]";
                FindModuleByAddr( ulAddr, pList, strModule, MAX_PATH );
                printf( "0x%04X\t%s\t0x%08X\t%s\r\n", entry.dwIndex,
                    strModule, ulAddr, pszName );
            }
        }
    }

    DestroyModuleList( pList );



下圖是示例程序SSDTDump在XP sp2上的部分運行截圖,,顯示了SSDT的基地址,、服務(wù)個數(shù),以及各個表項所對應(yīng)的服務(wù)號,、所在模塊,、地址和服務(wù)名。
下圖是示例程序SSDTDump在XP sp2上的部分運行截圖,,顯示了SSDT的基地址,、服務(wù)個數(shù),以及各個表項所對應(yīng)的服務(wù)號、所在模塊,、地址和服務(wù)名,。

結(jié)語

ring3與ring0,城里與城外之間為一道嘆息之墻所間隔,,SSDT則是越過此墻的一道必經(jīng)之門,。因此,很多殺毒軟件也勢必會圍繞著它大做文章,。無論是System Safety Monitor的系統(tǒng)監(jiān)控,,還是卡巴斯基的主動防御,都是掛鉤了SSDT,。這樣,,病毒尚在ring3內(nèi)發(fā)作之時,便被扼殺于搖籃之內(nèi),。
內(nèi)核最高權(quán)限,,本就是兵家必爭之地,魔高一尺道高一丈的爭奪于此亦已變成頗為稀松平常之事,??梢哉f和這些爭奪比起來,SSDT的相關(guān)技術(shù)簡直不值一提,。但最初發(fā)作的病毒體總是從ring3開始的——換句話說,,任你未來會成長為何等的武林高手,我都可以在你學走路的時候殺掉你——知曉了SSDT的這點優(yōu)勢,,所有的病毒咂吧咂吧也就都沒味兒了,。所以說么,殺毒莫如防毒,。
——就此打住罷,,貌似扯遠大發(fā)了。

附件:ssdtdump.zip

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,,所有內(nèi)容均由用戶發(fā)布,,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式,、誘導購買等信息,,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,,請點擊一鍵舉報,。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多