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