Coder Social home page Coder Social logo

mandibule's Introduction

mandibule: linux elf injector

intro

Mandibule is a program that allows to inject an ELF file into a remote process.

Both static & dynamically linked programs can be targetted. Supported archs:

  • x86
  • x86_64
  • arm
  • aarch64

Example usage: https://asciinema.org/a/KkOHP2Jef0E6wViPCglkXLRcV

@ixty 2018

installation

git clone https://github.com/ixty/mandibule
make

usage

usage: ./mandibule <elf> [-a arg]* [-e env]* [-m addr] <pid>

loads an ELF binary into a remote process.

arguments:
    - elf: path of binary to inject into <pid>
    - pid: pid of process to inject into

options:
    -a arg: argument to send to injected program - can be repeated
    -e env: environment value sent to injected program - can be repeated
    -m mem: base address at which program is loaded in remote process, default=AUTO

Note: order of arguments must be respected (no getopt sry)

example run

$ make x86_64

# in shell 1
$ ./target
> started.
......

# in shell 2
$ ./mandibule ./toinject `pidof target`
> target pid: 6266
> arg[0]: ./toinject
> args size: 51
> shellcode injection addr: 0x7f0f4719c000 size: 0x5000 (available: 0x195000)
> success attaching to pid 6266
> backed up mem & registers
> injected shellcode at 0x7f0f4719c000
> running shellcode..
> shellcode executed!
> restored memory & registers
> successfully injected shellcode into pid 6266

# back to shell 1
...
> target pid: 6266
> arg[0]: ./toinject
> args size: 51
> auxv len: 304
> auto-detected manual mapping address 0x55f6e1000000
> mapping './toinject' into memory at 0x55f6e1000000
> reading elf file './toinject'
> loading elf at: 0x55f6e1000000
> load segment addr 0x55f6e1000000 len 0x1000 => 0x55f6e1000000
> load segment addr 0x55f6e1200dd8 len 0x1000 => 0x55f6e1200000
> max vaddr 0x55f6e1212000
> loading interp '/lib64/ld-linux-x86-64.so.2'
> reading elf file '/lib64/ld-linux-x86-64.so.2'
> loading elf at: 0x55f6e1212000
> load segment addr 0x55f6e1212000 len 0x23000 => 0x55f6e1212000
> load segment addr 0x55f6e1435bc0 len 0x2000 => 0x55f6e1435000
> max vaddr 0x55f6e1448000
> eop 0x55f6e1212c20
> setting auxv
> set auxv[3] to 0x55f6e1000040
> set auxv[5] to 0x9
> set auxv[9] to 0x55f6e10006e0
> set auxv[7] to 0x55f6e1000000
> eop 0x55f6e1212c20
> starting ...

# oh hai from pid 6266
# arg[0]: ./toinject
# :)
# :)
# :)
# bye!
...........

injection proces

mandibule has no dependency (not even libc) and is compiled with pie and fpie in order to make it fully relocatable.

This way we can copy mandibule's code into any process and it will be able to run as if it were a totally independant shellcode.

Here is how mandibule works:

  • find an executable section in target process with enough space (~5Kb)
  • attach to process with ptrace
  • backup register state
  • backup executable section
  • inject mandibule code into executable section
  • let the execution resume on our own injected code
  • wait until exit() is called by the remote process
  • restore registers & memory
  • detach from process

In the remote process, mandibule does the following:

  • read arguments, environment variables and other options from its own memory
  • find a suitable memory address to load the target elf file if needed
  • manually load & map the elf file into memory using only syscalls
  • load the ld-linux interpreter if needed
  • call the main function of the manually loaded binary

tested on

  • x86: Linux debian 4.9.0-3-amd64 #1 SMP Debian 4.9.30-2+deb9u5 (2017-09-19) x86_64 GNU/Linux
  • x86_64: Linux debian 4.9.0-3-amd64 #1 SMP Debian 4.9.30-2+deb9u5 (2017-09-19) x86_64 GNU/Linux
  • arm64: Linux buildroot 4.13.6 #1 SMP Sat Mar 3 16:40:18 UTC 2018 aarch64 GNU/Linux
  • arm: Linux buildroot 4.11.3 #1 SMP Sun Mar 4 02:36:56 UTC 2018 armv7l GNU/Linux

arm & arm64 where tested using arm_now by @chaignc to easily spawn qemu vms with the desired arch.

mandibule's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

mandibule's Issues

"base_seg" variable is incorrect

