Branduolinis apvalkalas virš ICMP

Branduolinis apvalkalas virš ICMP

Lt; DR: Rašau branduolio modulį, kuris nuskaitys komandas iš ICMP naudingosios apkrovos ir vykdys jas serveryje, net jei jūsų SSH strigtų. Patiems nekantriesiems visas kodas yra GitHub.

Atsargiai! Patyrę C programuotojai rizikuoja apsiverkti krauju! Galbūt net klystu dėl terminijos, bet bet kokia kritika yra sveikintina. Įrašas skirtas tiems, kurie labai apytiksliai supranta C programavimą ir nori pažvelgti į Linux vidų.

Komentaruose prie mano pirmojo straipsnis paminėjo SoftEther VPN, kuris gali imituoti kai kuriuos „įprastus“ protokolus, ypač HTTPS, ICMP ir net DNS. Įsivaizduoju, kad veikia tik pirmasis iš jų, nes esu labai susipažinęs su HTTP(S) ir turėjau išmokti tuneliavimo per ICMP ir DNS.

Branduolinis apvalkalas virš ICMP

Taip, 2020 m. sužinojau, kad į ICMP paketus galite įterpti savavališką naudingą apkrovą. Bet geriau vėliau nei niekada! Ir kadangi su tuo galima ką nors padaryti, tai reikia daryti. Kadangi kasdieniame gyvenime dažniausiai naudoju komandinę eilutę, taip pat ir per SSH, ICMP apvalkalo idėja man kilo pirmiausia. Ir norėdamas surinkti pilną „bullshield“ bingo, nusprendžiau jį parašyti kaip „Linux“ modulį ta kalba, kurią turiu tik apytiksliai. Tokio apvalkalo procesų sąraše nematysite, jį galite įkelti į branduolį ir jo nebus failų sistemoje, nieko įtartino nepamatysite klausymosi prievadų sąraše. Kalbant apie savo galimybes, tai yra pilnavertis rootkit, bet tikiuosi jį patobulinti ir panaudoti kaip paskutinę priemonę, kai apkrovos vidurkis yra per didelis, kad būtų galima prisijungti per SSH ir vykdyti bent jau echo i > /proc/sysrq-triggeratkurti prieigą be perkrovimo.

Imame teksto redaktorių, pagrindinius programavimo įgūdžius Python ir C, Google ir virtualus kurių neprieštaraujate pakišti po peiliu, jei viskas sugenda (neprivaloma – vietinis VirtualBox/KVM/tt) ir pirmyn!

Kliento pusė

Man atrodė, kad kliento daliai turėsiu parašyti apie 80 eilučių scenarijų, bet buvo malonių žmonių, kurie tai padarė už mane visus darbus. Kodas pasirodė netikėtai paprastas, telpantis į 10 reikšmingų eilučių:

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()

Scenarijus turi du argumentus: adresą ir naudingą apkrovą. Prieš siunčiant naudingą krovinį įvedamas raktas run:, mums jo reikės, kad neįtrauktume paketų su atsitiktiniais naudingaisiais kroviniais.

Branduoliui reikia privilegijų, kad būtų galima kurti paketus, todėl scenarijus turės būti paleistas kaip supervartotojas. Nepamirškite suteikti vykdymo leidimų ir įdiegti patį „scapy“. Debianas turi paketą pavadinimu python3-scapy. Dabar galite patikrinti, kaip visa tai veikia.

Komandos vykdymas ir išvedimas
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!

Taip atrodo uostyklėje
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

Naudingoji apkrova atsakymo pakete nesikeičia.

Branduolio modulis

Norėdami sukurti Debian virtualią mašiną, jums reikės bent make и linux-headers-amd64, likusi dalis bus priklausomybių forma. Straipsnyje nepateiksiu viso kodo, galite jį klonuoti „Github“.

Kablio nustatymas

Iš pradžių mums reikia dviejų funkcijų, kad galėtume įkelti modulį ir jį iškrauti. Iškrovimo funkcija nereikalinga, bet tada rmmod jis neveiks, modulis bus iškraunamas tik išjungus.

