當運行 Linux 內核的機器死機時……

2020-12-23 CSDN

【CSDN 編者按】事件陷入死地無可挽救之際,可能會有人選擇不了了之,有人選擇就此放棄……但換個思路想一想,既然都無可挽回了,那幹嘛不試試弄點有價值的信息回來?

作者 | dog250 責編 | 張文

頭圖 | CSDN 下載自視覺中國

曾經寫過一個模塊,當運行 Linux 內核的機器死機時,SSH肯定無法登錄了,但只要它還響應中斷,就盡力讓它可以通過網絡帶回一些信息。陳年的事了:https://blog.csdn.net/dog250/article/details/43370611

今日重提這件事,不陌生,但糾結。

本文不談 sysrq,也不談別的。

Linux 內核在發生 soft lockup 的時候,是可以 ping 通的,只要沒有關中斷,ping 通一般沒有問題。既然可以 ping 通,何必不帶回一些真正重要的信息而不僅僅是 echo 的 reply?

且慢,你可能會覺得這一切沒有意義,懂 kdump 的人都會這麼抬槓,畢竟如果這個時候讓內核 panic 掉,保留一個 vmcore,事後便可以隨便分析了。

哈哈,我也不是不懂 kdump,我當然懂得如何分析 vmcore,我只是不信任它而已。我不覺得它保留有足夠的信息,相比之下,我只想知道在事故發生的當時,到底發生了什麼。因此,我需要儘可能的去 debug 將死未死的系統,也就是說,我想要獲取已經 soft lockup 的內核的信息。

如果你重啟了內核,保留了一具 vmcore 屍體,如果是攻擊的情況,很可能在系統重啟的過程中,攻擊者就發覺了,暫停了攻擊或者更改了方式…

不要在既有的框架內就事論事,找些沒文化的流氓一起討論會比和經理討論可能更有收穫。有的時候我不想爭論,不是說我不善於爭論,而是我覺得和我爭論的人根本不知道我在說什麼,唉。

SSH 已經不能指望了,怎麼辦?

想法簡單,不足道,僅僅是個 POC,也希望能有人一起討論:

註冊一個新的四層協議,除了 TCP/UDP/ICMP 等熟知協議之外的新協議,這是為了避免每一個數據包都要經過過濾,避免影響性能。事先分配 skb,避免當事故發生時回送信息時分配 skb 失敗。好了,看代碼,先給出載入內核的代碼,這個代碼的大部分都是我從網上抄來的,並不是自己寫的,我只是重組了邏輯:

#include<net/protocol.h>#include<linux/if_ether.h>#include<linux/ip.h>#include<linux/udp.h>#define IPPROTO_MYPROTO 123#define QUOTA 30structsk_buff *eskb[QUOTA];staticint quota = QUOTA - 1;//module_param(quota, int, 0644);//MODULE_PARM_DESC(quota, "soft_lockup");unsignedshort _csum(unsignedshort* data, int len){int pad = 0;int i = 0;unsignedshort ret = 0;unsignedint sum = 0;if (len % 2 != 0) pad = 1; len /= 2;for ( i = 0; i < len; i++) { sum += data[i]; }if (pad == 1) sum += ((unsignedchar*)(data + len))[0] ; sum = (sum & 0xffff) + (sum >> 16); sum += (sum >> 16); ret = ~sum;return ret;}intmyproto_rcv(struct sk_buff *skb){structudphdr *uh, *euh;structiphdr *iph, *eiph;structethhdr *eh, *ethh;char esaddr[6];unsignedchar *pos;if (quota < 0) {goto end; } iph = ip_hdr(skb); uh = udp_hdr(skb); eh = (struct ethhdr *)(((unsignedchar *)iph) - sizeof(struct ethhdr));// 出事的時候,直接構造已經分配的skb eskb[quota]->ip_summed = CHECKSUM_NONE; eskb[quota]->protocol = htons(ETH_P_IP); eskb[quota]->priority = 0; eskb[quota]->dev = skb->dev; eskb[quota]->pkt_type = PACKET_OTHERHOST; skb_reserve(eskb[quota], 1300 + sizeof(struct ethhdr) + sizeof(struct iphdr) + sizeof(struct udphdr)); pos = skb_push(eskb[quota], 1300);strcpy(pos, "abcdefghijk123456789"); pos = skb_push(eskb[quota], sizeof(struct udphdr)); skb_reset_transport_header(eskb[quota]); euh = (struct udphdr *)pos; euh->source = uh->dest; euh->dest = uh->source; euh->len = htons(1300 + sizeof(struct udphdr)); euh->check = 0;memcpy(pos - 12, &iph->daddr, 4);memcpy(pos - 8, &iph->saddr, 4); ((unsignedshort *)(pos - 4))[0] = 0x1100;memcpy(pos - 2, &euh->len, sizeof(euh->len)); euh->check = _csum((unsignedshort*)(pos - 12), 12 + 1300 + sizeof(struct udphdr)); pos = skb_push(eskb[quota], sizeof(struct iphdr)); skb_reset_network_header(eskb[quota]); eiph = (struct iphdr *)pos; eiph->version = 4; eiph->ihl = 5; eiph->tos = 0; eiph->tot_len = htons(1300 + sizeof(struct udphdr) + sizeof(struct iphdr)); eiph->id = 0x1122; eiph->frag_off = 0; eiph->ttl = 64; eiph->protocol = 0x11; eiph->check = 0; eiph->saddr = iph->daddr; eiph->daddr = iph->saddr; eiph->check = _csum((unsignedshort*)pos, sizeof(struct iphdr)); pos = skb_push(eskb[quota], sizeof(struct ethhdr)); skb_reset_mac_header(eskb[quota]); ethh = (struct ethhdr *)pos;memcpy(esaddr, eh->h_dest, 6);memcpy(ethh->h_dest, eh->h_source, ETH_ALEN);memcpy(ethh->h_source, eh->h_dest, ETH_ALEN); ethh->h_proto = htons(ETH_P_IP); printk("myproto_rcv is called, length:%d %x %x\n", skb->len, esaddr[2], esaddr[3]); dev_queue_xmit(eskb[quota]); quota --;end: kfree_skb(skb);return0;}intmyproto_rcv_err(struct sk_buff *skb, unsignedint err){ printk("myproto_rcv is called:%d\n", err); kfree_skb(skb);return0;}staticconststructnet_protocolmyproto_protocol = { .handler = myproto_rcv, .err_handler = myproto_rcv_err, .no_policy = 1, .netns_ok = 1,};intinit_module(void){int ret = 0, i;// 事先分配skbfor (i = 0; i < QUOTA; i++) { eskb[i] = alloc_skb(1300 + sizeof(struct ethhdr) + sizeof(struct iphdr) + sizeof(struct udphdr), GFP_ATOMIC);if (eskb[i] == NULL) {//int j;//for () {//} printk("alloc failed\n");return-1; } }// 註冊123協議,它不是TCP,UDP,ICMP ret = inet_add_protocol(&myproto_protocol, IPPROTO_MYPROTO);if (ret) { printk("failed\n");return ret; } printk("successful\n");return0;}voidcleanup_module(void){int rc = 0; inet_del_protocol(&myproto_protocol, IPPROTO_MYPROTO);//for (i = quota; i >=0; i--) {//kfree_skb(eskb[i]);//}return;}intinit_module(void);voidcleanup_module(void);MODULE_LICENSE("GPL");

來來來,看一下客戶端的代碼。

客戶端需要通過 raw 套接字發送一個「請求」,它的傳輸層協議是 123,然而回復的卻是一個標準的 UDP 報文,所以客戶端需要在該 UDP 上接收。

代碼如下:

