Linux逆向之hook&注入
2019-12-07 11:59:54 Author: xz.aliyun.com(查看原文) 阅读量:393 收藏

简单来说,hook也就是我们常说的钩子,以替换的方式把改变程序中原有的函数功能,而注入,则更偏向于插入自定义函数/代码,代码注入一般是一次性的,而Hook劫持是比较稳定持久的

利用LD_PRELOAD自定义加载so

​ 正常情况下, Linux 动态加载器ld-linux(见 man 手册 ld-linux(8)) 会搜寻并装载程序所需的共享链接库文件, 而LD_PRELOAD是一个可选的环境变量, 包含一个或多个指向共享链接库文件的路径. 加载器会先于 C 语言运行库之前载入LD_PRELOAD指定的共享链接库,也就是所谓的预装载 preload

做个简单的演示

#include <stdio.h>
#include <string.h>

int main(int argc, char const *argv[])
{
    puts("welcome!");
    sleep(1);
    char *ptr = malloc(0x100);
    puts("what's your name:");
    read(0,ptr,0x20);

    printf("nice to meet you,%s\n", ptr);

    return 0;
}

这个是我们的目标程序target,编译gcc ./target.c -o target

#include <stdio.h>
int sleep(int t)
{
    puts("your sleep is hook by me!");
}

这个是要用于制作so文件的hook1.c

编译生成so:gcc -fPIC --shared hook1.c -o hook1.so

然后进行hook

LD_PRELOAD=./hook1.so ./target

可以看到sleep函数已经被替换成功了,这就是简单的hook演示,但这种东西似乎并没有什么卵用,就跟给程序打个patch一样

因此这里演示一个稍微有点卵用的东西,如果我们想统计某个函数在整个程序运行过程中运行了几次,每次运行的相关数据情况等等,那么hook就能派上一点用场

修改一下我们的target程序

#include <stdio.h>
#include <string.h>

void function()
{
    for (int i = 0; i < 10; ++i)
    {
        sleep(1);
    }

    puts("good bye~");
}

int main(int argc, char const *argv[])
{
    puts("welcome!");
    sleep(1);
    char *ptr = malloc(0x100);
    puts("what's your name:");
    read(0,ptr,0x20);
    printf("nice to meet you,%s\n", ptr);
    function();
    return 0;
}

然后hook2.c如下

#include <stdio.h>
#include <string.h>
#include <dlfcn.h>

typedef int(*SLEEP)(unsigned int t);
static int sleep_times=0;

int  sleep(unsigned int t)
{
    static void *handle = NULL;
    static SLEEP true_sleep = NULL;
    sleep_times++;
    if( !handle )
    {
        handle = dlopen("libc.so.6", RTLD_LAZY);
        true_sleep = (SLEEP)dlsym(handle, "sleep");
    }
    printf("sleep has been called for %d times!\n", sleep_times);
    return true_sleep(t);
}

这次的hook的作用是自定义sleep函数,每次调用sleep就计数一次,然后马上执行glibc中真正的sleep函数

编译的命令是gcc -fPIC -shared -o hook2.so hook2.c -ldl

最后一个参数-ldl是为了加载<dlfcn.h>所在的共享库dl

void *dlopen(const char **filename*, int flag**);**

而dlsym函数用于取函数的地址,存放在一个函数指针中

void *dlsym(void **handle*, const char **symbol*);

上面的hook2.c中也就是用这两个函数实现先调用自定义sleep记录次数,然后再调用glibc中的sleep,从而既达到了我们的目的,又不影响程序的执行逻辑

运行效果如下,可以看到sleep被调用了11次

为了方便hook,可以定义以下宏

#include <sys/types.h>  
#include <dlfcn.h>

#if defined(RTLD_NEXT)
#  define REAL_LIBC RTLD_NEXT
#else
#  define REAL_LIBC ((void *) -1L)
#endif

#define FN(ptr,type,name,args)  ptr = (type (*)args)dlsym (REAL_LIBC, name)

当调用dlsym的时传入RTLD_NEXT参数,gcc的共享库加载器会按照装载顺序获取下一个共享库中的符号地址

