Kodolu šella virs ICMP

Kodolu šella virs ICMP

TL;DR: rakstu kodola moduļu, kas lasīs komandas no ICMP payload un izpildīs tās serverī pat tad, ja jūsu SSH ir kritusi. Visiem pacietīgajiem viss kods ir github.

Uzmanību! Pieredzējuši C programmētāji riskē asarot asins asaras! Es varu kļūdīties pat terminoloģijā, bet jebkura kritika tiks laipni gaidīta. Ieraksts paredzēts tiem, kuriem ir vispārīga izpratne par programmēšanu C valodā un vēlas ieskatīties Linux iekšējās darbībās.

Manas pirmās ieraksta komentāros rakstā tika pieminēts SoftEther VPN, kas spēj mimikēt dažus "parastos" protokolus, īpaši HTTPS, ICMP un pat DNS. Es saprotu tikai pirmā no tiem darbību, jo labi pazīstu HTTP(S), bet ICMP un DNS tunelēšanu vajadzēja pētīt.

Kodolu šella virs ICMP

Jā, 2020. gadā es uzzināju, ka ICMP paketes var pievienot patvaļīgu payload. Bet labāk vēlu, nekā nekad! Un, ja ar to ir iespējams kaut ko darīt, tas ir jādara. Tā kā ikdienā biežāk izmantoju komandu līniju, tostarp caur SSH, ideja par ICMP šellu man radās vispirms. Un, lai izveidotu pilnu bullshita bingo, izlemu rakstīt kā Linux moduli valodā, par kuru man ir tikai vispārīga izpratne. Šāds šells nebūs redzams procesu sarakstā, to var ielādēt kodolā, un tas nebūs redzams failu sistēmā, jūs neredzēsiet neko aizdomīgu atvērtajos portos. Savām iespējām tas ir pilnvērtīgs rootkits, bet es ceru to attīstīt un izmantot kā pēdējo cerību, kad Load Average ir pārāk augsts, lai ieietu caur SSH un izpildītu vismaz echo i > /proc/sysrq-trigger, lai atjaunotu piekļuvi bez restartēšanas.

Paņemam teksta redaktoru, pamata programmēšanas prasmes Python un C, Google un virtuālo mašīnu , ko nav žēl upurēt, ja viss sabojājas (pēc izvēles — lokālais VirtualBox/KVM/utt.) un uz priekšu!

Klienta daļa

Man šķita, ka klienta daļai būs jāuzraksta skripts apkārt 80 rindām, bet atradās labi cilvēki, kas izdarīja to manā vietā visu darbu. Kods izrādījās negaidīti vienkāršs, ietilpst 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()

Skripts pieņem divus argumentus: adresi un payload. Pirms nosūtīšanas payload tiek gatavots ar atslēgu palaist:, tas mums būs nepieciešams, lai izslēgtu paketes ar nejaušu payload.

Kernam ir nepieciešamas privilēģijas, lai izveidotu paketes, tāpēc skripts jāpalaiž ar superlietotāja tiesībām. Neaizmirstiet piešķirt izpildes tiesības un instalēt pašu scapy. Debian ir pakete, kas saucas python3-scapy. Tagad varam pārbaudīt, kā tas viss darbojas.

Komandas palaišana un iznākums
morq@laptop:~\/icmpshell$ sudo .\/send.py 45.11.26.232 "Sveika, pasaule!"
Sākt emisiju:
. Pabeigta 1 paketes sūtīšana.
*
Saņemtas 2 paketes, saņemta 1 atbilde, palikušas 0 paketes
###[ IP ]###
versija = 4
ihl = 5
tos = 0x0
garums = 45
id = 17218
karogi =
fragmentācija = 0
ttl = 58
proto = icmp
chksum = 0x3403
src = 45.11.26.232
dst = 192.168.0.240
opcijas
###[ ICMP ]###
tips = echo-atbilde
kods = 0
chksum = 0xde03
id = 0x0
sec = 0x0
###[ Raw ]###
slodze = 'run:Sveika, pasaule!

Tā tas izskatās snifferī
morq@laptop:~\/icmpshell$ sudo tshark -i wlp1s0 -O icmp -f "icmp and host 45.11.26.232"
Darbo ar lietotāju "root" un grupu "root". Tas var būt bīstami.
Capturing on 'wlp1s0'
Frame 1: 59 bytes on wire (472 bits), 59 bytes captured (472 bits) on interface wlp1s0, id 0
Interneta protokols Version 4, Src: 192.168.0.240, Dst: 45.11.26.232
Interneta kontroles ziņojumu protokols
Tips: 8 (Echo (ping) pieprasījums)
Kods: 0
Checksum: 0xd603 [pareizi]
[Checksum Status: Labi]
Identifikators (BE): 0 (0x0000)
Identifikators (LE): 0 (0x0000)
Secības numurs (BE): 0 (0x0000)
Secības numurs (LE): 0 (0x0000)
Dati (17 bytes)

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

Rāmja 2: 59 biti uz vadiem (472 biti), 59 biti noķerti (472 biti) uz saskarnes wlp1s0, id 0
Interneta protokols Versija 4, Src: 45.11.26.232, Dst: 192.168.0.240
Interneta kontroles ziņojumu protokols
Tips: 0 (Echo (ping) atbilde)
Kods: 0
Pārbaude: 0xde03 [pareizi]
[Checksum Status: Labi]
Identifikators (BE): 0 (0x0000)
Identifikators (LE): 0 (0x0000)
Secības numurs (BE): 0 (0x0000)
Secības numurs (LE): 0 (0x0000)
[Izsniegšanas rāmis: 1]
[Atbildes laiks: 19.094 ms]
Dati (17 bytes)

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

^C2 paketes noķertas

Payload atbildes paketā nemainās.

Kerna modulis

Lai būvētu virtuālajā mašīnā ar Debian, būs nepieciešami vismaz make un linux-headers-amd64, pārējais nāks kā atkarības. Rakstā kods pilnībā neietiek, jūs to varat iegūt no GitHub.

Huka iestatīšana

Vispirms mums būs nepieciešamas divas funkcijas, lai ielādētu moduli un to izkrautu. Funkcija izkraušanai nav obligāta, bet tad rmmod izpildīt neizdosies, modulis tiks izkrauts tikai izslēdzot.

#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. Divi galvenes faili tiek piegādāti manipulācijām ar pašu moduli un ar netfilter.
  2. Visas operācijas notiek caur netfilter, tajā var norādīt hook. Tam nepieciešams pieteikt struktūru, kurā hook tiks iestatīts. Visvarīgākais ir norādīt funkciju, kas tiks izpildīta kā hook: nfho.hook = icmp_cmd_executor; līdz pati funkcija, es vēl tikšu.
    Pēc tam es noteicu paketes apstrādes brīdi: NF_INET_PRE_ROUTING norāda apstrādāt paketi, kad tā tikai parādās kodolā. Var izmantot NF_INET_POST_ROUTING paketes apstrādei, kad tā iznāk no koda.
    Es uzlieku filtru IPv4: nfho.pf = PF_INET;.
    Piešķiru savam hook augstāko prioritāti: nfho.priority = NF_IP_PRI_FIRST;
    Un reģistrēju datu struktūru kā pašu hook: nf_register_net_hook(&init_net, &nfho);
  3. Noslēdzošajā funkcijā hook tiek izņemts.
  4. Licence ir skaidri norādīta, lai kompilators nekrītās.
  5. Funkcijas module_init() un module_exit() nosaka citas funkcijas kā inicializējošas un noslēdzošas moduli.

Payload izguve

Tagad ir jāizvelk payload, un tas izrādījās visgrūtākais uzdevums. Kodola iekšienē nav iebūvētu funkciju, kas strādātu ar payload, var tikai parsēt augstāka līmeņa protokolu virsrakstus.

