TL; DR: Es rakstu kodola moduli, kas nolasÄ«s komandas no ICMP slodzes un izpildÄ«s tÄs serverÄ« pat tad, ja jÅ«su SSH avarÄ. NepacietÄ«gÄkajiem viss kods ir GitHub.
UzmanÄ«bu! PieredzÄjuÅ”i C programmÄtÄji riskÄ izplÅ«st asiÅu asarÄs! VarbÅ«t pat kļūdos terminoloÄ£ijÄ, bet jebkura kritika ir apsveicama. Å is ieraksts ir paredzÄts tiem, kam ir ļoti aptuvens priekÅ”stats par C programmÄÅ”anu un kuri vÄlas ieskatÄ«ties Linux iekÅ”ienÄ.
KomentÄros manam pirmajam raksts minÄts SoftEther VPN, kas var atdarinÄt dažus āparastosā protokolus, jo Ä«paÅ”i HTTPS, ICMP un pat DNS. Es varu iedomÄties, ka darbojas tikai pirmais no tiem, jo āāes ļoti labi pÄrzinu HTTP(S), un man bija jÄiemÄcÄs tunelÄÅ”ana, izmantojot ICMP un DNS.
JÄ, 2020. gadÄ es uzzinÄju, ka ICMP paketÄs varat ievietot patvaļīgu lietderÄ«go slodzi. Bet labÄk vÄlu nekÄ nekad! Un tÄ kÄ ar to var kaut ko darÄ«t, tad tas ir jÄdara. TÄ kÄ manÄ ikdienÄ visbiežÄk izmantoju komandrindu, tai skaitÄ caur SSH, man vispirms ienÄca prÄtÄ doma par ICMP Äaulu. Un, lai saliktu pilnÄ«gu bullshield bingo, es nolÄmu to uzrakstÄ«t kÄ Linux moduli valodÄ, par kuru man ir tikai aptuvens priekÅ”stats. Å Äds apvalks nebÅ«s redzams procesu sarakstÄ, jÅ«s varat to ielÄdÄt kodolÄ un tas nebÅ«s failu sistÄmÄ, jÅ«s neredzÄsit neko aizdomÄ«gu klausÄ«Å”anÄs portu sarakstÄ. PÄc iespÄjÄm Å”is ir pilnvÄrtÄ«gs rootkit, taÄu es ceru to uzlabot un izmantot kÄ pÄdÄjo lÄ«dzekli, kad vidÄjÄ slodze ir pÄrÄk augsta, lai pieteiktos caur SSH un izpildÄ«tu vismaz echo i > /proc/sysrq-triggerlai atjaunotu piekļuvi bez atkÄrtotas palaiÅ”anas.
Å emam teksta redaktoru, programmÄÅ”anas pamatprasmes Python un C, Google un virtuÄls kuru jÅ«s neiebilstat likt zem naža, ja viss saplÄ«st (pÄc izvÄles ā vietÄjais VirtualBox/KVM/u.c.), un dodamies!
Klienta puse
Man likÄs, ka klienta daļai bÅ«s jÄraksta scenÄrijs ar apmÄram 80 rindiÅÄm, bet bija laipni cilvÄki, kas to izdarÄ«ja manÄ vietÄ visu darbu. Kods izrÄdÄ«jÄs negaidÄ«ti vienkÄrÅ”s, iekļaujoties 10 nozÄ«mÄ«gÄs rindÄs:
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()
Skriptam ir divi argumenti, adrese un lietderÄ«gÄ slodze. Pirms nosÅ«tÄ«Å”anas lietderÄ«gÄs kravas ievada atslÄga run:, mums tas bÅ«s nepiecieÅ”ams, lai izslÄgtu pakotnes ar nejauÅ”u lietderÄ«go slodzi.
Kodolam ir nepiecieÅ”amas privilÄÄ£ijas pakotÅu izstrÄdei, tÄpÄc skripts bÅ«s jÄpalaiž kÄ superlietotÄjam. Neaizmirstiet pieŔķirt izpildes atļaujas un instalÄt paÅ”u scapy. Debianam ir pakotne ar nosaukumu python3-scapy. Tagad jÅ«s varat pÄrbaudÄ«t, kÄ tas viss darbojas.
Komandas palaiŔana un izvadīŔana 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!
LÅ«k, kÄ tas izskatÄs sniferÄ« 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)
Lai izveidotu Debian virtuÄlo maŔīnu, jums bÅ«s nepiecieÅ”ams vismaz make Šø linux-headers-amd64, pÄrÄjais bÅ«s atkarÄ«bu veidÄ. Es nesniegÅ”u visu kodu rakstÄ; jÅ«s varat to klonÄt vietnÄ Github.
ÄÄ·a iestatÄ«Å”ana
SÄkumÄ mums ir nepiecieÅ”amas divas funkcijas, lai ielÄdÄtu moduli un to izlÄdÄtu. IzkrauÅ”anas funkcija nav nepiecieÅ”ama, bet tad rmmod tas nedarbosies; modulis tiks izlÄdÄts tikai tad, kad tas bÅ«s izslÄgts.
Tiek ievilkti divi galvenes faili, lai manipulÄtu ar paÅ”u moduli un tÄ«kla filtru.
Visas darbÄ«bas iet caur tÄ«kla filtru, tajÄ var iestatÄ«t ÄÄ·us. Lai to izdarÄ«tu, jums ir jÄdeklarÄ struktÅ«ra, kurÄ ÄÄ·is tiks konfigurÄts. VissvarÄ«gÄkais ir norÄdÄ«t funkciju, kas tiks izpildÄ«ta kÄ ÄÄ·is: nfho.hook = icmp_cmd_executor; Pie paÅ”as funkcijas es pievÄrsÄ«Å”os vÄlÄk.
Tad es iestatÄ«ju iepakojuma apstrÄdes laiku: NF_INET_PRE_ROUTING nosaka pakotnes apstrÄdi, kad tÄ pirmo reizi parÄdÄs kodolÄ. Var izmantot NF_INET_POST_ROUTING lai apstrÄdÄtu paketi, kad tÄ iziet no kodola.
Es iestatīju filtru uz IPv4: nfho.pf = PF_INET;.
Es pieŔķiru savam ÄÄ·im visaugstÄko prioritÄti: nfho.priority = NF_IP_PRI_FIRST;
Un es reÄ£istrÄju datu struktÅ«ru kÄ faktisko ÄÄ·i: nf_register_net_hook(&init_net, &nfho);
PÄdÄjÄ funkcija noÅem ÄÄ·i.
Licence ir skaidri norÄdÄ«ta, lai sastÄdÄ«tÄjs nesÅ«dzÄtos.
Funkcijas module_init() Šø module_exit() iestatiet citas funkcijas, lai inicializÄtu un pÄrtrauktu moduli.
Kravas izgūŔana
Tagad mums ir jÄizÅem krava, tas izrÄdÄ«jÄs visgrÅ«tÄkais uzdevums. Kodolam nav iebÅ«vÄtu funkciju darbam ar kravÄm; jÅ«s varat parsÄt tikai augstÄka lÄ«meÅa protokolu galvenes.
Man bija jÄiekļauj papildu galvenes faili, Å”oreiz, lai manipulÄtu ar IP un ICMP galvenÄm.
Es iestatÄ«ju maksimÄlo lÄ«nijas garumu: #define MAX_CMD_LEN 1976. KÄpÄc tieÅ”i tÄ? Jo sastÄdÄ«tÄjs par to sÅ«dzas! ViÅi jau man ieteica, ka man ir jÄsaprot kaudze un kaudze, kÄdreiz es noteikti to izdarÄ«Å”u un varbÅ«t pat laboÅ”u kodu. Es nekavÄjoties iestatÄ«ju rindu, kurÄ bÅ«s komanda: char cmd_string[MAX_CMD_LEN];. Tam jÄbÅ«t redzamam visÄs funkcijÄs; par to es runÄÅ”u sÄ«kÄk 9. punktÄ.
Tagad mums ir jÄinicializÄ (struct work_struct my_work;) struktÅ«ru un savienot to ar citu funkciju (DECLARE_WORK(my_work, work_handler);). Par to, kÄpÄc tas ir nepiecieÅ”ams, es runÄÅ”u arÄ« devÄ«tajÄ rindkopÄ.
Tagad es paziÅoju funkciju, kas bÅ«s ÄÄ·is. Veidu un pieÅemtos argumentus diktÄ netfilter, mÅ«s tikai interesÄ skb. Tas ir ligzdas buferis, pamata datu struktÅ«ra, kas satur visu pieejamo informÄciju par paketi.
Lai funkcija darbotos, jums bÅ«s nepiecieÅ”amas divas struktÅ«ras un vairÄki mainÄ«gie, tostarp divi iteratori.
MÄs varam sÄkt ar loÄ£iku. Lai modulis darbotos, nav nepiecieÅ”amas citas paketes, izÅemot ICMP Echo, tÄpÄc mÄs parsÄjam buferi, izmantojot iebÅ«vÄtÄs funkcijas, un izmetam visas paketes, kas nav ICMP un Echo. Atgriezties NF_ACCEPT nozÄ«mÄ pakas pieÅemÅ”anu, bet pakas var nomest arÄ« atgriežot NF_DROP.
Neesmu pÄrbaudÄ«jis, kas notiks, nepÄrbaudot IP galvenes. Manas minimÄlÄs zinÄÅ”anas par C man saka, ka bez papildu pÄrbaudÄm noteikti notiks kaut kas briesmÄ«gs. Es priecÄÅ”os, ja jÅ«s mani no tÄ atrunÄsit!
Tagad, kad pakotne ir tieÅ”i tÄda veida, kÄds jums nepiecieÅ”ams, varat iegÅ«t datus. Ja nav iebÅ«vÄtas funkcijas, vispirms ir jÄiegÅ«st rÄdÄ«tÄjs uz lietderÄ«gÄs slodzes sÄkumu. Tas tiek darÄ«ts vienuviet, jums ir jÄnoÅem rÄdÄ«tÄjs uz ICMP galvenes sÄkumu un jÄpÄrvieto lÄ«dz Ŕīs galvenes izmÄram. Viss izmanto struktÅ«ru icmph: user_data = (unsigned char *)((unsigned char *)icmph + (sizeof(icmph)));
Virsraksta beigÄm ir jÄatbilst lietderÄ«gÄs slodzes beigÄm skb, tÄpÄc mÄs to iegÅ«stam, izmantojot kodollÄ«dzekļus no atbilstoÅ”Äs struktÅ«ras: tail = skb_tail_pointer(skb);.
Bilde tika nozagta tÄtad, varat lasÄ«t vairÄk par ligzdas buferi.
Kad ir norÄdes uz sÄkumu un beigÄm, varat kopÄt datus virknÄ cmd_string, pÄrbaudiet, vai tajÄ nav prefiksa run: un vai nu izmetiet pakotni, ja tÄs trÅ«kst, vai pÄrrakstiet rindu vÄlreiz, noÅemot Å”o prefiksu.
Tas arÄ« viss, tagad varat piezvanÄ«t citam apdarinÄtÄjam: schedule_work(&my_work);. TÄ kÄ Å”Ädam izsaukumam parametru nodot nebÅ«s iespÄjams, rindai ar komandu jÄbÅ«t globÄlai. schedule_work() ievietos funkciju, kas saistÄ«ta ar nodoto struktÅ«ru, uzdevumu plÄnotÄja vispÄrÄjÄ rindÄ un pabeigs, ļaujot jums negaidÄ«t, kamÄr komanda tiks pabeigta. Tas ir nepiecieÅ”ams, jo ÄÄ·im jÄbÅ«t ļoti Ätram. PretÄjÄ gadÄ«jumÄ jÅ«su izvÄle ir tÄda, ka nekas nesÄksies, vai arÄ« jÅ«s saÅemsit kodola paniku. KavÄÅ”anÄs ir kÄ nÄve!
Tas ir viss, jÅ«s varat pieÅemt paku ar atbilstoÅ”u atgrieÅ”anu.
Programmas izsaukÅ”ana lietotÄja telpÄ
Å Ä« funkcija ir visskaidrÄkÄ. TÄ nosaukums tika dots DECLARE_WORK(), veids un pieÅemtie argumenti nav interesanti. MÄs uzÅemam lÄ«niju ar komandu un pilnÄ«bÄ nododam to apvalkam. Ä»aujiet viÅam nodarboties ar parsÄÅ”anu, binÄro failu meklÄÅ”anu un visu pÄrÄjo.
Iestatiet argumentus uz virkÅu masÄ«vu argv[]. Es pieÅemu, ka visi zina, ka programmas faktiski tiek izpildÄ«tas Å”ÄdÄ veidÄ, nevis kÄ nepÄrtraukta rinda ar atstarpÄm.
Iestatiet vides mainÄ«gos. Es ievietoju tikai PATH ar minimÄlu ceļu kopu, cerot, ka tie visi jau ir apvienoti /bin Ń /usr/bin Šø /sbin Ń /usr/sbin. Citiem ceļiem praksÄ reti ir nozÄ«me.
Gatavs, darÄ«sim to! Kodola funkcija call_usermodehelper() pieÅem ieeju. ceļŔ uz binÄro, argumentu masÄ«vs, vides mainÄ«go masÄ«vs. Å eit es arÄ« pieÅemu, ka visi saprot, ko nozÄ«mÄ ceļŔ uz izpildÄmo failu kÄ atseviŔķu argumentu, bet jÅ«s varat jautÄt. PÄdÄjais arguments norÄda, vai gaidÄ«t procesa pabeigÅ”anu (UMH_WAIT_PROC), procesa sÄkums (UMH_WAIT_EXEC) vai negaidi nemaz (UMH_NO_WAIT). Vai ir vÄl daži UMH_KILLABLE, es to neskatÄ«jos.
MontÄža
Kodola moduļu montÄža tiek veikta, izmantojot kodola make-framework. ZvanÄ«ja make Ä«paÅ”Ä direktorijÄ, kas saistÄ«ts ar kodola versiju (definÄts Å”eit: KERNELDIR:=/lib/modules/$(shell uname -r)/build), un moduļa atraÅ”anÄs vieta tiek nodota mainÄ«gajam M argumentos. icmpshell.ko un tÄ«rie mÄrÄ·i pilnÄ«bÄ izmanto Å”o sistÄmu. IN obj-m norÄda objekta failu, kas tiks pÄrveidots par moduli. Sintakse, kas pÄrtaisa main.o Š² icmpshell.o (icmpshell-objs = main.o) man neŔķiet ļoti loÄ£iski, bet lai tÄ bÅ«tu.
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
MÄs savÄcam: make. Notiek ielÄde: insmod icmpshell.ko. Gatavs, varat pÄrbaudÄ«t: sudo ./send.py 45.11.26.232 "date > /tmp/test". Ja jÅ«su datorÄ ir fails /tmp/test un tajÄ ir norÄdÄ«ts pieprasÄ«juma nosÅ«tÄ«Å”anas datums, kas nozÄ«mÄ, ka jÅ«s visu izdarÄ«jÄt pareizi un es visu izdarÄ«ju pareizi.
SecinÄjums
Mana pirmÄ pieredze kodolenerÄ£ijas attÄ«stÄ«bÄ bija daudz vieglÄka, nekÄ es gaidÄ«ju. Pat bez pieredzes, izstrÄdÄjot C valodÄ, koncentrÄjoties uz kompilatoru padomiem un Google rezultÄtiem, es varÄju uzrakstÄ«t darba moduli un justies kÄ kodola hakeris un tajÄ paÅ”Ä laikÄ skriptu mazulis. TurklÄt es devos uz Kernel Newbies kanÄlu, kur man lika izmantot schedule_work() tÄ vietÄ, lai piezvanÄ«tu call_usermodehelper() paÅ”Ä ÄÄ·a iekÅ”pusÄ un nokauninÄja viÅu, pamatoti aizdomÄjoties par krÄpniecÄ«bu. Simts koda rindiÅas man izmaksÄja apmÄram nedÄļu, ko attÄ«stÄ«ju brÄ«vajÄ laikÄ. VeiksmÄ«ga pieredze, kas iznÄ«cinÄja manu personÄ«go mÄ«tu par sistÄmas izstrÄdes milzÄ«go sarežģītÄ«bu.
Ja kÄds piekritÄ«s Github koda pÄrskatÄ«Å”anai, bÅ«Å”u pateicÄ«gs. Esmu diezgan pÄrliecinÄts, ka esmu pieļÄvis daudz stulbu kļūdu, it Ä«paÅ”i strÄdÄjot ar stÄ«gÄm.