因此通过上面的宏定义,REAL_LIBC代表当前调用链中紧接着下一个共享库,从调用方链接映射列表中的下一个关联目标文件获取符号

在使用的时候只需要在自定义hook函数中加入FN即可方便进行替换,如替换execve函数

int execve(const char *filename, char *const argv[], char *const envp[])
{
    static int (*func)(const char *, char **, char **);
    FN(func,int,"execve",(const char *, char **const, char **const)); 

    printf("execve has been called!");

    return (*func) (filename, (char**) argv, (char **) envp);
}

利用ptrace进行hook

利用LD_PRELOAD方法进行hook,很多时候是限制比较多的,它要求在程序在执行前就把hook.so加入环境变量中,对于已经运行了的程序,则没有办法采用这种方法进行hook

这里就介绍另外一种hook的方法,利用ptrace进行hook

众所周知,ptrace是Linux提供的一种专门用于调试的系统调用,具体的用法可见man文档

这里直接介绍利用ptrace进行hook的原理和步骤

  1. 首先需要使得hook程序利用ptrace attach target程序,保护现场,保存原寄存器内容和内存数据
  2. 通过得到指向link_map链表的指针,通过一系列的遍历操作,根据函数名查找到各种函数的真实地址
  3. 通过修改target程序的寄存器和内存使其调用dlopen函数,从而将hook.so加入target内存空间
  4. 修改需要被hook的func函数地址的GOT表为hook.so中hook_func函数地址
  5. 完成hook,恢复现场,恢复原寄存器内容和内存数据,退出ptrace

这5步当中最麻烦的就是第二步,接下来通过代码逐步分析五个步骤的实现方式,最终的完整代码可见附件

第一步

这里主要是涉及ptrace的基本运用,首先定义一系列有关ptrace的操作函数

void ptrace_attach(pid_t pid)
{
    if(ptrace(PTRACE_ATTACH, pid, NULL, NULL) < 0)
    {
        error_msg("ptrace_attach error\n");
    }

    waitpid(pid, NULL, WUNTRACED);

    ptrace_getregs(pid, &oldregs);
}
//oldregs为全局变量

在attach上目标程序后马上保存他的所有的原始寄存器的值,对应在最后detach的时候还原

第二步

为了调用dlopen函数加载hook.so到目标函数的内存空间,就必须知道dlopen函数的地址,但是一般情况下我们的程序不会#include <dlfcn.h>,因此我们这里选择找到__libc_dlopen_mode的地址,利用他来打开so,该函数的参数用法和dlopen完全一样

如何查找指定函数名的真实地址呢?

通过link_map链表的指针链,在各个so文件中寻找函数对应的地址

这里定义了两个函数

map = get_linkmap(pid);
sym_addr = find_symbol(pid, map, oldfunname);

首先从get_linkmap开始讲解

首先从程序头部IMAGE_ADDR(64为的一般为0x400000)开始读取信息找到头部表的地址

根据头部表再找.dynamic节

再遍历.dynamic节,找到.got.plt节,而这个就是我们平常说的got表了

GOT表中每一项都是64bit的Elf64_Addr地址

但其中GOT表前三项用于保存特殊的数据结构地址:

GOT[0]为段”.dynamic”的加载地址

GOT[1]为ELF所依赖的动态链接库链表头struct link_map结构体描述符地址

GOT[2]为_dl_runtime_resolve函数地址

于是这样就找到了link_map

struct link_map* get_linkmap(int pid)
{
    int i;
    Elf_Ehdr *ehdr = (Elf_Ehdr *) malloc(sizeof(Elf_Ehdr)); 
    Elf_Phdr *phdr = (Elf_Phdr *) malloc(sizeof(Elf_Phdr));
    Elf_Dyn  *dyn = (Elf_Dyn *) malloc(sizeof(Elf_Dyn));
    Elf_Addr *gotplt;

