ICMP ustidagi yadro qobig'i

ICMP ustidagi yadro qobig'i

TP; DR: Men yadro modulini yozyapman, u ICMP foydali yukidan buyruqlarni o'qiydi va hatto SSH ishlamay qolsa ham ularni serverda bajaradi. Eng sabrsizlar uchun barcha kodlar Github.

E'tibor bering! Tajribali C dasturchilari qon ko'z yoshlari bilan portlash xavfi bor! Men hatto terminologiyada noto'g'ri bo'lishim mumkin, ammo har qanday tanqid qabul qilinadi. Post C dasturlash haqida juda qo'pol tasavvurga ega bo'lgan va Linuxning ichki tomonlarini ko'rib chiqmoqchi bo'lganlar uchun mo'ljallangan.

Mening birinchi sharhimdagi sharhlarda maqola Ba'zi "oddiy" protokollarni, xususan HTTPS, ICMP va hatto DNSni taqlid qilishi mumkin bo'lgan SoftEther VPN-ni eslatib o'tdi. Men ulardan faqat birinchisi ishlayotganini tasavvur qila olaman, chunki men HTTP(S) bilan juda yaxshi tanishman va ICMP va DNS orqali tunnel qilishni o'rganishim kerak edi.

ICMP ustidagi yadro qobig'i

Ha, 2020 yilda men ICMP paketlariga o'zboshimchalik bilan foydali yukni kiritishingiz mumkinligini bilib oldim. Lekin hech qachon kechgandan yaxshiroqdir! Va bu haqda biror narsa qilish mumkin bo'lganligi sababli, buni qilish kerak. Kundalik hayotimda men ko'pincha buyruq satridan, shu jumladan SSH orqali foydalanaman, birinchi bo'lib ICMP qobig'i g'oyasi xayolimga keldi. To'liq bullshield bingoni yig'ish uchun men uni Linux moduli sifatida faqat taxminiy tasavvurga ega bo'lgan tilda yozishga qaror qildim. Bunday qobiq jarayonlar ro'yxatida ko'rinmaydi, siz uni yadroga yuklashingiz mumkin va u fayl tizimida bo'lmaydi, tinglash portlari ro'yxatida shubhali narsalarni ko'rmaysiz. Imkoniyatlari nuqtai nazaridan, bu to'liq huquqli rootkit, lekin men uni yaxshilashga umid qilaman va SSH orqali tizimga kirish va hech bo'lmaganda bajarish uchun o'rtacha yuklanish juda yuqori bo'lsa, uni oxirgi chora sifatida ishlatishga umid qilaman. echo i > /proc/sysrq-triggerqayta yuklamasdan kirishni tiklash uchun.

Biz matn muharriri, Python va C, Google va tillarida asosiy dasturlash ko'nikmalarini olamiz virtual Agar hamma narsa buzilsa (ixtiyoriy - mahalliy VirtualBox/KVM/va hokazo) pichoq ostiga qo'yishga qarshi emassiz va ketaylik!

Mijoz tomoni

Menimcha, mijoz qismi uchun men 80 satrdan iborat ssenariy yozishim kerak edi, lekin men uchun buni qilgan mehribon odamlar bor edi. hamma ish. Kod kutilmaganda oddiy bo'lib chiqdi, u 10 ta muhim qatorga to'g'ri keldi:

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()

Skript ikkita argumentni, manzilni va foydali yukni oladi. Yuborishdan oldin foydali yukning oldiga kalit qo'yiladi run:, bizga tasodifiy yuklamali paketlarni istisno qilish uchun kerak bo'ladi.

Yadro paketlarni yaratish uchun imtiyozlarni talab qiladi, shuning uchun skript superuser sifatida ishga tushirilishi kerak. Amalga oshirish uchun ruxsat berishni va scapy-ning o'zini o'rnatishni unutmang. Debian deb nomlangan paketga ega python3-scapy. Endi siz hammasi qanday ishlashini tekshirishingiz mumkin.

Buyruqni ishga tushirish va chiqarish
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!

Snifferda shunday ko'rinadi
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

Javoblar paketidagi foydali yuk o'zgarmaydi.

Yadro moduli

Debian virtual mashinasini yaratish uchun sizga kamida kerak bo'ladi make ΠΈ linux-headers-amd64, qolganlari qaramlik shaklida keladi. Maqolada butun kodni bermayman; uni Github-da klonlashingiz mumkin.

Kancani sozlash

Boshlash uchun modulni yuklash va tushirish uchun bizga ikkita funktsiya kerak bo'ladi. Yukni tushirish funktsiyasi shart emas, lekin keyin rmmod u ishlamaydi, modul faqat o'chirilganda tushiriladi.

#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);

Bu erda nima bo'lyapti:

  1. Modulning o'zi va tarmoq filtrini boshqarish uchun ikkita sarlavha fayli olinadi.
  2. Barcha operatsiyalar tarmoq filtridan o'tadi, siz unga ilgaklar o'rnatishingiz mumkin. Buni amalga oshirish uchun siz kanca konfiguratsiya qilinadigan tuzilmani e'lon qilishingiz kerak. Eng muhimi, kanca sifatida bajariladigan funktsiyani belgilashdir: nfho.hook = icmp_cmd_executor; Men funktsiyaning o'ziga keyinroq o'taman.
    Keyin paketga ishlov berish vaqtini belgiladim: NF_INET_PRE_ROUTING paketni yadroda birinchi marta paydo bo'lganda qayta ishlashni belgilaydi. Foydalanish mumkin NF_INET_POST_ROUTING yadrodan chiqayotganda paketni qayta ishlash uchun.
    Filtrni IPv4 ga o'rnatdim: nfho.pf = PF_INET;.
    Men kancamga eng katta ustunlik beraman: nfho.priority = NF_IP_PRI_FIRST;
    Va men ma'lumotlar strukturasini haqiqiy kanca sifatida ro'yxatdan o'tkazaman: nf_register_net_hook(&init_net, &nfho);
  3. Yakuniy funktsiya kancani olib tashlaydi.
  4. Litsenziya tuzuvchi shikoyat qilmasligi uchun aniq ko'rsatilgan.
  5. Vazifalar module_init() ΠΈ module_exit() modulni ishga tushirish va tugatish uchun boshqa funktsiyalarni o'rnating.

Foydali yukni olish

Endi biz foydali yukni olishimiz kerak, bu eng qiyin vazifa bo'lib chiqdi. Yadroda foydali yuklar bilan ishlash uchun o'rnatilgan funksiyalar mavjud emas, siz faqat yuqori darajadagi protokollarning sarlavhalarini tahlil qilishingiz mumkin.

#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 == '')
      break;

    if (j == MAX_CMD_LEN) {
      cmd_string[j] = '';
      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] == '')
	break;
    }
  }

  schedule_work(&my_work);

  return NF_ACCEPT;
}

