ixty / mandibule

linux elf injector for x86 x86_64 arm arm64
309 stars 66 forks source link

Questions about environment variable "MANMAP=1" #6

Closed Hackerl closed 1 year ago

Hackerl commented 3 years ago

I want to know the purpose of this environment variable.

FSTACK_PUSH_STR(sp, "MANMAP=1");
ixty commented 3 years ago

hello, thanks for your interest in this project. i'm sorry i havent gotten back to you sooner. i havent really had the time or will to take a look into all the issues you opened, but it looks like you spent quite a bit of effort into it :)

To answer this specific question, i dont remember, but it looks like dead code, i probably used it as a debug env var the child had access to - to differenciate program output while being launched normally versus being injected.

Hackerl commented 3 years ago

Thanks for your reply. I refactored based on your project. I separated the shellcode into a separate dynamic library. The process of ptrace is placed in a separate program, so that the standard library can be used for development, and the problems I raised here have been fixed. But currently only supports x64. I pass parameters directly through registers. The shellcode also has three parts. One part calls Malloc to allocate enough memory, which can reduce the probability of multi-threaded program crashes. The second part is the elf loader you write. The third part calls free to release the requested memory. Project: https://github.com/Hackerl/pangolin In addition, I implemented an interesting python injection tool with it: https://github.com/Hackerl/pyinject

Hackerl commented 3 years ago

My English is not good, these are from Google Translate, if my words are offensive, please forgive me.

wxingda commented 3 years ago

@Hackerl 你好,看了你fork后修复的内存泄露,测试了下,内存泄露依旧存在

Hackerl commented 3 years ago

@Hackerl 你好,看了你fork后修复的内存泄露,测试了下,内存泄露依旧存在

你指的是重复进行注入,被注入的进程中发生内存泄露? 可以使用我重构的版本:https://github.com/Hackerl/pangolin 虽然在注入之后会申请一块内存作为栈,并且不会释放,这种场景用于创建新线程驻留在目标进程中,也适用于golang这种自己管理栈并且栈空间比较小的场景。 但只要不重复注入,就不会有多余的内存泄露。

wxingda commented 3 years ago

@Hackerl 你好,看了你fork后修复的内存泄露,测试了下,内存泄露依旧存在

你指的是重复进行注入,被注入的进程中发生内存泄露?

是的,每次注入完成后,target进程的内存占用就会增长一点

可以使用我重构的版本:https://github.com/Hackerl/pangolin

我测试了下,是可以注入的,但是target在退出的时候有个Segmentation fault

[root@localhost bin]# ./target 
> started.
...................> elf loader start
> target: /home/admin/code/pangolin/bin/inject
> mapping '/home/admin/code/pangolin/bin/inject' into memory at 0x1000000
> load segment addr 0x400000 len 0x1000 => 0x400000
> load segment addr 0x601db8 len 0x1000 => 0x601000
> max addr 0x1613000
> loading interp '/lib64/ld-linux-x86-64.so.2'
> mapping '/lib64/ld-linux-x86-64.so.2' into memory at 0x1613000
> load segment addr 0x1613000 len 0x29000 => 0x1613000
> load segment addr 0x183b560 len 0x2000 => 0x183b000
> max addr 0x184e000
> eop 0x1614080
> setting auxv
> set auxv[3] to 0x400040
> set auxv[4] to 0x38
> set auxv[5] to 0x9
> set auxv[9] to 0x400980
> set auxv[7] to 0x400000
> eop 0x1614080
> fake stack: 0x7ffa7a9dede0
> starting ...
# oh hai from pid 354481
# arg 0: /home/admin/code/pangolin/bin/inject
# arg 1: 1
# arg 2: 2 3
# env: MANMAP=1
# env: GS=0x0
# env: FS=0x7ffa7a9e2f40
# :)
# :)
# :)
# bye!
Segmentation fault (core dumped)

虽然在注入之后会申请一块内存作为栈,并且不会释放,这种场景用于创建新线程驻留在目标进程中,也适用于golang这种自己管理栈并且栈空间比较小的场景。 但只要不重复注入,就不会有多余的内存泄露。

如果在注入代码退出后有内存泄露,那么在生产环境上需要重复注入的场景就不适合了,我们之前使用criu的compel库进行过注入,是没有泄露的

Hackerl commented 3 years ago

@Hackerl 你好,看了你fork后修复的内存泄露,测试了下,内存泄露依旧存在

你指的是重复进行注入,被注入的进程中发生内存泄露?

是的,每次注入完成后,target进程的内存占用就会增长一点

可以使用我重构的版本:https://github.com/Hackerl/pangolin

