TL;DR: po shkrimi një moduli bërthamor që do të lexojë komandat nga payload-i ICMP dhe do t'i ekzekutojë ato në server, edhe në rast se SSH-ja bie. Për më të padurueshmit, e gjithë kodi është në .
Opsioni âbootable pĂ«rcakton nga cilat pajisje blloku Ă«shtĂ« montuar direktoriumi dhe installon bootloader-in nĂ« kĂ«tĂ« pajisje. NĂ«se e harroni tĂ« montoni pajisjen ose gabimisht pĂ«rcaktoni njĂ« direktorium, qĂ« ndodhet, pĂ«r shembull, nĂ« /dev/sda, pĂ«rkatĂ«sisht bootloader-i nĂ« /dev/sda do tĂ« mbivendoset. PĂ«rdorni âbootable me kujdes. Programuesit e aftĂ« nĂ« C rrezikojnĂ« tĂ« qajnĂ« me lot gjaku! Mund tĂ« jem nĂ« gabim edhe nĂ« terminologji, por çdo kritikĂ« Ă«shtĂ« e mirĂ«pritur. Postimi Ă«shtĂ« pĂ«r ata qĂ« kanĂ« njĂ« ide tĂ« pĂ«rafĂ«rt pĂ«r programimin nĂ« C dhe duan tĂ« hedhin njĂ« sy nĂ« brendĂ«si tĂ« Linux-it.
Në komentet e postimit tim të parë u përmend SoftEther VPN, i cili di të mimikrojë disa protokolle "të zakonshme", veçanërisht HTTPS, ICMP dhe madje DNS. Unë e imagjinoj funksionimin vetëm të parërit, pasi jam shumë i njohur me HTTP(S), ndërsa tunneling mbi ICMP dhe DNS më duhej ta studioja.

