Can I blog an incomplete solution or an incomplete analysis? Why not! Thats the spirit of this blog entry!
More than one year ago I started a project with Kayaker, we decided to write a tool able to show hidden callbacks. If I remember correctly the idea was born while we were putting our hands on a rootkit. In the same days I bet there were many reversers around thinking the same thing because the same tool was developed by others. As you can imagine our tool never see the light, but not because there are similar tools available online; mostly because we are two old lazy reversers!
I bet you are thinking: why the hell are you writing this stupid intro? Well, the tools I mentioned before were bugged and some months ago I discovered the same thing, they are still bugged (I dont know if they have solved their problems right now). Strange that no one else noticed it yet.
Anyway, we wont complete the tool, but with this blog post I would like to tell you some notes about our investigations. At the beginning I wanted to write a detailed and complete article about the subject, but I dont know when Ill be able to end this project so I decided to spread out some of my notes.
Its a sort of two minds work so credit goes to Kayaker too!
The idea is to try to retrieve hidden callbacks that has been installed via CmRegisterCallback, PsSetCreateProcessNotifyRoutine, PsSetCreateThreadNotifyRoutine and PsSetLoadImageNotifyRoutine. After that it would be good to deregister one or more of them.
Where to start?
First of all you have to understand whats behind functions like CmRegisterCallback, and others. Then, youll have something to work on. Ill start with CmRegisterCallback (from XP SP2), the function is used to register a RegistryCallback routine, and I think the XP version is the most simple one to fully undestand the principles behind the function. There are some differencies between XP and 7 versions, but I think youll be able to fully understand 7 structure too! Here is the disassembled function (without useless parts of course):
Code:
487E6B push 'bcMC' ; Pool Tag: "CMcb"
487E70 xor ebx, ebx
487E72 push 38h ; NumberOfBytes: 0x38
487E74 inc ebx
487E75 push ebx ; PoolType: PAGEDPOOL
487E76 call ExAllocatePoolWithTag ; ExAllocatePoolWithTag(x,x,x): allocates pool memory
487E7B mov esi, eax ; eax is the pointer to the allocated pool memory, PCM_CALLBACK_CONTEXT_BLOCK
487E7D xor edi, edi
487E7F cmp esi, edi ; Is PCM_CALLBACK_CONTEXT_BLOCK a NULL pointer?
487E81 jz cmRegisterCallback_fails ; yes: function fails...
487E87 push esi
487E88 push [ebp+Function] ; PEX_CALLBACK_FUNCTION, pointer to callback function
487E8B call _ExAllocateCallBack ; allocates and fill EX_CALLBACK_ROUTINE_BLOCK structure (more on this later...)
487E90 cmp eax, edi ; ExAllocateCallback success or not?
487E92 mov [ebp+PEX_CALLBACK_ROUTINE_BLOCK], eax ; store the pointer to the allocated pool memory
487E95 jnz short _ExAllocateCallBack_success
... ; fill CM_CALLBACK_CONTEXT_BLOCK fields
487EDC mov ebx, offset CmpCallBackVector
487EE1 mov [ebp+i], edi ; i = 0
487EE4 try_next_slot:
487EE4 push edi ; OldBlock: NULL
487EE5 push [ebp+PEX_CALLBACK_ROUTINE_BLOCK] ; NewBlock with information to add
487EE8 push ebx ; CmpCallbackVector[i]
487EE9 call _ExCompareExchangeCallBack ; try to *insert* the new callback inside CmpCallBack vector
487EEE test al, al ;check the result...
487EF0 jnz short free_slot_has_been_found ; jump if the vector has an empty space for the new entry
487EF2 add [ebp+i], 4 ; i++, increase the counter
487EF6 add ebx, 4 ; shift to the next item of the vector to check
487EF9 cmp [ebp+i], 190h ; is the end of the vector?
487F00 jb short try_next_slot ; no: try another one. yes: no free slot!
...
487F11 cmRegisterCallback_fails:
487F11 mov eax, STATUS_INSUFFICIENT_RESOURCES
487F16 end_CmRegisterCallback:
...
487F1A retn 0Ch
...
487F1D free_slot_has_been_found:
487F1D mov eax, 1
487F22 mov ecx, offset _CmpCallBackCount ; CmpCallBackCount: number of not NULL item inside the vector
487F27 xadd [ecx], eax ; there's a new callback, it increases the number of item inside the vector
487F2A xor eax, eax
487F2C jmp short end_CmRegisterCallback
As you can see the idea behind the function is really simple!
Basically, it tries to add a new entry inside a vector named CmpCallBackVector, and when the entry is correctly inserted the registration process will end with a success.
How do I know is it using a vector? The add instruction at 0x487EF6 represents a clear clue, and the cmp at 0x487EF9 reveals the fixed length of the vector (the vector has 100 items (0190/4)). Now that I have this information Im going to try to explain the entire procedure in detail. The algorithm could be divided into 5 big blocks:
1: try to allocate 038 bytes for a structure named CM_CALLBACK_CONTEXT_BLOCK
2: try to allocate 0x0C bytes for a structure named EX_CALLBACK_ROUTINE_BLOCK
3: fill CM_CALLBACK_CONTEXT_BLOCK fields
4: look for an empty slot, insert a sort of PEX_CALLBACK_ROUTINE_BLOCK in it and update CmpCallBackCount
5: notify success or error and exit
Point #1 is pretty simple to understand, its only a call to ExAllocatePoolWithTag.
To understand point #2 you have to see whats going on behind ExAllocateCallBack procedure. Lets start taking a look at it:
Code:
52AB35 push 'brbC' ; Pool Tag: Cbrb
52AB3A push 0Ch ; NumberOfBytes: 0x0C
52AB3C push 1 ; PoolType: PAGED_POOL
52AB3E call ExAllocatePoolWithTag ; alloc a EX_CALLBACK_ROUTINE_BLOCK structure
52AB43 test eax, eax ; ExAllocatePoolWithTag success or not?
52AB45 jz short _ExAllocateCallBack_fails
52AB47 mov ecx, [ebp+_pex_callback_function] ; pointer to callback function (PEX_CALLBACK_FUNCTION)
52AB4A and dword ptr [eax], 0 ; 1 field: 0
52AB4D mov [eax+4], ecx ; 2 field: _pex_callback_function
52AB50 mov ecx, [ebp+_pool_allocated_memory] ; PCM_CALLBACK_CONTEXT_BLOCK
52AB53 mov [eax+8], ecx ; 3 field: _pcm_callback_context_block
52AB56 _ExAllocateCallBack_fails:
...
The procedure is used to allocate and fill a special structure:
Code:
typedef struct _EX_CALLBACK_ROUTINE_BLOCK
{
EX_RUNDOWN_REF RundownProtect;
PEX_CALLBACK_FUNCTION Function;
PCM_CALLBACK_CONTEXT_BLOCK Context;
} EX_CALLBACK_ROUTINE_BLOCK, *PEX_CALLBACK_ROUTINE_BLOCK;
As you can see from the lines above the first field has been setted to 0 while the other fields are filled with two pointers: the function to register and the context containing info about the callback.
While point #3 is just a series of mov instructions used to fill CM_CALLBACK_ROUTINE_BLOCK structure, point #4 gives some usefull information to us: CmpCallBackVector has 100 elements and this part of code is used to scan the entire vector until an empty element is found. A failure leads us to a non-registration of the callback. What happens when theres a empty slot inside the vector? The new entry will be added inside the vector. Most of the job is done by the function named ExCompareExchangeCallBack, here is the core of the function:
Code:
52AB81 mov eax, [ebp+CmpCallbackVector] ; vector at the current position
52AB84 mov ebx, [eax] ; ebx is a PEX_CALLBACK_ROUTINE_BLOCK, the item could be NULL or not
52AB86 mov eax, ebx
52AB88 xor eax, [ebp+OldBlock] ; OldBlock is NULL for a registration process
52AB8B mov [ebp+current_pex_callback_routine_block], ebx
52AB8E cmp eax, 7 ; check used to see if the current item is NULL or not
52AB91 ja short loc_52ABB5 ; jump if not NULL
52AB93 test esi, esi ; is NewBlock NULL?
52AB95 jz short loc_52ABA1 ; jump if it's NULL
52AB97 mov eax, esi ; esi, NewBlock pointer (changed...)
52AB99 or eax, 7 ; PAY ATTENTION HERE: or 7 !?!
52AB9C mov [ebp+NewBlock], eax ; change NewBlock pointer: NewBlock = NewBlock OR 7
52AB9F jmp short loc_52ABA5
...
52ABA5 mov eax, [ebp+var_4] ; here if CmpCallbackVector's item is null
52ABA8 mov ecx, [ebp+CmpCallbackVector] ; current empty slot
52ABAB mov edx, [ebp+NewBlock] ; new pointer to insert
52ABAE cmpxchg [ecx], edx ; insert the new pointer inside the empty slot!
52ABB1 cmp eax, ebx
...