Nuklear granat over ICMP

Nuklear granat over ICMP

TL; DR: Jeg skriver et kernemodul, der læser kommandoer fra ICMP-nyttelasten og udfører dem på serveren, selvom din SSH går ned. For de mest utålmodige er al koden github.

Forsigtig! Erfarne C-programmører risikerer at bryde ud i tårer af blod! Jeg kan endda tage fejl i terminologien, men enhver kritik er velkommen. Indlægget er beregnet til dem, der har en meget grov ide om C-programmering og ønsker at se ind i Linux.

I kommentarerne til min første artiklen nævnte SoftEther VPN, som kan efterligne nogle "almindelige" protokoller, især HTTPS, ICMP og endda DNS. Jeg kan forestille mig, at kun den første af dem virker, da jeg er meget fortrolig med HTTP(S), og jeg skulle lære tunneling over ICMP og DNS.

Nuklear granat over ICMP

Ja, i 2020 lærte jeg, at du kan indsætte en vilkårlig nyttelast i ICMP-pakker. Men bedre sent end aldrig! Og da der kan gøres noget ved det, så skal det gøres. Da jeg i mit daglige liv oftest bruger kommandolinjen, herunder via SSH, kom ideen om en ICMP-skal først op i mit sind. Og for at samle en komplet bullshield-bingo, besluttede jeg at skrive det som et Linux-modul på et sprog, som jeg kun har en grov idé om. En sådan shell vil ikke være synlig på listen over processer, du kan indlæse den i kernen, og den vil ikke være på filsystemet, du vil ikke se noget mistænkeligt på listen over lytteporte. Med hensyn til dets muligheder er dette et fuldgyldigt rootkit, men jeg håber at forbedre det og bruge det som en sidste udvej, når belastningsgennemsnittet er for højt til at logge på via SSH og udføre mindst echo i > /proc/sysrq-triggerfor at gendanne adgang uden at genstarte.

Vi tager en teksteditor, grundlæggende programmeringsfærdigheder i Python og C, Google og virtuelle som du ikke har noget imod at lægge under kniven, hvis alt går i stykker (valgfrit - lokal VirtualBox/KVM/etc) og lad os gå!

Kundedel

Det forekom mig, at jeg for klientdelen skulle skrive et manuskript med omkring 80 linjer, men der var venlige mennesker, der gjorde det for mig alt arbejdet. Koden viste sig at være uventet enkel og passede ind i 10 væsentlige linjer:

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

Scriptet tager to argumenter, en adresse og en nyttelast. Inden afsendelsen indledes nyttelasten af ​​en nøgle run:, skal vi bruge det til at ekskludere pakker med tilfældige nyttelast.

Kernen kræver privilegier til at lave pakker, så scriptet skal køres som superbruger. Glem ikke at give udførelsestilladelser og installere selve scapy. Debian har en pakke kaldet python3-scapy. Nu kan du tjekke, hvordan det hele fungerer.

Kørsel og udsendelse af kommandoen
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!

Sådan ser det ud i snifferen
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

Nyttelasten i svarpakken ændres ikke.

Kernel modul

For at indbygge en virtuel Debian-maskine skal du mindst bruge make и linux-headers-amd64, vil resten komme i form af afhængigheder. Jeg vil ikke give hele koden i artiklen; du kan klone den på Github.

Opsætning af krog

Til at begynde med har vi brug for to funktioner for at indlæse modulet og for at aflæse det. Funktionen til aflæsning er ikke påkrævet, men da rmmod det vil ikke virke; modulet vil kun blive aflæst, når det er slukket.

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