Nima bo'lyapti:

  1. Bu safar IP va ICMP sarlavhalarini boshqarish uchun qo'shimcha sarlavha fayllarini kiritishim kerak edi.
  2. Men maksimal chiziq uzunligini o'rnatdim: #define MAX_CMD_LEN 1976. Nima uchun aynan shunday? Chunki kompilyator bundan shikoyat qiladi! Ular menga stek va to'plamni tushunishim kerakligini allaqachon taklif qilishgan, bir kun kelib men buni albatta qilaman va hatto kodni tuzataman. Men darhol buyruqni o'z ichiga olgan qatorni o'rnatdim: char cmd_string[MAX_CMD_LEN];. U barcha funktsiyalarda ko'rinishi kerak, men bu haqda 9-bandda batafsilroq gaplashaman.
  3. Endi biz ishga tushirishimiz kerak (struct work_struct my_work;) tuzilishi va uni boshqa funktsiya bilan bog'lash (DECLARE_WORK(my_work, work_handler);). To'qqizinchi xatboshida nima uchun bu kerakligi haqida ham gapiraman.
  4. Endi men ilgak bo'ladigan funktsiyani e'lon qilaman. Turi va qabul qilingan argumentlar netfiltr tomonidan belgilanadi, bizni faqat qiziqtiradi skb. Bu soket buferi, paket haqidagi barcha mavjud ma'lumotlarni o'z ichiga olgan asosiy ma'lumotlar tuzilmasi.
  5. Funktsiyaning ishlashi uchun sizga ikkita tuzilma va bir nechta o'zgaruvchilar, jumladan ikkita iterator kerak bo'ladi.
      struct iphdr *iph;
      struct icmphdr *icmph;
    
      unsigned char *user_data;
      unsigned char *tail;
      unsigned char *i;
      int j = 0;
  6. Biz mantiqdan boshlashimiz mumkin. Modul ishlashi uchun ICMP Echo-dan boshqa paketlar kerak emas, shuning uchun biz o'rnatilgan funksiyalar yordamida buferni tahlil qilamiz va barcha ICMP bo'lmagan va Echo bo'lmagan paketlarni chiqarib tashlaymiz. Qaytish NF_ACCEPT to'plamni qabul qilishni anglatadi, lekin siz qaytarib berish orqali ham paketlarni tashlab yuborishingiz mumkin 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;
      }

    Men IP sarlavhalarini tekshirmasdan nima bo'lishini sinab ko'rmadim. Mening C haqida minimal bilimim menga qo'shimcha tekshiruvlarsiz dahshatli narsa sodir bo'lishini aytadi. Agar meni bundan qaytarsangiz xursand bo'laman!

  7. Endi paket sizga kerak bo'lgan aniq turdagi bo'lsa, siz ma'lumotlarni chiqarib olishingiz mumkin. O'rnatilgan funksiyasiz, avvalo, foydali yukning boshiga ko'rsatgich olishingiz kerak. Bu bir joyda amalga oshiriladi, siz ko'rsatgichni ICMP sarlavhasining boshiga olib borishingiz va uni ushbu sarlavhaning o'lchamiga o'tkazishingiz kerak. Hamma narsa strukturadan foydalanadi icmph: user_data = (unsigned char *)((unsigned char *)icmph + (sizeof(icmph)));
    Sarlavhaning oxiri foydali yukning oxiriga mos kelishi kerak skb, shuning uchun biz uni tegishli strukturadan yadroviy vositalar yordamida olamiz: tail = skb_tail_pointer(skb);.

    ICMP ustidagi yadro qobig'i

    Rasm o'g'irlangan shu yerda, siz rozetka buferi haqida ko'proq o'qishingiz mumkin.

  8. Boshi va oxiri uchun ko'rsatgichlar mavjud bo'lgandan so'ng, ma'lumotlarni satrga nusxalashingiz mumkin cmd_string, prefiks mavjudligi uchun uni tekshiring run: va agar u etishmayotgan bo'lsa, paketni tashlang yoki ushbu prefiksni olib tashlagan holda qatorni qayta yozing.
  9. Hammasi shu, endi siz boshqa ishlov beruvchiga qo'ng'iroq qilishingiz mumkin: schedule_work(&my_work);. Bunday qo'ng'iroqqa parametrni o'tkazish mumkin bo'lmagani uchun, buyruq bilan chiziq global bo'lishi kerak. schedule_work() topshirilgan tuzilma bilan bog'langan funksiyani vazifa rejalashtiruvchining umumiy navbatiga qo'yadi va tugallaydi, bu sizga buyruq tugashini kutmaslikka imkon beradi. Bu kerak, chunki kanca juda tez bo'lishi kerak. Aks holda, sizning tanlovingiz - hech narsa boshlanmaydi yoki siz yadro vahima olasiz. Kechikish o'limga o'xshaydi!
  10. Hammasi shu, siz paketni mos keladigan qaytarish bilan qabul qilishingiz mumkin.

Foydalanuvchilar maydonida dasturni chaqirish