    // 读取文件头
    ptrace_getdata(pid, IMAGE_ADDR, ehdr, sizeof(Elf_Ehdr));
    // 获取program headers table的地址
    phdr_addr = IMAGE_ADDR + ehdr->e_phoff;
    // 遍历program headers table,找到.dynamic
    for (i = 0; i < ehdr->e_phnum; i++) 
    {
        ptrace_getdata(pid, phdr_addr + i * sizeof(Elf_Phdr), phdr, sizeof(Elf_Phdr));
        if (phdr->p_type == PT_DYNAMIC) 
        {
            dyn_addr = phdr->p_vaddr;
            break;
        }
    }

    if (0 == dyn_addr) 
    {
        error_msg("cannot find the address of .dynamin\n");
    } else 
    {
        printf("[+]the address of .dynamic is %p\n", (void *)dyn_addr);
    }

    // 遍历.dynamic,找到.got.plt 
    for (i = 0; i * sizeof(Elf_Dyn) <= phdr->p_memsz; i++ )
    {
        ptrace_getdata(pid, dyn_addr + i * sizeof(Elf_Dyn), dyn, sizeof(Elf_Dyn));
        if (dyn->d_tag == DT_PLTGOT) 
        {
            gotplt = (Elf_Addr *)(dyn->d_un.d_ptr);
            break;
        }
    }
    if (NULL == gotplt) 
    {
        error_msg("cannot find the address of .got.plt\n");

    }else 
    {
        printf("[+]the address of .got.plt is %p\n", gotplt);
    }

    // 获取link_map地址
    ptrace_getdata(pid, (Elf_Addr)(gotplt + 1), &lmap_addr, sizeof(Elf_Addr));
    printf("[+]the address of link_map is %p\n", (void *)lmap_addr);

    free(ehdr);
    free(phdr);
    free(dyn);

    return (struct link_map *)lmap_addr;
}

找到后返回一个结构指针,link_map的结构体如下

typedef struct link_map {
    caddr_t     l_addr;         /* Base Address of library */
#ifdef __mips__
    caddr_t     l_offs;         /* Load Offset of library */
#endif
    const char  *l_name;        /* Absolute Path to Library */
    const void  *l_ld;          /* Pointer to .dynamic in memory */
    struct link_map *l_next, *l_prev;   /* linked list of of mapped libs */
} Link_map;

接下来讲解find_symbol函数

上面说到GOT[2]为_dl_runtime_resolve函数地址

该函数的作用是遍历GOT[1]指向的动态链接库链表直至找到某个符号的地址,然后将该符号地址保存至相应的GOT表项中,而find_symbol函数的作用正是模拟_dl_runtime_resolve函数,在动态链接库中找到我们想要的函数地址

lf_Addr find_symbol(int pid, Elf_Addr lm_addr, char *sym_name)
{
    char buf[STRLEN] = {0};
    struct link_map lmap;
    unsigned int nlen = 0;
    while (lm_addr) 
    {
        // 读取link_map结构内容
        ptrace_getdata(pid, lm_addr, &lmap, sizeof(struct link_map));
        lm_addr = (Elf_Addr)(lmap.l_next);//获取下一个link_map

        // 判断l_name是否有效
        if (0 == lmap.l_name) 
        {
            printf("[-]invalid address of l_name\n");
            continue;
        }
        nlen = ptrace_getstr(pid, (Elf_Addr)lmap.l_name, buf, 128);
        //读取so名称
        if (0 == nlen || 0 == strlen(buf)) 
        {
            printf("[-]invalud name of link_map at %p\n", (void *)lmap.l_name);
            continue;
        }
        printf(">> start search symbol in %s:\n", buf);

        Elf_Addr sym_addr = find_symbol_in_linkmap(pid, &lmap, sym_name);
        if (sym_addr) 
        {
            return sym_addr;
        }
    }

    return 0;
}

最后执行了Elf_Addr sym_addr = find_symbol_in_linkmap(pid, &lmap, sym_name);

继续来看find_symbol_in_linkmap函数,这个函数的主要作用是根据handle_one_lmap返回的lmap_result结构体中的信息来判断 我们需要找的函数是否在这个so中