#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 nācās piesaistīt papildu virsrakstu failus, šoreiz lai manipulētu ar IP un ICMP virsrakstiem.
  2. Es uzstādīju maksimālo virknes garumu: #define MAX_CMD_LEN 1976. Kāpēc konkrēti tādu? Jo ar lielāku kompilators nestrādā! Man jau ieteica, ka jāizpēta steks un kaudze, kadreiz es to noteikti izdarīšu un varbūt pat labošanu. Sākumā es uzstādu virkni, kurā būs komanda: char cmd_string[MAX_CMD_LEN];. Tai jābūt redzamai visās funkcijās, par to es sīkāk pastāstīšu punktā 9.
  3. Tagad jāsāk inicializēt (struct work_struct my_work;) struktūra un jāsasaista ar vēl vienu funkciju (DECLARE_WORK(my_work, work_handler);). Par to, kādēļ tas ir nepieciešams, es arī pastāstīšu devītajā punktā.
  4. Tagad izsludinu funkciju, kura būs hook. Tips un pieņemamie argumenti tiek noteikti netfilter, bet mūs interesē tikai skb. Tas ir soketa buferis, fundamentālā datu struktūra, kas satur visu pieejamo informāciju par paketi.
  5. Funkcijai būs nepieciešamas divas struktūras un dažas mainīgās, tostarp divi iteratori.
      struct iphdr *iph;
      struct icmphdr *icmph;
    
      unsigned char *user_data;
      unsigned char *tail;
      unsigned char *i;
      int j = 0;
  6. Varam pievērsties loģikai. Moduļa darbībai nav nepieciešami citi paketes, izņemot ICMP Echo, tāpēc mēs parsējam buferi ar iebūvētām funkcijām un izmetam visus ne ICMP un ne Echo paketes. Atgriešana NF_ACCEPT apzīmē paketes pieņemšanu, bet varat arī noraidīt paketes, 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;
      }

    Es neesmu pārbaudījis, kas notiks bez IP virsrakstu pārbaudes. Mana minimālā C zināšana norāda, ka bez papildu pārbaudēm obligāti notiks kaut kas šausmīgs. Es būšu priecīgs, ja jūs mani pārliecināsiet pretēji!

  7. Tagad, kad pakete ir tieši vēlamajā veidā, var sākt izvilkt datus. Bez iebūvētas funkcijas vispirms ir jāiegūst rādītājs uz payload sākumu. To izpilda caur noteiktu vietu, ir jāņem rādītājs uz ICMP virsraksta sākumu un jāpārvieto to par šī virsraksta izmēru. Visam tiek izmantota struktūra icmph: user_data = (unsigned char *)((unsigned char *)icmph + (sizeof(icmph)));
    Zīmei jābūt saskaņota ar slodzes beigām skb, tāpēc iegūstam to ar kodola līdzekļiem no atbilstošas struktūras: tail = skb_tail_pointer(skb);.

    Kodolu šella virs ICMP

    Attēlu aizņēmu no šejienes, varat izlasīt sīkāk par socket buferi.

  8. Iegūstot rādītājus uz sākumu un galu, var kopēt datus rindā cmd_string, pārbaudīt to uz prefiksa klātbūtni palaist: un, vai nu izmest paketi, ja tā trūkst, vai atkārtoti pārrakstīt rindu, noņemot šo prefiksu.
  9. Nu, viss, tagad var izsaukt vēl vienu apstrādātāju: schedule_work(&my_work);. Tā kā šādā izsaukumā nodot parametru neizdosies, komandai jābūt globālai. schedule_work() ievietos funkciju, kas saistīta ar nodoto struktūru kopējā uzdevumu plānotāja rindā un pabeigsies, ļaujot gaidīt komandas pabeigšanu. Tas ir nepieciešams, jo iestāde jābūt ļoti ātrai. Citādi jūsu rīcībā būs tikai neko nesāks vai saņemsiet kernel panic. Kavēšanās ir līdzīga nāvei!
  10. Viss, var pieņemt paketi ar attiecīgu atgriešanu.

Programmas izsaukšana lietotāju telpā

