簡體   English   中英

我可以用模塊替換Linux內核函數嗎?

[英]Can I replace a Linux kernel function with a module?

我正在進行內核工作以進行一些夏季研究。 我們希望在特定的RTT計算中對TCP進行修改。 我想要做的是將tcp_input.c中的一個函數的分辨率替換為動態加載的內核模塊提供的函數。 我認為這將提高我們開發和分發修改的速度。

我感興趣的函數被聲明為static,但是我已經使用非靜態函數重新編譯內核並通過EXPORT_SYMBOL導出。 這意味着該功能現在可供內核的其他模塊/部分訪問。 我通過“cat / proc / kallsyms”驗證了這一點。

現在我希望能夠加載一個可以重寫符號地址的模塊,從初始到動態加載的函數。 同樣,當要卸載模塊時,它將恢復原始地址。 這是一種可行的方法嗎? 你們都有建議如何更好地實施這項建議嗎?

謝謝!

Linux內核中的模塊的覆蓋功能相同

編輯:
這是我的最終方法。
給定以下函數(我想要覆蓋,並且不導出):

static void internal_function(void) 
{
  // do something interesting
  return;
}

像這樣修改:

static void internal_function_original(void)
{
  // do something interesting
  return;
}

static void (*internal_function)(void) = &internal_function_original;
EXPORT_SYMBOL(internal_function);

這將重新定義了預期的函數標識符,而不是指向原始實現的函數指針(可以以類似的方式調用)。 EXPORT_SYMBOL()使地址可以全局訪問,因此我們可以從模塊(或其他內核位置)修改它。

現在您可以使用以下格式編寫內核模塊:

static void (*original_function_reference)(void);
extern void (*internal_function)(void);

static void new_function_implementation(void)
{
  // do something new and interesting
  // return
}

int init_module(void)
{
  original_function_reference = internal_function;
  internal_function           = &new_function_implementation;
  return 0;
}

void cleanup_module(void)
{
  internal_function = original_function_reference;
}

此模塊使用動態加載的版本替換原始實現。 卸載后,將恢復原始引用(和實現)。 在我的具體案例中,我為TCP中的RTT提供了一個新的估算器。 通過使用模塊,我可以進行小的調整並重新開始測試,所有這些都無需重新編譯和重新啟動內核。

我不確定它是否可行 - 我相信對於要替換的函數的內部調用的符號解析將在模塊加載時完成。

相反,您可以通過重命名現有函數來更改代碼,然后使用函數的原始名稱創建全局函數指針。 初始化函數指針指向內部函數的地址,因此現有代碼將不加修改地工作。 導出全局函數指針的符號,然后您的模塊可以通過在模塊加載和卸載時分配來更改其值。

您可以嘗試使用ksplice - 您甚至不需要將其設置為非靜態。

我曾經做過一個劫持模塊概念的證明,它插入了它自己的函數來代替內核函數。 我碰巧新的內核調整架構使用了一個非常相似的系統。

我通過使用指向我的自定義函數的跳轉覆蓋前幾個字節的代碼,在內核中注入了我自己的函數。 一旦真正的函數被調用,它就會跳轉到我的函數,在它完成它的工作后稱為原始函數。


#include <linux/module.h>
#include <linux/kernel.h>

#define CODESIZE 12

static unsigned char original_code[CODESIZE];
static unsigned char jump_code[CODESIZE] =
    "\x48\xb8\x00\x00\x00\x00\x00\x00\x00\x00" /* movq $0, %rax */
    "\xff\xe0"                                          /* jump *%rax */
        ;
/* FILL THIS IN YOURSELF */
int (*real_printk)( char * fmt, ... ) = (int (*)(char *,...) )0xffffffff805e5f6e;

int hijack_start(void);
void hijack_stop(void);
void intercept_init(void);
void intercept_start(void);
void intercept_stop(void);
int fake_printk(char *, ... );


int hijack_start()
{
    real_printk(KERN_INFO "I can haz hijack?\n" );
    intercept_init();
    intercept_start();

    return 0;
}

void hijack_stop()
{
    intercept_stop();
    return;
}

void intercept_init()
{
    *(long *)&jump_code[2] = (long)fake_printk;
    memcpy( original_code, real_printk, CODESIZE );

    return;
}

void intercept_start()
{
    memcpy( real_printk, jump_code, CODESIZE );
}

void intercept_stop()
{
    memcpy( real_printk, original_code, CODESIZE );
}

int fake_printk( char *fmt, ... )
{
    int ret;
    intercept_stop();
    ret = real_printk(KERN_INFO "Someone called printk\n");
    intercept_start();
    return ret;
}

module_init( hijack_start );
module_exit( hijack_stop );

我警告你,當你要嘗試這些事情時,要注意內核恐慌和其他災難性事件。 我建議你在虛擬化環境中這樣做。 這是我剛才寫的一個概念驗證代碼,我不確定它是否仍然有用。

這是一個非常簡單的原則,但非常有效。 當然,一個真正的解決方案是使用鎖來確保在你覆蓋它時沒有人會調用該函數。

玩得開心!

我想你想要的是Kprobe

caf提到的另一種方法是在原始例程中添加一個鈎子,並在模塊中注冊/取消注冊鈎子。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM