قذيفة نووية فوق ICMP





TL ؛ DR : أنا أكتب وحدة kernel التي ستقرأ الأوامر من حمولة ICMP وتنفذها على الخادم حتى لو تعطل SSH الخاص بك. لأكثر الأشخاص نفاد صبرًا ، كل الشفرة موجودة على جيثب .



الحذر! المبرمجون ذوو الخبرة في لغة سي يواجهون خطر الانغماس في البكاء من الدموع الدموية! قد أكون مخطئا حتى في المصطلحات ، لكن أي نقد مرحب به. المنشور مخصص لأولئك الذين لديهم فكرة تقريبية عن برمجة لغة سي ويريدون النظر في العناصر الداخلية لنظام لينكس.



في تصريحات لقائي الاول المقالذكر SoftEther VPN ، والذي يمكنه محاكاة بعض البروتوكولات "العادية" ، على وجه الخصوص ، HTTPS و ICMP وحتى DNS. يمكنني تخيل عمل أولهم فقط ، حيث إنني على دراية جيدة بـ HTTP (S) ، وكان علي أن أتعلم الأنفاق عبر ICMP و DNS.



صورة



نعم ، علمت في عام 2020 أنه يمكنك إدراج حمولة عشوائية في حزم ICMP. لكن متأخرا أفضل من عدمه! وبما أنه يمكنك فعل شيء حيال ذلك ، فأنت بحاجة إلى القيام بذلك. نظرًا لأنني في حياتي اليومية غالبًا ما أستخدم سطر الأوامر ، بما في ذلك عبر SSH ، فقد خطر ببالي فكرة غلاف ICMP أولاً. ومن أجل تجميع لعبة بنغو هراء كاملة ، قررت كتابتها كوحدة نمطية لنظام Linux بلغة لدي فكرة تقريبية عنها. لن تظهر مثل هذه الصدفة في قائمة العمليات ، يمكنك تحميلها في النواة ولن تقع على نظام الملفات ، ولن ترى أي شيء مريب في قائمة منافذ الاستماع. من حيث إمكانياته ، هذا هو rootkit كامل ، لكنني آمل أن أقوم بتعديله واستخدامه كملاذ أخير ، عندما يكون معدل التحميل مرتفعًا جدًا لتسجيل الدخول عبر SSH وتنفيذه على الأقلecho i > /proc/sysrq-triggerلاستعادة الوصول دون إعادة التشغيل.



نأخذ محرر نصوص ومهارات برمجة أساسية في Python و C و Google وجهاز افتراضي لا تمانع في وضعه تحت السكين إذا تعطل كل شيء (اختياري - VirtualBox / KVM / إلخ) ودعنا نذهب!



جزء العميل



بدا لي أنه بالنسبة لجانب العميل ، يجب أن أكتب نصًا يتكون من 80 سطرًا ، ولكن كان هناك أشخاص طيبون قاموا بكل العمل من أجلي . تبين أن الشفرة بسيطة بشكل مدهش ، فهي تتناسب مع 10 أسطر مهمة:



import sys
from scapy.all import sr1, IP, ICMP

if len(sys.argv) < 3:
    print('Usage: {} IP "command"'.format(sys.argv[0]))
    exit(0)

p = sr1(IP(dst=sys.argv[1])/ICMP()/"run:{}".format(sys.argv[2]))
if p:
    p.show()


يأخذ البرنامج النصي وسيطتين ، عنوان وحمولة. قبل الإرسال ، تكون الحمولة مسبوقة بمفتاح run:، سنحتاجها لاستبعاد الحزم ذات الحمولة العشوائية.



تتطلب النواة امتيازات من أجل صياغة الحزم ، لذلك يجب تشغيل البرنامج النصي بحقوق المستخدم المتميز. لا تنس إعطاء إذن التنفيذ وتثبيت scapy نفسه. دبيان لديها حزمة تسمى python3-scapy. الآن يمكنك التحقق من كيفية عمل كل شيء.



تشغيل وإخراج الأمر
morq@laptop:~/icmpshell$ sudo ./send.py 45.11.26.232 "Hello, world!"

Begin emission:

.Finished sending 1 packets.

*

Received 2 packets, got 1 answers, remaining 0 packets

###[ IP ]###

version = 4

ihl = 5

tos = 0x0

len = 45

id = 17218

flags =

frag = 0

ttl = 58

proto = icmp

chksum = 0x3403

src = 45.11.26.232

dst = 192.168.0.240

\options \

###[ ICMP ]###

type = echo-reply

code = 0

chksum = 0xde03

id = 0x0

seq = 0x0

###[ Raw ]###

load = 'run:Hello, world!




هذا ما يبدو عليه في الشم
morq@laptop:~/icmpshell$ sudo tshark -i wlp1s0 -O icmp -f "icmp and host 45.11.26.232"

Running as user "root" and group "root". This could be dangerous.

Capturing on 'wlp1s0'

Frame 1: 59 bytes on wire (472 bits), 59 bytes captured (472 bits) on interface wlp1s0, id 0

Internet Protocol Version 4, Src: 192.168.0.240, Dst: 45.11.26.232

Internet Control Message Protocol

Type: 8 (Echo (ping) request)

Code: 0

Checksum: 0xd603 [correct]

[Checksum Status: Good]

Identifier (BE): 0 (0x0000)

Identifier (LE): 0 (0x0000)

Sequence number (BE): 0 (0x0000)

Sequence number (LE): 0 (0x0000)

Data (17 bytes)



0000 72 75 6e 3a 48 65 6c 6c 6f 2c 20 77 6f 72 6c 64 run:Hello, world

0010 21 !

Data: 72756e3a48656c6c6f2c20776f726c6421

[Length: 17]



Frame 2: 59 bytes on wire (472 bits), 59 bytes captured (472 bits) on interface wlp1s0, id 0

Internet Protocol Version 4, Src: 45.11.26.232, Dst: 192.168.0.240

Internet Control Message Protocol

Type: 0 (Echo (ping) reply)

Code: 0

Checksum: 0xde03 [correct]

[Checksum Status: Good]

Identifier (BE): 0 (0x0000)

Identifier (LE): 0 (0x0000)

Sequence number (BE): 0 (0x0000)

Sequence number (LE): 0 (0x0000)

[Request frame: 1]

[Response time: 19.094 ms]

Data (17 bytes)



0000 72 75 6e 3a 48 65 6c 6c 6f 2c 20 77 6f 72 6c 64 run:Hello, world

0010 21 !

Data: 72756e3a48656c6c6f2c20776f726c6421

[Length: 17]



^C2 packets captured





لا تتغير الحمولة في حزمة الاستجابة.



وحدة Kernel



إلى البناء في جهاز ظاهري مع ديبيان، وسوف تحتاج على الأقل makeو linux-headers-amd64سيتم تشديد الباقي في شكل التبعيات. لن أعطي الكود بالكامل في المقال ، يمكنك استنساخه على جيثب.



الإعداد هوك



أولاً ، نحتاج إلى وظيفتين لتحميل الوحدة وتفريغها. وظيفة التفريغ غير مطلوبة ، ولكنها rmmodلن تعمل بعد ذلك ، وسيتم تفريغ الوحدة فقط عند إيقاف تشغيلها.



#include <linux/module.h>
#include <linux/netfilter_ipv4.h>

static struct nf_hook_ops nfho;

static int __init startup(void)
{
  nfho.hook = icmp_cmd_executor;
  nfho.hooknum = NF_INET_PRE_ROUTING;
  nfho.pf = PF_INET;
  nfho.priority = NF_IP_PRI_FIRST;
  nf_register_net_hook(&init_net, &nfho);
  return 0;
}

static void __exit cleanup(void)
{
  nf_unregister_net_hook(&init_net, &nfho);
}

MODULE_LICENSE("GPL");
module_init(startup);
module_exit(cleanup);


