简单来说,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的原理和步骤
- 首先需要使得hook程序利用ptrace attach target程序,保护现场,保存原寄存器内容和内存数据
- 通过得到指向link_map链表的指针,通过一系列的遍历操作,根据函数名查找到各种函数的真实地址
- 通过修改target程序的寄存器和内存使其调用dlopen函数,从而将hook.so加入target内存空间
- 修改需要被hook的func函数地址的GOT表为hook.so中hook_func函数地址
- 完成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, ®s);//获取所有寄存器值
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, ®s, 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, ®s) < 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, ®s);
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一个函数,我还想让程序运行一系列的代码,该如何操作?
- 比较容易被想到的就是模仿上面的ptrace操作,对目标程序的内存数据和寄存器进行修改,从而达到注入代码的目标,但是这种方法比较麻烦一方面要考虑注入前后对目标程序的影响,又要兼顾执行注入代码时的信号的发送,才能让hook程序时刻注意目标程序的执行状态
- 先注入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/
-
ptrace_hook3.zip 下载
-
-
-
-