ZaiRoN

  1. Some notes on how to find out hidden callbacks

    Can I blog an incomplete solution or an incomplete analysis? Why not! Thatís 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 donít know if they have solved their problems right nowÖ). Strange that no one else noticed it yet.
    Anyway, we wonít 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 donít know when Iíll be able to end this project so I decided to spread out some of my notes.

    Itís 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 whatís behind functions like CmRegisterCallback, and others. Then, youíll have something to work on. Iíll 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 youíll 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 (0◊190/4Ö)). Now that I have this information Iím going to try to explain the entire procedure in detail. The algorithm could be divided into 5 big blocks:

    1: try to allocate 0◊38 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, itís only a call to ExAllocatePoolWithTag.

    To understand point #2 you have to see whatís going on behind ExAllocateCallBack procedure. Letís 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 thereís 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
    ...
    Categories
    Uncategorized