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 .
Forsigtig! Erfarne C-programmører risikerer at græde blodige tårer! Jeg kan tage fejl selv i terminologien, men enhver kritik er velkommen. Dette indlæg er beregnet til dem, der kun har en grundlæggende forståelse af C-programmering og ønsker at kigge ind under motorhjelmen. Linux.
I kommentarerne til min første 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.

Ja, jeg lærte i 2020, at man kan indsætte vilkårlige nyttelast i ICMP-pakker. Men bedre sent end aldrig! Og hvis der kan gøres noget ved det, så skal det gøres. Da jeg mest bruger kommandolinjen i mit daglige arbejde, inklusive via SSH, var ideen om en ICMP-shell det første, der faldt mig ind. Og for at bygge en komplet bullshield-bingo besluttede jeg at skrive det som et modul. Linux i et sprog, jeg kun har en nogenlunde forståelse af. En sådan shell vil ikke være synlig i proceslisten, den kan indlæses i kernen og vil ikke ligge på filsystemet, og du vil ikke se noget mistænkeligt på listen over lytteporte. Det er et fuldgyldigt rootkit i sine muligheder, men jeg håber at kunne forfine det og bruge det som en shell i sidste ende, når den gennemsnitlige belastning er for høj til at logge ind via SSH og udføre selv basale opgaver. 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 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 . 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 rettigheder for at kunne lave pakker, så scriptet skal køres som root. Glem ikke at give udførelsestilladelser og installere selve scapy. Debian der er en pakke der hedder 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:Hej verden
0010 21 !
Data: 72756e3a48656c6c6f2c20776f726c6421
[Længde: 17]
Frame 2: 59 bytes på wire (472 bit), 59 bytes fanget (472 bit) på interface wlp1s0, id 0
Internetprotokol version 4, kilde: 45.11.26.232, destination: 192.168.0.240
Protokol til kontrol af internetkontrol
Type: 0 (Ekko (ping) svar)
Code: 0
Kontrolsum: 0xde03 [korrekt]
[Kontrolsumstatus: God]
Identifikator (BE): 0 (0x0000)
Identifikator (LE): 0 (0x0000)
Sekvensnummer (BE): 0 (0x0000)
Sekvensnummer (LE): 0 (0x0000)
[Anmodningsramme: 1]
[Responstid: 19.094 ms]
Data (17 bytes)
0000 72 75 6e 3a 48 65 6c 6c 6f 2c 20 77 6f 72 6c 64 run:Hej verden
0010 21 !
Data: 72756e3a48656c6c6f2c20776f726c6421
[Længde: 17]
^C2-pakker optaget
Nyttelasten i svarpakken ændres ikke.
Kernel modul
At bygge en virtuel maskine med Debian vil have brug for mindst 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:
- To header-filer trækkes ind for at manipulere selve modulet og netfilteret.
- 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_ROUTINGspecificerer at behandle pakken, når den først vises i kernen. Kan brugesNF_INET_POST_ROUTINGat 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); - Den sidste funktion fjerner krogen.
- Licensen er tydeligt angivet, så compileren ikke klager.
- 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:
- Jeg var nødt til at inkludere yderligere header-filer, denne gang for at manipulere IP- og ICMP-headere.
- 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. - 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. - 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. - 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; - 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_ACCEPTbetyder accept af pakken, men du kan også droppe pakker ved at 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 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!
- 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 indskb, derfor får vi det ved hjælp af nukleare midler fra den tilsvarende struktur:tail = skb_tail_pointer(skb);.
Billedet blev stjålet , kan du læse mere om socket bufferen. - 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æfiksrun:og kasser enten pakken, hvis den mangler, eller skriv linjen om igen, fjern dette præfiks. - 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! - 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);
}- 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. - 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. - 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 flereUMH_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
alle: icmpshell.ko
icmpshell.ko:main.c
make -C $(KERNELDIR) M=$(PWD) moduler
ren:
gør -C $(KERNELDIR) M=$(PWD) ren
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.
Kilde: www.habr.com