for(int i=0; i<ehdr->e_phnum; i++)
{
    phdr = (elf_phdr *)(elf_buf + ehdr->e_phoff + i * ehdr->e_phentsize);
    // printf("> seg[%d] load: %d addr 0x%llx size 0x%llx\n", i, phdr->p_type == PT_LOAD, phdr->p_vaddr, phdr->p_memsz);
    if(phdr->p_type == PT_LOAD && load_segment(elf_buf, ehdr, phdr, base_off))
        return -1;
    if(!base_seg)
        base_seg = phdr->p_vaddr;
    base_next = phdr->p_vaddr + phdr->p_memsz > base_next ? phdr->p_vaddr + phdr->p_memsz : base_next;
}

ALIGN_PAGE_DOWN(base_seg);

if(ehdr->e_type == ET_DYN)
    base_seg += base_off;
// printf("> program base: 0x%llx\n", base_seg);

Now the code to get the base address is written like this, get the non-zero offset of any segment (phdr->p_vaddr). But when I compile the program with the "-static-pie" option, the section information is as follows.

  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  LOAD           0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x00000000000e6965 0x00000000000e6965  R E    0x200000
  LOAD           0x00000000000e7188 0x00000000002e7188 0x00000000002e7188
                 0x0000000000007360 0x000000000000b550  RW     0x200000
  DYNAMIC        0x00000000000ed8e8 0x00000000002ed8e8 0x00000000002ed8e8
                 0x0000000000000170 0x0000000000000170  RW     0x8
  TLS            0x00000000000e7188 0x00000000002e7188 0x00000000002e7188
                 0x0000000000000000 0x0000000000000010  R      0x8
  GNU_EH_FRAME   0x00000000000bb650 0x00000000000bb650 0x00000000000bb650
                 0x000000000000697c 0x000000000000697c  R      0x4
  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000000 0x0000000000000000  RW     0x10
  GNU_RELRO      0x00000000000e7188 0x00000000002e7188 0x00000000002e7188
                 0x0000000000006e78 0x0000000000006e78  R      0x1

You can see that the first segment is LOAD, but its virtual address is 0, then the program is judging that "if (!base_seg)" will enter the branch, so the final value of "base_seg" is "2e7188", the second Load segment Virtual address.
"base_seg" should take the virtual address of the first LOAD segment, whether it is zero or not.

int base_set = 0;

for(int i=0; i<ehdr->e_phnum; i++)
{
    phdr = (elf_phdr *)(elf_buf + ehdr->e_phoff + i * ehdr->e_phentsize);
    // printf("> seg[%d] load: %d addr 0x%llx size 0x%llx\n", i, phdr->p_type == PT_LOAD, phdr->p_vaddr, phdr->p_memsz);
    if(phdr->p_type == PT_LOAD)
    {
        if(!base_set)
        {
            base_set = 1;
            base_seg = phdr->p_vaddr;
        }

        if (load_segment(elf_buf, ehdr, phdr, base_off))
            return -1;
    }

    base_next = phdr->p_vaddr + phdr->p_memsz > base_next ? phdr->p_vaddr + phdr->p_memsz : base_next;
}

/usr/include/unistd.h:603:13: error: conflicting types for '_exit'

Good work on the method for injection with fPIE compilation ELF!

I am not so sure why I have problem with the build, as per seen in the the title or in the error snip below.
It seems that the macro (for arch recognition) is having a problem or icrt_syscall.h has been conflicted with /usr/include/unistd.h ?
If you would be kind to upload the mandiblue you compiled for the both architecture in the repo or onto virus total etc, it would be very helpful. I am onto reproduction the method your described.
I use Debian for the purpose, and doing plenty of build on the other apps without problem.. Thank you.

On x32:

$ make x86
rm -rf mandibule target toinject
cc -D_GNU_SOURCE -std=gnu99 -static-libgcc -lgcc -I icrt/ -I code/ -fno-common -fno-stack-protector -fomit-frame-pointer -fno-exceptions -fno-asynchronous-unwind-tables -fno-unwind-tables -pie -fPIE -fno-builtin -nostdlib -m32 -o mandibule mandibule.c
In file included from /usr/include/i386-linux-gnu/sys/user.h:25:0,
                 from code/ptinject.h:15,
                 from mandibule.c:28:
/usr/include/unistd.h:603:13: error: conflicting types for '_exit'
 extern void _exit (int __status) __attribute__ ((__noreturn__));
             ^
In file included from icrt/icrt.h:15:0,
                 from mandibule.c:25:
icrt/icrt_syscall.h:169:25: note: previous definition of '_exit' was here
 _syscall1(SYS_exit,     _exit,      int,        int);
                         ^
icrt/icrt_syscall.h:149:23: note: in definition of macro '_syscall1'
 static inline rettype sys_name(t1 a1)                                       \
                       ^
Makefile:25: recipe for target 'x86' failed
make: *** [x86] Error 1