Elf_Addr find_symbol_in_linkmap(int pid, struct link_map *lm, char *sym_name)
{
    int i = 0;
    char buf[STRLEN] = {0};
    unsigned int nlen = 0;
    Elf_Addr ret;
    Elf_Sym *sym = (Elf_Sym *)malloc(sizeof(Elf_Sym)); 

    struct lmap_result *lmret = handle_one_lmap(pid, lm);
    //lmap_result结构体,包含了SYMTAB、STRTAB、RELPLT、REPLDYN等信息
    /*
    struct lmap_result 
    {
        Elf_Addr symtab;
        Elf_Addr strtab;
        Elf_Addr jmprel;
        Elf_Addr reldyn;
        uint64_t link_addr;
        uint64_t nsymbols;
        uint64_t nrelplts;
        uint64_t nreldyns;
    };
    */
    for(i = 0; i >= 0; i++) 
    {
        // 读取link_map的符号表
        ptrace_getdata(pid, lmret->symtab + i * sizeof(Elf_Sym) ,sym ,sizeof(Elf_Sym));

        // 如果全为0,是符号表的第一项
        if (!sym->st_name && !sym->st_size && !sym->st_value) 
        {
            continue;
        }
        nlen = ptrace_getstr(pid, lmret->strtab + sym->st_name, buf, 128);
        if (buf[0] && (32 > buf[0] || 127 == buf[0]) ) 
        {
            printf(">> nothing found in this so...\n\n");
            return 0;
        }

        if (strcmp(buf, sym_name) == 0) 
        {
            printf("[+]has find the symbol name: %s\n",buf);
            if(sym->st_value == 0) 
            {//如果sym->st_value值为0,代表这个符号本身就是重定向的内容  
                continue;
            }
            else 
            {// 否则说明找到了符号
                return (lmret->link_addr + sym->st_value);
            }
        }

    }
    free(sym);
    return 0;
}

再来康康handle_one_lmap是如何把当前link_map指向的so中的SYMTAB、STRTAB、RELPLT、REPLDYN信息提取出来的:

struct lmap_result *handle_one_lmap(int pid, struct link_map *lm)
{
    Elf_Addr dyn_addr;
    Elf_Dyn  *dyn = (Elf_Dyn *)calloc(1, sizeof(Elf_Dyn));
    struct lmap_result *lmret = NULL;

    // 符号表
    Elf_Addr    symtab;
    Dyn_Val     syment;
    Dyn_Val     symsz;
    // 字符串表
    Elf_Addr    strtab;
    // rel.plt
    Elf_Addr    jmprel;
    Dyn_Val     relpltsz;
    // rel.dyn
    Elf_Addr    reldyn;
    Dyn_Val     reldynsz;
    // size of one REL relocs or RELA relocs
    Dyn_Val     relent;
    // 每个lmap对应的库的映射基地址
    Elf_Addr    link_addr;

    link_addr = lm->l_addr;
    dyn_addr = lm->l_ld;

    ptrace_getdata(pid, dyn_addr, dyn, sizeof(Elf_Dyn));

    while(dyn->d_tag != DT_NULL)
    {
        switch(dyn->d_tag)
        {
        // 符号表
            case DT_SYMTAB:
            symtab = dyn->d_un.d_ptr;
            break;
            case DT_SYMENT:
            syment = dyn->d_un.d_val;
            break;
            case DT_SYMINSZ:
            symsz = dyn->d_un.d_val;
            break;
        // 字符串表
            case DT_STRTAB:
            strtab = dyn->d_un.d_ptr;
            break;
        // rel.plt, Address of PLT relocs
            case DT_JMPREL:
            jmprel = dyn->d_un.d_ptr;
            break;
        // rel.plt, Size in bytes of PLT relocs
            case DT_PLTRELSZ:
            relpltsz = dyn->d_un.d_val;
            break;
        // rel.dyn, Address of Rel relocs
            case DT_REL:
            case DT_RELA:
            reldyn = dyn->d_un.d_ptr;
            break;
        // rel.dyn, Size of one Rel reloc
            case DT_RELENT:
            case DT_RELAENT:
            relent = dyn->d_un.d_val;
            break;
        //rel.dyn  Total size of Rel relocs
            case DT_RELSZ:
            case DT_RELASZ:
            reldynsz = dyn->d_un.d_val;
            break;
        }
        ptrace_getdata(pid, dyn_addr += (sizeof(Elf_Dyn)/sizeof(Elf_Addr)), dyn, sizeof(Elf_Dyn));
    }
    if (0 == syment || 0 == relent)
    {
        printf("[-]Invalid ent, syment=%u, relent=%u\n", (unsigned)syment, (unsigned)relent);
        return lmret;
    }