#include<stdlib.h>#include<unistd.h>#include<stdio.h>#include<string.h>#include<sys/socket.h>#include<netinet/in.h>#include<linux/ip.h>#include<linux/udp.h>#define PCKT_LEN 8192unsignedshortcsum(unsignedshort *buf, int nwords){unsignedlong sum;for(sum=0; nwords>0; nwords--) sum += *buf++; sum = (sum >> 16) + (sum &0xffff); sum += (sum >> 16);return (unsignedshort)(~sum);}intmain(int argc, charconst *argv[]){int sd, usd;structiphdr *ip;structudphdr *udp;structsockaddr_insin, usin, csin;u_int16_t src_port, dst_port;u_int32_t src_addr, dst_addr;int one = 1;constint *val = &one;int dlen, rlen, clen = sizeof(csin);char *data;char buf[1300];if (argc != 6) {printf("Usage: %s <source hostname/IP> <source port> <target hostname/IP> <target port>\n", argv[0]);exit(1); } src_addr = inet_addr(argv[1]); dst_addr = inet_addr(argv[3]); src_port = atoi(argv[2]); dst_port = atoi(argv[4]); dlen = atoi(argv[5]); data = malloc(sizeof(struct iphdr) + sizeof(struct udphdr) + dlen); ip = (struct iphdr *)data; udp = (struct udphdr *)(data + sizeof(struct iphdr)); sd = socket(PF_INET, SOCK_RAW, IPPROTO_UDP);if (sd < 0) { perror("raw error");exit(2); }if(setsockopt(sd, IPPROTO_IP, IP_HDRINCL, val, sizeof(one)) < 0) { perror("setsockopt() error");exit(2); }sin.sin_family = AF_INET;sin.sin_port = htons(dst_port);sin.sin_addr.s_addr = inet_addr(argv[3]); ip->ihl = 5; ip->version = 4; ip->tos = 16; // low delay ip->tot_len = sizeof(struct iphdr) + sizeof(struct udphdr) + dlen; ip->id = htons(54321); ip->ttl = 64; // hops ip->protocol = 123; // UDP ip->saddr = src_addr; ip->daddr = dst_addr; udp->source = htons(src_port); udp->dest = htons(dst_port); udp->len = htons(sizeof(struct udphdr) + dlen); ip->check = csum((unsignedshort *)data, sizeof(struct iphdr) + sizeof(struct udphdr) + dlen); usd = socket(AF_INET, SOCK_DGRAM, 0);if (usd < 0) { perror("usd error");exit(2); } bzero(&usin, sizeof(usin)); usin.sin_family = AF_INET; usin.sin_port = htons(src_port); usin.sin_addr.s_addr = inet_addr(argv[1]);if (bind(usd, (struct sockaddr *)&usin, sizeof(usin))) { perror("bind error");exit(2); }if (sendto(sd, data, ip->tot_len, 0, (struct sockaddr *)&sin, sizeof(sin)) < 0) { perror("sendto()");exit(3); } rlen = recvfrom(usd, buf, sizeof(buf), 0, (struct sockaddr*)&csin, &clen);printf("recv:%s\n", buf); close(sd);return0;}

好了,我們在服務端加載內核模塊,製造一個死鎖或者玩一個 fork 炸彈,SSH 已經無法登錄但是能 ping 通的情況下,執行我們的客戶端程序,可以完美給出結果。

我們只需要把「abcdefghijk123456789」改成當前內核能取到的信息即可,沒意思也不好玩了。

哦,對了,必須補充一段。這個代碼有很多不可行的情況,比如你用了_irq 前綴把硬中斷禁用了,比如你的網絡拓撲不是直來直往的,比如你有更好的帶外系統,比如各種其它的不適用。

但是至少,在直連的情況下,你 SSH 都登錄不上了,我這個破爛玩意兒可以帶回一些信息,哪怕只是一雙皮鞋。