On x64:

$ make x86_64
rm -rf mandibule target toinject
cc -D_GNU_SOURCE -std=gnu99 -static-libgcc -lgcc -I icrt/ -I code/ -fno-common -fno-stack-protector -fomit-frame-pointer -fno-exceptions -fno-asynchronous-unwind-tables -fno-unwind-tables -pie -fPIE -fno-builtin -nostdlib -o mandibule mandibule.c
In file included from /usr/include/x86_64-linux-gnu/sys/user.h:25:0,
                 from code/ptinject.h:15,
                 from mandibule.c:28:
/usr/include/unistd.h:603:13: error: conflicting types for '_exit'
 extern void _exit (int __status) __attribute__ ((__noreturn__));
             ^
In file included from icrt/icrt.h:15:0,
                 from mandibule.c:25:
icrt/icrt_syscall.h:169:25: note: previous definition of '_exit' was here
 _syscall1(SYS_exit,     _exit,      int,        int);
                         ^
icrt/icrt_syscall.h:149:23: note: in definition of macro '_syscall1'
 static inline rettype sys_name(t1 a1)                                       \
                       ^
Makefile:32: recipe for target 'x86_64' failed
make: *** [x86_64] Error 1

end of issue

mandibule_beg function address is exactly 0x10000

build by gcc (Debian 8.3.0-6) 8.3.0 on debian 10, the function address is exactly 0x1000, so there is no space to store parameters.
Is there any other way to specify the function address? Or leave space between the text section and the "mandibule_beg" function?
Many thanks!
image

fake stack argv ptr incorrect

    FSTACK_PUSH_LONG(sp, 0);
    for(int i=0; i<ac; i++)
        FSTACK_PUSH_LONG(sp, (unsigned long)av_0 + (ac - i - 1) * sizeof(unsigned long));
    // argc
    FSTACK_PUSH_LONG(sp, ac);

"(unsigned long)av_0 + (ac - i - 1) * sizeof(unsigned long)" not the correct string pointer, each parameter has a different length.
The correct pointer should be temporarily saved after the parameter is pushed onto the stack.
commit: Hackerl@0ba0a46

program stuck at "starting ..."

For my last issue: #2
I commit the patch: Hackerl@92de629

unsigned long mandibule_beg(int aligned)
{
    if(!aligned)
        return (unsigned long)mandibule_beg;

    unsigned long align_size = (unsigned long)mandibule_beg % 0x1000;

    return (unsigned long)mandibule_beg - (align_size == 0 ? 0x1000 : align_size);
}

After successful compilation, it can run successfully, but the process is stuck.
image
I modified the source code to output detailed logs, and found that the program was stuck in a system call.
stuck log:

./mandibule $(pwd)/toinject $(pidof target)
> syscall: 10
> syscall: 9
> syscall: 9
> syscall: 9
> syscall: 9
> syscall: 9
> syscall: 9
> syscall: 9
> syscall: 9
> syscall: 3
> syscall: 3
> syscall: 158
> syscall: 158
> syscall: 18446744073709551615
> syscall: 18446744073709551615
> syscall: 18446744073709551615
> syscall: 18446744073709551615
> syscall: 18446744073709551615
> syscall: 18446744073709551615
> syscall: 18446744073709551615
> syscall: 18446744073709551615

after syscall 158, the output of syscall will be messy, so I guess there is a problem with the system call 158.
What's interesting is that I shortened the name of the injected program "toinject" a bit, and renamed it to "toinj" and it could run successfully.
success log:

./mandibule $(pwd)/toinj $(pidof target)
> syscall: 10
> syscall: 9
> syscall: 9
> syscall: 9
> syscall: 9
> syscall: 9
> syscall: 9
> syscall: 9
> syscall: 9
> syscall: 3
> syscall: 3
> syscall: 158
> syscall: 158
> syscall: 10
> syscall: 10
> syscall: 10
> syscall: 10
> syscall: 10
> syscall: 10
> syscall: 11
> syscall: 11
> syscall: 39
> syscall: 39
> syscall: 5
> syscall: 5
> syscall: 12
> syscall: 12
> syscall: 12
> syscall: 12
> syscall: 1
> syscall: 1
> syscall: 1
> syscall: 1
> syscall: 1
> syscall: 1
> syscall: 35
> syscall: 35
> syscall: 1
> syscall: 1
> syscall: 35
> syscall: 35
> syscall: 1
> syscall: 1
> syscall: 35
> syscall: 35
> syscall: 1
> syscall: 1
> syscall: 231
> shellcode executed!
> restored memory & registers
> successfully injected shellcode into pid 15313

Can continue to run after the system call 158, I am going to debug further, I will add information here later.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.