1. gzyueqian
      13352868059
      首頁 > 新聞中心 > > 正文

      關于Linux內核級后門的原理和簡單實戰

      更新時間: 2006-03-10 15:43:46來源: 粵嵌教育瀏覽量:2723

        以下代碼均在linux i86 2.0.x的內核下面測試通過。它也許可以在之前的版本通過, 但并沒有被測試過. 因為從2.1.x內核版本就引入了相當大的改變, 顯著地內存管理上的差別, 但這些不是我們現在要討論的內容.

        用戶空間與內核空間
        ---------------------------

        linux是一個具有保護模式的操作系統。它一直工作在i386 cpu的保護模式之下。

        內存被分為兩個單元: 內核區域和用戶區域。(譯者注:我覺得還是這樣叫比較順口)內核區域存放并運行著核心代碼,當然,顧名思義,用戶區域也存放并運行用戶程序。當然,作為用戶進程來講它是不能訪問內核區域內存空間以及其他用戶進程的地址空間的。

        不幸地是, 核心進程也有同樣的情況。核心代碼也同樣不能訪問用戶區地地址空間。

        那么,這樣做到底有什么意義呢?好, 我們假設當一個硬件驅動試圖去寫數據到一個用戶內存空間的程序里的時候, 它是不可以直接去完成的, 但是它可以利用一些特殊的核心函數來間接完成。同樣, 當參數需要傳遞地址到核心函數中時,核心函數也不能直接的來讀取該參數。同樣的,它可以利用一些特殊的核心函數來傳遞參數。

        這里有一些比較有用的核心函數用來作為內核區與用戶區相互傳遞參數用。

        #include

        get_user(ptr)

        從用戶內存獲取給定的字節, 字,或者長整形。這只是一個宏(在核心代碼里面有此宏的詳細定義),并且它依據參數類型來確定傳輸數量。所以你必須巧妙地利用它。

        put_user(ptr)和get_user()非常相似, 但,它不是從用戶內存讀取數據,而是想用戶內存寫數據。

        memcpy_fromfs(void *to, const void *from,unsigned long n)
        從用戶內存中的*from拷貝n個字節到指向核心內存的指針*to。

        memcpy_tofs(void *to,const *from,unsigned long n)
        從核心內存中的*from拷貝n個字節數據到用戶內存中的*to。

        /*譯者注:這四個函數足以在2.0.x中解決內核和用戶區的參數傳遞問題,在2.0.x以上的版本有新的實現,即copy_user_to(...)以及copy_user_from(...)根據內核版本這些特殊函數會有不同,請關注核心代碼的實現方法。*/

        系統調用

        大部分的c函數庫的調用都依賴于系統調用, 就是一些使用戶程序可以調用的簡單核心包裝函數。 這些系統調用運行在內核本身或者在可加載內核模塊中, 就是一些可動態的加載卸載的核心代碼。

        就象MS-DOS和其他許多系統一樣, linux中的系統調用依賴一個給定的中斷來調用多個系統調用。linux系統中,這個中斷就是int 0x80。當調用'int 0x80'中斷的時候,控制權就轉交給了內核(或者,我們確切點地說, 交給_system_call()這個函數), 并且實際上是一個正在進行的單處理過程。

      * _system_call()是如何工作的 ?

        首先, 所有的寄存器被保存并且%eax寄存器全面檢查系統調用表, 這張表列舉了所有的系統調用和他們的地址信息。它可以通過extern void *sys_call_table[]來被訪問到。 該表中的每個定義的數值和內存地址都對應每個系統調用。大家可以在/usr/include/sys/syscall.h這個頭中找到系統調用的標示數。他們對應相應的SYS_systemcall名。假如一個系統調用不存在, 那么它在sys_call_table中相應的標示就為0, 并且返回一個出錯信息。否則,系統調用存在并在表里相應的入口為系統調用代碼的內存地址。

        這兒是一個有問題的系統調用例程:

       

      [root@plaguez kernel]# cat no1.c
      #include
      #include
      #include
      extern void *sys_call_table[];
      sc()
      { // 165這個系統調用號是不存在的。
          __asm__(
              "movl $165,%eax
                   int $0x80");
      }
      main()
      {
          errno = -sc();
          perror("test of invalid syscall");
      }
      [root@plaguez kernel]# gcc no1.c
      [root@plaguez kernel]# ./a.out
      test of invalid syscall: Function not implemented
      [root@plaguez kernel]# exit

       

        系統控制權就會轉向真正的系統調用, 用來完成你的請求并返回。 然后_system_call()調用_ret_from_sys_call()來檢查不同的返回值, 并且返回到用戶內存。

        * libc

        這int $0x80 并不是直接被用作系統調用; 更確切地是, libc函數,經常用來包裝0x80中斷,這樣使用的。

        libc通常利用_syscallX()宏來描述系統調用, X是系統調用的總參數個數。

        舉個例子吧, libc中的write(2)就是利用_syscall3這個系統調用宏來實現的, 因為實際的write(2)原型需要3個參數。在調用0x80中斷之前,這個_syscallX宏假定系統調用的堆棧結構和要求的參數列表,,當_system_call()(通過int &0x80來引發)返回的時候,_syscallX()宏將會查出錯誤的返回值(在%eax)并且為其設置errno。

      讓我們看一下另一個write(2)例程并看看它是如何進行預處理的。

       

      [root@plaguez kernel]# cat no2.c
      #include
      #include
      #include
      #include
      #include
      #include
      #include
      #include
      #include
      _syscall3(ssize_t,write,int,fd,const void *,buf,size_t,count);/*構建一個write調用*/
      main()
      {
          char *t = "this is a test.\n";
          write(0, t, strlen(t));
      }
      [root@plaguez kernel]# gcc -E no2.c > no2.C
      [root@plaguez kernel]# indent no2.C -kr
      indent:no2.C:3304: Warning: old style assignment ambiguity in "=-".  Assuming "= -"
      [root@plaguez kernel]# tail -n 50 no2.C

       

      #9 "no2.c" 2


      ssize_t write(int fd, const void *buf, size_t count)
      {
          long __res;
          __asm__ __volatile("int $0x80":"=a"(__res):"0"(4), "b"((long) (fd)), "c"((long) (buf)), "d"((long) (count)));
          if (__res >= 0)
          return (ssize_t) __res;
          errno = -__res;
          return -1;
      };
      main()
      {
          char *t = "this is a test.\n";
          write(0, t, strlen(t));
      }
      [root@plaguez kernel]# exit

       

        注意那個write()里的"0"這個參數匹配SYS_write,在/usr/include/sys/syscall.h中定義。

        * 構建你自己的系統調用。

        這里給出了幾個構建你自己的系統調用的方法。舉個例子, 你可以修改內核代碼并且加入你自己的代碼。一個比較簡單可行的方法, 不過, 一定要被寫成可加載內核模塊。

        沒有一個代碼可以象可加載內核模塊那樣可以當內核需要的時候被隨時加載的。

        我們的主要意圖是需要一個很小的內核,當我們需要的時候運行insmod命令,給定的驅動就可以被自動加載。這樣卸除來的lkm程序一定比在內核代碼樹里寫代碼要簡單易行多了。

        * 寫lkm程序

        一個lkm程序可以用c來很容易編寫出來。它包含了大量的 #defines, 一些函數, 一個初始化模塊的函數,叫做init_module(),和一個卸載函數:cleanup_module()。

      這里有一個經典的lkm代碼結構:


      #define MODULE
      #define __KERNEL__
      #define __KERNE_SYSCALLS__
      #include
      #ifdef MODULE
      #include
      #include
      #else
      #define MOD_INC_USE_COUNT
      #define MOD_DEC_USE_COUNT
      #endif
      #include
      #include
      #include
      #include
      #include
      #include
      #include
      #include
      #include
      #include
      #include
      #include
      #include
      int errno;
      char tmp[64];
      /* 假如,我們要用到ioctl調用 */
      _syscall3(int, ioctl, int, d, int, request, unsigned long, arg);
      int myfunction(int parm1,char *parm2)
      {
         int i,j,k;
         /* ... */
      }
      int init_module(void)
      {
         /* ... */
         printk("\nModule loaded.\n");
         return 0;
      }
      void cleanup_module(void)
      {
         /* ... */
      }
      檢查代碼中的 #defines (#define MODULE, #define __KERNEL__)和
      #includes (#include ...)

       

        一定要注意的是我們的lkm講要被運行在內核狀態,我們就不能用libc包裝的函數了, 但是我們可以通過前面所討論的_syscallX()宏來構建系統調用。

        你可以這樣編譯你的模塊'gcc -c -O3 module.c' 并且利用'insmod module.o'來加載。

        提一個建議, lkm也可以用來在不完全重建核心代碼的情況下來修改內核代碼。舉個例子, 你可以修改write系統調用讓它隱藏一部分給定的文件,就象我們把我們的backdoors放到一個非常好的地方:當你無法再信任你的系統內核的時候會怎么樣呢?

        * 內核和系統調用后門

        在簡單介紹了上述理論,我們主要可以用來做什么呢。我們可以利于lkm截獲一些對我們有影響的系統調用, 這樣可以強制內核按照我們的方式運行。例如:我們可以利用ioctl系統調用來隱藏sniffer所造成的網卡PROMISC模式的顯示。非常有效。

        去改變一個給定的系統調用,只需要在你的lkm程序中增加一個定義extern void *sys_call_table[],并且利用init_module()函數來改變sys_call_table里的入口來指向我們自己的代碼。改變后的調用可以做我們希望它做的一切事情, 利用改變sys_call_table來導出更多的原系統調用,并且。。。。


        譯者后話:這篇文章相對比較淺顯易懂,所以可以作為大家入門lkm編程來用,它著重講述了linux系統調用system call的原理,以及我們如何通過lkm來截獲它并換成我們想要的代碼來建立后門程序。再次強調本文的依據是linux內核版本2.0.x,大家在自己系統實現時請對比內核代碼來做改變。

      免費預約試聽課

      亚洲另类欧美综合久久图片区_亚洲中文字幕日产无码2020_欧美日本一区二区三区桃色视频_亚洲AⅤ天堂一区二区三区

      
      

      1. 亚洲人成自拍网站在线观看 | 天天免费在线视频网 | 日本欧美日韩亚洲综合区 | 一级爱视频欧美国产 | 亚洲乱码一二三四区 | 亚洲一区天堂午夜 |