#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 čia vyksta:

  1. Įtraukiami du antraštės failai, kad būtų galima valdyti patį modulį ir tinklo filtrą.
  2. Visos operacijos eina per tinklo filtrą, jame galite nustatyti kabliukus. Norėdami tai padaryti, turite deklaruoti struktūrą, kurioje bus sukonfigūruotas kabliukas. Svarbiausia yra nurodyti funkciją, kuri bus vykdoma kaip kabliukas: nfho.hook = icmp_cmd_executor; Prie pačios funkcijos pakalbėsiu vėliau.
    Tada nustatau pakuotės apdorojimo laiką: NF_INET_PRE_ROUTING nurodo apdoroti paketą, kai jis pirmą kartą pasirodo branduolyje. Gali būti naudojamas NF_INET_POST_ROUTING apdoroti paketą, kai jis išeina iš branduolio.
    Aš nustatiau filtrą į IPv4: nfho.pf = PF_INET;.
    Savo kabliui skiriu didžiausią prioritetą: nfho.priority = NF_IP_PRI_FIRST;
    Ir aš registruoju duomenų struktūrą kaip tikrąjį kabliuką: nf_register_net_hook(&init_net, &nfho);
  3. Galutinė funkcija pašalina kabliuką.
  4. Licencija yra aiškiai nurodyta, kad sudarytojas nesiskųstų.
  5. Funkcijos module_init() и module_exit() nustatykite kitas modulio inicijavimo ir nutraukimo funkcijas.

Naudingojo krovinio paėmimas

Dabar mums reikia išgauti naudingą apkrovą, tai pasirodė pati sunkiausia užduotis. Branduolys neturi integruotų funkcijų, skirtų darbui su naudingomis apkrovomis, galite analizuoti tik aukštesnio lygio protokolų antraštes.

#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 vyksta:

  1. Turėjau įtraukti papildomų antraščių failų, šį kartą manipuliuoti IP ir ICMP antraštėmis.
  2. Nustačiau didžiausią linijos ilgį: #define MAX_CMD_LEN 1976. Kodėl būtent tai? Nes kompiliatorius tuo skundžiasi! Jie jau man pasiūlė, kad reikia suprasti stacką ir krūvą, kada nors tai tikrai padarysiu ir gal net kodą pataisysiu. Iš karto nustatiau eilutę, kurioje bus komanda: char cmd_string[MAX_CMD_LEN];. Tai turėtų būti matoma visose funkcijose. Aš apie tai pakalbėsiu išsamiau 9 pastraipoje.
  3. Dabar turime inicijuoti (struct work_struct my_work;) struktūrą ir susieti ją su kita funkcija (DECLARE_WORK(my_work, work_handler);). Apie tai, kodėl to reikia, pakalbėsiu ir devintoje pastraipoje.
  4. Dabar paskelbiu funkciją, kuri bus kabliukas. Tipą ir priimtus argumentus padiktuoja netfilter, mus tik domina skb. Tai yra lizdo buferis, pagrindinė duomenų struktūra, kurioje yra visa turima informacija apie paketą.
  5. Kad funkcija veiktų, jums reikės dviejų struktūrų ir kelių kintamųjų, įskaitant du iteratorius.
      struct iphdr *iph;
      struct icmphdr *icmph;
    
      unsigned char *user_data;
      unsigned char *tail;
      unsigned char *i;
      int j = 0;
  6. Galime pradėti nuo logikos. Kad modulis veiktų, nereikia jokių kitų paketų, išskyrus ICMP Echo, todėl mes analizuojame buferį naudodami integruotas funkcijas ir išmetame visus ne ICMP ir ne Echo paketus. Grįžti NF_ACCEPT reiškia siuntos priėmimą, tačiau pakuotes galite mesti ir grąžindami 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;
      }

    Neišbandžiau, kas nutiks nepatikrinus IP antraštės. Mano minimalios C kalbos žinios man sako, kad be papildomų patikrinimų būtinai atsitiks kažkas baisaus. Man bus malonu, jei jūs mane nuo to atkalbėsite!

  7. Dabar, kai paketas yra tokio tipo, kokio jums reikia, galite išgauti duomenis. Be integruotos funkcijos, pirmiausia turite gauti naudingos apkrovos pradžios rodyklę. Tai daroma vienoje vietoje, reikia nuvesti žymeklį į ICMP antraštės pradžią ir perkelti ją į šios antraštės dydį. Viskas naudoja struktūrą icmph: user_data = (unsigned char *)((unsigned char *)icmph + (sizeof(icmph)));
    Antraštės pabaiga turi sutapti su naudingojo krovinio pabaiga skb, todėl jį gauname naudodami branduolines priemones iš atitinkamos struktūros: tail = skb_tail_pointer(skb);.

    Branduolinis apvalkalas virš ICMP

    Paveikslas buvo pavogtas taigi, galite perskaityti daugiau apie lizdo buferį.

  8. Kai turėsite nuorodas į pradžią ir pabaigą, galite nukopijuoti duomenis į eilutę cmd_string, patikrinkite, ar nėra priešdėlio run: ir arba išmeskite paketą, jei jo trūksta, arba dar kartą perrašykite eilutę, pašalindami šį priešdėlį.
  9. Viskas, dabar galite paskambinti kitam tvarkytojui: schedule_work(&my_work);. Kadangi tokiam iškvietimui parametro perduoti nebus įmanoma, eilutė su komanda turi būti globali. schedule_work() su perduota struktūra susietą funkciją įdės į bendrąją užduočių planavimo priemonės eilę ir užbaigs, todėl galėsite nelaukti, kol komanda bus baigta. Tai būtina, nes kabliukas turi būti labai greitas. Priešingu atveju jūs pasirenkate, kad niekas neprasidės arba sulauksite branduolio panikos. Vėlavimas yra kaip mirtis!
  10. Tai viskas, galite priimti paketą su atitinkamu grąžinimu.