Bu funksiya eng tushunarli. Uning nomi berilgan DECLARE_WORK(), turi va qabul qilingan argumentlar qiziq emas. Biz buyruq bilan chiziqni olamiz va uni butunlay qobiqqa o'tkazamiz. Unga tahlil qilish, ikkilik va boshqa narsalarni qidirish bilan shug'ullansin.

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. Argumentlarni qatorlar qatoriga o'rnating argv[]. O'ylaymanki, dasturlar bo'shliqlar bilan uzluksiz chiziq sifatida emas, balki aslida shu tarzda amalga oshirilishini hamma biladi.
  2. Atrof-muhit o'zgaruvchilarini o'rnating. Men faqat PATH ni minimal yo'llar to'plami bilan kiritdim, ularning hammasi allaqachon birlashtirilgan deb umid qilaman /bin с /usr/bin и /sbin с /usr/sbin. Boshqa yo'llar amalda kamdan-kam ahamiyatga ega.
  3. Bajarildi, qilaylik! Yadro funktsiyasi call_usermodehelper() kirishni qabul qiladi. binarga yo'l, argumentlar massivi, muhit o'zgaruvchilari massivi. Bu erda men har bir kishi alohida argument sifatida bajariladigan faylga yo'lni o'tkazish ma'nosini tushunadi deb o'ylayman, lekin siz so'rashingiz mumkin. Oxirgi argument jarayon tugashini kutish yoki kutish kerakligini bildiradi (UMH_WAIT_PROC), jarayonni boshlash (UMH_WAIT_EXEC) yoki umuman kutmang (UMH_NO_WAIT). Yana bor UMH_KILLABLE, Men bunga qaramadim.

O'rnatish

Yadro modullarini yig'ish yadro make-framework orqali amalga oshiriladi. Chaqirildi make yadro versiyasiga bog'langan maxsus katalog ichida (bu erda aniqlangan: KERNELDIR:=/lib/modules/$(shell uname -r)/build) va modulning joylashuvi o'zgaruvchiga uzatiladi M argumentlarda. Icmpshell.ko va toza maqsadlar ushbu ramkadan butunlay foydalanadi. IN obj-m modulga aylantiriladigan obyekt faylini bildiradi. Qayta tiklanadigan sintaksis main.o Π² icmpshell.o (icmpshell-objs = main.o) menga juda mantiqiy ko'rinmaydi, lekin shunday bo'lsin.

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

Biz yig'amiz: make. Yuklanmoqda: insmod icmpshell.ko. Bajarildi, tekshirishingiz mumkin: sudo ./send.py 45.11.26.232 "date > /tmp/test". Agar kompyuteringizda fayl bo'lsa /tmp/test va u so'rov yuborilgan sanani o'z ichiga oladi, ya'ni siz hamma narsani to'g'ri qildingiz va men hamma narsani to'g'ri qildim.

xulosa

Yadroviy rivojlanish bo'yicha birinchi tajribam men kutganimdan ancha oson bo'ldi. Kompilyator bo'yicha maslahatlar va Google natijalariga e'tibor qaratib, C tilida rivojlanish tajribasi bo'lmasa ham, men ishlaydigan modul yozishga muvaffaq bo'ldim va o'zimni yadro xakeri va shu bilan birga skript bolasi kabi his qila oldim. Bundan tashqari, men Kernel Newbies kanaliga bordim, u erda foydalanishim kerak edi schedule_work() qo'ng'iroq qilish o'rniga call_usermodehelper() kancaning o'zi ichida va uni sharmanda qildi, to'g'ri ravishda firibgarlikda gumon qildi. Yuz satr kod menga bo'sh vaqtimda bir haftalik rivojlanishga to'g'ri keldi. Tizim rivojlanishining haddan tashqari murakkabligi haqidagi shaxsiy afsonani yo'q qilgan muvaffaqiyatli tajriba.

Agar kimdir Github-da kodni ko'rib chiqishga rozi bo'lsa, men minnatdor bo'laman. Ishonchim komilki, men juda ko'p ahmoqona xatolarga yo'l qo'yganman, ayniqsa torlar bilan ishlashda.

ICMP ustidagi yadro qobig'i

Manba: www.habr.com

a Izoh qo'shish