歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Unix知識 >> Unix教程

Freebsd內核模塊源碼實現以及應用探秘

FreeBSD
  1. 介紹
  1.1. 內核模塊
  1.2. 一些有用的函數
  
  2. 方法
  2.1. 替換函數指針
  2.1.2. 系統調用
  2.1.3. 其它的表
  2.1.4. 單一的函數指針
  2.2. 修改內核空間的隊列
  2.3. 讀寫內核空間
  2.3.1. 查找符號的地址
  2.3.2. 讀數據
  2.3.3. 修改內核數據
  
  3. 應用
  3.1. 隱藏並重定向文件
  3.2. 隱藏進程
  3.3. 隱藏網絡連接
  3.4. 隱藏防火牆規則
  3.5. 觸發器
  3.6. 隱藏模塊
  3.7. 其它的應用
  
  4. 內核補丁
  4.1 介紹
  4.2 插入跳轉指令
  4.3 替換內核代碼
  
  5. 越過重啟
  
  6. 實戰
  
  7. 保護自己:貓和老鼠的游戲
  7.1. 檢查符號表
  7.2. 構件一個陷阱模塊
  7.3. 重新直接得到數據
  7.4. 注意事項
  
  8. 結論
  
  9. 代碼
  
  ---------------------------------------------
  
  1. 介紹
  
  首先介紹內核模塊的概念,還有系統調用的概念,說明的一點就是freebsd安全級別問題,通常在2級就不可以加載模塊了
  可以用sysctl 調整設置或者在/etc/rc.conf中增加如下條目在啟動時調整:
  kern_securelevel_enable="YES"
  kern_securelevel="2"
  本文only用來教育目的,:)所有涉及的代碼都可以在Curious Yellow (CY)中找到.
  
  1.2. 內核模塊
  
  請參考 scz@nsfocus 前輩翻譯的 內核鏈接機制(KLD)編程指南>,如果你對linux的lkm了解,這個很好理解。在/usr/share/examples/kld/ 有簡單的例子。
  
  1.2 一些有用的的函數
  
  這裡給出一些有用的函數,通常在系統調用中用到copyin/copyout/copyinstr/copyoutstr 這幾個函數可以用來從用戶空間得到
  連續的大塊數據,manpage copy(9)可以得到更多了解,在KLD tutorial也可以找到
  下面是個小例子來展示copyin的用法,我們構造了一個帶有一個字符串指針做參數的系統調用,通過copyin把字符串從用戶空間移動
  到內核空間來
  struct example_call_args {
  char *buffer;
  };
  
  int
  example_call(struct proc *p, struct example_call_args *uap)
  {
  int error;
  char kernel_buffer_copy[BUFSIZE];
  
  /* copy in the user data */
  error = copyin(uap->buffer, &kernel_buffer_copy, BUFSIZE);
  [...]
  }
  fetch/store
  這兩個函數用來得到比較小塊的數據,小到字節或者字長的數據
  spl..
  這個函數用來調整中斷優先級,可以用來阻止某些中斷處理程序的執行,下面的例子中當中斷處理函數指針icmp_input修改時,因為
  它通常要經過一些時時間,所以我們要防止對這個中斷的處理。
  
  2. 方法
  
  這節列出一些常用的方法,將在後面的具體技術中使用,比如隱藏進程,網絡連接。當然這些方法也可以用來實現其他的..
  
  2.1. 修改函數指針
  
  最古老也最經常用的方法,修改函數指針,用來指向你的函數,或者通過改寫/dev/kmem達到相同的目的。(下面)
  注意當你修改了函數指針後,你的新的函數要和原來的函數有相同的調用參數。下面介紹了一些通常用來hook的內核函數
  
  2.1.1 系統調用
  
  經典的hook方法,freebsd通過一個全局的sysent結構數組保持了一系列的系統調用,參見/sys/kern/init_sysent.c
  struct sysent sysent[] = {
  { 0, (sy_call_t *)nosys },           /* 0 = syscall */
  { AS(rexit_args), (sy_call_t *)exit },     /* 1 = exit */
  { 0, (sy_call_t *)fork },            /* 2 = fork */
  { AS(read_args), (sy_call_t *)read },      /* 3 = read */
  { AS(write_args), (sy_call_t *)write },     /* 4 = write */
  { AS(open_args), (sy_call_t *)open },      /* 5 = open */
  { AS(close_args), (sy_call_t *)close },     /* 6 = close */
  [...]
  結構sysent在/sys/sys/syscall.h定義,還有系統調用號也在此文件中定義
  比方說你想替換open這個系統調用,在你的模塊加載函數的MOD_LOAD節中這樣做
  sysent[SYS_open] = (sy_call_t *)your_new_open
  然後在你的模塊卸載節中修復原來的系統調用
  sysent[SYS_open].sy_call = (sy_call_t *)open;
  
  2.1.2. 其它一些有用的表
  
  系統調用不是唯一可以修改的地方,在freebsd內核中還有一些其它的地方也可以利用,特別是inetsw和各種文件系統的vnode表.
  struct ipprotosw intesw[]保存了一系列被支持的inet協議的信息,這其中包括了當這種協議的數據報到達時或送出時用來處
  理的函數 參見/sys/netinet/in_proto.c得到更多的信息,所以我們也可以hook這裡的函數:)
  下面我們就可以在模塊中hook了
  inetsw[ip_protox[IPPROTO_ICMP]].pr_input = new_icmp_input;
  
  通常每種文件系統的vnode表都是由多個具體的函數組成。所以我們可以替換它們來隱藏我們的文件。
  ufs_vnodeop_p[VOFFSET(vop_lookup)] = (vop_t *) new_ufs_lookup;
  
  在內核中當然還有很多地方可以hook,這就取決你的目的了,kernel source 是最重要的文檔
  
  2.1.3 單個的函數指針
  
  偶爾我們也會碰到單個的函數函數指針,比如說ip_fw_ctl_ptr,這個函數用來處理ipfw的請求,這裡我們也可以用來hook。
  
  2.2. 修改內核隊列
  
  也許你想修改內核中的一些數據,一些感興趣的東西都以隊列的形式存儲在內核中,如果你從來沒有
  使用過/sys/sys/queue.h的一些宏,你先要熟悉一下它然後在進行下面的閱讀。這可以讓你輕松面對下面的kernel source
  並且在你使用這些宏時不會出錯。
  
  一些感興趣的隊列進程隊列:struc proclist allproc 和 zombproc 也許你並不想修改這的東西因為進程調度的目的,除非你想重寫大部分的
  內核代碼,但是你可以過濾它當有用戶請求時。
  
  linker_files隊列:這個隊列中包括了連接到了kernel的文件,每個文件可以包含多個模塊,它的描述可以在這裡找到(THC art
  icle)這篇文章的連接是http://www.thehackerschoice.com/papers/bsdkern.html),自己找吧。:)這個隊列非常重要
  當我們改變符號的地址,或者隱瞞這個文件所包含的模塊。
  
  模塊隊列:module list_t 這個隊列包含了加載的內核模塊,注意這個模塊隊列區別於linker_files隊列,這對於隱藏模塊很重要。
  
  還是那句話,最好的文檔就是kernel source。
  
  2.3 讀寫內核內存
  
  模塊並不是唯一的修改內核的途徑,我們還可以直接修改內核空間通過/dev/kmem。
  
  2.3.1. 查找一個符號的地址
  
  當你處理內核內存時,你首先感興趣的是用來讀寫的符號的正確的地址(比如函數,變量),在freebsd中 函數Fvm(3)提供了一些有
  用的的功能請參考manpage查詢具體的用法,下面給出一個例子讀取指定的符號的地址 在CY 包中可以找到 tools/findsym.c.
  
  [...]
  char errbuf[_POSIX2_LINE_MAX];
  kvm_t *kd;
  struct nlist nl[] = { { NULL }, { NULL }, };
  
  nl[0].n_name = argv[1];
  
  kd = kvm_openfiles(NULL,NULL,NULL,O_RDONLY,errbuf);
  if(!kd) {
  fprintf(stderr,"ERROR: %s\n",errbuf);
  exit(-1);
  }
  
  if(kvm_nlist(kd,nl) < 0) {
  fprintf(stderr,"ERROR: %s\n",kvm_geterr(kd));
  exit(-1);
  }
  
  if(nl[0].n_value)
  printf("symbol %s is 0x%x at 0x%x\n",nl[0].n_name,nl[0].n_type,nl[0].n_value);
  else
  printf("%s not found\n",nl[0].n_name);
  
  if(kvm_close(kd) < 0) {
  fprintf(stderr,"ERROR: %s\n",kvm_geterr(kd));
  exit(-1);
  }
  [...]
  
  2.3.2 讀數據
  
  現在你找到了一些正確的符號地址(比如說函數,變量),你可能想要讀一些數據,利用函數kvm_read ,代碼tools/kvmread.c
  和tools/listprocs.c提供了一個例子。
  
  如果你想讀取隊列的全部,你只要找到隊列頭然後用next指針來找到下一個元素(結構體),同樣你可以獲得其他的數據通過
  這個struct 指針比如說用戶的表示符(在這個結構中包含了uid,euid) 下面給出了一個例子(在listproc.c),當我們找到了allproc的地址,這個隊列
  的頭就確定了
  [...]
  
  kvm_read(kd,nl[0].n_value, &allproc, sizeof(struct proclist)); //allproc 是所有進程的隊列頭
  
  printf("PID\tUID\n\n");
  
  for(p_ptr = allproc.lh_first; p_ptr; p_ptr = p.p_list.le_next) {
  
  /* read this proc structure */
  kvm_read(kd,(u_int32_t)p_ptr, &p, sizeof(struct proc)); //p_ptr指向結構proc 進程控制塊
  
  /* read the user credential */
  kvm_read(kd,(u_int32_t)p.p_cred, &cred, sizeof(struct pcred));//p_cred 指向包含ruid,suid的結構pcred
  
  
  printf("%d\t%
Copyright © Linux教程網 All Rights Reserved