找回密码
 立即注册
  • QQ空间
  • 回复
  • 收藏

linux下进程注入姿势

admin 2019-12-7 17:31 174人围观 Linux相关


最近在开发一些东西的时候遇到了一些比较奇特的需求用到了该姿势,就顺势学习了一波,在一些情景下,我们需要无进程启动一些程序,此时线程注入就非常好用了,此处介绍下linux下的简单线程注入姿势

适用场景


  • 无进程运行程序

  • 动态打补丁(替换函数)

  • 调试器,逆向软件开发

  • 程序辅助器?可能dll注入更多些2333

一般目标


  • 常驻服务程序

  • 特定目标文件

Tips


  • 在没有特殊手段的情况下,我们是无法用两个调试器同时调试同一个进程的

  • 我们只有在拥有对该进程的相应权限的时候才可以注入进程

注入手法


我们都知道在windows下我们可以用dll注入来进行线程注入,那么在Linux下,我们也可以用类似dll注入的方法,即共享库so注入来实现功能

那么我们在linux下该如何进行so注入呢?

0x00 LD_PRELOAD


在载入so文件的时候,init初始化是先于main函数运行的,因此我们可以通过LDPRELOAD来载入一个写有init_的.so库文件例如
    //myso.so#include<stdio.h>#include<dlfcn.h>
    void _init(){printf("inject success!\n");}/*gcc -fPIC -shared myso.c -c -o myso.old -shared -ldl myso.o -o myso.so*/


    和我们想要注入的程序
      //test.c#include<stdlib.h>
      intmain(){printf("This is my program!");}//gcc test.c -o test


      然后我们编译一下
        gcc-fPIC-sharedmyso.c-c-omyso.old-shared-ldlmyso.o-omyso.sogcctest.c-otest


        之后再运行程序时输入
          LD_PRELOAD=./myso.so ./test
          结果:

            ~/inject/Ld : LD_PRELOAD=./myso.so ./testinjectsuccess!Thisis my program!

            想要取消也很简单
              unset LD_PRELOAD

              0x01 ld.so.preload


              我们通过篡改预处理文件就可以达到我们想要的效果

              我们将我们的恶意so文件写入ld.so.preload即可

              0x02 strace


              strace常用来跟踪进程执行时的系统调用和所接收的信号,因此我们也可以通过strace来注入进程,举个例子:
                strace -f -p pid -o /tmp/.log -e trace=read,write -s 1024

                这里也解释一下所用到参数的意思-f 指可以追踪进程fork出来的进程-p 指定进程pid号-o 将strace的输出写入指定文件-e trace=read,write 跟踪进程读写的系统调用-s 指定输出的字符串的最大长度.默认为32

                关于更多参数,可以到strace 跟踪进程中的系统调用处查询

                0x2 ptrace


                利用ptrace注入的过程总结一下就是:

                1. 获取内存读写权限

                2. 使用dlopen(libcdlopenmode)函数载入so文件

                3. 调用so中的函数

                获取进程内存读写权限


                ptrace提供了一种使父进程得以监视和控制其它进程的方式,它还能够改变子进程中的寄存器和内核映像,因而可以实现断点调试和系统调用的跟踪(我们的注入就是基于这个

                ptrace函数定义如下:
                  #include<sys/ptrace.h>
                  longptrace(enum __ptrace_request request, pid_t pid,void *addr, void *data);
                  其中第一个参数可以选择

                    PTRACE_TRACEME,PTRACE_ATTACH,PTRACE_SEIZE,PTRACE_INTERRUPT,PTRACE_KILL,PTRACE_PEEKTEXT,PTRACE_PEEKDATAPTRACE_PEEKUSERPTRACE_POKETEXT ,PTRACE_POKEDATAPTRACE_POKEUSERPTRACE_GETREGS,PTRACE_GETFPREGS,PTRACE_SETREGS,PTRACE_SETFPREGSPTRACE_SETREGSET (sinceLinux 2.6.34)PTRACE_GETSIGINFO (sinceLinux 2.3.99-pre6)PTRACE_SETSIGINFO (sinceLinux 2.3.99-pre6)PTRACE_PEEKSIGINFO (sinceLinux 3.10)PTRACE_GETSIGMASK (sinceLinux 3.11)PTRACE_SETSIGMASK (sinceLinux 3.11)PTRACE_CONTPTRACE_SYSCALL,PTRACE_SINGLESTEPPTRACE_DETACHetc.
                    更详细的内容可以在这里(http://man7.org/linux/man-pages/man2/ptrace.2.html)或者这里(https://linux.die.net/man/2/ptrace) 查询

                    当然我们也可以直接打开我们的terminal,然后 man ptrace

                    这里我们只举例几个我们会用到的几个参数
                      PTRACE_ATTACH,PTRACE_TRACEME //关联到进程PTRACE_CONT //让子程序继续运行PTRACE_PEEKTEXT, PTRACE_PEEKDATA, PTRACE_PEEKUSR //读取指定进程的内存PTRACE_POKETEXT,PTRACE_POKEDATA, PTRACE_POKEUSR //写进指定进程的内存PTRACE_GETREGS //读取寄存器详细信息PTRACE_SETREGS //设置当前进程的一组处理器寄存器值PTRACE_DETACH, PTRACE_KILL //脱离进程


                      那么我们现在暂时拥有了内存的读写权限,现在就该向我们的进程中注入代码了

                      获得libcdlopenmode函数地址


                      首先我们需要了解两个个函数,dlopen()和dlsym(),函数定义如下:
                        #include<dlfcn.h>void * dlopen( constchar * pathname, int mode);void* dlsym(void* handler, constchar* symbol);
                        这个函数的作用就是打开一个动态链接库,并且返回动态链接库的句柄

                        mode参数是so文件的打开方式我们这里只用到RTLD_LAZY,即在dlopen返回前,对于动态库中的未定义的符号不执行解析,也就是延迟绑定,关于其他的参数各位师傅感兴趣的话可以自行了解

                        而通过dlsym()函数我们可以从句柄中取出我们所需要用的函数来调用

                        那么即然我们已经获取了进程内存的读写权限,那么我们现在就直接开始调用dlopen函数搞事吧:)

                        这里要提及一句,dlopen并不是每一个进程都有的,但是libcdlopenmode是默认包含的,所以在dlopen不能使用的时候,我们可以选择调用libcdlopenmode

                        该函数定义如下:
                          void * __libc_dlopen_mode (constchar *name, int mode)
                          为便于通用性,这里我们就拿libcdlopen_mode作为实例来进行进程注入

                          那么现在我们确定了需要使用的函数,下一步要做的就是确定该函数在进程中的内存地址,这里有两种方法,这里都介绍一下

                          1. cat /proc/$pid/maps 得到基址之后根据偏移来得到

                          2. 用类似于pwn中常用的ret2dl-resolve技巧来寻找

                          我们再来看一下elf是如何通过重定位来找到函数地址的,这里贴一段来自一个师傅对最核心的代码和分析(具体来源有点找不到了)
                            _dl_fixup(struct link_map *l, ElfW(Word) reloc_arg){// 首先通过参数reloc_arg计算重定位入口,这里的JMPREL即.rel.plt,reloc_offset即reloc_argconst PLTREL *const reloc = (const void *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_offset);// 然后通过reloc->r_info找到.dynsym中对应的条目const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)];// 这里还会检查reloc->r_info的最低位是不是R_386_JUMP_SLOT=7 assert (ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT);// 接着通过strtab+sym->st_name找到符号表字符串,result为libc基地址 result = _dl_lookup_symbol_x (strtab + sym->st_name, l, &sym, l->l_scope, version, ELF_RTYPE_CLASS_PLT, flags, NULL);// value为libc基址加上要解析函数的偏移地址,也即实际地址 value = DL_FIXUP_MAKE_VALUE (result, sym ? (LOOKUP_VALUE_ADDRESS (result) + sym->st_value) : 0);// 最后把value写入相应的GOT表条目中return elf_machine_fixup_plt (l, result, reloc, rel_addr, value);}

                            具体实现过程师傅们可以看一下我之前的关于ret2dl的文章或者其他师傅的一些文章
                              https://bbs.pediy.com/thread-227034.htmhttps://xz.aliyun.com/t/6471https://xz.aliyun.com/t/4473


                              当然,此处安利<<程序员的自我修养---链接、装载与库>>

                              那么现在我们就可以通过类似的方法来获取_libcdlopen_mode的地址啦

                              因为不是pwn题,所以我们需要做的并不是那么麻烦,在可以获得进程的内存读写权限的时候,我们只需要遍历一下linkmap和相关链表就可以完成libcdlopenmode的符号解析从而获取地址

                              此时我们需要的就是调用了

                              函数调用


                              那么我们该如何调用函数呢?如果直接修改eip指针有可能会导致程序崩坏,因此我们在这里可以寻找一片nop内存进行注入,

                              我们可以选择调用mmap函数来将我们的文件写入到进程中,之后通过干扰重定位或者注入eip指针来调用我们所需要的函数,这里看一下mmap函数的定义:
                                #include<sys/mman.h>void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);其他的参数想必大家都很熟悉,因此这里我只列出prot的一些参数选择
                                  PROT_EXEC //页内容可以被执行PROT_READ //页内容可以被读取PROT_WRITE //页可以被写入PROT_NONE //页不可访问


                                  而这个是可以通过or符号来组合选择的

                                  比如 "PROTREAD | PROTEXEC | PROT_WRITE",可以    弄出一块可读可写可执行的区域,之后完成我们所需要做的操作即可

                                  比如调用某个函数,或者替换某个函数

                                  但是如果没有特殊需求(比如某某触发条件时),只需要执行我们想要执行的程序时,更简单的方法是通过共享对象构造函数来完成,也就是
                                    __attribute __((constructor))装饰器
                                    在c++中,对于一个类我们可以通过编写构造函数来完成类中某些元素的初始化,在写好构造函数后,我们所创建的每一个对象都会自动调用构造函数来做一些初始化的操作,而共享对象构造函数也十分类似共享库可以在加载时自动调用__attribute __((constructor))装饰器来加载我们所写的代码,如:

                                      //库文件:myso.so#include<stdio.h>#include<system>void __attribute__((constructor)) test(void) { system("date");}/* gcc -fPIC -shared myso.c -c -o myso.o ld -shared -ldl myso.o -o myso.so */


                                      so文件的构造函数效果是输出时间,那么便于观看效果,我们写一个程序来检测效果(因为注入的线程会中断原本的程序流畅,因此建议另起一个线程来调用
                                        //测试文件 :test.c#include<stdio.h>#include<dlfcn.h>intmain(){ dlopen("./myso.so", RTLD_LAZY);}// gcc test.c -o test -ldl


                                        然后运行一下,运行结果:
                                          ~/inject/attribute  ./testTue Nov 1907:41:11 PST 2019


                                          成功:)

                                          这时如果有人测试LD_PRELOAD=./myso.so ./test的话,那么恭喜你,效果十分显著,具体效果师傅们可以自己试试(手动滑稽

                                          另起线程的示例代码:
                                            //myso.so pthread版#include<stdio.h>#include<unistd.h>#include<pthread.h>void* test(void* a){while (1) { system("date"); sleep(2); }}void __attribute__((constructor)) pthread_test(void) {pthread_t my_pthread; pthread_create(&my_pthread, NULL, test, NULL);}/* gcc -fPIC -shared myso.c -c -o myso.o ld -shared -ldl -lpthread myso.o -o myso.so */


                                            而如果想替换函数,这有一篇文章讲的很好,虽然有些老了
                                              https://www.freebuf.com/articles/system/6388.html
                                              最后的最后,向大家推荐几款比较好用的进程注入工具:

                                              linux-inject, saruman, vegule, cub3, vlany


                                              ----------------------------------------------------------------------------------------------------------------------
                                              我们尊重原创,也注重分享,文章来源于微信公众号:星盟安全,建议关注公众号查看原文。如若侵权请联系qter@qter.org。
                                              ----------------------------------------------------------------------------------------------------------------------

                                              鲜花

                                              握手

                                              雷人

                                              路过

                                              鸡蛋

                                              yafeilinux和他的朋友们微信公众号二维码

                                              微信公众号

                                              专注于Qt嵌入式Linux开发等。扫一扫立即关注。

                                              Qt开源社区官方QQ群二维码

                                              QQ交流群

                                              欢迎加入QQ群大家庭,一起讨论学习!

                                              我有话说......