TL;DR: jag skriver en kÀrnmodul som kommer att lÀsa kommandon frÄn ICMP-payloadet och utföra dem pÄ servern Àven om din SSH gÄr ner. För de mest otÄliga finns hela koden pÄ .
Var försiktig! Erfarna programmerare i C riskerar att brista ut i blodiga tÄrar! Jag kan ha fel Àven nÀr det kommer till terminologin, men all kritik vÀlkomnas. InlÀgget Àr riktat till dem som har en ganska rudimentÀr förstÄelse för programmering i C och vill fÄ en inblick i Linux inre funktioner.
I kommentarerna till mitt första nÀmndes SoftEther VPN, som kan mimikera vissa "vanliga" protokoll, sÀrskilt HTTPS, ICMP och till och med DNS. Jag kan bara förestÀlla mig hur det första fungerar, eftersom jag Àr vÀl insatt i HTTP(S), och jag har varit tvungen att studera tunneling över ICMP och DNS.

Ja, jag upptÀckte 2020 att man kan sÀtta in valfri payload i ICMP-paket. Men bÀttre sent Àn aldrig! Och eftersom det gÄr att göra nÄgot med detta, sÄ mÄste jag göra det. Eftersom jag oftast arbetar med kommandoraden, inklusive via SSH, var idén om en ICMP-shell det första som dök upp i mitt huvud. För att samla hela bullshitspelet beslutade jag att skriva det som en Linux-modul pÄ ett sprÄk jag bara har en vag förstÄelse för. En sÄdan shell kommer inte att synas i processlistan, den kan laddas in i kÀrnan och kommer inte att lÀggas pÄ filsystemet, du kommer inte att se nÄgot misstÀnkt i listan över lyssnande portar. Funktionellt sett Àr detta en komplett rootkit, men jag hoppas kunna utveckla och anvÀnda det som en sista utvÀg-shell nÀr Load Average Àr för hög för att logga in via SSH och utföra Ätminstone echo i > /proc/sysrq-trigger, för att ÄterfÄ Ätkomst utan att starta om.
Vi tar en textredigerare, grundlÀggande programmeringskunskaper i Python och C, Google och som inte gör nÄgot om den gÄr sönder (valfritt - lokal VirtualBox/KVM/osv) och kör igÄng!
Klientdel
Jag trodde att jag skulle behöva skriva ett skript pÄ runt 80 rader för klientdelen, men det fanns snÀlla mÀnniskor som gjorde allt arbete Ät mig . Koden visade sig ovÀntat enkel, den fÄr plats i 10 meningsfulla rader:
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 tvÄ argument, adress och payload. Innan skickandet föregÄs payloaden av nyckeln run:, vi kommer att behöva det för att utesluta paket med slumpmÀssig payload.
KÀrnan krÀver privilegier för att skapa paket, sÄ skriptet mÄste köras med superanvÀndarrÀttigheter. Glöm inte att ge körbehörighet och installera scapy. I Debian finns det ett paket som heter python3-scapy. Nu kan vi kontrollera hur allt fungerar.
Körning och utdata av kommandot
morq@laptop:~\/icmpshell$ sudo .\/send.py 45.11.26.232 "Hej vÀrlden!"
Börjar sÀnda:
.FĂ€rdig med att skicka 1 paket.
*
Mottagna 2 paket, fick 1 svar, kvarvarande 0 paket
###[ 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:Hej vÀrlden!
SÄ hÀr ser det ut i sniffaren
morq@laptop:~\/icmpshell$ sudo tshark -i wlp1s0 -O icmp -f "icmp and host 45.11.26.232"
Kör som anvÀndare "root" och grupp "root". Detta kan vara farligt.
FÄngar pÄ 'wlp1s0'
Ram 1: 59 byte pÄ kablaget (472 bitar), 59 byte fÄngade (472 bitar) pÄ grÀnssnittet wlp1s0, id 0
Internetprotokoll version 4, Src: 192.168.0.240, Dst: 45.11.26.232
Internet Control Message Protocol
Typ: 8 (Echo (ping) begÀran)
Kod: 0
Kontrollsumma: 0xd603 [korrekt]
[Kontrollsumma Status: Bra]
Identifierare (BE): 0 (0x0000)
Identifierare (LE): 0 (0x0000)
Sekvensnummer (BE): 0 (0x0000)
Sekvensnummer (LE): 0 (0x0000)
Data (17 byte)
0000 72 75 6e 3a 48 65 6c 6c 6f 2c 20 77 6f 72 6c 64 run:Hello, world
0010 21 !
Data: 72756e3a48656c6c6f2c20776f726c6421
[LĂ€ngd: 17]
Ram 2: 59 byte pÄ trÄden (472 bitar), 59 byte fÄngade (472 bitar) pÄ grÀnssnitt wlp1s0, id 0
Internetprotokoll Version 4, Src: 45.11.26.232, Dst: 192.168.0.240
Internet Control Message Protocol
Typ: 0 (Echo (ping) svar)
Kod: 0
Kontrollsumma: 0xde03 [korrekt]
[Kontrollsumma Status: Bra]
Identifierare (BE): 0 (0x0000)
Identifierare (LE): 0 (0x0000)
Sekvensnummer (BE): 0 (0x0000)
Sekvensnummer (LE): 0 (0x0000)
[FörfrÄgningsram: 1]
[Svarstid: 19.094 ms]
Data (17 byte)
0000 72 75 6e 3a 48 65 6c 6c 6f 2c 20 77 6f 72 6c 64 run:Hello, world
0010 21 !
Data: 72756e3a48656c6c6f2c20776f726c6421
[LĂ€ngd: 17]
^C2 paket fÄngade
Payload i svaret paket Àndras inte.
KĂ€rnmodulen
För att bygga i en virtuell maskin med Debian krÀvs minst make och linux-headers-amd64, resten kommer som beroenden. Jag kommer inte att ge hela koden i artikeln, du kan klona den pÄ GitHub.
Konfigurering av hook
Först behöver vi tvÄ funktioner för att ladda modulen och för att avladda den. Funktionen för avladdning Àr inte obligatorisk, men dÄ kan rmmod inte utföras, modulen avlastas endast vid avstÀngning.
#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);Vad som hÀnder hÀr:
- TvÄ headerfiler laddas för att manipulera sjÀlva modulen och netfilter.
- Alla operationer gÄr genom netfilter, dÀr kan man ange hooks. För detta behöver man deklarera en struktur dÀr hooken kommer att konfigureras. Det viktigaste Àr att ange den funktion som kommer att utföras som hook:
nfho.hook = icmp_cmd_executor;jag kommer att nÄ sjÀlva funktionen snart.
DÀrefter angav jag tidpunkten för paketbehandlingen:NF_INET_PRE_ROUTINGindikerar att paketet ska behandlas sÄ snart det dyker upp i kÀrnan. Man kan anvÀndaNF_INET_POST_ROUTINGför att bearbeta paketet vid utfarten frÄn kÀrnan.
Jag sÀtter en filter pÄ IPv4:nfho.pf = PF_INET;.
Jag tilldelar min hook högsta prioritet:nfho.priority = NF_IP_PRI_FIRST;
Och registrerar datastrukturen som sjÀlva hook:nf_register_net_hook(&init_net, &nfho); - I avslutningsfunktionen tas hooken bort.
- Licensen anges tydligt sÄ att kompilatorn inte klagar.
- Funktioner
module_init()ochmodule_exit()anger andra funktioner som initierande och avslutande arbete för modulen.
Utdrag av payload
Nu mÄste vi extrahera payloaden, vilket visade sig vara den mest utmanande uppgiften. I kÀrnan finns inga inbyggda funktioner för att arbeta med payloaden, man kan bara parsa rubrikerna för mer avancerade protokoll.
#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;
}Vad hÀnder:
- Jag var tvungen att ansluta ytterligare header-filer, den hÀr gÄngen för att manipulera IP- och ICMP-rubriker.
- Jag stÀller in den maximala radlÀngden:
#define MAX_CMD_LEN 1976. Varför just sÄ? För att kompilatorn klagar pÄ en större! Jag har redan fÄtt tips om att jag mÄste kolla pÄ stacken och heapen, nÄgon gÄng ska jag absolut göra det och kanske till och med fixa koden. Jag stÀller snabbt in en rad dÀr kommandot kommer att ligga:char cmd_string[MAX_CMD_LEN];. Den ska vara synlig i alla funktioner, mer om detta kommer jag att berÀtta i punkt 9. - Nu mÄste jag initiera (
struct work_struct my_work;) strukturen och koppla den till en annan funktion (DECLARE_WORK(my_work, work_handler);). Jag kommer ocksÄ att berÀtta om varför detta behövs i punkt nio. - Nu deklarerar jag funktionen som kommer att vara hooken. Typen och de mottagna argumenten dikteras av netfilter, vi Àr bara intresserade av
skb. Det hÀr Àr socket-buffer, en grundlÀggande datakonstruktion som innehÄller all tillgÀnglig information om paketet. - För att funktionen ska fungera behövs tvÄ strukturer och flera variabler, inklusive tvÄ iteratorer.
struct iphdr *iph; struct icmphdr *icmph; unsigned char *user_data; unsigned char *tail; unsigned char *i; int j = 0; - Nu kan vi gĂ„ vidare till logiken. För att modulen ska fungera behövs inga paket förutom ICMP Echo, sĂ„ vi parser bufferten med inbyggda funktioner och slĂ€nger bort alla icke-ICMP och icke-Echo paket. Ă
terkomst
NF_ACCEPTbetyder acceptans av paketet, men du kan Àven droppa paketen genom att ÄtervÀndaNF_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; }Jag har inte kontrollerat vad som hÀnder utan kontroll av IP-rubriker. Min begrÀnsade kunskap i C sÀger mig: utan extra kontroller kommer nÄgot hemskt definitivt att hÀnda. Jag skulle vara glad om du övertygar mig om motsatsen!
- Nu nÀr paketet Àr av rÀtt typ kan vi extrahera data. Utan inbyggd funktion mÄste jag först fÄ en pekare till början av payloaden. Detta görs genom en viss metod, jag behöver ta pekaren till början av ICMP-rubriken och flytta den med storleken pÄ denna rubrik. För allt anvÀnds strukturen
icmph:user_data = (unsigned char *)((unsigned char *)icmph + (sizeof(icmph)));
Rubriken ska avslutas pÄ samma sÀtt som nyttolasten iskb, sÄ vi fÄr den via kÀrnmetoder frÄn motsvarande struktur:tail = skb_tail_pointer(skb);.
Jag har tagit bilden , du kan lÀsa mer om bufferten för socket. - NÀr vi har pekarna pÄ början och slutet kan vi kopiera data till strÀngen
cmd_string, kontrollera den för ett prefixrun:och antingen kasta paketet om det saknas, eller skriva över strÀngen igen, och ta bort det hÀr prefixet. - NÄvÀl, nu kan vi kalla ytterligare en handler:
schedule_work(&my_work);. Eftersom vi inte kan överföra en parameter i ett sÄdant samtal, mÄste strÀngen med kommandot vara global.schedule_work()kommer att placera funktionen kopplad till den angivna strukturen i den gemensamma kö för schemalÀggaren och avslutas, vilket gör att vi inte behöver vÀnta pÄ att kommandot ska avslutas. Detta Àr nödvÀndigt eftersom hooken mÄste vara mycket snabb. Annars fÄr man, som val, inget att starta eller fÄ kernel panic. Fördröjning Àr dödlig! - Vi Àr klara, nu kan paketet tas emot med motsvarande returvÀrde.
Anrop av program i anvÀndarlÀge
Denna funktion Àr den mest förstÄeliga. Dess namn angavs i DECLARE_WORK(), typ och mottagna argument spelar ingen roll. Vi tar strÀngen med kommandot och skickar den helt till shell. LÄt den sjÀlv hantera parsingen, sökningen av binÀrer och allt annat.
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 anger argumenten i en strÀngmatris
argv[]. Jag antar att alla vet att program faktiskt körs pÄ det hÀr sÀttet, inte som en enda strÀng med mellanslag. - Vi angir miljövariabler. Jag har bara lagt till PATH med en minimal uppsÀttning av sökvÀgar, och förutsÀtter att alla redan har sammanfogade
/binmed/usr/binoch/sbinmed/usr/sbin. Andra sökvÀgar spelar sÀllan roll i praktiken. - Klart, vi kör! KÀrnfunktionen
call_usermodehelper()tar emot. sökvÀgen till binÀren, array av argument, array av miljövariabler. HÀr förutsÀtter jag ocksÄ att alla förstÄr syftet med att överföra sökvÀgen till den körbara filen som ett separat argument, men du kan frÄga. Det sista argumentet anger om man ska vÀnta pÄ att processen ska avslutas (UMH_WAIT_PROC), starta processen (UMH_WAIT_EXEC) eller inte vÀnta alls (UMH_NO_WAIT). Det finns ocksÄUMH_KILLABLE, jag har inte grÀvt i detta.
Samling
Byggande av kÀrnmoduler utförs genom kÀrnans make-ramverk. Det anropas make inuti en speciell katalog kopplad till kernelversionen (definieras hÀr: KERNELDIR:=/lib/modules/$(shell uname -r)/build), och modulfyndigheten överförs av variabeln M i argumenten. I mÄlen icmpshell.ko och clean anvÀnds detta ramverk helt. I obj-m anges objektfilen som ska omvandlas till en modul. Syntaxen som omvandlar main.o pÄ icmpshell.o (icmpshell-objs = main.o) ser inte sÀrskilt logisk ut för mig, men sÄ fÄr det bli.
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 bygger: make. Laddar: insmod icmpshell.ko. Klart, du kan kolla: sudo ./send.py 45.11.26.232 "date > /tmp/test". Om du har fÄtt en fil pÄ din maskin /tmp/test och den innehÄller datumet för begÀran, betyder det att du har gjort allt rÀtt och jag har gjort allt rÀtt.
Slutsats
Min första erfarenhet av kĂ€rnutveckling visade sig vara mycket enklare Ă€n jag förvĂ€ntade mig. Ăven utan erfarenhet av C-programmering, och med hjĂ€lp av kompilatorns förslag och Googles resultat, kunde jag skriva en fungerande modul och kĂ€nna mig som en kernel-hacker, samtidigt som jag ocksĂ„ blev en script-kiddie. Dessutom gick jag med i kanalen Kernel Newbies, dĂ€r jag fick rĂ„det att anvĂ€nda schedule_work() istĂ€llet för att ringa call_usermodehelper() inuti sjĂ€lva hooken och blev skammad, dĂ„ det var rimligt att misstĂ€nka ett bedrĂ€geri. Hundra rader kod kostade mig cirka en veckas utveckling pĂ„ fritiden. En lyckad erfarenhet som raserade min personliga myt om den oöverkomliga komplexiteten i systemutveckling.
Om nÄgon Àr villig att utföra en kodgranskning pÄ GitHub skulle jag vara tacksam. Jag Àr nÀstan sÀker pÄ att jag gjorde mÄnga dumma fel, sÀrskilt nÀr det gÀller arbete med strÀngar.
KĂ€lla: habr.com

