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: 不同操作下寫入或讀取的操作資料
執行的操作有如下幾種:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIiNx8FesU2cfdGLwczX0xiRGZkRGZ0Xy9GbvNGLwIzXlpXazxSPN52YsJ1VhdnTuRWQClGVF5UMR9Fd4VGdsATNfd3bkFGazxycykFaKdkYzZUbapXNXlleSdVY2pESa9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL3YDNzAjN0ETMxITOwEjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
貼一個經典的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);
}
}