相關焦點

  • 一張圖看懂Linux內核中Percpu變量的實現
    但你知道嗎,不僅是在程式語言中,在linux內核中,也有一個類似的機制,用來實現類似的目的,它叫做percpu變量。percpu變量,顧名思義,就是對於同一個變量,每個cpu都有自己的一份,它可以被用來存放一些cpu獨有的數據,比如cpu的id,cpu上正在運行的線程等等,因該機制可以非常方便的解決一些特定問題,所以在內核編程中被廣泛使用。
  • 「正點原子Linux連載」第三十七章Linux內核移植
    使用FileZilla將其發送到Ubuntu中並解壓,得到名為linux-imx-rel_imx_4.1.15_2.1.0_ga的目錄,為了和NXP官方的名字區分,可以使用「mv」命令對其重命名,我這裡將其重命名為「linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek」,命令如下:mv linux-imx-rel_imx_4.1.15
  • Linux內核學習神器,Bochs安裝及常見問題解決
    在Ubuntu上的安裝bochs可以跨平臺安裝,可以安裝在Linux或者Windows等作業系統,Ubuntu(本文基於16.04版本)上安裝非常簡單,執行如下命令既可以安裝:sudo apt-get install bochs使用bochs我們以運行Linux0.12版本內核為例簡單介紹一下bochs的使用。
  • 什麼Linux,Linux內核及Linux作業系統
    對於傳統概念,作業系統應該是一個具備完整功能的系統,它不僅僅包括內核,還要包括很多應用軟體。因此,後來逐漸將Linux的含義由Linux轉換為Linux作業系統,而Linux的內核則用"Linux內核"作為稱呼,以便於進行區分。Linux作業系統及各種發行版前面我們簡要的介紹了Linux作業系統、內核和發行版。
  • Linux提權的幾種常用方式
    在滲透測試過程中,提升權限是非常關鍵的一步,攻擊者往往可以通過利用內核漏洞/權限配置不當/root權限運行的服務等方式尋找突破點,來達到提升權限的目的。1、內核漏洞提權提起內核漏洞提權就不得不提到髒牛漏洞(Dirty Cow),是存在時間最長且影響範圍最廣的漏洞之一。
  • Linux系統的Linux應該怎麼讀?正確讀法在這裡,很多人都讀錯了!
    3、有人綜合網上和linux自己的讀音,概括出幾個自認為最合適也最通用的讀法:/li'n^ks/(「裡那克斯」)或/'li:nэks/(「裡訥克斯」)或/li'nju:ks/(「裡紐克斯」)。4、這幾個應該是誰都聽得懂的。至於哪個比較正宗,當然是linux的原因。但事實上使用linux哪種讀法的人似乎都不在少數。
  • Unix和Linux作業系統有什麼區別?
    他不僅在這臺機器上開發出了作業系統內核,還將自己的遊戲重寫了一遍。現在很多年輕人都玩遊戲,但是,有多少人像他那麼執著呢?為了玩個遊戲,自己開發個系統。至於丹尼斯裡奇則是因為發明了C語言編程工具,終結了彙編編程的時代。於是在1973年,Unix系統用C語言進行重寫。是的Unix系統可移植性大大提高。所以才說是這位兩位大神創造了Unix系統。
  • Linux:運行級別與重新啟動
    學習:•設置默認運行級別•運行級別間的變化•改為單用戶模式•從命令行關閉或重啟系統•警告用戶主要系統事件,包括切換到另一個運行級別•適當地終止進程除非特殊說明,本文中的示例使用帶有 2.6.26 內核的 Fedora 8。
  • 從串口驅動到Linux驅動模型,想轉Linux的必會!
    完全兼容POSIX1.0標準這使得可以在Linux下通過相應的模擬器運行常見的DOS、Windows的程序。這為用戶從Windows轉到Linux奠定了基礎。許多用戶在考慮使用Linux時,就想到以前在Windows下常見的程序是否能正常運行,這一點就消除了他們的疑慮。
  • Linux 內核報TCP SACK漏洞 CVE-2019-11477/78/79,需儘快處理
    概述在Linux內核處理TCP網絡數據包時候存在缺陷導致三個漏洞,CVE編號分別為:CVE-2019-11477,CVE-2019-11478和CVE-2019-11479。最嚴重的漏洞CVE-2019-11477,可以讓遠程攻擊者DDOS系統導致內核崩潰,從而影響系統的可用性。
  • Linux基礎教程:CentOS開機流程詳解
    第三步:啟動Boot Loader Boot Loader 就是在作業系統內核運行之前運行的一段小程序。通過這段小程序,我們可以初始化硬體設備、建立內存空間的映射圖,從而將系統的軟硬體環境帶到一個合適的狀態,以便為最終調用作業系統內核做好一切準備。Boot Loader有若干種,其中Grub、Lilo是常見的Loader。
  • Linux之父如何定義Linux!主要想讓黑客、計算機學生使用和學習!
    LINUX是一個免費類unix內核,適用於386-AT計算機,附帶完整原始碼。 主要讓黑客、計算機科學學生使用,學習和享受。 它大部分用C編寫,但是一小部分是用gnu格式彙編,而且引導序列用的是因特爾086彙編語言。
  • ARM64 Linux 內核頁表的塊映射
    作者 | 宋寶華 責編 | 張文頭圖 | CSDN 下載自視覺中國內核文檔 Documentation/arm64/memory.rst 描述了 ARM64 Linux 內核空間的內存映射情況,應該是此方面最權威文檔。
  • 簡單了解一下Linux 的內核結構
    Linux 的內核結構內核直接坐落在硬體上,內核的主要作用就是 I/O 交互、內存管理和控制 CPU 訪問。上圖中還包括了中斷和調度器,中斷是與設備交互的主要方式。中斷出現時調度器就會發揮作用。這裡的低級代碼停止正在運行的進程,將其狀態保存在內核進程結構中,並啟動驅動程序。
  • 深入作業系統,從內核理解網絡包的接收過程(Linux篇)
    在Linux內核實現中,鏈路層協議靠網卡驅動來實現,內核協議棧來實現網絡層和傳輸層。內核對更上層的應用層提供socket接口來供用戶進程訪問。4.1 創建ksoftirqd內核線程Linux的軟中斷都是在專門的內核線程(ksoftirqd)中進行的,因此我們非常有必要看一下這些進程是怎麼初始化的,這樣我們才能在後面更準確地了解收包過程。該進程數量不是1個,而是N個,其中N等於你的機器的核數。
  • 淺談內核的Makefile、Kconfig和.config文件
    Kconfig:一個文本形式的文件,內核的配置菜單。.config:編譯內核所依據的配置。2三者的語法1.Makefile參考:linux-3.4.2/drivers/Makefile作用:用來定義哪些內容作為模塊編譯,哪些條件編譯等。子目錄Makefile被頂層Makefile包含。
  • 電腦死機按什麼鍵恢復
    如何判斷電腦是否死機   判斷機器處於死機狀態。如果不改變,那麼就是處於死機狀態。      電腦死機按什麼鍵恢復   1、按CTRL+ALT+delete鍵可以打開任務管理器,之後可以看到正在運行的任務,點擊「結束任務」,將所有程序關閉即可。
  • IoT中的Linux選擇
    在超市的類比中,這是一個更接近散裝食品商店,在那裡可以得到預先衡量的食物與詳細的機器可讀的烹飪說明,會有一個花哨的炊具,可以讀取這些說明, 並處理一系列食譜的調整,如調整為糙米而不是白米。 這個類比稍微弱了一點。這些基於原始碼的發行版通常是基於 linux 的嵌入式設備和物聯網設備的首選。
  • 基於Linux研發的深度Deepin系統,到底算不算真國產
    不過現在,基本所有的電腦和智能設備都需要「作業系統」,因為現在基本所有的程序的運行都需要作業系統。因此,作業系統的重要性也是直線上升。其實,自己研發內核不難,就像是Linux的創始人寫出第一版 linux 作業系統內核的時候,也就是個21歲的大學生。而現在你去大學裡面找個相關專業的學生,他也可以寫出來。但是,如何讓一個內核具有通用性,才是難點!因為作業系統運行,一定要有硬體支持(包括但不限於各種硬體的驅動程序)。
  • Linux中的DTrace:BPF進入4.9內核
    你也不能創建一個內核內部的延遲柱狀圖,也不能追蹤 USDT 探針,甚至不能寫個自定義的程序。DTrace 可以做到所有這些,但僅限於 Solaris 或 BSD 系統。在 Linux 系統中,有些不在主線內核的追蹤器,比如 SystemTap 就可以滿足你的這些需求,但它也有自身的不足。(理論上說,你可以寫一個基於探針的內核模塊來滿足需求-但實際上沒人這麼做。)