本文為畢業設計過程中學習相關知識、動手實踐記錄下來的完整筆記,通過閱讀本系列文章,您可以從零基礎了解系統調用的底層原理并對系統調用進行攔截。由于本人能力有限,文章中可能會出現部分錯誤資訊,如有錯誤歡迎指正。
完整系列文章清單
系統調用捕獲和分析—通過ptrace擷取系統調用資訊
系統調用捕獲和分析—通過strace擷取系統調用資訊
系統調用捕獲和分析—必備的系統安全的知識點
系統調用捕獲和分析—使用LKM方法添加系統調用
系統調用捕獲和分析—修改核心方法添加系統調用
系統調用捕獲和分—Ring3層LD_PRELOAD機制進行庫函數劫持
kprobe輕量級核心調試機制
利用kprobe技術,使用者可以自定義自己的回調函數,可以在幾乎所有的函數中動态插入探測點。
當核心執行流程執行到指定的探測函數時,會調用該回調函數,使用者即可收集所需的資訊了,同時核心最後還會回到原本的正常執行流程。如果使用者已經收集足夠的資訊,不再需要繼續探測,則同樣可以動态的移除探測點。
kprobes技術包括的3種探測手段分别是kprobe、jprobe和kretprobe。
首先kprobe是最基本的探測方式,是實作後兩種的基礎,它可以在任意的位置放置探測點(就連函數内部的某條指令處也可以),它提供了探測點的調用前、調用後和記憶體通路出錯3種回調方式,分别是pre_handler、post_handler和fault_handler,其中pre_handler函數将在被探測指令被執行前回調,post_handler會在被探測指令執行完畢後回調(注意不是被探測函數),fault_handler會在記憶體通路出錯時被調用;
jprobe基于kprobe實作,它用于擷取被探測函數的入參值;
kretprobe同樣基于kprobe實作,用于擷取被探測函數的傳回值。
kprobe機制劫持系統調用實驗
核心源碼
/linux-4.13.10/samples/kprobes/
中有示例程式。
注意在linux-4.15.0中register_jprobe函數失敗傳回-38,網上說4.15.0以後廢除。
4.15.5版本核心下實驗krpobe
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/kprobes.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/cred.h>
#include <asm/current.h>
//定義要hook的函數
static struct kprobe kp = {
.symbol_name = "_do_fork",
};
static int handler_pre(struct kprobe *p, struct pt_regs *regs){
printk(KERN_INFO "<%s> pre_handler: p->addr = 0x%p, ip = %lx, flags = 0x%lx, proc_name = %s, pid = %d\n",
p->symbol_name, p->addr, regs->ip, regs->flags, current->comm, current->pid);
return 0;
}
static void handler_post(struct kprobe *p, struct pt_regs *regs, unsigned long flags){
printk(KERN_INFO "<%s> post_handler: p->addr = 0x%p, flags = 0x%lx, proc_name = %s, pid = %d\n",
p->symbol_name, p->addr, regs->flags, current->comm, current->pid);
}
static int handler_fault(struct kprobe *p, struct pt_regs *regs, int trapnr){
printk(KERN_INFO "fault_handler: p->addr = 0x%p, trap #%dn", p->addr, trapnr);
return 0;
}
static int __init kprobe_init(void)
{
int ret;
kp.pre_handler = handler_pre;
kp.post_handler = handler_post;
kp.fault_handler = handler_fault;
ret = register_kprobe(&kp);
if (ret < 0) {
pr_err("register_kprobe failed, returned %d\n", ret);
return ret;
}
pr_info("Planted kprobe at %p\n", kp.addr);
return 0;
}
static void __exit kprobe_exit(void)
{
unregister_kprobe(&kp);
pr_info("kprobe at %p unregistered\n", kp.addr);
}
module_init(kprobe_init)
module_exit(kprobe_exit)
MODULE_LICENSE("GPL");
Makefile
obj-m := hello.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) clean
編譯,插入子產品,檢視核心資訊
make
sudo insmod hello.ko
dmesg
附4.13.10可運作成功jprobs機制代碼
準備代碼,攔截write系統調用hello.c
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kprobes.h>
//同linux-4.14.15/include/linux/syscalls.h#603下sys_write定義
long my_sys_write(unsigned int fd, const char __user *buf, size_t count){
pr_info("fd=%u, buf=%lx, count=%lu\n", fd, buf, count);
/* Always end with a call to jprobe_return(). */
jprobe_return(); //官方規定在回調函數執行完畢以後,必須調用jprobe_return函數
return 0;
}
/*
struct jprobe {
struct kprobe kp;
void *entry; // probe handling code to jump to
};
struct kprobe定義位置/linux-4.14.15/include/linux/kprobes.h#74
*/
struct jprobe jprobe_write = {
.entry= my_sys_write,
.kp = {
.symbol_name = "sys_write",
},
};
static int __init mymodule_init(void){
//挂載hook
int ret;
ret = register_jprobe(&jprobe_write); //向核心注冊jprobe探測點
if (ret < 0) {
printk("register_jprobe failed, ret=%d\n", ret);
return -1;
}
printk("register_jprobe success, syscall: write\n");
return 0;
}
static void __exit mymodule_exit(void){
unregister_jprobe(&jprobe_write); //解除安裝jprobe探測點
}
module_init(mymodule_init);
module_exit(mymodule_exit);
MODULE_LICENSE("GPL");
Makefile檔案
obj-m := hello.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) clean
編譯,插入子產品,檢視核心資訊
make
sudo insmod hello.ko
dmesg