linux hook是一个非常常见且成熟的技术,用户态Hook的方法有很多中,本次主要记录下LD_PRELOAD动态链接库劫持方法。
LD_PRELOAD是一个全局变量,linux程序在运行时会优先加载该路径变量下的动态链接库,因此我们只需要通过设置LD_PRELOAD加载我们编写的同名函数,后续的加载过程中就会忽略后面的同名函数,从而完成对系统库的劫持。
一般情况下linux动态加载库的顺序为LD_PRELOAD>LD_LIBRARY_PATH>/etc/ld.so.cache>/lib>/usr/lib
glibc是为GUN操作系统开发的一个C标准库,动态位于libc.so.6,其中的函数也是我们常用来Hook的对象。
下面以劫持connect为例,实现对连接端口的伪造。
正常的连接程序main.c:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
int main(){
int fd;
struct sockaddr_in remote_addr;
remote_addr.sin_family = AF_INET;
remote_addr.sin_port = htons(1234);
remote_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
fd = socket(AF_INET,SOCK_STREAM,0);
if (connect(fd,(struct sockaddr*)&remote_addr,sizeof(remote_addr))){
exit(0);
}
close(fd);
}
劫持connect函数的hook.c代码如下:
#include <stdio.h>
#include <netinet/in.h>
#include <string.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
// 声明一个connect函数指针
typedef int (*CONNECT)(int, const struct sockaddr *, socklen_t);
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
{
static void *handle = NULL;
static CONNECT old_con = NULL;
handle = dlopen("libc.so.6", RTLD_LAZY);
old_con = (CONNECT)dlsym(handle, "connect");
struct sockaddr_in *tmp = (struct sockaddr_in *)addr;
tmp->sin_port = htons(1235);
printf("connect function invoked, connect ip : %s \n ",inet_ntoa(tmp->sin_addr));
printf("connect port changes to %d\n",ntohs(tmp->sin_port));
return old_con(sockfd, (struct sockaddr *)tmp , addrlen);
}
这样就实现了对目的连接端口的篡改。
看一下效果:
[[email protected] libchook]# gcc main.c -o main
[[email protected] libchook]# gcc -fPIC -shared -o hook.so hook.c
[[email protected] libchook]# LD_PRELOAD=./hook.so ./main
connect function invoked, connect ip : 127.0.0.1
connect port changes to 1235
ptrace大法
但是现在基于golang的程序越来越多,而大多数golang程序都是静态编译的,会将静态链接库封装到二进制文件中,因此动态HOOK具有很大的局限性,为了解决该问题,一般可以采用内核级Hook,如果直接进行内核编程,代码写起来太费劲,因此选用神奇ptrace大法,既能服用线程的用户态代码,也能实现内核级别的Hook,很是舒服。
ptrace是一种系统调用,函数声明如下:
#include <sys/ptrace.h>
long ptrace(enum __ptrace_request request, pid_t pid,
void *addr, void *data);
(1) request : 请求ptrace执行的操作
(2) pid: 目标进程的ID
(3) addr: 目标进程的地址
(4) data: 不同操作下写入或读取的操作数据
执行的操作有如下几种:
贴一个经典的ptrace示例:
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <sys/reg.h> /* For constants ORIG_RAX etc */
#include <sys/user.h>
#include <sys/syscall.h> /* SYS_write */
#include <stdio.h>
#include <unistd.h>
int main() {
pid_t child;
long orig_rax;
int status;
int iscalling = 0;
struct user_regs_struct regs;
child = fork();
if(child == 0) {
ptrace(PTRACE_TRACEME, 0, NULL, NULL);
execl("/bin/ls", "ls",NULL);
} else {
while(1) {
wait(&status);
if(WIFEXITED(status))
break;
orig_rax = ptrace(PTRACE_PEEKUSER,
child, 8 * ORIG_RAX,
NULL); // 获取当前系统调用值
if(orig_rax == SYS_write) {
ptrace(PTRACE_GETREGS, child, NULL, ®s); // 获取参数并放置寄存器结构体regs中
if(!iscalling) {
iscalling = 1; // 调用前获取初始化参数
printf("SYS_write call with %lld, %lld, %lld\n",
regs.rdi, regs.rsi, regs.rdx);
}
}
ptrace(PTRACE_SYSCALL, child, NULL, NULL);
}
}
return 0;
}
fork后获取进程pid,pid为0表示主进程,否则为子进程,主进程中设置PTRACE_TRACEME标志进行探测,子进程中获取进行ptrace跟踪与修改等操作。
读取内存数据以及写入数据可以参考下面代码:
const int long_size =sizeof(long);
void getdata(pid_t child, long addr,
char *str, int len) {
char *laddr;
int i, j;
union u {
long val;
char chars[long_size];
}data; // 用union方便数据读取与判断
i = 0;
j = len / long_size; // 每次获取long_size
laddr = str;
while(i < j) {
data.val = ptrace(PTRACE_PEEKDATA,
child, addr + i * 8,
NULL);
if(data.val == -1)
if(errno) {
printf("READ error: %s\n", strerror(errno));
}
memcpy(laddr, data.chars, long_size);
++i;
laddr += long_size;
}
j = len % long_size;
if(j != 0) {
data.val = ptrace(PTRACE_PEEKDATA,
child, addr + i * 8,
NULL);
memcpy(laddr, data.chars, j);
}
str[len] = '\0';
}
void putdata(pid_t child,long addr, char *str,int len)
{
char *laddr;
int i, j;
union u {
long val;
char chars[long_size];
} data;
i =0;
j = len / long_size;
laddr = str;
while(i < j) {
memcpy(data.chars, laddr, long_size);
ptrace(PTRACE_POKEDATA, child,
addr + i *8, data.val);
++i;
laddr += long_size;
}
j = len % long_size;
if(j != 0) {
memcpy(data.chars, laddr, j);
ptrace(PTRACE_POKEDATA, child,
addr + i *8, data.val);
}
}