我测试了下,是可以注入的,但是target在退出的时候有个Segmentation fault

[root@localhost bin]# ./target 
> started.
...................> elf loader start
> target: /home/admin/code/pangolin/bin/inject
> mapping '/home/admin/code/pangolin/bin/inject' into memory at 0x1000000
> load segment addr 0x400000 len 0x1000 => 0x400000
> load segment addr 0x601db8 len 0x1000 => 0x601000
> max addr 0x1613000
> loading interp '/lib64/ld-linux-x86-64.so.2'
> mapping '/lib64/ld-linux-x86-64.so.2' into memory at 0x1613000
> load segment addr 0x1613000 len 0x29000 => 0x1613000
> load segment addr 0x183b560 len 0x2000 => 0x183b000
> max addr 0x184e000
> eop 0x1614080
> setting auxv
> set auxv[3] to 0x400040
> set auxv[4] to 0x38
> set auxv[5] to 0x9
> set auxv[9] to 0x400980
> set auxv[7] to 0x400000
> eop 0x1614080
> fake stack: 0x7ffa7a9dede0
> starting ...
# oh hai from pid 354481
# arg 0: /home/admin/code/pangolin/bin/inject
# arg 1: 1
# arg 2: 2 3
# env: MANMAP=1
# env: GS=0x0
# env: FS=0x7ffa7a9e2f40
# :)
# :)
# :)
# bye!
Segmentation fault (core dumped)

虽然在注入之后会申请一块内存作为栈,并且不会释放,这种场景用于创建新线程驻留在目标进程中,也适用于golang这种自己管理栈并且栈空间比较小的场景。 但只要不重复注入,就不会有多余的内存泄露。

如果在注入代码退出后有内存泄露,那么在生产环境上需要重复注入的场景就不适合了,我们之前使用criu的compel库进行过注入,是没有泄露的

因为我的需求是注入之后驻留线程,所以肯定需要保留一块空间,为了避免重复注入,我设置了新线程名称"xxx",然后遍历进程的线程,根据/proc/pid/task/id/comm来判断是否存在线程"xxx",来判断是否已经注入。 另外能否将编译后的产出以及core dump发给我,我看看原因,根据我的使用体验来说,应该是比较稳定的。 如果你不需要驻留在进程内,可以参考commit,将"loader_main"函数还原,应该就不会有内存泄漏问题了。

void loader_main(void *ptr) {
    LOG("elf loader start");

    struct CLoaderArgs *loader_args = ptr;
    elf_loader(loader_args);

    __exit(0);
}
wxingda commented 3 years ago

bin.tar.gz 我的系统是Centos8.3.2011,内核4.18,gcc 8.3.1

Hackerl commented 3 years ago

bin.tar.gz 我的系统是Centos8.3.2011,内核4.18,gcc 8.3.1

crash原因是你的gcc默认编译出来的产物是EXEC类型:

ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x400960
  Start of program headers:          64 (bytes into file)
  Start of section headers:          11616 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         9
  Size of section headers:           64 (bytes)
  Number of section headers:         30
  Section header string table index: 29

EXEC类型的elf加载进内存的时候,地址是固定的,不会加上随机基地址。 所以target在运行时,代码段在0x400000,将inject注入进去后,由于inject也是EXEC类型,所以elf loader将其代码段加载到了0x400000,覆盖了原有的内存空间,导致crash。 要进行注入必须满足以下任意一点:

  1. 目标程序DYN,注入程序EXEC
  2. 目标程序EXEC,注入程序DYN
wxingda commented 3 years ago

EXEC类型的elf加载进内存的时候,地址是固定的,不会加上随机基地址。 所以target在运行时,代码段在0x400000,将inject注入进去后,由于inject也是EXEC类型,所以elf loader将其代码段加载到了0x400000,覆盖了原有的内存空间,导致crash。 要进行注入必须满足以下任意一点:

  1. 目标程序DYN,注入程序EXEC
  2. 目标程序EXEC,注入程序DYN

感谢解答,使用g++ -fpie -pie -o inject inject.cpp编译后问题解决,但是还是存在内存泄露的问题: 在用最新代码,即不修改"loader_main"函数的情况下,每次注入完成后,target的VIRT就会增长13732,并且mapping '/home/admin/code/pangolin/bin/inject' into memory at的点每次都会增加

