乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      動態(tài)替換Linux核心函數(shù)的原理和實(shí)現(xiàn)

       老匹夫 2014-02-06

      1、 目的

      在調(diào)試核心模塊的過程中發(fā)現(xiàn),當(dāng)運(yùn)行了一段時間后內(nèi)核提供的函數(shù)在執(zhí)行的過程中表現(xiàn)出與預(yù)期不一致的狀態(tài),這種狀態(tài)有可能是核心模塊調(diào)用該函數(shù)時傳入的參數(shù)出現(xiàn)了異常造成的,也可能是Linux核心受插入模塊的影響,造成了其內(nèi)部狀態(tài)的不一致。此時需要有一種機(jī)制可以跟蹤察看被質(zhì)疑的函數(shù)的執(zhí)行流程。但是由于當(dāng)前的核心處于運(yùn)行狀態(tài),一貫被廣泛使用的在目標(biāo)函數(shù)中增加打印語句等方法需要重新編譯和啟動內(nèi)核,將會破壞難得的現(xiàn)場,因此不適用于這種場合,只有能夠動態(tài)替換動態(tài)運(yùn)行的內(nèi)核函數(shù)的機(jī)制才能起到真正的作用。

      回頁首

      2、 基本原理

      Linux操作系統(tǒng)在執(zhí)行程序(內(nèi)核也可以被看作正在運(yùn)行的大程序)時,需要兩個最為基本的前提條件:(1)存放參數(shù)、返回地址及局部變量的堆棧(stack);(2)可執(zhí)行程序二進(jìn)制代碼。在調(diào)用某一個函數(shù)執(zhí)行之前,需要在堆棧中為該函數(shù)準(zhǔn)備好傳入的參數(shù)、函數(shù)執(zhí)行完之后的返回地址,然后設(shè)置處理器的程序計數(shù)器(eip,指向處理器即將執(zhí)行的下一個條指令)為被調(diào)用函數(shù)的第一條執(zhí)行代碼的地址,這樣下一個處理器周期將跳轉(zhuǎn)到被調(diào)用函數(shù)處執(zhí)行。下圖所示為調(diào)用執(zhí)行函數(shù)func(parameter1, parameter2, ... parametern)時的場景,該函數(shù)可執(zhí)行代碼在內(nèi)核空間中的地址為func_addr:

      動態(tài)替換內(nèi)核涵數(shù)的目的或者想要達(dá)到的效果就是改變內(nèi)核原有的執(zhí)行流程,跳轉(zhuǎn)到由我們自己定制的函數(shù)流程上。從上述函數(shù)調(diào)用的原理圖可以看出,有三個地方可以作為函數(shù)替換的著手點(diǎn):

      (1) 修改堆棧
      但是,這種方式只能修改函數(shù)執(zhí)行的參數(shù)和返回地址,達(dá)不到改變執(zhí)行流程的目的;

      (2) 修改程序計數(shù)器的內(nèi)容
      在操作系統(tǒng)內(nèi)部無法直接給eip賦值,沒有提供這樣的指令碼;

      (3) 修改原函數(shù)代碼
      當(dāng)調(diào)用某個函數(shù)執(zhí)行時,eip將指向被調(diào)用函數(shù)代碼的起始地址,將根據(jù)該函數(shù)的第一條指令決定eip的下一個指向的值。因此我們可以在保留現(xiàn)有的堆棧內(nèi)容不變的情況下,修改原函數(shù)代碼的首部,使得它將eip的內(nèi)容跳轉(zhuǎn)到我們提供的替代函數(shù)代碼上。

      指令集中能夠跳轉(zhuǎn)程序執(zhí)行流程的指令有兩個:call和jmp。

      call是函數(shù)調(diào)用指令,由前面的論述知道,在call執(zhí)行之前,需要先在堆棧中設(shè)置好該函數(shù)執(zhí)行所需要的參數(shù),在此,由于進(jìn)入原函數(shù)之前已經(jīng)設(shè)置了參數(shù),所以我們必須將這些參數(shù)拷貝到堆棧頂部。這種拷貝過程涉及的堆棧地址與參數(shù)個數(shù)相關(guān),因此對不同的函數(shù)都需要重新計算,比較容易出錯。

      jmp是直接進(jìn)行正常的跳轉(zhuǎn)(類似c語言中的goto語句),可以繼續(xù)使用原函數(shù)準(zhǔn)備好的參數(shù)及返回地址信息,無需重新拷貝堆棧的內(nèi)容,因此相對而言比較安全,實(shí)現(xiàn)起來也更為方便。

      下圖是動態(tài)函數(shù)替換的一個場景示意圖。replace_func是func函數(shù)的替換函數(shù),其地址為new_address。

      整個替換過程由一個核心模塊來完成。該核心模塊在初始化時,用跳轉(zhuǎn)指令碼替換原函數(shù)func開始部分的指令代碼,使得這部分代碼變成一個條轉(zhuǎn)到函數(shù)replace_func的指令。同時為了最后能夠恢復(fù)原函數(shù)func,必須將原函數(shù)被替換部分的指令碼保存下來,這樣在我們達(dá)到預(yù)期的目的之后卸載模塊時,可用保存的指令碼重新覆蓋回原地址即可,這樣,當(dāng)后續(xù)內(nèi)核再次執(zhí)行函數(shù)func時,就又能夠繼續(xù)執(zhí)行該函數(shù)原來的執(zhí)行代碼,不會破壞內(nèi)核的狀態(tài)。

      回頁首

      3、 函數(shù)替換的實(shí)例

      在此,提供針對i386 32位平臺,版本為2.4.18 Linux環(huán)境下用上述描述的這種機(jī)制動態(tài)替換內(nèi)核函數(shù),比如vmtruncate、fget等函數(shù)的例子

      3.1. 前提條件

      在使用這種方法時,有兩個必須注意的前提條件:

      (1) 原函數(shù)正在被替換的時刻,也就是插入替換核心模塊時,沒有被其它進(jìn)程所使用,否則其結(jié)果有可能造成內(nèi)核狀態(tài)不一致的現(xiàn)象。

      (2) 替換函數(shù)和原函數(shù)具有相同的參數(shù)列表,且對應(yīng)次序上的參數(shù)類型相同,參數(shù)個數(shù)相同,同時函數(shù)具有相同的返回值。一般來說,我們替換核心函數(shù)的目的并不是改變它的功能而是要跟蹤該函數(shù)的執(zhí)行流程是否出現(xiàn)異常,各變量和參數(shù)是否具有預(yù)期的值,因此,替換函數(shù)和原函數(shù)具有相同的功能。

      3.2. 替換過程

      整個替換流程的實(shí)現(xiàn)分為如下幾個步驟:

      (1) 替換指令碼:
      b8 00 00 00 00 /*movl $0, $eax;這里的$0將被具體替換函數(shù)的地址所取代*/
      ff e0 /*jmp *$eax ;跳轉(zhuǎn)函數(shù)*/
      將上述7個指令碼存放在一個字符數(shù)組中:
      replace_code[7]

      (2) 用替換函數(shù)的地址覆蓋第一條指令中的后面8個0,并保留原來的指令碼:
      memcpy (orig_code, func, 7); /* 保留原函數(shù)的指令碼 */
      *((long*)&replace_code[1])= (long) replace_func; /* 賦替換函數(shù)的地址 */
      memcpy (func, replace_code, 7); /* 用新的指令碼替換原函數(shù)指令碼 */

      (3) 恢復(fù)過程用保留的指令碼覆蓋原函數(shù)代碼:
      memcpy (func, orig_code, 7)

      3.3. 替換vmtruncate函數(shù)

      下面給出的是替換內(nèi)核函數(shù)vmtruncate的詳細(xì)內(nèi)核模塊實(shí)現(xiàn)代碼:

      #ifndef __KERNEL__
      #define __KERNEL__
      #endif
      #ifndef MODULE
      #define MODULE
      #endif
      #include <linux/kernel.h>
      #include <linux/config.h>
      #include <linux/module.h>
      #include <asm/string.h>
      #include <asm/unistd.h>
      #include <linux/fs.h>
      #include <linux/sched.h>
      #include <linux/mm.h>
      #include <linux/pagemap.h>
      #include <asm/smplock.h>
      int (*orig_vmtruncate) (struct inode * inode, loff_t offset) = (int(*)
      (struct inode *inode, loff_t offset))0xc0125d70; 
      /* 原vmtruncate函數(shù)的地址0xc0125d70可到system.map文件中查找*/
      #define CODESIZE 7    /*替換代碼的長度 */
      static char orig_code[7];  /*保存原vmtruncate函數(shù)被覆蓋部分的執(zhí)行碼 */
      static char code[7] = 
                  "\xb8\x00\x00\x00\x00"
      "\xff\xe0";  /* 替換碼 */
      /* 如果該函數(shù)沒有export出來,則需要自己實(shí)現(xiàn),供vmtruncate調(diào)用 */
      static void _vmtruncate_list(struct vm_area_struct *mpnt, unsigned long pgoff)
      {
      	do {
      		struct mm_struct *mm = mpnt->vm_mm;
      		unsigned long start = mpnt->vm_start;
      		unsigned long end = mpnt->vm_end;
      		unsigned long len = end - start;
      		unsigned long diff;
      		if (mpnt->vm_pgoff >= pgoff) {
      			zap_page_range(mm, start, len);
      			continue;
      		}
      		len = len >> PAGE_SHIFT;
      		diff = pgoff - mpnt->vm_pgoff;
      		if (diff >= len)
      			continue;
      		start += diff << PAGE_SHIFT;
      		len = (len - diff) << PAGE_SHIFT;
      		zap_page_range(mm, start, len);
      	} while ((mpnt = mpnt->vm_next_share) != NULL);
      }
      /* vmtruncate的替換函數(shù) */
      int _vmtruncate(struct inode * inode, loff_t offset)
      {
      	unsigned long pgoff;
      	struct address_space *mapping = inode->i_mapping;
      	unsigned long limit;
          /* 在該函數(shù)中我們增加了許多判斷參數(shù)的打印信息 */
      	printk (KERN_ALERT "Enter into my vmtruncate, pid: %d\n",
                      current->pid);
      	printk (KERN_ALERT "inode->i_ino: %d, inode->i_size: %d, pid: %d\n",
                                  inode->i_ino, inode->i_size, current->pid);
      	printk (KERN_ALERT "offset: %ld, pid: %d\n", offset, current->pid);
      	printk (KERN_ALERT "Do nothing, pid: %d\n", current->pid);
      	return 0;
      	if (inode->i_size < offset)
      		goto do_expand;
      	inode->i_size = offset;
      	spin_lock(&mapping->i_shared_lock);
      	if (!mapping->i_mmap && !mapping->i_mmap_shared)
      		goto out_unlock;
      	pgoff = (offset + PAGE_CACHE_SIZE - 1) >> PAGE_CACHE_SHIFT;
      	printk (KERN_ALERT "Begin to truncate mmap list, pid: %d\n",
      		current->pid);
      	if (mapping->i_mmap != NULL)
      		_vmtruncate_list(mapping->i_mmap, pgoff);
      	if (mapping->i_mmap_shared != NULL)
      		_vmtruncate_list(mapping->i_mmap_shared, pgoff);
      out_unlock:
      	printk (KERN_ALERT "Before to truncate inode pages, pid:%d\n",
      		current->pid);
      	spin_unlock(&mapping->i_shared_lock);
      	truncate_inode_pages(mapping, offset);
      	goto out_truncate;
      do_expand:
      	limit = current->rlim[RLIMIT_FSIZE].rlim_cur;
      	if (limit != RLIM_INFINITY && offset > limit)
      		goto out_sig;
      	if (offset > inode->i_sb->s_maxbytes)
      		goto out;
      	inode->i_size = offset;
      out_truncate:
      	printk (KERN_ALERT "Come to out_truncate, pid: %d\n",
      		current->pid);
      	if (inode->i_op && inode->i_op->truncate) {
      		lock_kernel();
      		inode->i_op->truncate(inode);
      		unlock_kernel();
      	}
      	printk (KERN_ALERT "Leave, pid: %d\n", current->pid);
      	return 0;
      out_sig:
      	send_sig(SIGXFSZ, current, 0);
      out:
      	return -EFBIG;
      }
      /* 核心中內(nèi)存拷貝的函數(shù),用于拷貝替換代碼 */
      void* _memcpy (void *dest, const void *src, int size)
      {
      	const char *p = src;
      	char *q = dest;
      	int i;
      	for (i=0; i<size; i++) *q++ = *p++;
      	return dest;
      } 
      int init_module (void)
      {
      	*(long *)&code[1] = (long)_vmtruncate; /* 賦替換函數(shù)地址 */
      	_memcpy (orig_code, orig_vmtruncate, CODESIZE);
      	_memcpy (orig_vmtruncate, code, CODESIZE);
      	return 0;
      }
      void cleanup_module (void)
      {
          /* 卸載該核心模塊時,恢復(fù)原來的vmtruncate函數(shù) */
      	_memcpy (orig_vmtruncate, orig_code, CODESIZE);
      }

      3.4. 替換fget函數(shù)

      下面是替換fget函數(shù)的實(shí)現(xiàn)代碼:

      #ifndef __KERNEL__
      #define __KERNEL__
      #endif
      #ifndef MODULE
      #define MODULE
      #endif
      #include <linux/kernel.h>
      #include <linux/config.h>
      #include <linux/module.h>
      #include <asm/string.h>
      #include <asm/unistd.h>
      #include <linux/fs.h>
      #include <linux/sched.h>
      #include <asm/smplock.h>
      struct file * (*orig_fget) (unsigned int fd) = 
          (struct file * (*)(unsigned int))0xc0138800; /*原fget函數(shù)的地址 */
      #define CODESIZE 7
      static char orig_fget_code[7];
      static char fget_code[7] = 
                  "\xb8\x00\x00\x00\x00"
                  "\xff\xe0";
      void* _memcpy (void *dest, const void *src, int size)
      {
        const char *p = src;
        char *q = dest;
        int i;
        for (i=0; i<size; i++) *q++ = *p++;
        return dest;
      } 
      /* 如果該函數(shù)沒有export出來,則需要自己實(shí)現(xiàn) */
      static inline struct file * _fcheck (unsigned int fd)
      {
        struct file * file = NULL;
        struct files_struct  *files = current->files;
        
        if (fd < files->max_fds)
          file = files->fd[fd];
        return file;
      }
      /* 替換fget的函數(shù) */
      struct file* _fget (unsigned int fd)
      {
        struct file * file;
        struct files_struct *files = current->files;
        
        read_lock(&files->file_lock); 
        file = _fcheck (fd);
        if (file)  {
          struct dentry *dentry = file -> f_dentry;
          struct inode  *inode;
          if (dentry && dentry->d_inode) {
            inode = dentry -> d_inode;
            if (inode->i_ino == 298553) {
                      /* 在此,我們打印出所關(guān)心的變量的信息,以供查詢 */
              printk ("Enter into my fget for file: name: %s, ino: %d\n",
                       dentry->d_name.name,
                 inode->i_ino);
            }
          }  
          get_file(file);
        }
        read_unlock (&files->file_lock);
        return file;
      }
      int init_module (void)
      {
        lock_kernel();
        *(long *)&fget_code[1] = (long)_fget;
        _memcpy (orig_fget_code, orig_fget, CODESIZE);
        _memcpy (orig_fget, fget_code, CODESIZE);
        unlock_kernel();
        return 0;
      }
      void cleanup_module (void)
      {
          /* 卸載模塊,恢復(fù)原函數(shù) */
        _memcpy (orig_fget, orig_fget_code, CODESIZE);
      }

      回頁首

      4、 該方法的局限性

      在替換前需要定制自己的替換函數(shù),同時必須能夠查到被替換函數(shù)在該運(yùn)行核心中的地址(通過System.map或/proc/ksyms)。另外在對目標(biāo)計算機(jī)上的函數(shù)進(jìn)行替換之前,最好先在其它具有相同硬件平臺和操作系統(tǒng)核心的節(jié)點(diǎn)上先做通試驗,因為自己寫的替換函數(shù)往往會存在一些問題而無法一次就通,以免造成不必要的麻煩。

        本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報。
        轉(zhuǎn)藏 分享 獻(xiàn)花(0

        0條評論

        發(fā)表

        請遵守用戶 評論公約

        類似文章 更多