[toc]
概述
搞一个包装,把c库函包起来,同时顺带加上自己的私货。
抄的如下链接,作者总结的挺好,不过代码有编译错误等问题。这里修正一下,所有的代码都能直接跑
https://blog.csdn.net/liangzc1124/article/details/104974649
编译时打桩
直接自己写个库函数调用,加上私货包起来,再用宏把库函数里面的实现,给替换成自己的实现
建立包装函数
建立mymalloc.c文件,定义需要的包装函数mymalloc和myfree.
#ifdef COMPILETIME
#include <stdio.h>
#include <malloc.h>
void *mymalloc(size_t size)
{
void *ptr = malloc(size);
printf("malloc(%d)=%p\n", size, ptr);
return ptr;
}
void myfree(void *ptr)
{
free(ptr);
printf("free(%p)\n", ptr);
}
#endif
建立头文件 malloc.h
该文件向预处理器指明用mymalloc.c中的包装函数替换库里的目标函数,需要在gcc编译的时候,让编译器优先去用这个 .h
#define malloc(size) mymalloc(size)
#define free(ptr) myfree(ptr)
void *mymalloc(size_t size);
void myfree(void *ptr);
建立自己的程序文件
建立文件myprog.c,并在其中正常调用malloc函数
#include <stdio.h>
#include "mymalloc.h"
int main(void)
{
int *p = malloc(32);
free(p);
return 0;
}
编译链接
gcc -c -DCOMPILETIME mymalloc.c
gcc -I. mymalloc.o -o myprog myprog.c
-I.:指示C预处理器在搜索通常的系统目录前,先在当前目录中查找malloc.h
链接时打桩
wrap是一种非常常见的打桩方法
-Wl,option:将option传递给链接器。
option中的每个逗号都要用空格来替换
所以-Wl,--wrap,malloc意味着把--wrap malloc传递给链接器。
当链接器看到 --warp ABC
(ABC代表任何字符串)选项的时候,会去所有的文件下去寻找 ABC
,然后把 ABC
换成 __warp_ABC
之后把所有的 __real_ABC
换成正常的 ABC
包装函数
创建mymalloc.c文件,定义包装函数
#include <stdio.h>
void *__real_malloc(size_t size);
void __real_free(void *ptr);
//定义malloc 包装函数
void *__wrap_malloc(size_t size)
{
void *ptr = __real_malloc(size); //调用标准库里的malloc
printf("malloc(%d) = %p\n", (int)size, ptr);
return ptr;
}
//定义free 包装函数
void __wrap_free(void *ptr)
{
__real_free(ptr); //调用标准库里的free
printf("free(%p) = %p\n", ptr);
}
建立程序
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int *p = malloc(32);
free(p);
return 0;
}
编译链接
gcc -c mymalloc.c
gcc -c myprog.c
gcc -Wl,--wrap,malloc -Wl,--wrap,free -o myprog myprog.o mymalloc.o
或者直接
gcc -Wl,--wrap,malloc -Wl,--wrap,free -o myprog myprog.c mymalloc.c
运行时打桩
编译时打桩需要访问程序的源代码,而链接时需要访问可重定位目标文件。那有没有一种办法让仅仅访问可执行目标就能达到同样的目的呢?我们可以利用基于动态链接器的LD_PRELOAD环境变量来实现。
当你将LD_PRELOAD环境变量设置为一个共享路径名的列表(以空格或分号分开),那么在运行一个程序时,动态链接器(LD-LINUX.SO)会先搜索列表里的库,然后才搜素系统其它库。
利用这个原理,你可以对任何共享库中的任何函数打桩,包括libc.so。
建立包装函数文件
下面建立mymalloc.c文件,其中定义了malloc和free的包装函数。每个包装函数中,利用dlsym调用libc中的标准函数。
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
//定义malloc 包装函数
void *malloc(size_t size)
{
void *(*mallocp)(size_t size);
char *error;
//获得libc中malloc函数的地址
mallocp = dlsym(RTLD_NEXT, "malloc");
if((error = dlerror()) != NULL){
fputs(error, stderr);
exit(1);
}
//利用函数指针间接调用libc中的malloc函数
char *ptr = mallocp(size);
printf("malloc()");
return ptr;
}
//定义free 包装函数
void free(void *ptr)
{
void (*freep)(void *) = NULL;
char *error;
if(!ptr) {
return;
}
//获得libc中free函数的地址
freep = dlsym(RTLD_NEXT, "free");
if((error = dlerror()) != NULL){
fputs(error, stderr);
exit(1);
}
freep(ptr); //利用函数指针间接调用libc中的free函数
printf("free(%p)\n", ptr);
}
建立自己的程序文件
建立文件myprog.c,并在其中正常调用malloc函数.
#include <stdio.h>
#include <malloc.h>
int main(void)
{
int *p = malloc(32);
free(p);
return 0;
}
编译包含包装函数的共享库
gcc -shared -fpic -o mymalloc.so mymalloc.c -ldl
-fpic:(position independent code)指示生成位置无关的目标文件;编译共享库时该参数为必选!
-shared:指示编译器生成一个共享目标文件。
-ldl:指示链接器链接到 libdl.so 共享库。
编译与运行主程序
gcc -o myprog myprog.c
编译时打桩,必须要能访问程序的源代码,然后重新编译
链接时打桩,必须要能访问程序的可重定位文件.o,然后重新链接。
基于动态链接器的 LD_PRELOAD 环境变量,我们能用这个机制,只访问可执行文件.out就可以库打桩
如果 LD_PRELOAD 环境变量被设置为一个共享库路径名的列表(空格或者分号分隔),那么当你加载和执行一个程序,需要解析未定义的引用时,动态链接器会先搜索 LD_PRELOAD库,然后才搜索任何其他的库。
通过这个机制,当我们加载和执行可执行文件时,可以对任何共享库中的任何函数打桩,包括libc.so
LD_PRELOAD环境变量并且运行
LD_PRELOAD="./mymalloc.so" ./myprog
以为这就完事了么,如果按照上面的代码来操作,最后一步一定会coredump
而且会正好挂在 malloc 的 printf("malloc")
那一行
原因:printf 会调用 printf malloc,当场进行一个死循环
另外提个问题:为什么wrap链接的时候,我的程序就能正常运行,而不是coredump,但是动态链接的时候就会coredump呢?
因为链接的时候,wrap替换是看不到printf内部的,这个时候libc参与链接,只是把printf这个符号扔给了程序,想看到内部,必须等到程序运行的时候,程序自己看到了printf,主动找libc.so获取内容的时候才行。所以wrap并不会去替换libc.so里面的东西。
但动态链接不一样,LD_PRELOAD="./mymalloc.so" ./myprog
这句话的意思是,先不要看别的so,优先去看 mymalloc.so,从这里面去找函数的实现。然后这个so还会去 libc.so 里面去找printf的实现,这样就直接套娃了,printf去找malloc,malloc又是优先被 mymalloc.so定义的,无限循环。
情景训练场景一
已有一个 libadd.so,内有符号add(int size),同时被main和libadd.so内的其他符号引用,不重新编译liba.so的情况下对add进行打桩
搞4个文件:
两个add_test文件做成libadd.so
一个myprog文件放main函数
一个my_add文件放wrap
上代码
libadd_test1.c
#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
libadd_test2.c
#include <stdio.h>
int add(int a, int b);
int Calculation()
{
int a = 10;
int b = 10;
int ret = add(a , b);
}
myprog.c
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <dlfcn.h>
int add(int a, int b);
int main()
{
int a = 1;
int b = 1;
int ret = add(a, b);
return 0;
}
my_add.c
#include <stdio.h>
int __real_add(int a, int b);
int __wrap_add(int a, int b)
{
int ret = __real_add(a, b);
printf("add result=%d\n", ret);
return ret;
}
运行
生成so
gcc -shared -fpic -o libadd.so libadd_test1.c libadd_test2.c
wrap并链接,生成可执行程序
gcc -Wl,--wrap,add -o myprog myprog.c my_add.c -ldl -rdynamic -L. -ladd
运行时需要指定动态库的路径
LD_LIBRARY_PATH=`pwd` ./myprog
如果你链接的时候直接指定就链这个 libadd.so,那么可以直接运行,但是假设有多个so,最好还是用目录的方法
gcc -Wl,--wrap,add -o myprog myprog.c my_add.c -ldl -rdynamic ./libadd.so