【CSDN 编者按】事件陷入死地无可挽救之际,可能会有人选择不了了之,有人选择就此放弃……但换个思路想一想,既然都无可挽回了,那干嘛不试试弄点有价值的信息回来?

作者 | dog250  责编 | 张文

头图 | CSDN 下载自视觉中国

出品 | CSDN(ID:CSDNnews)

曾经写过一个模块,当运行 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  30
struct sk_buff *eskb[QUOTA];
static int quota = QUOTA - 1;//module_param(quota, int, 0644);//MODULE_PARM_DESC(quota, "soft_lockup");
unsigned short _csum(unsigned short* data, int len){  int pad = 0;  int i = 0;  unsigned short ret = 0;  unsigned int sum = 0;  if (len % 2 != 0)    pad = 1;  len /= 2;  for ( i = 0; i < len; i++) {    sum += data[i];  }  if (pad == 1)    sum += ((unsigned char*)(data + len))[0] ;  sum = (sum & 0xffff) + (sum >> 16);  sum += (sum >> 16);  ret = ~sum;  return ret;}
int myproto_rcv(struct sk_buff *skb){  struct udphdr *uh, *euh;  struct iphdr *iph, *eiph;  struct ethhdr *eh, *ethh;  char esaddr[6];  unsigned char *pos;if (quota < 0) {    goto end;  }  iph = ip_hdr(skb);  uh = udp_hdr(skb);  eh = (struct ethhdr *)(((unsigned char *)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);  ((unsigned short *)(pos - 4))[0] = 0x1100;  memcpy(pos - 2, &euh->len, sizeof(euh->len));  euh->check = _csum((unsigned short*)(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((unsigned short*)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);  return 0;}
int myproto_rcv_err(struct sk_buff *skb, unsigned int err){  printk("myproto_rcv is called:%d\n", err);  kfree_skb(skb);  return 0;}
static const struct net_protocol myproto_protocol = {  .handler = myproto_rcv,  .err_handler = myproto_rcv_err,  .no_policy = 1,  .netns_ok = 1,};
int init_module(void){  int ret = 0, i;// 事先分配skb  for (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");  return 0;}
void cleanup_module(void){  int rc = 0;  inet_del_protocol(&myproto_protocol, IPPROTO_MYPROTO);  //for (i = quota; i >=0; i--) {    //kfree_skb(eskb[i]);  //}  return;}
int init_module(void);void cleanup_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 8192
unsigned short csum(unsigned short *buf, int nwords){  unsigned long sum;  for(sum=0; nwords>0; nwords--)    sum += *buf++;  sum = (sum >> 16) + (sum &0xffff);  sum += (sum >> 16);  return (unsigned short)(~sum);}
int main(int argc, char const *argv[]){  int sd, usd;  struct iphdr *ip;  struct udphdr *udp;  struct sockaddr_in sin, usin, csin;  u_int16_t src_port, dst_port;  u_int32_t src_addr, dst_addr;  int one = 1;  const int *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((unsigned short *)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);  return 0;}
…

好了,我们在服务端加载内核模块,制造一个死锁或者玩一个 fork 炸弹,SSH  已经无法登录但是能 ping 通的情况下,执行我们的客户端程序,可以完美给出结果。

我们只需要把“abcdefghijk123456789”改成当前内核能取到的信息即可,没意思也不好玩了。

哦,对了,必须补充一段。这个代码有很多不可行的情况,比如你用了_irq 前缀把硬中断禁用了,比如你的网络拓扑不是直来直往的,比如你有更好的带外系统,比如各种其它的不适用。

但是至少,在直连的情况下,你 SSH 都登录不上了,我这个破烂玩意儿可以带回一些信息,哪怕只是一双皮鞋????。

更多精彩推荐
☞曾被“劝退”的 C++ 20 正式发布!
☞TCP:一个悲伤的故事
☞跨平台将终结☞25 岁的 JavaScript 都经历了什么?
☞最令人讨厌的编程语言:C++ Java 上榜☞Rust 2020 调查报告出炉,95%的开发者吐槽Rust难学☞从“卡脖子”到“主导”,国产数据库 40 年的演变!

Linux 在 soft lockup 时,可以远程调试吗?相关推荐

  1. ida调试linux程序,MAC使用IDA PRO远程调试LINUX程序

    1 背景 在学习Linux系统上的一些漏洞知识的时候,往往需要进行"实地测试",但是在Linux系统上进行调试并不太方便,因为LINUX自带的GDB调试工具真的不太人性化,即使有G ...

  2. vs远程编译linux程序,使用Visual Studio 2015远程调试Linux程序

    ##安装 Visual Studio 2015 安装时注意将跨平台移动开发->Visual C++移动开发->Viaual C++ Android 开发的选项勾上 ##安装PUTTY Vi ...

  3. linux的gdb远程调试,嵌入式Linux的GDB远程调试如何实现呢?

    有道启新嵌入式研究院--远程调试环境由宿主机GDB和目标机调试stub共同构成,两者通过串口或TCP连接.使用GDB标准远程串行协议协同工作,实现对目标机上的系统内核和上层应用的监控和调试功能.调试s ...

  4. eclipse gdb gdbserver 远程调试

    https://blog.csdn.net/linuxarmsummary/article/details/44975495 我们在 Linux 主机中搭建我们的开发环境,使用 Ubuntu 10.0 ...

  5. NMI watchdog: BUG: soft lockup - CPU#2 stuck for 23s!

    <NMI watchdog: BUG: soft lockup> <kernel:NMI watchdog: BUG: soft lockup - CPU#6 stuck for 2 ...

  6. 请记住内核中这个勤劳的监测卫士---Watchdog(Soft lockup篇)

    ​更多内核安全.eBPF分析和实践文章,请关注博客和公众号: CSDN博客:内核功守道 公众号: 内核功守道 背景 从内核稳定性问题的角度来看内核安全,是基础,也是必备技能.很多时候,一个内核稳定性问 ...

  7. asp.net core 错误定位 vs2017 远程调试部署在centos上的asp.net core程序

    前言 程序运行中会出现各种BUG. 排除BUG有三种方式. 一.访问页面直接报错误信息 出于安全,服务器是关闭这个功能的.在centos上可以用 命令设置环境变量来解决:   export ASPNE ...

  8. Idea远程调试教学

    idea远程调试 前提 以使用dubbo框架的项目为例: 首先 在linux上,启动项目必须是自定义使用"*.sh"脚本的方式启动.如图所示: 其次 选择一个.sh脚本,作为deb ...

  9. SpringBoot 启用远程调试

    文章目录 SpringBoot 远程调试 什么是远程调试 原理 调试要求 使用方法 SpringBoot 远程调试 什么是远程调试 ​ 开发的过程中有时需要快速定位测试环境中的问题,在日志输出不足以定 ...

最新文章

  1. leetcode算法题--数组中两个数的最大异或值
  2. How to Build Your Own Blockchain Part 4.1 — Bitcoin Proof of Work Difficulty Explained
  3. linux 关闭redis 命令_redis----------linux和mac如何安装redis和启动,关闭
  4. k8s与caas--容器云caas平台的落地实践
  5. 篇章级关系抽取(Doc-RE)论文列表整理
  6. MongoDB find方法
  7. 如何加强云端的SSH安全性 TechTarget中国原创内容,原文链接:http://www.searchcl
  8. 信息收集-目录扫描(7kbscan御剑版)下载及使用
  9. html 简单表格制作(看了它足以应对大部分表格)
  10. 前端程序员的焦虑感从何而来?web前端发展如何
  11. Python3操作EXCEL,取汉字首字母,拼接全拼
  12. Unable to check if JNs are ready for formatting 问题解决
  13. 第二次作业:微信实例分析
  14. 如何制作Gif动态图
  15. 如何创建二维数组 微信小程序_微信小程序遍历二维数组
  16. 如何处理RuntimeError: _cdist_backward requires X1(X2) to be contiguous
  17. 虽然不信god,但是技术上还是算是有信yan的人吧
  18. [IT 男人帮 -10/31] 雨林木风CEO赖霖枫: 互联网冬天前的思考
  19. 大数据方向可以找什么工作
  20. 冲刺80万辆!明年再翻倍!行泊一体赛道「激活」市场新引擎

热门文章

  1. CSDN markdown编辑器 页面内跳转目录
  2. mac iterm2 安装 lrzsz rz sz命令
  3. 分布式系列九: kafka
  4. 华为模拟器ensp代码错误2,41,40问题的解决
  5. C#图解教程 第七章 类和继承
  6. Linux 下socket通信终极指南(附TCP、UDP完整代码)
  7. Flash MX 2004 中的文本遮罩
  8. 函数 getaddrinfo 学习
  9. 30. 连续子数组最大和
  10. [论文阅读] iCaRL: Incremental Classifier and Representation Learning