    lmret = (struct lmap_result *)calloc(1, sizeof(struct lmap_result));
    lmret->symtab = symtab;
    lmret->strtab = strtab;
    lmret->jmprel = jmprel;
    lmret->reldyn = reldyn;
    lmret->link_addr = link_addr;
    lmret->nsymbols = symsz / syment;
    lmret->nrelplts = relpltsz / relent;
    lmret->nreldyns = reldynsz / relent;

    free(dyn);

    return lmret;
}

可以看到 这里利用了link_map->l_ld读取到 Elf_Dyn *dyn,从而拿到有关当前so的 .dynamic的内容

再用switch语句区分各种dyn->d_tag下的不同类别的信息

循环处理完毕后将存储的有用信息的(struct lmap_result *)lmret 返回

至此,我们构造了一个find_symbol函数用于查找目标程序内存空间里已加载so的函数

第三步

通过第二步的find_symbol函数,可以得到__libc_dlopen_mode的地址,接下来就是对目标程序的寄存器进行操作

/* 查找要被替换的函数 */
    old_sym_addr = find_symbol(pid, map, oldfunname);      
    /* 查找hook.so中hook的函数 */
    new_sym_addr = find_symbol(pid, map, newfunname);
    /* 查找__libc_dlopen_mode,并调用它加载hook.so动态链接库 */
    dlopen_addr = find_symbol(pid, map, "__libc_dlopen_mode");

    /*把hook.so动态链接库加载进target程序 */
    inject_code(pid, dlopen_addr, libpath);

这里的重点在于inject_code(pid, dlopen_addr, libpath);

int inject_code(pid_t pid, unsigned long dlopen_addr, char *libc_path)
{
    char sbuf1[STRLEN], sbuf2[STRLEN];
    struct user_regs_struct regs, saved_regs;
    int status;
    puts(">> start inject_code to call the dlopen");

    ptrace_getregs(pid, &regs);//获取所有寄存器值
    ptrace_getdata(pid, regs.rsp + STRLEN, sbuf1, sizeof(sbuf1));
    ptrace_getdata(pid, regs.rsp, sbuf2, sizeof(sbuf2));//获取栈上数据并保存在sbuf1、2

    /*用于引发SIGSEGV信号的ret内容*/
    unsigned long ret_addr = 0x666;

    ptrace_setdata(pid, regs.rsp, (char *)&ret_addr, sizeof(ret_addr));
    ptrace_setdata(pid, regs.rsp + STRLEN, libc_path, strlen(libc_path) + 1); 

    memcpy(&saved_regs, &regs, sizeof(regs));

    printf("before inject:rsp=%zx rdi=%zx rsi=%zx rip=%zx\n", regs.rsp,regs.rdi, regs.rsi, regs.rip);

    regs.rdi = regs.rsp + STRLEN;
    regs.rsi = RTLD_NOW|RTLD_GLOBAL|RTLD_NODELETE;
    regs.rip = dlopen_addr+2;

    printf("after inject:rsp=%zx rdi=%zx rsi=%zx rip=%zx\n", regs.rsp,regs.rdi, regs.rsi, regs.rip);

    if (ptrace(PTRACE_SETREGS, pid, NULL, &regs) < 0)
    {//设置寄存器
        error_msg("inject_code:PTRACE_SETREGS 1 failed!");
    }

    if (ptrace(PTRACE_CONT, pid, NULL, NULL) < 0)
    {//设置完寄存器后让目标进程继续运行
        error_msg("inject_code:PTRACE_CONT failed!");
    }


    waitpid(pid, &status, 0);//按照最后的ret指令会使得rip=0x666,从而引发SIGSEGV
    ptrace_getregs(pid, &regs);
    printf("after waitpid inject:rsp=%zx rdi=%zx rsi=%zx rip=%zx\n", regs.rsp,regs.rdi, regs.rsi, regs.rip);

    //恢复现场,恢复所有寄存器和栈上数据
    if (ptrace(PTRACE_SETREGS, pid, 0, &saved_regs) < 0)
    {
        error_msg("inject_code:PTRACE_SETREGS 2 failed!");;
    }
    ptrace_setdata(pid, saved_regs.rsp + STRLEN, sbuf1, sizeof(sbuf1));
    ptrace_setdata(pid, saved_regs.rsp, sbuf2, sizeof(sbuf2));
    puts("-----inject_code done------");
    return 0;
}