........................................................> elf loader start
> target: /home/admin/code/pangolin/bin/inject
> mapping '/home/admin/code/pangolin/bin/inject' into memory at 0x1000000
> load segment addr 0x1000000 len 0x1000 => 0x1000000
> load segment addr 0x1200d98 len 0x1000 => 0x1200000
> max addr 0x1212000
> loading interp '/lib64/ld-linux-x86-64.so.2'
> mapping '/lib64/ld-linux-x86-64.so.2' into memory at 0x1212000
> load segment addr 0x1212000 len 0x29000 => 0x1212000
> load segment addr 0x143a560 len 0x2000 => 0x143a000
> max addr 0x144d000
> eop 0x1213080
> setting auxv
> set auxv[3] to 0x1000040
> set auxv[4] to 0x38
> set auxv[5] to 0x9
> set auxv[9] to 0x1000880
> set auxv[7] to 0x1000000
> eop 0x1213080
> fake stack: 0x7facb3dd0de0
> starting ...
# oh hai from pid 384857
# bye!
...........> elf loader start
> target: /home/admin/code/pangolin/bin/inject
> mapping '/home/admin/code/pangolin/bin/inject' into memory at 0x2000000
> load segment addr 0x2000000 len 0x1000 => 0x2000000
> load segment addr 0x2200d98 len 0x1000 => 0x2200000
> max addr 0x2212000
> loading interp '/lib64/ld-linux-x86-64.so.2'
> mapping '/lib64/ld-linux-x86-64.so.2' into memory at 0x2212000
> load segment addr 0x2212000 len 0x29000 => 0x2212000
> load segment addr 0x243a560 len 0x2000 => 0x243a000
> max addr 0x244d000
> eop 0x2213080
> setting auxv
> set auxv[3] to 0x2000040
> set auxv[4] to 0x38
> set auxv[5] to 0x9
> set auxv[9] to 0x2000880
> set auxv[7] to 0x2000000
> eop 0x2213080
> fake stack: 0x7facb3d97de0
> starting ...
# oh hai from pid 384857
# bye!
Hackerl commented 3 years ago

EXEC类型的elf加载进内存的时候,地址是固定的,不会加上随机基地址。 所以target在运行时,代码段在0x400000,将inject注入进去后,由于inject也是EXEC类型,所以elf loader将其代码段加载到了0x400000,覆盖了原有的内存空间,导致crash。 要进行注入必须满足以下任意一点:

  1. 目标程序DYN,注入程序EXEC
  2. 目标程序EXEC,注入程序DYN

感谢解答,使用g++ -fpie -pie -o inject inject.cpp编译后问题解决,但是还是存在内存泄露的问题: 在用最新代码,即不修改"loader_main"函数的情况下,每次注入完成后,target的VIRT就会增长13732,并且mapping '/home/admin/code/pangolin/bin/inject' into memory at的点每次都会增加

........................................................> elf loader start
> target: /home/admin/code/pangolin/bin/inject
> mapping '/home/admin/code/pangolin/bin/inject' into memory at 0x1000000
> load segment addr 0x1000000 len 0x1000 => 0x1000000
> load segment addr 0x1200d98 len 0x1000 => 0x1200000
> max addr 0x1212000
> loading interp '/lib64/ld-linux-x86-64.so.2'
> mapping '/lib64/ld-linux-x86-64.so.2' into memory at 0x1212000
> load segment addr 0x1212000 len 0x29000 => 0x1212000
> load segment addr 0x143a560 len 0x2000 => 0x143a000
> max addr 0x144d000
> eop 0x1213080
> setting auxv
> set auxv[3] to 0x1000040
> set auxv[4] to 0x38
> set auxv[5] to 0x9
> set auxv[9] to 0x1000880
> set auxv[7] to 0x1000000
> eop 0x1213080
> fake stack: 0x7facb3dd0de0
> starting ...
# oh hai from pid 384857
# bye!
...........> elf loader start
> target: /home/admin/code/pangolin/bin/inject
> mapping '/home/admin/code/pangolin/bin/inject' into memory at 0x2000000
> load segment addr 0x2000000 len 0x1000 => 0x2000000
> load segment addr 0x2200d98 len 0x1000 => 0x2200000
> max addr 0x2212000
> loading interp '/lib64/ld-linux-x86-64.so.2'
> mapping '/lib64/ld-linux-x86-64.so.2' into memory at 0x2212000
> load segment addr 0x2212000 len 0x29000 => 0x2212000
> load segment addr 0x243a560 len 0x2000 => 0x243a000
> max addr 0x244d000
> eop 0x2213080
> setting auxv
> set auxv[3] to 0x2000040
> set auxv[4] to 0x38
> set auxv[5] to 0x9
> set auxv[9] to 0x2000880
> set auxv[7] to 0x2000000
> eop 0x2213080
> fake stack: 0x7facb3d97de0
> starting ...
# oh hai from pid 384857
# bye!

符合预期,可以用我说的遍历/proc避免重复注入