As you my know there are two methods
1) using LD_PRELOAD, don`t work if you want to inject into already running (and perhaps even many days) process
2) ptrace. Has the following inherent disadvantages
- target process can be ptraced by somebody else
- victim program can detect ptrace
- you just want to avoid in logs something like ptrace attach of "./a.out"[PID] was attempted by "XXX"
So I developed very rough analogs of famous VirtualAllocEx/VirtualProtectEx + simple hook to hijack execution onto assembly written shellcode to call dlopen/dlsym. Currently only x86_64 supported bcs I am too lazy to rewrite this asm stub
Prerequisites
You must have root privileges and be able to build and load kernel modules. I tested code on kernel 6.8, 5.15 and probably it also can work on 4.x, not sure about more old versions
Lets start lighting the dirty details in reverse order
Asm code in target process
execution hijacking
- good old malloc_hook trick. Actually I used free_hook too in my implementation
- patch PLT/GOT. Presence of RELRO makes this method slightly harder but don`t forget that you have code in kernel - couple of additional mprotect calls can fix this
- for stdc++ there is also set_new_handler function
run kernel code in context of right process
As far as I understand the main reason that linux kernel never had analogs of VirtualAllocEx/VirtualProtectEx is that functions do_mmap/do_mprotect_pkeyalways operating with current process. Ok, not big deal - we just must find method to execute our code in context of right process. As usually there are plenty ways to do this
kprobe
probabilistic method - if you know which kernel functions most often called by target process - you can register kprobe on one of them. Obvious drawback is that your kprobe will be fired for all processes and this can lead to performance degradation
task_struct.restart_block
Unfortunately it doesn't work. I've made for my lkmem -p option to show task details, for normal processes it looks likePID 557580 at 0xffff90e2507b99c0
thread.flags: 0
flags: 400000
sched_class: 0xffffffff976f4818 - kernel!fair_sched_class
restart_block.fn: 0xffffffff960cfbc0 - kernel!do_no_restart_syscall
With patched restart_block.fn:
PID 557580 at 0xffff90e2507b99c0
thread.flags: 0
flags: 400000
sched_class: 0xffffffff976f4818 - kernel!fair_sched_class
restart_block.fn: 0xffffffffc12d9a80 - lkcd!main_horror
for unknown reason restart_block.fn was never called
preempt_notifier_register
Don`t ask me why but registered notifier was never called
task_works
PID 582439 at 0xffff90e151b8b380
thread.flags: 0
flags: 400000
works_count: 1
work[0] 0xffffffffc12d9a80 - lkcd!main_horror
process death notification
Putting all together
./test -t
pid 593502
on other console load driver and run/a.out -p 593502 -i `pwd`/test.so
dlopen 0x7f8747ea1e48 0x7f8747ea1e48
/usr/lib/x86_64-linux-gnu/libc-2.31.so base 7F34E1643000
wait
...
injected at 0x7f34e1ad2000
Now first console shows[+] greeting from injected, addr 0x7f34e1ad2000
And yet another couple of proofs:
dmesg | tail
patch 7F34E182FB70 to 7F34E1AD2000 and 7F34E1831E48 to 7F34E1AD2009
grep
7f34e1ad2000 /proc/
593502/maps
7f34e1aca000-7f34e1ad2000 r--p 00024000 103:02 20187524 /usr/lib/x86_64-linux-gnu/ld-2.31.so
7f34e1ad2000-7f34e1ad3000 r-xp 00000000 00:00 0
grep test.so
/proc/
593502/maps
7f34e1875000-7f34e1876000 r--p 00000000 08:02 4456719 /home/redp/lkcd/inject/test.so
7f34e1876000-7f34e1877000 r-xp 00001000 08:02 4456719 /home/redp/lkcd/inject/test.so
7f34e1877000-7f34e1878000 r--p 00002000 08:02 4456719 /home/redp/lkcd/inject/test.so
7f34e1878000-7f34e1879000 r--p 00002000 08:02 4456719 /home/redp/lkcd/inject/test.so
7f34e1879000-7f34e187a000 rw-p 00003000 08:02 4456719 /home/redp/lkcd/inject/test.so