通过以上代码,就能使得hook.so被加载进target程序,从而实现注入so

第四、五步

这里就简单很多了,有了函数地址,再找到got表地址就能通过修改got表从而实现hook函数

这里首先实现一个find_sym_in_rel函数,用于找到指定函数的的got表地址

Elf_Addr find_sym_in_rel(int pid, char *sym_name)
{
    Elf_Rel *rel = (Elf_Rel *) malloc(sizeof(Elf_Rel));
    Elf_Sym *sym = (Elf_Sym *) malloc(sizeof(Elf_Sym));
    int i;
    char str[STRLEN] = {0};
    unsigned long ret;
    struct lmap_result *lmret = get_dyn_info(pid);

    for (i = 0; i<lmret->nrelplts; i++) 
    {
        ptrace_getdata(pid, lmret->jmprel + i*sizeof(Elf_Rela), rel, sizeof(Elf_Rela));
        ptrace_getdata(pid, lmret->symtab + ELF64_R_SYM(rel->r_info) * sizeof(Elf_Sym), sym, sizeof(Elf_Sym));
        int n = ptrace_getstr(pid, lmret->strtab + sym->st_name, str, STRLEN);
        printf("self->st_name: %s, self->r_offset = %p\n",str, rel->r_offset);
        if (strcmp(str, sym_name) == 0) 
        {
            break;
        }
    }
    if (i == lmret->nrelplts)
        ret = 0;
    else
        ret = rel->r_offset;
    free(rel);
    return ret;
}

找好了got表地址后最后进行的就是修改got表了

/* 找到旧函数在重定向表的地址 */          
    old_rel_addr = find_sym_in_rel(pid, oldfunname);

    ptrace_getdata(pid, old_rel_addr, &target_addr, sizeof(Elf_Addr));
    ptrace_setdata(pid, old_rel_addr, &new_sym_addr, sizeof(Elf_Addr));
    //修改oldfun的got表内容为newfun

    To_detach(pid);//退出并还原ptrace attach前的寄存器内容

至此利用ptrace进行hook的操作就这样完成了,其实可以发现,这种hook手段离不开注入技术

ptrace hook演示

在这里,我们的target程序如下

#include <stdio.h>
#include <unistd.h>
int main()
{
    int num=10;
    printf("my pid is %d\n", getpid());
    puts("start hook?");
    while(--num)
    {
        puts("hello?");
        sleep(1);
    }

    return 0;
}
//gcc target.c -o target

hook_so源码如下

#include <stdio.h>
int newputs(const char *str)
{
    write(1,"hook puts! ",11);
    puts(str);
    return 0;
}
//gcc hook_so.c -o hook_so.so -fPIC --shared

hook3源码见附件,太长了不贴了

编译gcc hook3.c -o hook3 -ldl && gcc target.c -o target && gcc hook_so.c -o hook_so.so -fPIC --shared

运行:

$ sudo ./hook3 ./hook_so.so puts newputs 26600
---------------------------------
target pid = 26600
target oldfunname: puts
patch libpath: ./hook_so.so
patch newfunname: newputs
---------------------------------

