前言
注冊表是windows的重要數據庫,存放了很多重要的信息以及一些應用的設置,對注冊表進行監(jiān)控并防止篡改是十分有必要的。在64位系統(tǒng)下微軟提供了CmRegisterCallback
這個回調函數來實時監(jiān)控注冊表的操作,那么既然這里微軟提供了這么一個方便的接口,病毒木馬自然也會利用,這里我們就來探究其實現(xiàn)的原理和如何利用這個回調函數進行對抗
CmRegisterCallback
要想更好的進行對抗,就是深入底層去看這個函數到底做了什么事情,無論是監(jiān)控還是反監(jiān)控,這樣我們才能夠更好的進行利用
首先我們去msdn里面看一下它的結構
NTSTATUS?CmRegisterCallback(
??[in]???????????PEX_CALLBACK_FUNCTION?Function,
??[in,?optional]?PVOID?????????????????Context,
??[out]??????????PLARGE_INTEGER????????Cookie
);
image-20220501100957527.png
第一個參數指向RegistryCallback
,它這里其實是通過EX_CALLBACK_FUNCTION
這個回調函數實現(xiàn),結構如下
EX_CALLBACK_FUNCTION?ExCallbackFunction;
NTSTATUS?ExCallbackFunction(
??[in]???????????PVOID?CallbackContext,
??[in,?optional]?PVOID?Argument1,
??[in,?optional]?PVOID?Argument2
)
{...}
image-20220501101203532.png
主要是看第三個參數,REG_NOTIFY_CLASS
結構如下
typedef?enum?_REG_NOTIFY_CLASS?{
????RegNtDeleteKey,
????RegNtPreDeleteKey?=?RegNtDeleteKey,
????RegNtSetValueKey,
????RegNtPreSetValueKey?=?RegNtSetValueKey,
????RegNtDeleteValueKey,
????RegNtPreDeleteValueKey?=?RegNtDeleteValueKey,
????RegNtSetInformationKey,
????RegNtPreSetInformationKey?=?RegNtSetInformationKey,
????RegNtRenameKey,
????RegNtPreRenameKey?=?RegNtRenameKey,
????RegNtEnumerateKey,
????RegNtPreEnumerateKey?=?RegNtEnumerateKey,
????RegNtEnumerateValueKey,
????RegNtPreEnumerateValueKey?=?RegNtEnumerateValueKey,
????RegNtQueryKey,
????RegNtPreQueryKey?=?RegNtQueryKey,
????RegNtQueryValueKey,
????RegNtPreQueryValueKey?=?RegNtQueryValueKey,
????RegNtQueryMultipleValueKey,
????RegNtPreQueryMultipleValueKey?=?RegNtQueryMultipleValueKey,
????RegNtPreCreateKey,
????RegNtPostCreateKey,
????RegNtPreOpenKey,
????RegNtPostOpenKey,
????RegNtKeyHandleClose,
????RegNtPreKeyHandleClose?=?RegNtKeyHandleClose,
????//
????//?.Net?only
????//????
????RegNtPostDeleteKey,
????RegNtPostSetValueKey,
????RegNtPostDeleteValueKey,
????RegNtPostSetInformationKey,
????RegNtPostRenameKey,
????RegNtPostEnumerateKey,
????RegNtPostEnumerateValueKey,
????RegNtPostQueryKey,
????RegNtPostQueryValueKey,
????RegNtPostQueryMultipleValueKey,
????RegNtPostKeyHandleClose,
????RegNtPreCreateKeyEx,
????RegNtPostCreateKeyEx,
????RegNtPreOpenKeyEx,
????RegNtPostOpenKeyEx,
????//
????//
????RegNtPreFlushKey,
????RegNtPostFlushKey,
????RegNtPreLoadKey,
????RegNtPostLoadKey,
????RegNtPreUnLoadKey,
????RegNtPostUnLoadKey,
????RegNtPreQueryKeySecurity,
????RegNtPostQueryKeySecurity,
????RegNtPreSetKeySecurity,
????RegNtPostSetKeySecurity,
????//
????//?per-object?context?cleanup
????//
????RegNtCallbackObjectContextCleanup,
????//
????//?new?in?Vista?SP2?
????//
????RegNtPreRestoreKey,
????RegNtPostRestoreKey,
????RegNtPreSaveKey,
????RegNtPostSaveKey,
????RegNtPreReplaceKey,
????RegNtPostReplaceKey,
????MaxRegNtNotifyClass?//should?always?be?the?last?enum
}?REG_NOTIFY_CLASS;
這里有幾個比較常見的類型
-
??RegNtPreCreateKey 創(chuàng)建注冊表 對應的
Argument2
為PREG_CREATE_KEY_INFORMATION
-
??RegNtPreOpenKey 打開注冊表 對應的
Argument2
為PREG_CREATE_KEY_INFORMATION
-
??RegNtPreDeleteKey 刪除鍵 對應的
Argument2
為PREG_DELETE_KEY_INFORMATION
-
??RegNtPreDeleteValueKey 刪除鍵值 對應的
Argument2
為PREG_DELETE_VALUE_KEY_INFORMATION
-
??RegNtPreSetValueKey 修改鍵值 對應的
Argument2
為PREG_SET_VALUE_KEY_INFORMATION
這里RegNtPreCreateKey
和RegNtPreOpenKey
的Argument2
都是使用到PREG_CREATE_KEY_INFORMATION
這個結構體,我們看下結構
其中兩個關鍵的參數就是CompleteName
表示指向路徑的指針,以及RootObject
表示指向注冊表項的指針
typedef?struct?_REG_CREATE_KEY_INFORMATION?{
????PUNICODE_STRING?????CompleteName;?//?IN
????PVOID???????????????RootObject;???//?IN
????PVOID???????????????ObjectType;???
????ULONG???????????????CreateOptions;
????PUNICODE_STRING?????Class;????????
????PVOID???????????????SecurityDescriptor;
????PVOID???????????????SecurityQualityOfService;
????ACCESS_MASK?????????DesiredAccess;
????ACCESS_MASK?????????GrantedAccess;
????????????????????????????????????????//?to?be?filled?in?by?callbacks?
??????????????????????????????????????//?when?bypassing?native?code
????PULONG??????????????Disposition;??
??????????????????????????????????????//?on?pass?through,?callback?should?fill?
??????????????????????????????????????//?in?disposition
????PVOID???????????????*ResultObject;
??????????????????????????????????????//?on?pass?through,?callback?should?return?
??????????????????????????????????????//?object?to?be?used?for?the?return?handle
????PVOID???????????????CallContext;??
????PVOID???????????????RootObjectContext;??
????PVOID???????????????Transaction;??
????PVOID???????????????Reserved;?????
}?REG_CREATE_KEY_INFORMATION,?REG_OPEN_KEY_INFORMATION,*PREG_CREATE_KEY_INFORMATION,?*PREG_OPEN_KEY_INFORMATION;
然后就是RegNtPreDeleteKey
對應PREG_DELETE_KEY_INFORMATION
結構體,這里的話因為只需要刪除注冊表,使用到Object
參數指向要刪除注冊表的指針
typedef?struct?_REG_DELETE_KEY_INFORMATION?{
????PVOID????Object;??????????????????????//?IN
????PVOID????CallContext;??
????PVOID????ObjectContext;
????PVOID????Reserved;?????
}?REG_DELETE_KEY_INFORMATION,?*PREG_DELETE_KEY_INFORMATION
image-20220501102243154.png
再就是RegNtPreDeleteValueKey
對應PREG_DELETE_VALUE_KEY_INFORMATION
結構體,我們通過名字可以判斷這里我們要刪除表項里面的值,所以這里Object
還是指向要刪除的注冊表的指針,而ValueName
就是指向具體需要刪除的值
typedef?struct?_REG_DELETE_VALUE_KEY_INFORMATION?{
??PVOID???????????Object;
??PUNICODE_STRING?ValueName;
??PVOID???????????CallContext;
??PVOID???????????ObjectContext;
??PVOID???????????Reserved;
}?REG_DELETE_VALUE_KEY_INFORMATION,?*PREG_DELETE_VALUE_KEY_INFORMATION;
image-20220501102233472.png
RegNtPreSetValueKey
對應的是PREG_SET_VALUE_KEY_INFORMATION
結構,同樣是Object
指向要修改的注冊表的指針,而ValueName
就是指向具體需要修改的值
typedef?struct?_REG_CREATE_KEY_INFORMATION?{
????PUNICODE_STRING?????CompleteName;?//?IN
????PVOID???????????????RootObject;???//?IN
????PVOID???????????????ObjectType;???
????ULONG???????????????CreateOptions;
????PUNICODE_STRING?????Class;????????
????PVOID???????????????SecurityDescriptor;
????PVOID???????????????SecurityQualityOfService;
????ACCESS_MASK?????????DesiredAccess;
????ACCESS_MASK?????????GrantedAccess;
????????????????????????????????????????//?to?be?filled?in?by?callbacks?
??????????????????????????????????????//?when?bypassing?native?code
????PULONG??????????????Disposition;??
??????????????????????????????????????//?on?pass?through,?callback?should?fill?
??????????????????????????????????????//?in?disposition
????PVOID???????????????*ResultObject;
??????????????????????????????????????//?on?pass?through,?callback?should?return?
??????????????????????????????????????//?object?to?be?used?for?the?return?handle
????PVOID???????????????CallContext;??
????PVOID???????????????RootObjectContext;??
????PVOID???????????????Transaction;??
????PVOID???????????????Reserved;?????
}?REG_CREATE_KEY_INFORMATION,?REG_OPEN_KEY_INFORMATION,*PREG_CREATE_KEY_INFORMATION,?*PREG_OPEN_KEY_INFORMATION;
我們這里了解了CmRegisterCallback
的一些常用結構,這里我們去IDA里面看一下其底層實現(xiàn)
這個函數首先調用了CmpRegisterCallbackInternal
image-20220501103504282.png
我們跟進去看看,首先是通過ExAllocatePoolWithTag
來申請0x30
大小的空間,然后通過test esi,esi
將Blink
和Flink
都指向自己,然后進行判斷后跳轉到69436B
這個地址
image-20220501103825124.png
這段函數主要是將雙向鏈表存入申請空間的前8字節(jié),然后將Context
結構保存到0x18
的位置,將回調函數保存到0x1C
的位置,然后進行判斷后跳轉到6943B1
這個地址
image-20220501104354270.png
這段函數的主要作用就是將Cookie
的值存入0x10
偏移處,這里的設計很巧妙,因為一個cookie是占8字節(jié)的,這里首先將[esi + 0x10]
的值存入eax,然后將ebx地址存入eax,相當于賦值前4位,然后再進行同樣的操作,這里取的是[esi + 0x14]
,也就是賦值后四位
image-20220501104945941.png
然后再就是后面的代碼,這里就是一些釋放內存的操作
image-20220501105207771.png
我們從上面的分析可以得出CmRegisterCallback
其實就是申請了一塊空間,保存了一個雙向鏈表、Cookie
、Context
、回調函數的地址,那么如果要存放注冊表,只可能是放在了雙向鏈表里面,這里我們就有理由猜測注冊表監(jiān)控的回調函數就是通過一個雙向鏈表連接起來的
監(jiān)控
我們在上面已經分析了CmRegisterCallback
函數的原理,那么我們首先注冊一個回調函數
NTSTATUS?status?=?CmRegisterCallback(RegisterCallback,?NULL,?&RegCookie);
if?(!NT_SUCCESS(status))
{
????ShowError("CmRegisterCallback",?status);
????RegCookie.QuadPart?=?0;
????return?status;
}
然后我們編寫RegisterCallback
這個回調函數,首先傳入3個參數
NTSTATUS?RegisterMonCallback(_In_?PVOID?CallbackContext,_In_opt_?PVOID?Argument1,_In_opt_?PVOID?Argument2)
我們在前面已經說過CmRegisterCallback
的第一個參數是操作類型,我們首先獲取一下
LONG?lOperateType?=?(REG_NOTIFY_CLASS)Argument1;
通過ExAllocatePool
申請一塊內存,使用ustrRegPath
定義注冊表的路徑,這里設置非分頁內存即可
ustrRegPath.Buffer?=?ExAllocatePool(NonPagedPool,?ustrRegPath.MaxLength);
然后我們通過switch...case
循環(huán)來判斷lOperateType
來進行具體的操作,比如這里是RegNtPreCreateKey
首先需要獲取注冊表的路徑,這里就需要用到ObQueryNameString
這個API
NTSTATUS
??ObQueryNameString(
????IN?PVOID??Object,
????OUT?POBJECT_NAME_INFORMATION??ObjectNameInfo,
????IN?ULONG??Length,
????OUT?PULONG??ReturnLength
????);
第二個參數指向OBJECT_NAME_INFORMATION
結構,保存的是返回的名稱
typedef?struct?_OBJECT_NAME_INFORMATION?{
????UNICODE_STRING?Name;
}?OBJECT_NAME_INFORMATION,?*POBJECT_NAME_INFORMATION;
那么這里我們調用ObQueryNameString
獲取函數地址,寫成一個函數方便調用
PVOID?lpObjectNameInfo?=?ExAllocatePool(NonPagedPool,?ulSize);
NTSTATUS?status?=?ObQueryNameString(pRegistryObject,?(POBJECT_NAME_INFORMATION)lpObjectNameInfo,?ulSize,?&ulRetLen);
RtlCopyUnicodeString(pRegistryPath,?(PUNICODE_STRING)lpObjectNameInfo);
獲取一下注冊表的路徑
GetRegisterPath(&ustrRegPath,?((PREG_CREATE_KEY_INFORMATION)Argument2)->RootObject);
這里我們定位到注冊表之后,就需要進行判斷是否是我們想要保護的注冊表,如果是的話就將status
改成STATUS_ACCESS_DENIED
即可達到保護注冊表的效果
通過wcsstr()
函數判斷路徑是否為我們想要保護的注冊表名稱,編寫為Compare
函數
BOOLEAN?Compare(UNICODE_STRING?ustrRegPath)
{
????if?(NULL?!=?wcsstr(ustrRegPath.Buffer,?L"RegTest"))
????{
????????return?TRUE;
????}
????return?FALSE;
}
如果名稱相同則設置為STATUS_ACCESS_DENIED
if?(Compare(ustrRegPath))
{
????status?=?STATUS_ACCESS_DENIED;
}
完整代碼如下,這里因為是RegNtPreOpenKey
所以Argument2
對應的類型就是PREG_CREATE_KEY_INFORMATION
,這里只需要修改Argunment2
的類型即可,剩下的幾個判斷再這里就不贅述了
????case?RegNtPreOpenKey:
????{
????????GetRegisterPath(&ustrRegPath,?((PREG_CREATE_KEY_INFORMATION)Argument2)->RootObject);
????????//?判斷是否是被保護的注冊表
????????if?(Compare(ustrRegPath))
????????{
????????????status?=?STATUS_ACCESS_DENIED;
????????}
????????//?顯示
????????DbgPrint("[RegNtPreOpenKey][%wZ][%wZ]\n",?&ustrRegPath,?((PREG_CREATE_KEY_INFORMATION)Argument2)->CompleteName);
????????break;
????}
image-20220501142500180.png
回調函數的完整代碼如下
NTSTATUS?RegisterCallback(_In_?PVOID?CallbackContext,_In_opt_?PVOID?Argument1,_In_opt_?PVOID?Argument2)
{
????NTSTATUS?status?=?STATUS_SUCCESS;
????UNICODE_STRING?ustrRegPath;
????LONG?lOperateType?=?(REG_NOTIFY_CLASS)Argument1;
????ustrRegPath.Length?=?0;
????ustrRegPath.MaximumLength?=?1024?*?sizeof(WCHAR);
????ustrRegPath.Buffer?=?ExAllocatePool(NonPagedPool,?ustrRegPath.MaximumLength);
????if?(NULL?==?ustrRegPath.Buffer)
????{
????????printf("ExAllocatePool?error?:?%d\n",?GetLastError());
????????return?status;
????}
????switch?(lOperateType)
????{
????????//?創(chuàng)建注冊表之前
????case?RegNtPreCreateKey:
????{
????????GetRegisterPath(&ustrRegPath,?((PREG_CREATE_KEY_INFORMATION)Argument2)->RootObject);
????????if?(Compare(ustrRegPath))
????????{
????????????status?=?STATUS_ACCESS_DENIED;
????????}
????????DbgPrint("[RegNtPreCreateKey][%wZ][%wZ]\n",?&ustrRegPath,?((PREG_CREATE_KEY_INFORMATION)Argument2)->CompleteName);
????????break;
????}
????//?打開注冊表之前
????case?RegNtPreOpenKey:
????{
????????GetRegisterPath(&ustrRegPath,?((PREG_CREATE_KEY_INFORMATION)Argument2)->RootObject);
????????if?(Compare(ustrRegPath))
????????{
????????????status?=?STATUS_ACCESS_DENIED;
????????}
????????DbgPrint("[RegNtPreOpenKey][%wZ][%wZ]\n",?&ustrRegPath,?((PREG_CREATE_KEY_INFORMATION)Argument2)->CompleteName);
????????break;
????}
????//?刪除鍵之前
????case?RegNtPreDeleteKey:
????{
????????GetRegisterPath(&ustrRegPath,?((PREG_DELETE_KEY_INFORMATION)Argument2)->Object);
????????if?(Compare(ustrRegPath))
????????{
????????????status?=?STATUS_ACCESS_DENIED;
????????}
????????DbgPrint("[RegNtPreDeleteKey][%wZ]\n",?&ustrRegPath);
????????break;
????}
????//?刪除鍵值之前
????case?RegNtPreDeleteValueKey:
????{
????????GetRegisterPath(&ustrRegPath,?((PREG_DELETE_VALUE_KEY_INFORMATION)Argument2)->Object);
????????if?(Compare(ustrRegPath))
????????{
????????????status?=?STATUS_ACCESS_DENIED;
????????}
????????DbgPrint("[RegNtPreDeleteValueKey][%wZ][%wZ]\n",?&ustrRegPath,?((PREG_DELETE_VALUE_KEY_INFORMATION)Argument2)->ValueName);
????????break;
????}
????//?修改鍵值之前
????case?RegNtPreSetValueKey:
????{
????????GetRegisterPath(&ustrRegPath,?((PREG_SET_VALUE_KEY_INFORMATION)Argument2)->Object);
????????if?(Compare(ustrRegPath))
????????{
????????????status?=?STATUS_ACCESS_DENIED;
????????}
????????//?顯示
????????DbgPrint("[RegNtPreSetValueKey][%wZ][%wZ]\n",?&ustrRegPath,?((PREG_SET_VALUE_KEY_INFORMATION)Argument2)->ValueName);
????????break;
????}
????default:
????????break;
????}
????if?(NULL?!=?ustrRegPath.Buffer)
????{
????????ExFreePool(ustrRegPath.Buffer);
????????ustrRegPath.Buffer?=?NULL;
????}
????PEPROCESS?pEProcess?=?PsGetCurrentProcess();
????if?(NULL?!=?pEProcess)
????{
????????UCHAR*?lpszProcessName?=?PsGetProcessImageFileName(pEProcess);
????????if?(NULL?!=?lpszProcessName)
????????{
????????????DbgPrint("Current?Process[%s]\n",?lpszProcessName);
????????}
????}
????return?status;
}
實現(xiàn)效果
我們加載驅動可以看到我們想要修改RegTest
的二進制的值被拒絕
image-20220501142641363.png
刪除也會被攔截
image-20220501142654260.png
重命名也同樣被攔截
image-20220501142706899.png
卸載驅動之后能夠成功重命名
image-20220501142719387.png
也能夠修改二進制的內容
image-20220501142758141.png
也可以刪除字符串
image-20220501142809044.png
反監(jiān)控
我們在前面已經逆向分析了CmRegisterCallback
的底層實現(xiàn),就是通過申請一塊內存空間存放雙向鏈表,那么我們只要定位到這個雙向鏈表刪除回調函數即可得到反監(jiān)控的效果
將鏈表加入我們內存的函數是SetRegisterCallback
,這里我因為pdb文件的問題顯示得有點問題,我們跟進去看看
image-20220501144151656.png
這里往下走可以看到offset CallbackListHead
的操作,這里就是將鏈表頭賦值給ebx
image-20220501144321163.png
然后將eax的值賦給[esi + 10]
,這里就是在進行初始化Cookie
的操作
image-20220501144430141.png
然后最后再比較edi
的值是否為ListBegin
的地址,跳轉到增加鏈表的代碼
image-20220501144540775.png
那么這里我們明確下思路,我們想要定位到鏈表頭,就首先需要通過在CmRegisterCallback
里面定位SetRegisterCallback
image-20220501144914828.png
然后定位到鏈表頭即可
image-20220501145028344.png
那么這里我們進行代碼編寫,首先定位到CmRegisterCallback
函數
????UNICODE_STRING?uStrFuncName?=?RTL_CONSTANT_STRING(L"CmRegisterCallback");
????pCmRegFunc?=?(PUCHAR)MmGetSystemRoutineAddress(&uStrFuncName);
然后通過硬編碼定位到鏈表頭
????pCmRegFunc?=?(PUCHAR)MmGetSystemRoutineAddress(&uStrFuncName);
????if?(pCmRegFunc?==?NULL)
????{
????????DbgPrint("MmGetSystemRoutineAddress?error?:?%d\r\n",GetLastError());
????????return?pListEntry;
????}
????while?(*pCmRegFunc?!=?0xC2)
????{
????????if?(*pCmRegFunc?==?0xE8)
????????{
????????????pCmcRegFunc?=?(PUCHAR)((ULONG)pCmRegFunc?+?5?+?*(PULONG)(pCmRegFunc?+?1));
????????????break;
????????}
????????pCmRegFunc++;
????}
????if?(pCmcRegFunc?==?NULL)
????{
????????DbgPrint("GetCmcRegFunc?error?:?%d\r\n",GetLastError());
????????return?pListEntry;
????}
????while?(*pCmcRegFunc?!=?0xC2)
????{
????????if?(*pCmcRegFunc?==?0x8B?&&?*(pCmcRegFunc?+?1)?==?0xC6?&&?*(pCmcRegFunc?+?2)?==?0xE8)
????????{
????????????pSetRegFunc?=?(PUCHAR)((ULONG)pCmcRegFunc?+?2?+?5?+?*(PULONG)(pCmcRegFunc?+?3));
????????????break;
????????}
????????pCmcRegFunc++;
????}
????if?(pSetRegFunc?==?NULL)
????{
????????DbgPrint("GetSetRegFunc?error?:?%d\r\n",?GetLastError());
????????return?pListEntry;
????}
????while?(*pSetRegFunc?!=?0xC2)
????{
????????if?(*pSetRegFunc?==?0xBB)
????????{
????????????pListEntry?=?(PULONG)?*?(PULONG)(pSetRegFunc?+?1);
????????????break;
????????}
????????pSetRegFunc++;
????}
定位到鏈表頭之后,我們通過MmIsAddressValid
對地址進行判斷是否可用,通過0x10
偏移定位到Cookie
pHead?=?GetRegisterList();
pListEntry?=?(PLIST_ENTRY)*pHead;
pLiRegCookie?=?(PLARGE_INTEGER)((ULONG)pListEntry?+?0x10);
pFuncAddr?=?(PULONG)((ULONG)pListEntry?+?0x1C);
然后調用CmUnRegisterCallback
刪除回調
status?=?CmUnRegisterCallback(*pLiRegCookie);
實現(xiàn)效果
這里如果連windbg有輸出效果會更明顯,但是我這臺win7沒有配雙機調試,這里就只能看一下輸出的效果了
首先直接加載一個exe,可以看到修改注冊表的值成功
image-20220501155155905.png
加載驅動發(fā)現(xiàn)創(chuàng)建注冊表值失敗
image-20220501155057647.png
然后再加載我們的繞過回調函數的驅動,又可以加載成功文章來源:http://www.zghlxwxcb.cn/news/detail-481547.html
文章來源地址http://www.zghlxwxcb.cn/news/detail-481547.html
到了這里,關于深入注冊表監(jiān)控的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網!