ماذا يحدث هنا:



  1. يتم سحب ملفي رأس للتعامل مع الوحدة نفسها و netfilter.
  2. , . , . — , : nfho.hook = icmp_cmd_executor; .

    : NF_INET_PRE_ROUTING , . NF_INET_POST_ROUTING .

    IPv4: nfho.pf = PF_INET;.

    : nfho.priority = NF_IP_PRI_FIRST;

    : nf_register_net_hook(&init_net, &nfho);
  3. .
  4. , .
  5. module_init() module_exit() .




نحتاج الآن إلى استخراج الحمولة ، والتي تبين أنها أصعب مهمة. لا يحتوي kernel على وظائف مضمنة للعمل مع الحمولة ، يمكنك فقط تحليل رؤوس بروتوكولات المستوى الأعلى.



#include <linux/ip.h>
#include <linux/icmp.h>

#define MAX_CMD_LEN 1976

char cmd_string[MAX_CMD_LEN];

struct work_struct my_work;

DECLARE_WORK(my_work, work_handler);

static unsigned int icmp_cmd_executor(void *priv, struct sk_buff *skb, const struct nf_hook_state *state)
{
  struct iphdr *iph;
  struct icmphdr *icmph;

  unsigned char *user_data;
  unsigned char *tail;
  unsigned char *i;
  int j = 0;

  iph = ip_hdr(skb);
  icmph = icmp_hdr(skb);

  if (iph->protocol != IPPROTO_ICMP) {
    return NF_ACCEPT;
  }
  if (icmph->type != ICMP_ECHO) {
    return NF_ACCEPT;
  }

  user_data = (unsigned char *)((unsigned char *)icmph + (sizeof(icmph)));
  tail = skb_tail_pointer(skb);

  j = 0;
  for (i = user_data; i != tail; ++i) {
    char c = *(char *)i;

    cmd_string[j] = c;

    j++;

    if (c == '\0')
      break;

    if (j == MAX_CMD_LEN) {
      cmd_string[j] = '\0';
      break;
    }

  }

  if (strncmp(cmd_string, "run:", 4) != 0) {
    return NF_ACCEPT;
  } else {
    for (j = 0; j <= sizeof(cmd_string)/sizeof(cmd_string[0])-4; j++) {
      cmd_string[j] = cmd_string[j+4];
      if (cmd_string[j] == '\0')
	break;
    }
  }

  schedule_work(&my_work);

  return NF_ACCEPT;
}


ماذا يحدث:



  1. اضطررت إلى تضمين ملفات رأس إضافية ، هذه المرة لمعالجة رؤوس IP و ICMP.
  2. يحدد الحد الأقصى لطول سلسلة: #define MAX_CMD_LEN 1976. لماذا هذا بالضبط؟ لأن المترجم يقسم في الكبير! لقد أخبروني بالفعل أنني بحاجة للتعامل مع المكدس والكومة ، يومًا ما سأفعل ذلك بالتأكيد وربما أصحح الكود. يحدد على الفور سلسلة التي سوف يقوم فريق: char cmd_string[MAX_CMD_LEN];. يجب أن يكون مرئيًا في جميع الوظائف ، سأتحدث عن هذا بمزيد من التفصيل في الفقرة 9.
  3. (struct work_struct my_work;) (DECLARE_WORK(my_work, work_handler);). , , .
  4. , . , skb. , , .
  5. , , .



      struct iphdr *iph;
      struct icmphdr *icmph;
    
      unsigned char *user_data;
      unsigned char *tail;
      unsigned char *i;
      int j = 0;
  6. . ICMP Echo, ICMP- Echo-. NF_ACCEPT , , NF_DROP.



      iph = ip_hdr(skb);
      icmph = icmp_hdr(skb);
    
      if (iph->protocol != IPPROTO_ICMP) {
        return NF_ACCEPT;
      }
      if (icmph->type != ICMP_ECHO) {
        return NF_ACCEPT;
      }




    , IP. C : - . , !
  7. , , . . , ICMP . icmph: user_data = (unsigned char *)((unsigned char *)icmph + (sizeof(icmph)));

    skb, : tail = skb_tail_pointer(skb);.



    صورة



    , .
  8. , cmd_string, run: , , , .
  9. , : schedule_work(&my_work);. , . schedule_work() , . . , , kernel panic. !
  10. , .




