a亚洲精品_精品国产91乱码一区二区三区_亚洲精品在线免费观看视频_欧美日韩亚洲国产综合_久久久久久久久久久成人_在线区

首頁 > 學(xué)院 > 操作系統(tǒng) > 正文

kernel hacking簡單入門

2024-07-26 00:30:28
字體:
供稿:網(wǎng)友

By: w00w00 Security Development article by Nicolas Dubee
譯:大鷹

以下代碼均在linux i86 2.0.x的內(nèi)核下面測試通過。它也許可以在之前的版本通
過, 但并沒有被測試過. 因?yàn)閺?.1.x內(nèi)核版本就引入了相當(dāng)大的改變, 顯著地內(nèi)存
管理上的差別, 但這些不是我們現(xiàn)在要討論的內(nèi)容.

Thanks to Halflife who first got the idea to use lkm for malicious
purposes, and tiepilot, my living hero.(一些感謝信息就不翻啦,西西)



用戶空間與內(nèi)核空間
---------------------------

linux是一個(gè)具有保護(hù)模式的操作系統(tǒng)。它一直工作在i386 cpu的保護(hù)模式之下。
內(nèi)存被分為兩個(gè)單元: 內(nèi)核區(qū)域和用戶區(qū)域。(譯者注:我覺得還是這樣叫比較順口)
內(nèi)核區(qū)域存放并運(yùn)行著核心代碼, 當(dāng)然,顧名思義,用戶區(qū)域也存放并運(yùn)行用戶程序。
當(dāng)然,作為用戶進(jìn)程來講它是不能訪問內(nèi)核區(qū)域內(nèi)存空間以及其他用戶進(jìn)程的地址
空間的。

不幸地是, 核心進(jìn)程也有同樣的情況。 核心代碼也同樣不能訪問用戶區(qū)地地址空間。
那么,這樣做到底有什么意義呢?好, 我們假設(shè)當(dāng)一個(gè)硬件驅(qū)動(dòng)試圖去寫數(shù)據(jù)到一個(gè)用
戶內(nèi)存空間的程序里的時(shí)候, 它是不可以直接去完成的, 但是它可以利用一些特殊的核
心函數(shù)來間接完成。同樣, 當(dāng)參數(shù)需要傳遞地址到核心函數(shù)中時(shí),核心函數(shù)也不能直接
的來讀取該參數(shù)。同樣的,它可以利用一些特殊的核心函數(shù)來傳遞參數(shù)。

這里有一些比較有用的核心函數(shù)用來作為內(nèi)核區(qū)與用戶區(qū)相互傳遞參數(shù)用。

#include 

get_user(ptr)
 從用戶內(nèi)存獲取給定的字節(jié), 字,或者長整形。這只是一個(gè)宏(在核心代碼里面有此宏
的詳細(xì)定義),并且它依據(jù)參數(shù)類型來確定傳輸數(shù)量。所以你必須巧妙地利用它。
 
put_user(ptr)
 和get_user()非常相似, 但,它不是從用戶內(nèi)存讀取數(shù)據(jù),而是想用戶內(nèi)存寫數(shù)據(jù)。
 
memcpy_fromfs(void *to, const void *from,unsigned long n)
 從用戶內(nèi)存中的*from拷貝n個(gè)字節(jié)到指向核心內(nèi)存的指針*to。
 
memcpy_tofs(void *to,const *from,unsigned long n)
 從核心內(nèi)存中的*from拷貝n個(gè)字節(jié)數(shù)據(jù)到用戶內(nèi)存中的*to。

/*譯者注:這四個(gè)函數(shù)足以在2.0.x中解決內(nèi)核和用戶區(qū)的參數(shù)傳遞問題,在2.0.x以上
的版本有新的實(shí)現(xiàn),即copy_user_to(...)以及copy_user_from(...)根據(jù)內(nèi)核版本這些
特殊函數(shù)會(huì)有不同,請(qǐng)關(guān)注核心代碼的實(shí)現(xiàn)方法。*/


系統(tǒng)調(diào)用
------------

大部分的c函數(shù)庫的調(diào)用都依賴于系統(tǒng)調(diào)用, 就是一些使用戶程序可以調(diào)用的簡單
核心包裝函數(shù)。 這些系統(tǒng)調(diào)用運(yùn)行在內(nèi)核本身或者在可加載內(nèi)核模塊中, 就是一些
可動(dòng)態(tài)的加載卸載的核心代碼。

就象MS-DOS和其他許多系統(tǒng)一樣, linux中的系統(tǒng)調(diào)用依賴一個(gè)給定的中斷來調(diào)用多
個(gè)系統(tǒng)調(diào)用。linux系統(tǒng)中,這個(gè)中斷就是int 0x80。當(dāng)調(diào)用'int 0x80'中斷的時(shí)候,
控制權(quán)就轉(zhuǎn)交給了內(nèi)核(或者,我們確切點(diǎn)地說, 交給_system_call()這個(gè)函數(shù)), 
并且實(shí)際上是一個(gè)正在進(jìn)行的單處理過程。

* _system_call()是如何工作的 ?

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

這兒是一個(gè)有問題的系統(tǒng)調(diào)用例程:

[root@plaguez kernel]# cat no1.c
#include 
#include 
#include 

extern void *sys_call_table[];