[+]the address of .dynamic is 0x600e28
[+]the address of .got.plt is 0x601000
[+]the address of link_map is 0x7fc95aa38168
[-]invalud name of link_map at 0x7fc95aa386f8
[-]invalud name of link_map at 0x7fc95aa38b90
>> start search symbol in /lib/x86_64-linux-gnu/libc.so.6:
[+]has find the symbol name: puts
found puts at addr 0x7fc95a4b6690
[-]invalud name of link_map at 0x7fc95aa386f8
[-]invalud name of link_map at 0x7fc95aa38b90
>> start search symbol in /lib/x86_64-linux-gnu/libc.so.6:
[+]has find the symbol name: __libc_dlopen_mode
found __libc_dlopen_mode at addr 0x7fc95a58a610
>> start inject_code to call the dlopen
before inject:rsp=7fff4b267ac8 rdi=7fff4b267ad0 rsi=7fff4b267ad0 rip=7fc95a5132f0
after inject:rsp=7fff4b267ac8 rdi=7fff4b267ec8 rsi=1102 rip=7fc95a58a612
after waitpid inject:rsp=7fff4b267ad0 rdi=7fc95aa37948 rsi=7fff4b267a98 rip=666
-----inject_code done------
[-]invalud name of link_map at 0x7fc95aa386f8
[-]invalud name of link_map at 0x7fc95aa38b90
>> start search symbol in /lib/x86_64-linux-gnu/libc.so.6:
>> nothing found in this so...

>> start search symbol in /lib64/ld-linux-x86-64.so.2:
>> nothing found in this so...

>> start search symbol in ./hook_so.so:
[+]has find the symbol name: newputs
===> found newputs at addr 0x7fc95a2456e0
self->st_name: puts, self->r_offset = 0x601018
oldfunname: puts  rel addr:0x601018
oldfunction addr:0x7fc95a4b6690
newfunction addr:0x7fc95a2456e0
hook has done!
***detach***

可以看到puts函数被hook成功

ps:我的环境是Ubuntu16.04,以上所有的源码编译操作都是在以64位进行的,32位的没有实现

注入技术

如果我们希望进行的操作不仅仅只是hook一个函数,我还想让程序运行一系列的代码,该如何操作?

  1. 比较容易被想到的就是模仿上面的ptrace操作,对目标程序的内存数据和寄存器进行修改,从而达到注入代码的目标,但是这种方法比较麻烦一方面要考虑注入前后对目标程序的影响,又要兼顾执行注入代码时的信号的发送,才能让hook程序时刻注意目标程序的执行状态
  2. 先注入so进行hook,在hook.so中设计一系列执行代码

这里主要想介绍第二种,这种方法执行注入代码非常方便,基本上不需要考虑目标程序的运行环境

把hook_so.c进行修改

#include <stdio.h>
//gcc hook_so.c -o hook_so.so -fPIC --shared
int newputs(const char *str)
{
    write(1,"hook puts! ",11);
    puts(str);
    return 0;
}


__attribute__((constructor))
void loadMsg()
{
    puts("hook.so has been injected!");
    puts("now let's do somesthing...");
    printf("->pid:%d\n\n", getpid());
}

__attribute__((destructor))
void eixtMsg()
{
    puts("bye bye~");

}

这里使用了 __attribute__关键词,专门用它设计两个函数分别在最开始的时候 执行和结束的时候执行

再次进行之前的hook操作:sudo ./hook3 ./hook_so.so puts newputs 26868

可以看到不仅成功hook,还多执行了两个函数,这里可以发挥想象,如果在hook3对target进行ptrace时得到的信息写入一个文本文件中,然后在hook.so中再读取这个文件,就能获取到本程序的大部分信息,如一些函数的地址,got表的地址等等,有了这些信息简直就是为所欲为之为所欲为

再骚一点的话,还可以新开一个子进程or线程执行execve,从而执行各种其他程序

参考链接

https://www.cnblogs.com/LittleHann/p/3854977.html
https://jmpews.github.io/2016/12/27/pwn/linux%E8%BF%9B%E7%A8%8B%E5%8A%A8%E6%80%81so%E6%B3%A8%E5%85%A5/

https://github.com/plivepatch/plivepatch


文章来源: http://xz.aliyun.com/t/6883
如有侵权请联系:admin#unsafe.sh