My Hacker Blog

Struggles with Threadless Injection

Turns out I did not fully understand CCobs ThreadlessInject when trying to implement it into my stitty InjectLoader. A quick overview of CCob's ThreadlessInject:


  original caller                                                    
 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                                                  
 β”‚                β”‚        hooked function                           
 β”‚                β”‚       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”       injected          
 β”‚ call ──────────┼───────► mov rax, lpInj◄┼───┐  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” 
 β”‚                β”‚       β”‚ jmp rax ───────┼───┼──► mov rax, hookedβ”‚ 
 β”‚                β”‚       β”‚                β”‚   β”‚  β”‚ push rax       β”‚ 
 β”‚                β”‚       β”‚                β”‚   β”‚  β”‚                β”‚ 
 β”‚                β”‚       β”‚                β”‚   β”‚  β”‚ ;do stuff      β”‚ 
 β”‚                β”‚       β”‚                β”‚   β”‚  β”‚ ;call shellcodeβ”‚ 
 β”‚                β”‚       β”‚                β”‚   β”‚  β”‚ ;restore hookedβ”‚ 
 β”‚                β”‚       β”‚                β”‚   β”‚  β”‚                β”‚ 
 β”‚                β”‚       β”‚                β”‚   β”‚  β”‚ pop rax; hookedβ”‚ 
 β”‚                β”‚       β”‚                β”‚   └──┼ jmp rax        β”‚ 
 β”‚                β”‚       β”‚                β”‚      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ 
 β”‚                β”‚       β”‚                β”‚                         
 β”‚                β”‚       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                         
 β”‚                β”‚                                                  
 β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                                                  

The mov rax, lpInj and jmp rax instructions are inserted by a remote injector, the "injected" code gets executed the next time this process calls the hooked function. The injected code does your malware stuff, restores the hooked function and returns to it. From the POV of the injected process everything should stay the same, just that it also executed the injected code.

What are volatile registers anyway?

I wrongly assumed volatile registers can be freely changed. Or I just read Microsoft's documentaion wrong.

... are considered volatile, or potentially changed by a callee on return.

Consider volatile registers destroyed on function calls ...

What I understood was that I could freely change volatile registers in injected code, and continue with the original function as if nothing happened. Obviously I cannot, not without risking a crash. This is because the volatile registers rcx,rdx,r8,r9 are used in x64_86 for function arguments. Meaning that at the start of the hooked function, the caller of the hooked function placed specific values into these 4 regs. Changing these 4 regs in the shellcode and returning back to the hooked function changes behaviour (or a crash).

Also, even if my injected shellcode would not change these 4, rcx,rdx,r8,r9, any function called by the injected shellcode may change them (as well as the 3 other volatile regs). Below is a depiction of my self-inflicted-not-reading-the-fucking-manual struggles:


  original caller                                                                             
 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                                                                           
 β”‚                β”‚        hooked                                                             
 β”‚                β”‚       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”       injected                                   
 β”‚ call ──────────┼───────► mov rax, lpInj◄┼───┐  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                          
 β”‚                β”‚       β”‚ jmp rax ───────┼───┼──► mov rax, hookedβ”‚                          
 β”‚                β”‚       β”‚                β”‚   β”‚  β”‚ push rax       β”‚                          
 β”‚                β”‚       β”‚                β”‚   β”‚  β”‚                β”‚        any syscall       
 β”‚                β”‚       β”‚                β”‚   β”‚  β”‚                β”‚       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” 
 β”‚                β”‚       β”‚                β”‚   β”‚  β”‚ syscall ───────┼───────► ;changes regs  β”‚ 
 β”‚                β”‚       β”‚                β”‚   β”‚  β”‚ ; other stuff ◄┼───────┬ ret; lol       β”‚ 
 β”‚                β”‚       β”‚                β”‚   β”‚  β”‚                β”‚       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ 
 β”‚                β”‚       β”‚                β”‚   β”‚  β”‚ pop rax; hookedβ”‚                          
 β”‚                β”‚       β”‚                β”‚   └──┼ jmp rax        β”‚                          
 β”‚                β”‚       β”‚                β”‚      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                          
 β”‚                β”‚       β”‚                β”‚                                                  
 β”‚                β”‚       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                                                  
 β”‚                β”‚                                                                           
 β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                                                                           