Hvad sker der her:

  1. To header-filer trækkes ind for at manipulere selve modulet og netfilteret.
  2. Alle operationer går gennem et netfilter, du kan sætte kroge i det. For at gøre dette skal du erklære den struktur, som krogen vil blive konfigureret i. Det vigtigste er at specificere den funktion, der skal udføres som en hook: nfho.hook = icmp_cmd_executor; Jeg kommer til selve funktionen senere.
    Så indstiller jeg behandlingstiden for pakken: NF_INET_PRE_ROUTING specificerer at behandle pakken, når den først vises i kernen. Kan bruges NF_INET_POST_ROUTING at behandle pakken, når den forlader kernen.
    Jeg indstiller filteret til IPv4: nfho.pf = PF_INET;.
    Jeg giver min krog højeste prioritet: nfho.priority = NF_IP_PRI_FIRST;
    Og jeg registrerer datastrukturen som den faktiske krog: nf_register_net_hook(&init_net, &nfho);
  3. Den sidste funktion fjerner krogen.
  4. Licensen er tydeligt angivet, så compileren ikke klager.
  5. Funktioner module_init() и module_exit() indstille andre funktioner for at initialisere og afslutte modulet.

Henter nyttelasten

Nu skal vi udtrække nyttelasten, dette viste sig at være den sværeste opgave. Kernen har ikke indbyggede funktioner til at arbejde med nyttelaster; du kan kun parse overskrifter for protokoller på højere niveau.

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

Hvad sker der:

  1. Jeg var nødt til at inkludere yderligere header-filer, denne gang for at manipulere IP- og ICMP-headere.
  2. Jeg indstiller den maksimale linjelængde: #define MAX_CMD_LEN 1976. Hvorfor netop dette? Fordi compileren klager over det! De har allerede foreslået mig, at jeg skal forstå stakken og heapen, en dag vil jeg helt sikkert gøre dette og måske endda rette koden. Jeg indstillede straks linjen, der skal indeholde kommandoen: char cmd_string[MAX_CMD_LEN];. Det skal være synligt i alle funktioner; jeg vil tale mere detaljeret om dette i afsnit 9.
  3. Nu skal vi initialisere (struct work_struct my_work;) strukturere og forbinde den med en anden funktion (DECLARE_WORK(my_work, work_handler);). Jeg vil også tale om, hvorfor det er nødvendigt i det niende afsnit.
  4. Nu erklærer jeg en funktion, som vil være en krog. Typen og accepterede argumenter er dikteret af netfilteret, vi er kun interesserede i skb. Dette er en socket-buffer, en grundlæggende datastruktur, der indeholder al tilgængelig information om en pakke.
  5. For at funktionen skal fungere, skal du bruge to strukturer og flere variabler, inklusive to iteratorer.
      struct iphdr *iph;
      struct icmphdr *icmph;
    
      unsigned char *user_data;
      unsigned char *tail;
      unsigned char *i;
      int j = 0;
  6. Vi kan starte med logik. For at modulet skal fungere, er der ikke brug for andre pakker end ICMP Echo, så vi parser bufferen ved hjælp af indbyggede funktioner og smider alle ikke-ICMP og ikke-Echo pakker ud. Vend tilbage NF_ACCEPT betyder accept af pakken, men du kan også droppe pakker ved at returnere 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;
      }

    Jeg har ikke testet, hvad der vil ske uden at tjekke IP-headerne. Mit minimale kendskab til C fortæller mig, at uden yderligere kontrol, vil der helt sikkert ske noget forfærdeligt. Jeg vil blive glad, hvis du fraråder mig dette!

  7. Nu hvor pakken er af den nøjagtige type, du har brug for, kan du udtrække dataene. Uden en indbygget funktion skal du først få en pointer til begyndelsen af ​​nyttelasten. Dette gøres ét sted, du skal tage markøren til begyndelsen af ​​ICMP-headeren og flytte den til størrelsen på denne header. Alt bruger struktur icmph: user_data = (unsigned char *)((unsigned char *)icmph + (sizeof(icmph)));
    Slutningen af ​​overskriften skal matche enden af ​​nyttelasten ind skb, derfor får vi det ved hjælp af nukleare midler fra den tilsvarende struktur: tail = skb_tail_pointer(skb);.

    Nuklear granat over ICMP

    Billedet blev stjålet dermed, kan du læse mere om socket bufferen.

  8. Når du har pointere til begyndelsen og slutningen, kan du kopiere dataene til en streng cmd_string, tjek det for tilstedeværelsen af ​​et præfiks run: og kasser enten pakken, hvis den mangler, eller skriv linjen om igen, fjern dette præfiks.
  9. Det er det, nu kan du ringe til en anden behandler: schedule_work(&my_work);. Da det ikke vil være muligt at videregive en parameter til et sådant kald, skal linjen med kommandoen være global. schedule_work() vil placere funktionen, der er knyttet til den beståede struktur, i den generelle kø i opgaveplanlæggeren og fuldføre, så du ikke kan vente på, at kommandoen er fuldført. Dette er nødvendigt, fordi krogen skal være meget hurtig. Ellers er dit valg, at intet vil starte, eller du vil få en kernepanik. Forsinkelse er som døden!
  10. Det er det, du kan acceptere pakken med en tilsvarende retur.

