引入
我們如果想hook對象的回調,在上篇文章里我們已經知道了對象回調函數存在一個列表里面,我們通過dt可以看見,這里他是一個LIST_ENTRY結構,但是實際調用的時候,這樣是行不通的,說明它結構不對
0: kd> dt _OBJECT_TYPE 86cf5d28
nt!_OBJECT_TYPE+0x000 TypeList : _LIST_ENTRY [ 0x86cf5d28 - 0x86cf5d28 ]+0x008 Name : _UNICODE_STRING "Process"+0x010 DefaultObject : (null) +0x014 Index : 0x7 ''+0x018 TotalNumberOfObjects : 0x25+0x01c TotalNumberOfHandles : 0xd0+0x020 HighWaterNumberOfObjects : 0x30+0x024 HighWaterNumberOfHandles : 0xf9+0x028 TypeInfo : _OBJECT_TYPE_INITIALIZER+0x078 TypeLock : _EX_PUSH_LOCK+0x07c Key : 0x636f7250+0x080 CallbackList : _LIST_ENTRY [ 0x996a88c8 - 0x996a88c8 ]
ObRegisterCallBack 分析
函數上來首先是合法性的檢查,先檢查版本號,然后從CallbackRegistration 取出OperationRegistrationCount也就是所要注冊的回調數
inserted = 0;if ( (CallbackRegistration->Version & 0xFF00) != 256 )return 0xC000000D;OperationRegistrationCount = CallbackRegistration->OperationRegistrationCount;if ( !OperationRegistrationCount )return 0xC000000D;
在這之后,函數申請了一個空間,這里需要強調,OperationRegistrationCount*36,也就是每個給回調信息結構的空間有36個字節,但是我們回憶一下之前的知識,回調所對應的結構**_OB_OPERATION_REGISTRATION**只有16個字節,也就是我們需要重新分析這個待會在申請空間中與_OB_OPERATION_REGISTRATION類似的結構
buf_size = 36 * OperationRegistrationCount + CallbackRegistration->Altitude.Length + 16;Alloc_buf = (DWORD *)ExAllocatePoolWithTag(PagedPool, buf_size, 0x6C46624Fu);Alloc_buf_copy = Alloc_buf;if ( !Alloc_buf )return 0xC000009A;memset(Alloc_buf, 0, buf_size);
typedef struct _OB_OPERATION_REGISTRATION {POBJECT_TYPE *ObjectType;OB_OPERATION Operations;POB_PRE_OPERATION_CALLBACK PreOperation;POB_POST_OPERATION_CALLBACK PostOperation;
} OB_OPERATION_REGISTRATION, *POB_OPERATION_REGISTRATION;
之后我分析我一點一點來,所貼出的代碼括號不一定是閉合的
在通過if語句驗證了CallbackRegistration->OperationRegistrationCount的合法性后除了去初始化這個Num(用來在While循環中遍歷),我們可以看見還有一個**v10 = Alloc_buf_copy + 12;**首先,回憶一下,在上面的代碼中,我們知道Alloc_buf_copy是所申請的內存的首地址指針,所以這個v10也就是存放了首地址偏移一段所對應的指針。
但是,這里我要特別指出:這個12是根據我們定義的變量類型來改變的,不同的指針類型,IDA分析出來的偽代碼都不太一樣,所以這種偏移我們最好都回到匯編代碼來進行分析,我就一起附在下面了,從下面那個匯編我們就可以看見,這個偏移實際上是0x30也就是48字節。
繼續向下看,進入了While循環開始遍歷這個回調列表,取出回調結構信息到v11這個變量,復制一份給v25(這里我還沒有重命名,變量名字可能不同但是相對位置應該是一樣的)
CallbackRegistrationa = 0;if ( CallbackRegistration->OperationRegistrationCount ){Num = 0;v10 = Alloc_buf_copy + 12;while ( 1 ){v11 = &CallbackRegistration->OperationRegistration[Num];v25 = v11;if ( !v11->Operations || ((*v11->ObjectType)->TypeInfo.ObjectTypeFlags & 0x40) == 0 )break;
PAGE:006D62B0 8D 7E 30 lea edi, [esi+30h]
上面代碼的if ( !v11->Operations || ((*v11->ObjectType)->TypeInfo.ObjectTypeFlags & 0x40) == 0 )檢查了兩個東西,第一個是我們之前學過的Operations
typedef struct _OB_OPERATION_REGISTRATION {POBJECT_TYPE *ObjectType;OB_OPERATION Operations;POB_PRE_OPERATION_CALLBACK PreOperation;POB_POST_OPERATION_CALLBACK PostOperation;
} OB_OPERATION_REGISTRATION, *POB_OPERATION_REGISTRATION;
第二個我們可以用windbg來看看是什么值,這里找到對應的TypeInfo里面的ObjectTypeFlags,源代碼里面異或的是0x40,也就是01000000,對應的就是SupportsObjectCallbacks,通過之前的學習我們知道,這里是對象是否支持回調的驗證位,也就是現在還是合法性檢查.
0: kd> dt _OBJECT_TYPE
nt!_OBJECT_TYPE........................+0x028 TypeInfo : _OBJECT_TYPE_INITIALIZER //這里+0x078 TypeLock : _EX_PUSH_LOCK..........................
0: kd> dt _OBJECT_TYPE_INITIALIZER
nt!_OBJECT_TYPE_INITIALIZER+0x000 Length : Uint2B+0x002 ObjectTypeFlags : UChar+0x002 CaseInsensitive : Pos 0, 1 Bit+0x002 UnnamedObjectsOnly : Pos 1, 1 Bit+0x002 UseDefaultObject : Pos 2, 1 Bit+0x002 SecurityRequired : Pos 3, 1 Bit+0x002 MaintainHandleCount : Pos 4, 1 Bit+0x002 MaintainTypeList : Pos 5, 1 Bit+0x002 SupportsObjectCallbacks : Pos 6, 1 Bit.............................................
我們繼續回到代碼分析上來,可以看見之后是用于檢查前后回調的合法性
if ( v11->PreOperation ){if ( !MmVerifyCallbackFunction((unsigned int)v11->PreOperation) )goto LABEL_21;v11 = v25;}else if ( !v11->PostOperation ){break;}if ( v11->PostOperation ){if ( !MmVerifyCallbackFunction((unsigned int)v11->PostOperation) ){
LABEL_21:inserted = 0xC0000022;goto LABEL_22;
再往下,我們看見了一個類似結構體初始化的操作,這個v10我們回憶一下,就是 **v10 = Alloc_buf_copy + 12;*這里的v10,我們已經知道它偏移了申請空間48字節。這里如果有細心的人來看,就會發現少了一個(v10 - 5)的賦值,這里我們后面再說,但是如果加上這個復制,這里的結構是36字節
*v10 = 0;*(v10 - 7) = (DWORD)(v10 - 8);*(v10 - 8) = (DWORD)(v10 - 8);*(v10 - 6) = v11->Operations;*(v10 - 4) = (DWORD)Alloc_buf_copy;*(v10 - 3) = (DWORD)*v11->ObjectType;*(v10 - 2) = (DWORD)v11->PreOperation;*(v10 - 1) = (DWORD)v11->PostOperation;
如果對最開始我們內存分配方式還有印象的人應該就可以回想起這個36,它正是我們為每個回調結構申請的大小,同時,還可以想起來申請的時候,申請的空間還加了16個字節,在這里也對上了。這里我需要強調的是這個結構的頭兩個成員,也是指針。
我們在上面強調過,指針相關的偏移是按照我們的定義來算的,所以,這里的減8實際上減的是DWORD*8,也就是32字節。進一步講,這兩個成員裝的是我們結構的首地址,其實這里劇透一下,這就是在初始化鏈表。
還需要注意的是第5個成員指向的是我們申請空間的首地址
有細心的人在讀到這里之前可能就有疑問,之前還有一段代碼為什么沒有介紹,這一段代碼經過本人初學的分析,就是在給上圖的16個字節賦值,具體的圖我放下來了,這里每個框是兩個字節。
其中,Length指的是海拔字符串的長度,buffer指向的是存放這段字符串的首地址。
*(_WORD *)Alloc_buf_copy = 256;Alloc_buf_copy[1] = (DWORD)CallbackRegistration->RegistrationContext;Length = CallbackRegistration->Altitude.Length;*((_WORD *)Alloc_buf_copy + 5) = Length;*((_WORD *)Alloc_buf_copy + 4) = Length;v9 = (char *)Alloc_buf_copy + buf_size - Length;//這里可以看出來算出了這個字符串的首地址v22 = *((unsigned __int16 *)Alloc_buf_copy + 4);Alloc_buf_copy[3] = (DWORD)v9;memcpy(v9, CallbackRegistration->Altitude.Buffer, v22);
我們繼續跟著代碼往下走,看見調用了一個函數,從語義上來說,這是將回調按照海拔(優先級)插入回調鏈表的函數,這里我在放一下前面文章提到過的_OBJECT_TYPE結構,也就是插入這個結構0x80的位置
inserted = ObpInsertCallbackByAltitude((int *)v10 - 8, *(v10 - 3));
0: kd> dt _OBJECT_TYPE
nt!_OBJECT_TYPE+0x000 TypeList : _LIST_ENTRY+0x008 Name : _UNICODE_STRING+0x010 DefaultObject : Ptr32 Void+0x014 Index : UChar+0x018 TotalNumberOfObjects : Uint4B+0x01c TotalNumberOfHandles : Uint4B+0x020 HighWaterNumberOfObjects : Uint4B+0x024 HighWaterNumberOfHandles : Uint4B+0x028 TypeInfo : _OBJECT_TYPE_INITIALIZER+0x078 TypeLock : _EX_PUSH_LOCK+0x07c Key : Uint4B+0x080 CallbackList : _LIST_ENTRY
這兩個參數,向上翻翻就知道一個是鏈表頭,一個是ObjectType,這里不過多分析了。
然后我們進入這個插入函數,來看看是怎么插入的。
我們已經知道了,傳入的第一個參數是我們之前分析結構的首地址,也是一個雙向鏈表頭
00000000 _OB_LIST_ENTRY struc ; (sizeof=0x24, mappedto_1307)
00000000 callbackList _LIST_ENTRY ?
00000008 Operation dd ?
0000000C UNKNOW dd ?
00000010 Self dd ?
00000014 Object_Type dd ?
00000018 PreOperation dd ?
0000001C PostOperation dd ?
00000020 UNKNOW1 dd ?
00000024 _OB_LIST_ENTRY ends
在知道我們傳入的參數a1的結構是分析出來的_OB_LIST_ENTRY參數a2是_OBJECT_TYPE之后,我們的分析就清晰很多了,首先是線程的鎖,這個這里不細說
v17 = 0;CurrentThread = KeGetCurrentThread();--CurrentThread->SpecialApcDisable;p_TypeLock = &a2->TypeLock;if ( _interlockedbittestandset((volatile signed __int32 *)&a2->TypeLock, 0) )ExfAcquirePushLockExclusive();
以后我大概以注釋的形式來解釋代碼,可以看見最后的結果就是將我們選中的鏈表插入這個callbacklist中,并按照海拔降序排列(其實就是優先級)
p_CallbackList = &a2->CallbackList;//取出鏈表Flink = a2->CallbackList.Flink;if ( Flink == p_CallbackList )//如果沒有其他鏈表goto LABEL_10;//直接插入Altitude2 = (const UNICODE_STRING *)(a1->Self + 8);//取出海拔while ( 1 ){v7 = RtlCompareAltitudes((PCUNICODE_STRING)&Flink[2].Flink[1], Altitude2);//比較海拔,如果當前所要插入鏈表頭的海拔大于所遍歷的鏈表的海拔,則插入。這樣就可以保證回調鏈表的降序,始終是高海拔的huii'dv8 = v7 == 0;if ( v7 <= 0 )//如果break;Flink = Flink->Flink;if ( Flink == p_CallbackList ){v8 = v7 == 0;break;}}if ( !v8 ){
LABEL_10://插入操作Blink = Flink->Blink;v10 = Blink->Flink;a1->callbackList.Flink = Blink->Flink;a1->callbackList.Blink = Blink;v10->Blink = &a1->callbackList;Blink->Flink = &a1->callbackList;}else{v17 = 0xC01C0011;//如果海拔相同就報這個錯}Value = p_TypeLock->Value;鎖相關這里不細說if ( (p_TypeLock->Value & 0xFFFFFFF0) <= 0x10 )v12 = 0;elsev12 = Value - 16;if ( (Value & 2) != 0 || _InterlockedCompareExchange((volatile signed __int32 *)p_TypeLock, v12, Value) != Value )//ExfReleasePushLockShared(p_TypeLock);v13 = KeGetCurrentThread();if ( !++v13->SpecialApcDisable && ($ECEA6BAF150BF07A2F607C21A5294F19 *)v13->ApcState.ApcListHead[0].Flink != &v13->64 )KiCheckForKernelApcDelivery();return v17;
}
回到ObRegisterCallBack函數,這里代碼大概意思就是移動到下一個結構體,繼續重復剛剛的操作
if ( inserted < 0 )goto LABEL_22;++*((_WORD *)Alloc_buf_copy + 1);CallbackRegistrationa = (_OB_CALLBACK_REGISTRATION *)((char *)CallbackRegistrationa + 1);++Num;v10 += 9;if ( (unsigned int)CallbackRegistrationa >= CallbackRegistration->OperationRegistrationCount )goto LABEL_29;}inserted = 0xC000000D;
最后就是我們之前提到在_OB_LIST_ENTRY中的一個位,它當時沒有被置位,在最后一段中,每個_OB_LIST_ENTRY的這個位(我們后面稱為Flags),都被或了一個1.最后返回了,所申請內存空間的首地址,作為RegistrationHandle,也就是ObRegisterCallBack的第二個參數。
到這里我們就分析完了
LABEL_30:v19 = 0;if ( *((_WORD *)Alloc_buf_copy + 1) )//OperationRegistrationCount{v20 = Alloc_buf_copy + 7;//這個就是之前我們標記UNKNOW的位置,我們以后記為FLag位do{*v20 |= 1u;//或了一個1++v19;v20 += 9;//這里的九指的是_OB_LIST_ENTRY 里面的9,這樣等價于跳到了下個相同結構的Flag,然后重復這個循環}while ( v19 < *((unsigned __int16 *)Alloc_buf_copy + 1) );}*RegistrationHandle = Alloc_buf_copy;//最后,這就是我們返回的RegistrationHandle}return inserted;
}
實踐一下,驗證逆向結果
在之前我們對象回調的代碼基礎上,打印出RegistrationHandle的位置
0: kd> g
[+] The state is 0
[+] RegistrationHandle is in 996a88b8
dd來看一下,為了方便我把圖又放了一次
0: kd> dd 996a88b8
ReadVirtual: 996a88b8 not properly sign extended
996a88b8 00010100 00000000 000c000c 996a88ec
996a88c8 86cf5da8 86cf5da8 00000003 00000001
996a88d8 996a88b8 86cf5d28 98c08150 98c08140//這里的996a88b8指向了自己的首地址,
996a88e8 00000000 00320031 00340033 00360035
996a88f8 06040209 74416553 00000000 996a8904
996a8908 996a8904 00000000 996a8910 996a8910
996a8918 06080204 61564d43 002c0000 80c4d874
996a8928 00176b76 80000004 ffffffff 00000004
首先便是一個00010100,0001指的是我們回調數量,為1;0100是256,符合我們的預期
然后就是RegistrationContext這是我們函數參數,我沒傳所以是00000000,然后是海拔的bufffer 996a88ec,db看一下這個值,可以看見123456,符合預期。這16字節正確之后,繼續向下走。
0: kd> db 996a88ec
ReadVirtual: 996a8938 not properly sign extended
996a88ec 31 00 32 00 33 00 34 00-35 00 36 00 09 02 04 06 1.2.3.4.5.6.....
996a88fc 53 65 41 74 00 00 00 00-04 89 6a 99 04 89 6a 99 SeAt......j...j.
996a890c 00 00 00 00 10 89 6a 99-10 89 6a 99 04 02 08 06 ......j...j.....
996a891c 43 4d 56 61 00 00 2c 00-74 d8 c4 80 76 6b 17 00 CMVa..,.t...vk..
996a892c 04 00 00 80 ff ff ff ff-04 00 00 00 01 00 35 00 ..............5.
996a893c 4d 69 6e 50 6f 73 31 32-37 34 78 31 31 39 39 78 MinPos1274x1199x
996a894c 39 36 28 31 29 2e 79 00-00 00 00 00 08 02 04 06 96(1).y.........
996a895c 53 65 41 74 00 00 00 00-64 89 6a 99 64 89 6a 99 SeAt....d.j.d.j.
再接下來的兩個86cf5da8其實是鏈表頭鏈表尾,但是我們只有一個函數所以一致
這個結構應該就是我們分析的_OB_LIST_ENTRY,首先是鏈表頭鏈表尾996a88c8 996a88c8 ,對照上面,這里確實是我們這個_OB_LIST_ENTRY的地址
0: kd> dd 86cf5da8
ReadVirtual: 86cf5da8 not properly sign extended
86cf5da8 996a88c8 996a88c8 04190019 d46a624f
86cf5db8 8dc05880 00080006 8dc011f8 00000000
86cf5dc8 86cf5d00 86cf5f08 00000000 00000000
86cf5dd8 00000002 00000000 00000000 13030002
86cf5de8 00000000 00000000 86cf5df0 86cf5df0
86cf5df8 00080006 8dc011f8 00000000 00000006
86cf5e08 00000002 00000002 00000004 00000004
86cf5e18 00080050 00000000 00000000 00020004
接下來是86cf5d28,這個應該是我們的OBJECT_TYPE,可以看見是進程對象,完美,并且最下面0x80的鏈表也對上了.
0: kd> dt _OBJECT_TYPE 86cf5d28
nt!_OBJECT_TYPE+0x000 TypeList : _LIST_ENTRY [ 0x86cf5d28 - 0x86cf5d28 ]+0x008 Name : _UNICODE_STRING "Process"+0x010 DefaultObject : (null) +0x014 Index : 0x7 ''+0x018 TotalNumberOfObjects : 0x25+0x01c TotalNumberOfHandles : 0xd0+0x020 HighWaterNumberOfObjects : 0x30+0x024 HighWaterNumberOfHandles : 0xf9+0x028 TypeInfo : _OBJECT_TYPE_INITIALIZER+0x078 TypeLock : _EX_PUSH_LOCK+0x07c Key : 0x636f7250+0x080 CallbackList : _LIST_ENTRY [ 0x996a88c8 - 0x996a88c8 ]
在接著就是98c08150 98c08140這兩個我們注冊的前后回調,這里沒必要演示了,最后就是我們之前分析的v10 = 0,整個結構體結束.
到這里我們就驗證了我們結構體的正確性
完結,撒花😀
最后的結果
00000000 _OB_LIST_ENTRY struc ; (sizeof=0x24, mappedto_1307)
00000000 ; XREF: _OB_HANDLE/r
00000000 callbackList _LIST_ENTRY ?
00000008 Operation dd ?
0000000C Flag dd ?
00000010 Self dd ?
00000014 Object_Type dd ?
00000018 PreOperation dd ?
0000001C PostOperation dd ?
00000020 UNKNOW1 dd ?
00000024 _OB_LIST_ENTRY ends
00000024
00000000 ; ---------------------------------------------------------------------------
00000000
00000000 _OB_HANDLE struc ; (sizeof=0x34, mappedto_1308)
00000000 Version dw ?
00000002 OperationRegistrationCount dw ?
00000004 RegistrationContext dd ?
00000008 Length1 dw ?
0000000A Length2 dw ?
0000000C buffer dd ?
00000010 CallBackList _OB_LIST_ENTRY ?
00000034 _OB_HANDLE ends
00000034
RegistrationHandle實際的結構就是上面寫的 _OB_HANDLE,然后回調鏈表的實際結構就是 _OB_LIST_ENTRY