Kodolapvalks virs ICMP

Kodolapvalks virs ICMP

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.

Kodolapvalks virs ICMP

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)

0000 72 75 6e 3a 48 65 6c 6c 6f 2c 20 77 6f 72 6c 64 run:Hello, world
0010 21 !
Data: 72756e3a48656c6c6f2c20776f726c6421
[Length: 17]

Frame 2: 59 bytes on wire (472 bits), 59 bytes captured (472 bits) on interface wlp1s0, id 0
Internet Protocol Version 4, Src: 45.11.26.232, Dst: 192.168.0.240
Internet Control Message Protocol
Type: 0 (Echo (ping) reply)
Code: 0
Checksum: 0xde03 [correct] [Checksum Status: Good] Identifier (BE): 0 (0x0000)
Identifier (LE): 0 (0x0000)
Sequence number (BE): 0 (0x0000)
Sequence number (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 run:Hello, world
0010 21 !
Data: 72756e3a48656c6c6f2c20776f726c6421
[Length: 17]

^C2 packets captured

Lietderīgā slodze atbildes pakotnē nemainās.

Kodola modulis

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.

#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);

Kas Ŕeit notiek:

  1. Tiek ievilkti divi galvenes faili, lai manipulētu ar paÅ”u moduli un tÄ«kla filtru.
  2. 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);
  3. Pēdējā funkcija noņem āķi.
  4. Licence ir skaidri norādīta, lai sastādītājs nesūdzētos.
  5. 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.

#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;
}

Kas notiek:

  1. Man bija jāiekļauj papildu galvenes faili, Å”oreiz, lai manipulētu ar IP un ICMP galvenēm.
  2. 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ā.
  3. 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ā.
  4. 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.
  5. Lai funkcija darbotos, jums būs nepiecieŔamas divas struktūras un vairāki mainīgie, tostarp divi iteratori.
      struct iphdr *iph;
      struct icmphdr *icmph;
    
      unsigned char *user_data;
      unsigned char *tail;
      unsigned char *i;
      int j = 0;
  6. 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.
      iph = ip_hdr(skb);
      icmph = icmp_hdr(skb);
    
      if (iph->protocol != IPPROTO_ICMP) {
        return NF_ACCEPT;
      }
      if (icmph->type != ICMP_ECHO) {
        return NF_ACCEPT;
      }

    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!

  7. 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);.

    Kodolapvalks virs ICMP

    Bilde tika nozagta tātad, varat lasīt vairāk par ligzdas buferi.

  8. 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.
  9. 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!
  10. 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.

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);
}

  1. 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.
  2. 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.
  3. 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.

Kodolapvalks virs ICMP

Avots: www.habr.com

Pievieno komentāru