Po, në vitin 2020 mora vesh se në paketat ICMP mund të fusim një payload të rastësishëm. Por më mirë vonë sesa kurrë! Dhe pasi që mund të bëhet diçka me këtë, duhet ta bëjmë. Duke qenë se në përditshmërinë time përdor më shpesh terminalin, përfshirë përmes SSH, ideja e shell-it ICMP më erdhi në mendje që në fillim. Dhe për të mbledhur një bullsh*t-bingo, vendosa ta shkruaj në formën e një moduli Linux në një gjuhë për të cilën kam vetëm një ide të përafërt. Ky shell nuk do të shihet në listën e proceseve, mund të ngarkohet në bërthamë dhe nuk do të jetë në sistemin e skedarëve, nuk do të shihni asgjë të dyshimtë në listën e porteve të dëgjimit. Në potencialet e tij, ky është një rootkit i plotë, por shpresoj ta përmirësoj dhe ta përdor si një shell të fundit të shpresës kur Load Average është shumë i lartë për të hyrë nëpërmjet SSH dhe të ekzekutoj të paktën echo i > /proc/sysrq-trigger, për të rikthyer qasjen pa e riluajtur.
Marrim njĂ« redaktor teksti, aftĂ«si bazike programimi nĂ« Python dhe C, Google dhe e cila nuk mĂ« vjen keq ta ndizja nĂ«se gjithçka shkatĂ«rrohet (opsionale â VirtualBox/KVM/local etc) dhe fillojmĂ«!
Pjesa klient
Më dukej se për pjesën e klientit do të duhej të shkruaja një skript me rreth 80 rreshta, por ndodhi që kishte njerëz të mirë që e bënë për mua . Kodi doli më i thjeshtë se sa pritej, u përshtat në 10 rreshta të rëndësishëm:
import sys
from scapy.all import sr1, IP, ICMP
if len(sys.argv) < 3:
print('Përdorimi: {} IP "komandë"'.format(sys.argv[0]))
exit(0)
p = sr1(IP(dst=sys.argv[1])/ICMP()/"run:{}".format(sys.argv[2]))
if p:
p.show() Skripti merr dy argumente, adresën dhe payload-in. Para dërgimit, payload-i paraprihet nga çelësi run:, do t'na nevojitet për të përjashtuar paketat me ngarkesë të rastësishme.
Nukka kërkon privilegje për të krijuar paketat, prandaj skripti duhet të ekzekutohet me të drejtat e administratorit. Mos harroni të jepni të drejtat e ekzekutimit dhe të instaloni vetë scapy. Në Debian ka një paketë, e cila quhet python3-scapy. Tani mund të kontrolloni se si funksionon gjithçka.
Ekzekutimi dhe dalja e komandës
morq@laptop:~\/icmpshell$ sudo .\/send.py 45.11.26.232 "Hello, world!"
Fillimi i emetimit:
.Përfundoi dërgimi i 1 paketave.
*
Mori 2 paketa, mori 1 përgjigje, mbetën 0 paketa
###[ 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!
Kështu duket në analizuesin
morq@laptop:~\/icmpshell$ sudo tshark -i wlp1s0 -O icmp -f "icmp dhe host 45.11.26.232"
Duket si përdoruesi "root" dhe grupi "root". Kjo mund të jetë e rrezikshme.
Duke kapur në 'wlp1s0'
Kadri 1: 59 byte në tel (472 bits), 59 byte të kapura (472 bits) në ndërfaqen wlp1s0, id 0
Protokolli i Internetit Versioni 4, Src: 192.168.0.240, Dst: 45.11.26.232
Protokolli i Mesazheve të Kontrollit të Internetit
Lloji: 8 (Kërkesë Echo (ping))
Kodi: 0
Kontrolli: 0xd603 [korrekt]
[Statusi i Kontrollit: Mirë]
Identifikuesi (BE): 0 (0x0000)
Identifikuesi (LE): 0 (0x0000)
Numri i sekuencës (BE): 0 (0x0000)
Numri i sekuencës (LE): 0 (0x0000)
Të dhënat (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
[Gjatësia: 17]
Kadri 2: 59 byte në tel (472 bits), 59 byte të kapura (472 bits) në ndërfaqen wlp1s0, id 0
Protokolli i Internetit Versioni 4, Src: 45.11.26.232, Dst: 192.168.0.240
Protokolli i Mesazheve të Kontrollit të Internetit
Tipi: 0 (Përgjigje (ping) e dërguar)
Kodi: 0
Kontrolli: 0xde03 [korrekt]
[Statusi i Kontrollit: Mirë]
Identifikuesi (BE): 0 (0x0000)
Identifikuesi (LE): 0 (0x0000)
Numri i sekuencës (BE): 0 (0x0000)
Numri i sekuencës (LE): 0 (0x0000)
[Kadri i kërkesës: 1]
[Koha e përgjigjes: 19.094 ms]
Të dhënat (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
[Gjatësia: 17]
^C2 paketa të kapura
Ngarkesa në paketën e përgjigjes nuk ndryshohet.
Moduli i bërthamës
Për ndërtimin në një makinë virtuale me Debian, do të nevojiten së paku make dhe linux-headers-amd64, e gjithë e tjera do të tërhiqen si varësi. Në artikull nuk do të jap kodin në tërësi, mund ta klononi atë në GitHub.
Konfigurimi i hooks
Së pari, na duhen dy funksione për të ngarkuar modulin dhe për ta shkarkuar atë. Funksioni për shkarkimin nuk është i detyrueshëm, por atëherë rmmod nuk do të mund të ekzekutohet, moduli do të shkarkohet vetëm kur të fiket.
#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);ĂfarĂ« ndodh kĂ«tu:
- Të dy skedarët e titullit për manipulimin me modulin dhe me netfilterin kërkohen.
- Të gjitha operacionet kalojnë përmes netfilterit, ku mund të vendosen hooks. Për këtë, duhet të shpallet struktura në të cilën hook do të konfigurohet. E rëndësishmja është të shënohet funksioni që do të ekzekutohet si hook:
nfho.hook = icmp_cmd_executor;do të arrij edhe te funksioni vetë.
Më pas përcaktoj momentin e trajtimit të paketës:NF_INET_PRE_ROUTINGtregon të trajtohet paketa, kur sapo ka ardhur në bërthamë. Mund të përdoretNF_INET_POST_ROUTINGpër trajtimin e paketës në daljen nga bërthama.
Vë një filtër mbi IPv4:nfho.pf = PF_INET;.
Caktoj prioritetin më të lartë për hook-un tim:nfho.priority = NF_IP_PRI_FIRST;
Dhe regjistroj strukturën e të dhënave si hook:nf_register_net_hook(&init_net, &nfho); - Në funksionin përfundimtar, hook-u hiqet.
- Licenca është e shënuar qartë, në mënyrë që kompajleri të mos ankohet.
- Funksionet
module_init()dhemodule_exit()përcaktojnë funksione të tjera si funksionin fillestar dhe përfundimtar të punës së modulit.
Nxjerrja e ngarkesës
Tani e nevojshme të nxjerrim payload-in, kjo rezultoi të ishte detyra më e vështirë. Në bërthamë nuk ka funksione të integruara për punën me payload-in, mund të analizojmë vetëm titujt e protokolleve më të nivelit të lartë.
#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;
}ĂfarĂ« po ndodh:
- Më duhet të lidh disa skedarë shtesë, këtë herë për manipulimin me IP dhe ICMP headers.
- Caktoj gjatësinë maksimale të vargut:
#define MAX_CMD_LEN 1976. Pse pikërisht kështu? Sepse kompajleri ankohet për një gjatësinë më të madhe! Më kanë dhënë disa këshilla për të punuar me stek dhe heap, ndonjëherë do ta bëj edhe këtë dhe ndoshta do ta rregulloj kodin. Dua të caktoj një varg, në të cilin do të vendos komandën:char cmd_string[MAX_CMD_LEN];. Ai duhet të jetë i dukshëm në të gjitha funksionet, për këtë do të flas më shumë në pikën 9. - Tani duhet të inicializoj (
struct work_struct my_work;) strukturën dhe ta lidh atë me një funksion tjetër (DECLARE_WORK(my_work, work_handler);). Pse është e nevojshme kjo, do ta shpjegoje gjithashtu në pikën e nëntë. - Tani shpall funksionin që do të jetë hook. Lloji dhe argumentet e marra përcaktohen nga netfilter, na intereson vetëm
skb. Kjo është një buffer soketi, një strukturë të dhënash themelore që përmban të gjitha informacionet e disponueshme mbi paketën. - Për funksionin do të nevojiten dy struktura, dhe disa variabla, përfshirë dy iterues.
struct iphdr *iph; struct icmphdr *icmph; unsigned char *user_data; unsigned char *tail; unsigned char *i; int j = 0; - Mund të filloj logjikën. Për punën e modulit nuk nevojiten paketat e tjera përveç ICMP Echo, kështu që analizojmë buffer-in me funksionet e integruara dhe hiqni të gjitha paketat që nuk janë ICMP ose Echo. Kthimi
NF_ACCEPTdo të thotë pranim i paketës, por gjithashtu mund të refuzoni paketat, duke kthyerNF_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; }Nuk kam kontrolluar se çfarë do të ndodhë pa verifikimin e titujve IP. Njohuria ime minimale për C më thotë: pa kontrolle shtesë, patjetër do të ndodhin gjëra të këqija. Do të isha i lumtur nëse do të më bindnin për të kundërtën!
- Tani, kur paketa është e sigurta e llojit të duhur, mund të nxjerrim të dhënat. Pa një funksion të integruar, më parë duhet të marr një tregues në fillimin e payload-in. Kjo bëhet përmes një vendi, duhet të marr një tregues në fillimin e titullit ICMP dhe ta zhvendos atë në madhësinë e këtij titulli. Për gjithçka përdoret struktura
icmph:user_data = (unsigned char *)((unsigned char *)icmph + (sizeof(icmph)));
Fundi i titullit duhet të përkojë me fundin e ngarkesës nëskb, prandaj e marrim me mjete bërthamore nga struktura përkatëse:tail = skb_tail_pointer(skb);.
I mora imazhin , mund të lexoni më shumë rreth buffer-it të soketëve. - Pas marrjes së treguesve në fillim dhe në fund, është e mundur të kopjoni të dhënat në vargun
cmd_string, ta kontrolloni për prapashtesënrun:dhe, nëse ajo mungon, ose ta flakni paketën ose ta ripërshtatni përsëri vargun, duke e hequr këtë prapashtesë. - Mire, tani mund të thërrasim një handler tjetër:
schedule_work(&my_work);. Duke qenë se në këtë thirrje nuk mund të kalosh një parameter, vargu me komandën duhet të jetë global.schedule_work()do ta vendosë funksionin e lidhur me strukturën e kaluar në radhën e përbashkët të planifikuesit të detyrave dhe do të përfundojë, duke e lejuar që të mos presim përfundimin e komandës. Kjo është e nevojshme sepse hook duhet të jetë shumë i shpejtë. Ndryshe, do të keni, sipas zgjedhjes, asgjë për të nisur ose do të merrni një kernel panic. Vonesat janë të rrezikshme! - Tani, mund të pranojmë paketën me kthimin përkatës.
Thirrja e programit në hapësirën e përdoruesit
Kjo funksion është më e kuptueshme. Emri i saj u dha në DECLARE_WORK(), lloji dhe argumentet e pranuara nuk janë interesante. Marrim vargun me komandën dhe e kalojmë tërësisht shell-it. Le ta kuptojë vetë për gramatikën, kërkimin e binarëve dhe gjithçka tjetër.
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);
}- Dizajnojmë argumentet në vargun e vargjeve
argv[]. Supozuj se të gjithë e dinë që programet në të vërtetë ekzekutohen kështu, dhe jo si një varg i pandërprerë me hapësira. - Dizajnojmë variablat e mjedisit. Vendosa vetëm PATH me një grup minimal të rrugëve, duke supozuar që të gjithë i kanë tashmë të konsoliduara
/binme/usr/bindhe/sbinme/usr/sbin. Rrugët e tjera janë mjaft të rralla për t'u marrë parasysh në praktikë. - Gati, le të ekzekutojmë! Funksioni i bërthamës
call_usermodehelper()pranon si input. rruga për binarin, vargu i argumenteve, vargu i variablave të mjedisit. Këtu gjithashtu supozoj se të gjithë e kuptojnë sensin e kalimit të rrugës për skedarin ekzekutues si një argument të veçantë, por mund të pyesni. Argumenti përfundimtar tregon nëse të pritet përfundimi i procesit (UMH_WAIT_PROC), fillimi i procesit (UMH_WAIT_EXEC) ose të mos pritet fare (UMH_NO_WAIT). Ka gjithashtuUMH_KILLABLE, nuk e shqyrtova këtë.
Ndërtimi
Ndërtimi i moduleve bërthamore bëhet përmes kuadrit miratojmë bërthamën. Thirret make në brendësi të një direktorie speciale që lidhet me versionin e bërthamës (caktuar këtu: KERNELDIR:=/lib/modules/$(shell uname -r)/build), dhe vendndodhja e modulit transmetohet përmes ndryshoreve M në argumenta. Në targetet icmpshell.ko dhe clean përdoret krejt ky kornizë. Në obj-m specifikohet skedari objekt që do të rishkruhet në modul. Sintaksa, e cila rishkruan main.o në icmpshell.o (icmpshell-objs = main.o) për mua duket jo shumë logjike, por le të jetë kështu.
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
Po e tregojmë: make. Ngarkojmë: insmod icmpshell.ko. Gati, mund të verifikoni: sudo ./send.py 45.11.26.232 "date > /tmp/test". Nëse keni një skedar në makinë tuaj /tmp/test dhe në të ka datën e dërgimit të kërkesës, atëherë keni bërë gjithçka siç duhet dhe unë kam bërë gjithçka siç duhet.
Përfundimi
Eksperienca ime e parë në zhvillimin e bërthamës ishte shumë më e thjeshtë se sa e prisja. Edhe pa pasur përvojë në zhvillimin në C, duke u orientuar nga treguesit e kompilatorit dhe rezultatet e Google, isha në gjendje të shkruaja një modul funksional dhe të ndihesha si një haker bërthamor, ndërkohë që e dija se isha gjithashtu një skript-ë kid. Përveç kësaj, hyra në kanalin Kernel Newbies, ku më sugjeruan të përdor schedule_work() në vend të thirrjes call_usermodehelper() në brendësi të vetë mongile dhe më shpifën, duke e dyshuar drejtë për skamin. Qindra rreshta kodi më kushtuan rreth një javë zhvillimi në kohën time të lirë. Një përvojë e suksesshme, që shkatërroi mitin tim personal mbi kompleksitetin e pamundur të zhvillimit sistemor.
Nëse dikush pranon të bëjë një rishikim kodi në GitHub, do të isha mirënjohës. Kam pothuajse siguri se kam bërë shumë gabime të kota, veçanërisht në punën me vargjet.
Burimi: habr.com

