文章作者:whitecell (sinister_at_whitecell.org)
文章來源:安全焦點(www.xfocus.net)
Author: PolyMeta
Email: [email protected]
Date: 2007-06-10
監視程序建立和銷毀,最常用的手段就是用 PsSetCreateProcessNotifyRoutine()
設定一個CALLBACK函數來完成。該函數的原形如下:
VOID
(*PCreate_PROCESS_NOTIFY_ROUTINE) (
IN HANDLE ParentId,
IN HANDLE ProcessId,
IN BOOLEAN Create
);
NTSTATUS
PsSetCreateProcessNotifyRoutine(
IN PCreate_PROCESS_NOTIFY_ROUTINE NotifyRoutine,
IN BOOLEAN Remove
安全類軟體,諸如防火牆,AV,包括系統自身也在用這種方法來監視程序。不要問我為啥,有
文檔的東西,大家都喜歡用。問題出來了,你有沒有想過自己的監控回調函數就一定能設定成
功嗎?很不幸的告訴你,不一定。特别是在非正規軟體充斥internet的今天。原因很簡單:
PsSetCreateProcessNotifyRoutine 最多隻能設定8個回調函數,這點很多玩核心的人都知道。
然而狼多肉少,大家都想通過程序建立監控對付自己腦海中的"敵人".于是乎,自古以來搶地盤
的戰争又在這裡爆發了。
最殘忍的搶地盤手段:為了自己的回調函數能夠正常設定成功,幹脆把之前其它軟體設定
的回調一并幹掉,也不管是正規或者非正規軟體,反正擋路者死!這下好,使用者倒黴了,很多
軟體包括防火牆咋不好使了呢?
作為正規軟體或者有道德的非正規軟體作者就開始郁悶了,既要讓自己的軟體生效,又要
不影響使用者的正常使用。難題既然出來了,就得想出來解決辦法。于是
PsSetCreateProcessNotifyRoutineMustSuccess(
這個函數就誕生,該函數被筆者封裝到了一個lib裡面供兄弟們寫驅動的時候使用,用以代替原始
的 PsSetCreateProcessNotifyRoutine 函數,使用方法和它一模一樣。該函數的特點就是可以使
通過它設定的程序監控回調函數,無論在8個蹲位是否已經被占滿的情況下都可以設定成功,并且
可以和之前由其它軟體設定的程序監控回調函數和平共存。
工作原理:
PsSetCreateProcessNotifyRoutineMustSuccess(NotifyRoutine)
||
||獲得PspCreateProcessNotifyRoutine位址
//
GetPspCreateProcessNotifyRoutine()
||調用原始函數
PsSetCreateProcessNotifyRoutine(NotifyRoutine) 調用GetFastRefObject()
|| /---------->pCallBackRoutineBlock
設定成功 || 設定失敗則直接操作 儲存 | ||
//------------//----------->PspCreateProcessNotifyRoutine[1]-----> 或 ||成員Function
|| || | //
|| ||替換為 /---------->pOldNotifyRoutine
// || ^
over 調用 // 再調用 |
[新程序建立]----或---------------->AgentProcessMonitor----------------------------/
| ||
| ||先調用
| 調用 //
/-----------------> NotifyRoutine
也不知道畫的流程清晰不,核心的工作原理就是真對PspCreateProcessNotifyRoutine
[PSP_MAX_Create_PROCESS_NOTIFY] 這個有8個蹲位的
(#define PSP_MAX_Create_PROCESS_NOTIFY 8 ) 數組進行操作而已。實質上
PsSetCreateProcessNotifyRoutine 這個函數也就是對 PspCreateProcessNotifyRoutine
數組進行操作,把使用者設定的NotifyRoutine給存到這個數組裡面,當有新程序建立的時候,
系統就會從這個數組中取得回調函數NotifyRoutine,并且調用之。這也就是為什麼我們最
多隻能設定8個回調函數的原因了。
既然用 PsSetCreateProcessNotifyRoutine 設定失敗,那麼我們隻好自己操作
PspCreateProcessNotifyRoutine 這個數組了,為我們的CALLBACK求得一個容身之處。
之前我們調用了 GetPspCreateProcessNotifyRoutine,該函數的目的就是要找到
PspCreateProcessNotifyRoutine 數組的位址,友善我們之後的DIY,嘿嘿。
找到PspCreateProcessNotifyRoutine了,就可以開始DIY了。我們的LIB中用了第二
個坑,即 PspCreateProcessNotifyRoutine[1] 兄弟們如果要自己實作的話,當然可以随
便選擇位置。這裡需要注意的是2000和xp以後系統的差別,在2000的系統上,
PsSetCreateProcessNotifyRoutine 直接将 NotifyRoutine 回調函數的位址存到了
PspCreateProcessNotifyRoutine 數組的坑裡,是以我們可以直接儲存和替換先前别的軟體
設定的回調函數。而xp之後進行了一些改進,出于安全性的考慮,
PspCreateProcessNotifyRoutine 不再直接存放回調函數的位址,而是存放了一個叫
FastRef 的結構,其大小也是一個DWORD,内容是一個 PEX_CALLBACK_ROUTINE_BLOCK 類型
的指針+該指針的引用記數,而真正的 NotifyRoutine 回調函數的位址被存放在了
PEX_CALLBACK_ROUTINE_BLOCK->Function 成員裡。函數 GetFastRefObject 就是為了從
FastRef 結構裡得到真正的 PEX_CALLBACK_ROUTINE_BLOCK 類型的指針 pCallBackRoutineBlock,
然後通過 pCallBackRoutineBlock->Function 就可以像 2000 上一樣友善的儲存和替換先前别
的軟體設定的回調函數了。
現在我們可以友善的替換已有的回調函數了,為了相容被替換掉的回調函數,與其和平
共處,我們需要在自己的回調函數中調用調用一下之前被我們替換掉的回調。我們要提供的
是一個新的庫函數,要做到對調用者的透明,當然不能要求函數的使用者在自己的回調函數
中調用之前被替換掉的回調函數,是以我們在庫的内部提供這麼一個代理回調
AgentProcessMonitor,真正替換原始回調的是這個代理函數而不是使用者設定的 NotifyRoutine。
接下來我們在 AgentProcessMonitor 中先調用新設定的 NotifyRoutine,再調用原始的被
替換掉的那個函數,就能實作和平共處了。
AgentProcessMonitor(
)
{
if( pNewNotifyRoutine)
pNewNotifyRoutine(ParentId,ProcessId,Create);
}
if(MmIsAddressValid((PVOID)pOldNotifyRoutine))
if(memcmp((PVOID)pOldNotifyRoutine,pSigCode,0x40)==0)
pOldNotifyRoutine(ParentId,ProcessId,Create);
兄弟們可能奇怪了,MmIsAddressValid((PVOID)pOldNotifyRoutine) 和
memcmp((PVOID)pOldNotifyRoutine,pSigCode,0x40)==0 是幹嘛的。因為我們用
AgentProcessMonitor 替換掉了原始的回調函數 pOldNotifyRoutine,這樣會導緻設定
pOldNotifyRoutine 該回調函數的驅動解除安裝這個回調的時候失敗,但這時驅動已經被卸
載掉了,如果我們還繼續調用 pOldNotifyRoutine,後果可想而之,是以我們再調用前
要判斷一下。接下來如果又有新驅動被加載起來,并且占用了 pOldNotifyRoutine 指
向的位址,那麼繼續調用 pOldNotifyRoutine 也會死,是以我們還得判斷一下這個地
址入口的若幹位元組是否為先前那個驅動的代碼,然後才能調它。
該函數在xpsp2和2000sp4下測試通過。
感謝大牛dingkai與我讨論,給予我一些提醒。
程序監視庫下載下傳
WSS(Whitecell Security Systems),一個非營利性民間技術組織,緻力于各種系統安全技術的研究。堅持傳統的hacker精神,追求技術的精純。