Programos iškvietimas vartotojo erdvėje

Ši funkcija yra labiausiai suprantama. Jo pavadinimas buvo suteiktas DECLARE_WORK(), tipas ir priimti argumentai neįdomūs. Mes paimame eilutę su komanda ir perduodame ją visiškai apvalkalui. Tegul jis užsiima analizavimu, dvejetainių failų paieška ir visa kita.

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. Nustatykite argumentus į eilučių masyvą argv[]. Darysiu prielaidą, kad visi žino, kad programos iš tikrųjų vykdomos tokiu būdu, o ne kaip ištisinė eilutė su tarpais.
  2. Nustatykite aplinkos kintamuosius. Įterpiau tik PATH su minimaliu kelių rinkiniu, tikėdamasis, kad jie visi jau sujungti /bin с /usr/bin и /sbin с /usr/sbin. Kiti keliai praktikoje retai svarbūs.
  3. Atlikta, padarykime tai! Branduolio funkcija call_usermodehelper() priima įėjimą. kelias į dvejetainį, argumentų masyvas, aplinkos kintamųjų masyvas. Čia taip pat darau prielaidą, kad visi supranta, ką reiškia perduoti kelias į vykdomąjį failą kaip atskirą argumentą, bet jūs galite paklausti. Paskutinis argumentas nurodo, ar laukti, kol procesas bus baigtas (UMH_WAIT_PROC), proceso pradžia (UMH_WAIT_EXEC) arba visai nelaukti (UMH_NO_WAIT). Ar yra daugiau UMH_KILLABLE, aš nežiūrėjau.

Asamblėja

Branduolio modulių surinkimas atliekamas naudojant branduolio make-framework. Skambino make specialiame kataloge, susietame su branduolio versija (apibrėžta čia: KERNELDIR:=/lib/modules/$(shell uname -r)/build), o modulio vieta perduodama kintamajam M argumentuose. icmpshell.ko ir švarūs tikslai visiškai naudoja šią sistemą. IN obj-m nurodo objekto failą, kuris bus konvertuojamas į modulį. Sintaksė, kuri perdaroma main.o в icmpshell.o (icmpshell-objs = main.o) man neatrodo labai logiška, bet tebūnie.

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

Mes renkame: make. Įkeliama: insmod icmpshell.ko. Atlikta, galite patikrinti: sudo ./send.py 45.11.26.232 "date > /tmp/test". Jei jūsų kompiuteryje yra failas /tmp/test ir jame yra užklausos išsiuntimo data, vadinasi, jūs viską padarėte teisingai, o aš viską padariau teisingai.

išvada

Mano pirmoji patirtis su branduoline plėtra buvo daug lengvesnė, nei tikėjausi. Net neturėdamas patirties kurdamas C, sutelkdamas dėmesį į kompiliatoriaus užuominas ir Google rezultatus, galėjau parašyti veikiantį modulį ir jaustis kaip branduolio įsilaužėlis, o tuo pačiu ir scenarijaus vaikas. Be to, nuėjau į Kernel Newbies kanalą, kur man buvo liepta naudotis schedule_work() užuot skambinęs call_usermodehelper() pačiame kabliuko viduje ir sugėdino jį, pagrįstai įtardamas sukčiavimą. Šimtas kodo eilučių man kainavo apie savaitę kūrimo laisvu laiku. Sėkminga patirtis, kuri sugriovė mano asmeninį mitą apie didžiulį sistemos kūrimo sudėtingumą.

Jei kas nors sutiks peržiūrėti kodą „Github“, būsiu dėkingas. Esu tikras, kad padariau daug kvailų klaidų, ypač dirbdamas su stygomis.

Branduolinis apvalkalas virš ICMP

Šaltinis: www.habr.com

Добавить комментарий