Šī funkcija ir visvienkāršākā. Tās nosaukums tika noteikts DECLARE_WORK(), tips un pieņemamie argumenti nav interesanti. Mēs ņemam komandas rindu un nododam to pilnībā shell. Lai tas pats nodarbojas ar parsēšanu, binary 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. Nosakām argumentus virkņu masīvā argv[]. Pieņemu, ka visi zina, ka programmas faktiski tiek izpildītas tieši tā, nevis kā nepārtraukta rinda ar atstarpēm.
  2. Nosakām vidējos mainīgos. Es pievienoju tikai PATH ar minimālo ceļu komplektu, cerot, ka visiem tas jau ir apvienots /bin ar /usr/bin un /sbin ar /usr/sbin. Pārējie ceļi praktiskā ziņā ir diezgan reti svarīgi.
  3. Gatavs, izpildām! Kodola funkcija call_usermodehelper() pieņem ievadē. ceļu uz bināro failu, argumentu masīvu, vides mainīgo masīvu. Šeit es arī pieņemu, ka visi saprot ceļa nodošanas jēgu kā atsevišķu argumentu, bet varat jautāt. Pēdējais arguments norāda, vai gaidīt procesa pabeigšanu (UMH_WAIT_PROC), procesa izsaukšanu (UMH_WAIT_EXEC) vai vispār negaidīt (UMH_NO_WAIT). Ir arī UMH_KILLABLE, es nesapratu to.

Montāža

Kodolu moduļu kompilācija tiek veikta caur paša kodola make-Ņemīgo. Tiek izsaukts make īpašā direktorijā, kas saistīta ar kodola versiju (noteikts šeit: KERNELDIR:=/lib/modules/$(shell uname -r)/build), un moduļa atrašanās vieta tiek nodota mainīgajam M argumenti. Mērķos icmpshell.ko un clean visā tiek izmantots šis ietvars. D obj-m norāda objektu failu, kas tiks pārveidots par moduli. Sintakse, kas pārvērš main.o par icmpshell.o (icmpshell-objs = main.o) man šķiet ne pārāk loģiska, bet lai tā būtu.

KERNELDIR:=/lib/modules/$(shell uname -r)/build

obj-m = icmpshell.o
icmpshell-objs = main.o

visas: icmpshell.ko

icmpshell.ko: main.c
make -C $(KERNELDIR) M=$(PWD) modules

tīrīt:
make -C $(KERNELDIR) M=$(PWD) clean

Kopjam: make. Ielādējam: insmod icmpshell.ko. Gatavs, var pārbaudīt: sudo ./send.py 45.11.26.232 "date > /tmp/test". Ja jūsu datorā ir parādījies fails /tmp/test un tajā ir pieprasījuma sūtīšanas datums, tas nozīmē, ka jūs visu izdarījāt pareizi un es arī to izdarīju.

Secinājums

Mans pirmais kodola izstrādes pieredze izrādījās daudz vieglāka, nekā es gaidīju. Pat neiegūstot pieredzi C programmēšanā, balstoties uz kompailēšanas norādēm un Google izsniegumiem, es spēju uzrakstīt strādājošu moduli un justies kā kodola hakerim, bet vienlaikus arī kā skriptu bērnam. Turklāt es apmeklēju Kernel Newbies kanālu, kur man ieteica lietot schedule_work() nevis izsaukt call_usermodehelper() paša huka iekšienē un norakstīja mani, pamatoti aizdomājoties par krāpšanu. Simts koda rindas man izmaksāja aptuveni nedēļu izstrādes brīvajā laikā. Veiksmīga pieredze, kas iznīcināja manu personīgo mītu par sistēmas izstrādes grūtību.

Ja kāds piekristu veikt koda pārskatu GitHub, es būtu pateicīgs. Es gandrīz esmu pārliecināts, ka esmu pieļāvis daudz stulbu kļūdu, it īpaši darbojoties ar virkņu apstrādi.

Kodolu šella virs ICMP

Avots: habr.com

Iegādājieties uzticamu tīmekļa hostingu ar DDoS aizsardzību, VPS VDS serveriem 🔥 Iegādājieties uzticamu tīmekļa hostingu ar DDoS aizsardzību, VPS VDS serveriem | ProHoster