TL;DR: kirjutan tuumamoodulit, mis loeb ICMP-payload'ist kÀske ja tÀidab neid serveris isegi siis, kui teie SSH kukub alla. Kodeerimine on kogu kodeeringu jaoks .
Olge ettevaatlik! Kogenud C-programmeerijad vÔivad nutta veretuks pisarateks! VÔin eksida isegi terminoloogias, kuid kÔik kriitika on teretulnud. Postitus on suunatud neile, kes on programmeerimisest C keeles vaid maamÀrkide jÀrgi kuulnud ja soovivad piiluda Linuxi sisemusse.
Minu esimeses kommentaaris mainiti SoftEther VPN-i, mis suudab peituda mÔnede 'tavaliste' protokollide, sealhulgas HTTPS-i, ICMP-i ja isegi DNS-i alla. Kujutlen ainult esimeselt töötamist, kuna olen hoolsalt tuttav HTTP(S)-ga, ja ICMP- ja DNS-i kaudu tunneldamine tuli mul Ôppida.

Jah, ma sain 2020. aastal teada, et ICMP-pakettidesse saab sisestada suvalisi koormusi. Kuid parem hilja kui mitte kunagi! Ja kuna sellega saab midagi teha, tulebki seda teha. Oma igapĂ€evases elus kasutan ma kĂ”ige sagedamini kĂ€suread, sealhulgas SSH kaudu, seega tuli idee ICMP-shelle kĂ”igepealt meelde. Ja et saavutada tĂ€ielik bullshiti bingo, otsustasin kirjutada selle Linuxi moodulina keeles, millest mul on vaid umbkaudne ettekujutus. Selline shell ei ole nĂ€htav protsesside nimekirjas, seda saab laadida tuuma ja see ei asu failisĂŒsteemis, te ei nĂ€e kahtlasi asju kuulamisportide nimekirjas. Oma vĂ”imete poolest on see tĂ€ielik rootkit, kuid ma loodan seda tĂ€iustada ja kasutada viimase lootuse shelle, kui koormus on liiga kĂ”rge, et SSH kaudu sisse logida ja vĂ€hemalt echo i > /proc/sysrq-trigger, et taastada ligipÀÀs ilma taaskĂ€ivitamiseta.
VĂ”tame tekstiredaktori, pĂ”hioskused Pythonis ja C-s, Google'i ja utili, mida pole kahju vajadusel hukka saata (valikuline â kohalik VirtualBox / KVM / jne) ja hakkame pihta!
Kliendi osa
Eeldasin, et kliendiosale tuleb kirjutada umbes 80 rida skripti, kuid head inimesed tegid selle minu eest . Kood osutus ootamatult lihtsaks, mahub 10 olulisse ritta:
import sys
from scapy.all import sr1, IP, ICMP
if len(sys.argv) < 3:
print('Kasutus: {} 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() Skript vÔtab vastu kaks argumenti: aadress ja payload. Enne saatmist eelnen payload vÔtmega run:, meil on seda vaja, et vÀlistada pakette juhusliku payloadiga.
Kernel vajab Ă”igusi pakettide koostamiseks, seega tuleb skript kĂ€ivitada administraatori Ă”igustes. Ăra unusta anda kĂ€itamisĂ”igused ja installida scapy. Debiani sĂŒsteemis on pakett nimega python3-scapy. NĂŒĂŒd saab kontrollida, kuidas see kĂ”ik töötab.
KÀivitamine ja kÀsu vÀljund
morq@laptop:~/icmpshell$ sudo ./send.py 45.11.26.232 "Tere, maailm!"
KĂ€ivita emissioon:
.LÔpetatud 1 paketi saatmine.
*
Saadud 2 paketti, 1 vastust, 0 paketti jÀÀb
###[ IP ]###
versioon = 4
ihl = 5
tos = 0x0
pikkus = 45
id = 17218
lipud =
frag = 0
ttl = 58
proto = icmp
chk = 0x3403
src = 45.11.26.232
dst = 192.168.0.240
options
###[ ICMP ]###
tĂŒĂŒp = echo-vastus
kood = 0
chk = 0xde03
id = 0x0
seq = 0x0
###[ Raw ]###
koormus = 'jooksuta:Tere, maailm!
Nii see vÀlja nÀeb snifferis
morq@laptop:~/icmpshell$ sudo tshark -i wlp1s0 -O icmp -f "icmp and host 45.11.26.232"
KÀitatakse kasutajana "root" ja grupis "root". See vÔib olla ohtlik.
PĂŒĂŒdmine toimub 'wlp1s0' peal
Raam 1: 59 baiti traadis (472 bitti), 59 baiti pĂŒĂŒtud (472 bitti) liidesel wlp1s0, id 0
Internetiprotokoll Versioon 4, Src: 192.168.0.240, Dst: 45.11.26.232
Interneti kontrollsÔnumi protokoll
TĂŒĂŒp: 8 (Echo (ping) pĂ€ring)
Kood: 0
Kontrollsumma: 0xd603 [Ôige]
[Kontrollsumma olek: Hea]
Identifikaator (BE): 0 (0x0000)
Identifikaator (LE): 0 (0x0000)
JĂ€rjekorra number (BE): 0 (0x0000)
JĂ€rjekorra number (LE): 0 (0x0000)
Andmed (17 baiti)
0000 72 75 6e 3a 48 65 6c 6c 6f 2c 20 77 6f 72 6c 64 run:Hello, world
0010 21 !
Data: 72756e3a48656c6c6f2c20776f726c6421
[Pikkus: 17]
Raam 2: 59 baiti juhtmes (472 bitti), 59 baiti salvestatud (472 bitti) liidese wlp1s0 peal, id 0
Internet Protocol Version 4, Src: 45.11.26.232, Dst: 192.168.0.240
Interneti kontrollsÔnumi protokoll
TĂŒĂŒp: 0 (Echo (ping) vastus)
Kood: 0
Kontrollsumma: 0xde03 [Ôige]
[Kontrollsumma olek: Hea]
Identifikaator (BE): 0 (0x0000)
Identifikaator (LE): 0 (0x0000)
JĂ€rjekorra number (BE): 0 (0x0000)
JĂ€rjekorra number (LE): 0 (0x0000)
[PĂ€ringu raam: 1]
[Vastuse aeg: 19.094 ms]
Andmed (17 baiti)
0000 72 75 6e 3a 48 65 6c 6c 6f 2c 20 77 6f 72 6c 64 run:Hello, world
0010 21 !
Data: 72756e3a48656c6c6f2c20776f726c6421
[Pikkus: 17]
^C2 paketti salvestati
Payload vastuspaketis ei muutu.
Kernelimoodul
Debiani virtuaalkeskkonnas koostamiseks on vajalikud vÀhemalt make ja linux-headers-amd64, kÔik muu installitakse sÔltuvustena. Artiklisse ma koodi tervikuna ei too, saate selle GITHUB-ist kloonida.
Hundi seadistamine
Esiteks vajame kahte funktsiooni, et laadida moodulit ja see vĂ€lja laadida. VĂ€ljaladimise funktsioon ei ole kohustuslik, kuid siis ei saa rmmod rakendada, moodul vĂ€ljastatakse ainult vĂ€lja lĂŒlitamisel.
#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);Mis siin toimub:
- Kaks pealkirjafaili tÔmmatakse mooduli ja netfiltri manipuleerimiseks.
- KÔik operatsioonid toimuvad netfiltri kaudu, seal saab seadistada hundid. Selleks on vaja mÀÀrata struktuur, millele hundi seadistamine tuginemise. KÔige olulisem on mÀÀrata funktsioon, mis tuleb seadistamise kÀigus tÀita:
nfho.hook = icmp_cmd_executor;jÔuan veel funktsiooni juurde.
Siis mÀÀrasin paketi töötlemise hetke:NF_INET_PRE_ROUTINGmÀrkides, et pakett tuleb töödelda kohe, kui see kernelisse ilmub. Saate kasutadaNF_INET_POST_ROUTINGpaketi töötlemiseks kernelist vÀljumisel.
Panen filtri IPv4 peale:nfho.pf = PF_INET;.
MÀÀran oma hundile kÔrgeima prioriteedi:nfho.priority = NF_IP_PRI_FIRST;
Ja registreerin andmestruktuuri kui tegelikult hĂŒbriid:nf_register_net_hook(&init_net, &nfho); - LĂ”ppfunktsioonis hĂŒbriid eemaldatakse.
- Litsents on selgelt mÀÀratletud, et kompilaator ei nuriseks.
- Funktsioonid
module_init()jamodule_exit()moodustavad teised funktsioonid mooduli alguse ja lÔpetamise.
Payloadi vÀljavÔtt
NĂŒĂŒd on vaja ekstraktida payload, see osutus kĂ”ige keerulisemaks ĂŒlesandeks. SĂŒsteemis ei ole sisseehitatud funktsioone payloadiga töötamiseks, saab ainult analĂŒĂŒsida kĂ”rgema taseme protokollide pealkirju.
#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;
}Mis toimub:
- Pidin lisama tÀiendavaid pealkirju, seekord IP- ja ICMP-pealkirjade manipuleerimiseks.
- MÀÀran maksimaalse rea pikkuse:
#define MAX_CMD_LEN 1976. Miks just selline? Sest suurema pikkuse puhul nuriseb kompilaator! Keegi on juba soovitanud mul tegeleda virna ja kuhjaga, kunagi kindlasti teen seda ja vĂ”ib-olla isegi parandan koodi. Otse mÀÀran rea, kuhu paigutatakse kĂ€sk:char cmd_string[MAX_CMD_LEN];. See peab olema nĂ€htav kĂ”igis funktsioonides, sellest rÀÀgin pĂ”hjalikumalt punktis 9. - NĂŒĂŒd tuleb initsialiseerida (
struct work_struct my_work;) struktuur ja siduda see veel ĂŒhe funktsiooniga (DECLARE_WORK(my_work, work_handler);). RÀÀgin ka sellest, miks see vajalik on, ĂŒheksandas punktis. - NĂŒĂŒd kuulutan vĂ€lja funktsiooni, mis ongi hook. TĂŒĂŒp ja vastuvĂ”etavad argumendid dikteeritakse netfiltri poolt, meid huvitab ainult
skb. See on soketibufar, pÔhistruktuur, mis sisaldab kÔiki saadaval olevaid andmeid paketi kohta. - Funktsiooni töö jaoks on vaja kahte struktuuri ja mÔnda muutujat, sealhulgas kahte iteraatorit.
struct iphdr *iph; struct icmphdr *icmph; unsigned char *user_data; unsigned char *tail; unsigned char *i; int j = 0; - VÔime jÀtkata loogikaga. Mooduli tööks pole vaja muid pakette peale ICMP Echo, seega parsim bufri sisseehitatud funktsioonide abil ja viskame kÔik mitte-ICMP ja mitte-Echo paketid vÀlja. Tagastamine
NF_ACCEPTtĂ€hendab paketi aktsepteerimist, kuid vĂ”ite ka paketid Ă€ra visata, tagastadesNF_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; }Ma ei ole kontrollinud, mis juhtub ilma IP-peade kontrollita. Minu minimaalne C-keele teadmine ĂŒtleb, et ilma tĂ€iendavate kontrollideta juhtub kindlasti midagi kohutavat. Mul oleks hea meel, kui te mind selles veenaksite!
- NĂŒĂŒd, kui pakett on tĂ€pselt Ă”ige, saab andmeid vĂ€lja vĂ”tta. Ilma sisseehitatud funktsioonita tuleb esmalt saada osutaja koormuse algusele. See toimub lĂ€bi ĂŒhe koha, tuleb vĂ”tta osutaja ICMP pĂ€ise algusele ja nihutada see selle pĂ€ise suuruse vĂ”rra. KĂ”ike seda kasutatakse struktuuri kaudu.
icmph:user_data = (unsigned char *)((unsigned char *)icmph + (sizeof(icmph)));
PÀise lÔpp peab langema kasuliku koormuse lÔpu pealeskb, seega saame selle tuumimeetodite abil vastavast struktuurist:tail = skb_tail_pointer(skb);.
Pildi tĂ”in Ă€ra , vĂ”ite lugeda rohkem socketâi puhasti kohta. - Saades osutajad algusele ja lĂ”ppu, saab andmed kopeerida stringi
cmd_string, kontrollida selle olemasolu eelneva tĂ€hegarun:ja kas visata pakett vĂ€lja, kui see puudub, vĂ”i kirjutada string uuesti, eemaldades selle eelneva tĂ€he. - Noh, nĂŒĂŒd saab kutsuda veel ĂŒhe kĂ€sitleja:
schedule_work(&my_work);. Kuna korralduseks ei saa parameetrit edastada, peab kĂ€sk string olema globaalne.schedule_work()paigutab funktsiooni, mis on seotud edastatud struktuuriga, ĂŒlesande ajakava ĂŒldisse jĂ€rjekorda ja lĂ”peb, vĂ”imaldades oota kĂ€su tĂ€itmist. See on vajalik, kuna hook peab olema vĂ€ga kiire. Vastasel juhul ei kĂ€ivitu midagi vĂ”i saate kernel panic'i. Iga viivitus vĂ”ib olla saatuslik! - KĂ”ik, saate vastava tagastusega paki vastu vĂ”tta.
Programmi kÀivitamine kasutusruumis
See funktsioon on kĂ”ige arusaadavam. Selle nimi on mÀÀratud DECLARE_WORK(), tĂŒĂŒp ja vastuvĂ”etavad argumendid ei huvita. VĂ”tame kĂ€surea ja edastame selle tĂ€ielikult shellile. Las ta lahendab parsing'u, binaaride otsimise ja kĂ”ik muu.
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);
}- Seame argumentidega stringi massiivi
argv[]. Eeldan, et kĂ”ik teavad, et programmid tegelikult tĂ€idetakse just nii, mitte lihtsalt pideva stringina ruumide vahel. - Seame keskkonnamuutujad. Panin ainult PATH minimeeritud teede kogumiga, lootes, et kĂ”igil on need juba ĂŒhendatud
/binkoos/usr/binja/sbinkoos/usr/sbin. Muud teed on praktikas ĂŒsna harva olulised. - Valmis, kĂ€ivitame! Kerneli funktsioon
call_usermodehelper()vĂ”tab sisendiks failitee, argumentide massiivi ja keskkonna muutuja massiivi. Siin ma eeldan ka, et kĂ”ik saavad aru, et tĂ€itevfaili tee edastamine eraldi argumendina on vajalik, aga vĂ”ite kĂŒsida. Viimane argument nĂ€itab, kas oodata protsessi lĂ”petamist (UMH_WAIT_PROC), protsessi kĂ€ivitamist (UMH_WAIT_EXEC) vĂ”i mitte oodata ĂŒldse (UMH_NO_WAIT). Veel on olemasUMH_KILLABLE, aga ma ei hakanud sellega sĂŒvitsi minema.
Koostamine
Kerneli moodulite koostamine toimub sama kerneli make-raamistiku kaudu. See kutsub esile make spetsiaalses kaustas, mis on seotud kerneli versiooniga (mÀÀratakse siit: KERNELDIR:=/lib/modules/$(shell uname -r)/build), ja mooduli asukoht edastatakse muutujana M argumentides. Icmpshell.ko ja clean sihiks kasutatakse tĂ€ielikult seda raamistiku. obj-m mÀÀrab objekti fail, mis muudetakse mooduliks. SĂŒntaks, mis muudab main.o ĂŒhes icmpshell.o (icmpshell-objs = main.o) tundub mulle mitte vĂ€ga loogiline, aga las olla.
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
Koostame: make. Laadime: insmod icmpshell.ko. Valmis, saab kontrollida: sudo ./send.py 45.11.26.232 "date > /tmp/test". Kui teie masinas on vÀlja tekkinud fail /tmp/test ja, ning selles on pÀringu saatmise kuupÀev, mis tÀhendab, et te tegite kÔik Ôigesti ja ma tegin kÔik Ôigesti.
KokkuvÔte
Minu esimene kogemus tuumaarenduses oli palju lihtsam, kui ma ootasin. Isegi ilma C arenduskogemuseta, tuginedes kompilaatori vihjetele ja Google'i tulemusele, suutsin ma kirjutada töötava mooduli ja tunda end tuumahĂ€kkerina, samas ka skripditiitina. Lisaks sellele kĂŒlastasin Kernel Newbies kanalit, kus mulle soovitati kasutada schedule_work() tĂ€iendavalt kutsumiseks call_usermodehelper() otse ise konksu sees ja mind naljatati, Ă”igustatult kahtlustades petuskeemi. Saja rea kood maksis mulle rohkem kui nĂ€dal vabade pĂ€evade arendust. Edukas kogemus, mis lĂ”hkus minu isikliku mĂŒĂŒdi sĂŒsteemse arenduse ĂŒlemÀÀra keerukusest.
Kui keegi nĂ”ustub GitHubis koodi ĂŒle vaatama, oleksin tĂ€nulik. Olen peaaegu kindel, et tegin palju tobe vigu, eriti stringidega töötamisel.
Allikas: habr.com