Kald et program i brugerrummet

Denne funktion er den mest forståelige. Dens navn blev givet ind DECLARE_WORK(), typen og accepterede argumenter er ikke interessante. Vi tager linjen med kommandoen og sender den helt til skallen. Lad ham beskæftige sig med parsing, søgning efter binære filer og alt muligt andet.

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. Indstil argumenterne til en række strenge argv[]. Jeg vil antage, at alle ved, at programmer faktisk udføres på denne måde, og ikke som en kontinuerlig linje med mellemrum.
  2. Indstil miljøvariabler. Jeg indsatte kun PATH med et minimumssæt af stier i håb om, at de alle allerede var kombineret /bin с /usr/bin и /sbin с /usr/sbin. Andre veje har sjældent betydning i praksis.
  3. Færdig, lad os gøre det! Kernel funktion call_usermodehelper() tager imod adgang. sti til det binære, array af argumenter, array af miljøvariabler. Her går jeg også ud fra, at alle forstår meningen med at videregive stien til den eksekverbare fil som et separat argument, men du kan spørge. Det sidste argument angiver, om der skal ventes på, at processen er fuldført (UMH_WAIT_PROC), processtart (UMH_WAIT_EXEC) eller slet ikke vente (UMH_NO_WAIT). Er der nogle flere UMH_KILLABLE, jeg kiggede ikke på det.

samling

Samlingen af ​​kernemoduler udføres gennem kernens make-framework. Hedder make inde i en speciel mappe knyttet til kerneversionen (defineret her: KERNELDIR:=/lib/modules/$(shell uname -r)/build), og placeringen af ​​modulet videregives til variablen M i argumenterne. icmpshell.ko og de rene mål bruger udelukkende denne ramme. I obj-m angiver objektfilen, der vil blive konverteret til et modul. Syntaks, der genskaber main.o в icmpshell.o (icmpshell-objs = main.o) ser ikke særlig logisk ud for mig, men så må det være.

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

Vi indsamler: make. Indlæser: insmod icmpshell.ko. Færdig, du kan tjekke: sudo ./send.py 45.11.26.232 "date > /tmp/test". Hvis du har en fil på din maskine /tmp/test og den indeholder datoen, hvor anmodningen blev sendt, hvilket betyder, at du gjorde alt rigtigt, og jeg gjorde alt rigtigt.

Konklusion

Min første oplevelse med nuklear udvikling var meget lettere, end jeg havde forventet. Selv uden erfaring med at udvikle i C, med fokus på compiler-tip og Google-resultater, var jeg i stand til at skrive et arbejdsmodul og føle mig som en kernehacker og samtidig en script-kiddie. Derudover gik jeg til Kernel Newbies-kanalen, hvor jeg fik besked på at bruge schedule_work() i stedet for at ringe call_usermodehelper() inde i selve krogen og skammede ham, med rette mistanke om en fidus. Hundrede linjer kode kostede mig omkring en uges udvikling i min fritid. En succesoplevelse, der ødelagde min personlige myte om systemudviklingens overvældende kompleksitet.

Hvis nogen accepterer at lave en kodegennemgang på Github, vil jeg være taknemmelig. Jeg er ret sikker på, at jeg lavede mange dumme fejl, især når jeg arbejdede med strenge.

Nuklear granat over ICMP

Kilde: www.habr.com

Tilføj en kommentar