sc()
{ // 165這個(gè)系統(tǒng)調(diào)用號(hào)是不存在的。
    __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


系統(tǒng)控制權(quán)就會(huì)轉(zhuǎn)向真正的系統(tǒng)調(diào)用, 用來完成你的請(qǐng)求并返回。 然后_system_call()
調(diào)用_ret_from_sys_call()來檢查不同的返回值, 并且最后返回到用戶內(nèi)存。


* libc

這int $0x80 并不是直接被用作系統(tǒng)調(diào)用; 更確切地是, libc函數(shù),經(jīng)常用來包裝0x80中斷,
這樣使用的。

libc通常利用_syscallX()宏來描述系統(tǒng)調(diào)用, X是系統(tǒng)調(diào)用的總參數(shù)個(gè)數(shù)。

舉個(gè)例子吧, libc中的write(2)就是利用_syscall3這個(gè)系統(tǒng)調(diào)用宏來實(shí)現(xiàn)的, 因?yàn)閷?shí)際的
write(2)原型需要3個(gè)參數(shù)。在調(diào)用0x80中斷之前,這個(gè)_syscallX宏假定系統(tǒng)調(diào)用的堆棧結(jié)
構(gòu)和要求的參數(shù)列表,最后,當(dāng)_system_call()(通過int &0x80來引發(fā))返回的時(shí)候,
_syscallX()宏將會(huì)查出錯(cuò)誤的返回值(在%eax)并且為其設(shè)置errno。

讓我們看一下另一個(gè)write(2)例程并看看它是如何進(jìn)行預(yù)處理的。

[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);/*構(gòu)建一個(gè)write調(diào)用*/

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

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



* 構(gòu)建你自己的系統(tǒng)調(diào)用。

這里給出了幾個(gè)構(gòu)建你自己的系統(tǒng)調(diào)用的方法。
舉個(gè)例子, 你可以修改內(nèi)核代碼并且加入你自己的代碼。一個(gè)比較簡單可行的方法, 
不過, 一定要被寫成可加載內(nèi)核模塊。

沒有一個(gè)代碼可以象可加載內(nèi)核模塊那樣可以當(dāng)內(nèi)核需要的時(shí)候被隨時(shí)加載的。

我們的主要意圖是需要一個(gè)很小的內(nèi)核,當(dāng)我們需要的時(shí)候運(yùn)行insmod命令,給定的驅(qū)動(dòng)就可以被
自動(dòng)加載。這樣卸除來的lkm程序一定比在內(nèi)核代碼樹里寫代碼要簡單易行多了。

* 寫lkm程序

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

這里有一個(gè)經(jīng)典的lkm代碼結(jié)構(gòu):


#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調(diào)用 */
_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講要被運(yùn)行在內(nèi)核狀態(tài),我們就不能用libc包裝的函數(shù)了, 但是我們可以
通過前面所討論的_syscallX()宏來構(gòu)建系統(tǒng)調(diào)用。

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

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



* 內(nèi)核和系統(tǒng)調(diào)用后門

在簡單介紹了上述理論,我們主要可以用來做什么呢。我們可以利于lkm截獲一些對(duì)我們有影響的系
統(tǒng)調(diào)用, 這樣可以強(qiáng)制內(nèi)核按照我們的方式運(yùn)行。例如:我們可以利用ioctl系統(tǒng)調(diào)用來隱藏sniffer
所造成的網(wǎng)卡PROMISC模式的顯示。非常有效。

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


譯者后話:這篇文章相對(duì)比較淺顯易懂,所以可以作為大家入門lkm編程來用,它著重講述了linux系
統(tǒng)調(diào)用system call的原理,以及我們?nèi)绾瓮ㄟ^lkm來截獲它并換成我們想要的代碼來建立后門程序。
再次強(qiáng)調(diào)本文的依據(jù)是linux內(nèi)核版本2.0.x,大家在自己系統(tǒng)實(shí)現(xiàn)時(shí)請(qǐng)對(duì)比內(nèi)核代碼來做改變。

發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 国产精品福利在线 | 国产免费拔擦拔擦8x高清 | 吊视频一区二区三区 | 日韩激情在线观看 | 精品中文在线 | 得得啪在线视频 | 国产欧美日韩一区二区三区 | 91日日 | 狠狠躁夜夜躁人人爽天天高潮 | 久久久久亚洲精品 | 久久9久久 | 99视频只有精品 | 中文字幕在线资源 | 高清日韩av | 韩国精品视频在线观看 | 成人深夜福利视频 | 色综合一区| 亚洲免费在线观看 | 欧美日本免费一区二区三区 | 久久精品视频免费 | 韩国精品主播一区二区在线观看 | 国产精品一区二区在线看 | 久久青 | 密臀av | 精品久久久久一区二区国产 | 日本中文在线 | 麻豆一区一区三区四区 | 日韩有码一区二区三区 | 亚洲人成人一区二区在线观看 | 久草在线在线精品观看 | 成人免费毛片嘿嘿连载视频 | 精品国产一区二区三区久久久蜜月 | av一区二区三区 | 日韩一区二区在线免费观看 | 国产超碰人人模人人爽人人添 | 国产精品禁久久精品 | 久久亚洲国产精品日日av夜夜 | 久久久久久久久久久国产精品 | 欧美性久久 | 精品国产一区二区三区性色av | 色婷婷狠狠 |