Linux | c&cpp | Email | github | QQ群:425043908 关注本站

itarticle.cc

您现在的位置是:网站首页 -> Linux 文章内容

Linux逆向之hook&注入-itarticl.cc-IT技术类文章记录&分享

发布时间: 5年前Linux 136人已围观返回

简单来说,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

from clipboard

可以看到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次

from clipboard

为了方便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***

from clipboard

可以看到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

from clipboard

可以看到不仅成功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


文件下载地址:https://github.com/cppstore/linux_hook/blob/master/20191201151413-2d70c948-140a-1.zip

发布时间: 5年前Linux136人已围观返回回到顶端

很赞哦! (1)

文章评论

  • 请先说点什么
    热门评论
    135人参与,0条评论

站点信息

  • 建站时间:2016-04-01
  • 文章统计:728条
  • 文章评论:82条
  • QQ群二维码:扫描二维码,互相交流