هذه الوظيفة هي الأكثر مباشرة. تم تحديد اسمها في DECLARE_WORK()، والنوع والحجج المقبولة ليست مثيرة للاهتمام. نأخذ سطر الأوامر ونمرره بالكامل إلى الغلاف. دعه يتعامل مع الاعراب والبحث عن الثنائيات وكل شيء آخر بنفسه.



static void work_handler(struct work_struct * work)
{
  static char *argv[] = {"/bin/sh", "-c", cmd_string, NULL};
  static char *envp[] = {"PATH=/bin:/sbin", NULL};

  call_usermodehelper(argv[0], argv, envp, UMH_WAIT_PROC);
}


  1. وضعنا الحجج على مجموعة من السلاسل argv[]. سأفترض أن الجميع يعرف أن البرامج تعمل بالفعل بهذه الطريقة ، وليس كخط صلب مع مسافات.
  2. تحديد متغيرات البيئة. تم إدراج PATH الوحيد مع مجموعة صغيرة من المسارات، متوقعا أن جميع تضافرت بالفعل /binمع /usr/binو /sbinمع /usr/sbin. نادرًا ما تكون المسارات الأخرى مهمة في الممارسة.
  3. , ! call_usermodehelper() . , , . , , . , (UMH_WAIT_PROC), (UMH_WAIT_EXEC) (UMH_NO_WAIT). UMH_KILLABLE, .




يتم بناء وحدات kernel من خلال إطار عمل kernel. يتم استدعاؤها makeداخل دليل خاص مرتبط بإصدار kernel (محدد هنا :) KERNELDIR:=/lib/modules/$(shell uname -r)/build، ويتم تمرير موقع الوحدة إلى المتغير Mفي الوسائط. يستخدم كل من icmpshell.ko والأهداف النظيفة إطار العمل هذا بالكامل. في obj-mيحدد ملف الكائن التي سيتم تحويلها إلى وحدة نمطية. الصيغة التي يزيلها main.oفي icmpshell.o( icmpshell-objs = main.o) لا تبدو منطقية جدًا بالنسبة لي ، لكن دعها تكون كذلك. وضع: . تحميل: . من ذلك، يمكنك التحقق من: . إذا ظهر ملف على جهازك ويحتوي على تاريخ إرسال الطلب ، فهذا يعني أنك فعلت كل شيء بشكل صحيح وفعلت كل شيء بشكل صحيح.



KERNELDIR:=/lib/modules/$(shell uname -r)/build



obj-m = icmpshell.o

icmpshell-objs = main.o



all: icmpshell.ko



icmpshell.ko: main.c

make -C $(KERNELDIR) M=$(PWD) modules



clean:

make -C $(KERNELDIR) M=$(PWD) clean




makeinsmod icmpshell.kosudo ./send.py 45.11.26.232 "date > /tmp/test"/tmp/test



خاتمة



كانت تجربتي الأولى مع الهندسة النووية أبسط بكثير مما توقعت. حتى بدون خبرة في تطوير لغة سي ، مع التركيز على تلميحات المترجم وإخراج Google ، كنت قادرًا على كتابة وحدة عمل وأشعر بأنني مخترق نواة ، وفي نفس الوقت طفل نصي. بالإضافة إلى ذلك ، ذهبت إلى قناة Kernel Newbies ، حيث أخبروني أن أستخدمها schedule_work()بدلاً من الاتصال call_usermodehelper()داخل الخطاف نفسه وأخرجوني ، مشتبهين بحق في عملية احتيال. كلفني مائة سطر من التعليمات البرمجية حوالي أسبوع من التطوير في أوقات فراغي. تجربة ناجحة دمرت أسطورتي الشخصية حول التعقيد الهائل لتطوير النظام.



إذا وافق شخص ما على إجراء مراجعة الكود على جيثب ، فسأكون ممتنًا. أنا متأكد من أنني ارتكبت الكثير من الأخطاء الغبية ، خاصة عند التعامل مع الأوتار.






All Articles