So I cannot have the non-volatile regs changed when re-entering the hooked function. I can also not have the function argument regs changed when re-entering the hooked function (unless I only use functions with <4 args, but I want it to stay universal). And as a bonus, I also have to keep the stack balanced when re-entering, unless I want to crash the proc as soon as hooked returns.

A solution?

With all the above in mind, there is only rax,r10,r11 that my injected code can freely change and re-enter hooked. Also, any syscall in injected might change these 3 regs, placing further restrictions how I can handle my registers during the injected shellcode. Nevertheless, I may have found a working generic prototype, see below:

                           must be restored                                                    
                           when returned to        injected may only change                    
  original caller          from inj.               rax,r10,r11 when returning                  
 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                                to hooked                                   
 β”‚                β”‚        hooked                                                              
 β”‚                β”‚       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”       injected                                    
 β”‚ call ──────────┼───────► mov rax, lpInj◄┼───┐  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                           
 β”‚                β”‚       β”‚ jmp rax ───────┼───┼──► mov rax, hookedβ”‚                           
 β”‚                β”‚       β”‚                β”‚   β”‚  β”‚ push rax       β”‚                           
 β”‚                β”‚       β”‚                β”‚   β”‚  β”‚ ; other stuff  β”‚        any syscall        
 β”‚                β”‚       β”‚                β”‚   β”‚  β”‚                β”‚       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  
 β”‚                β”‚       β”‚                β”‚   β”‚  β”‚ syscall ───────┼───────►                β”‚  
 β”‚                β”‚       β”‚                β”‚   β”‚  β”‚ ; other stuff ◄┼───────┬─               β”‚  
 β”‚                β”‚       β”‚                β”‚   β”‚  β”‚                β”‚       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  
 β”‚                β”‚       β”‚                β”‚   β”‚  β”‚ pop rax; hookedβ”‚        volatile regs:     
 β”‚                β”‚       β”‚                β”‚   └──┼ jmp rax        β”‚         injected code must
 β”‚                β”‚       β”‚                β”‚      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜         assume they change
 β”‚                β”‚       β”‚                β”‚                                                   
 β”‚                β”‚       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜       volatile regs:           non-volatile regs: 
 β”‚                β”‚                                 expected to change       not changed       
 β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜        x64 call regs:           by the syscalls                            
                            set by og caller,                                                  
  volatile regs:            expected to have       non-volatile regs:                          
   expect hooked func       a certain value         preserved at syscall                       
   to change them           at hooked entry                                                    
                            -> rcx,rdx,r8,r9                                                   
  non-volatile regs:           (ignoring xmm)                                                  
   expect hooked func       -> injected must                                                   
   to preserve them            preserve them                                                   

                           other vol. regs:                                                    
                            can be changed by                                                  
                            injected code                                                      
                            -> rax,r10,r11                                                     

                           non-volatile regs:                                                  
                            must be preserved for                                              
                            original caller                                                    

Bonus

When you wouldn't even dare to overwrite rax, this might be a solution to preserve all registers:

; hooked function
push rax
mov rax, lpInj
jmp rax
; ...
; injector
; old rax is on top of stack
mov rax, hooked
push rax       ; safe return addr -> stack: retAddr,oldRax

; safe all other regs
; restore hooked function
; do other stuff

pop rax        ; dummy pop, this was the retAddr
pop rax        ; real rax restored
jmp [rsp-10h]  ; return to hooked

About Me

GitHub

LinkedIn

/dev/null