Kerndop oor ICMP

Kerndop oor ICMP

TL; DR: Ek skryf 'n kernmodule wat opdragte vanaf die ICMP loonvrag sal lees en dit op die bediener sal uitvoer, selfs as jou SSH ineenstort. Vir die mees ongeduldige is al die kode Github.

Versigtigheid! Ervare C-programmeerders loop die risiko om in bloedtrane uit te bars! Ek kan selfs verkeerd wees in die terminologie, maar enige kritiek is welkom. Die pos is bedoel vir diegene wat 'n baie rowwe idee van C-programmering het en na die binnekant van Linux wil kyk.

In die kommentaar op my eerste Artikel SoftEther VPN genoem, wat sommige "gewone" protokolle kan naboots, veral HTTPS, ICMP en selfs DNS. Ek kan my voorstel dat net die eerste van hulle werk, aangesien ek baie vertroud is met HTTP(S), en ek moes tonnel oor ICMP en DNS leer.

Kerndop oor ICMP

Ja, in 2020 het ek geleer dat jy 'n arbitrêre loonvrag in ICMP-pakkies kan plaas. Maar beter laat as nooit! En aangesien iets daaraan gedoen kan word, dan moet dit gedoen word. Aangesien ek in my daaglikse lewe die opdragreël meestal gebruik, insluitend via SSH, het die idee van 'n ICMP-dop eerste by my opgekom. En om 'n volledige bullshield-bingo saam te stel, het ek besluit om dit as 'n Linux-module te skryf in 'n taal waarvan ek net 'n rowwe idee het. So 'n dop sal nie sigbaar wees in die lys van prosesse nie, jy kan dit in die kern laai en dit sal nie op die lêerstelsel wees nie, jy sal niks verdag in die lys van luisterpoorte sien nie. Wat sy vermoëns betref, is dit 'n volwaardige rootkit, maar ek hoop om dit te verbeter en as 'n dop van laaste uitweg te gebruik wanneer die lasgemiddelde te hoog is om via SSH aan te meld en ten minste uit te voer echo i > /proc/sysrq-triggerom toegang te herstel sonder om te herlaai.

Ons neem 'n teksredigeerder, basiese programmeringsvaardighede in Python en C, Google en virtuele masjien wat jy nie omgee om onder die mes te sit as alles breek nie (opsioneel - plaaslike VirtualBox/KVM/ens) en laat ons gaan!

Kliëntkant

Dit het vir my gelyk of ek vir die kliëntgedeelte 'n draaiboek met so 80 reëls moet skryf, maar daar was gawe mense wat dit vir my gedoen het al die werk. Die kode blyk onverwags eenvoudig te wees en pas in 10 belangrike reëls:

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

Die skrif neem twee argumente, 'n adres en 'n loonvrag. Voordat dit gestuur word, word die loonvrag deur 'n sleutel voorafgegaan run:, sal ons dit nodig hê om pakkette met ewekansige loonvragte uit te sluit.

Die kern vereis voorregte om pakkette te maak, so die skrip sal as supergebruiker uitgevoer moet word. Moenie vergeet om uitvoeringstoestemmings te gee en scapy self te installeer nie. Debian het 'n pakket genaamd python3-scapy. Nou kan jy kyk hoe dit alles werk.

Begin en voer die opdrag uit
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!

Dit is hoe dit in die snuffel lyk
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

Die loonvrag in die antwoordpakket verander nie.

Kern module

Om 'n virtuele Debian-masjien in te bou, sal jy ten minste nodig hê make и linux-headers-amd64, sal die res in die vorm van afhanklikhede kom. Ek sal nie die hele kode in die artikel verskaf nie; jy kan dit op Github kloon.

Haak opstelling

Om mee te begin, het ons twee funksies nodig om die module te laai en om dit af te laai. Die funksie vir aflaai is nie nodig nie, maar dan rmmod dit sal nie werk nie; die module sal slegs afgelaai word wanneer dit afgeskakel is.

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

Wat gaan hier aan:

  1. Twee koplêers word ingetrek om die module self en die netfilter te manipuleer.
  2. Alle bewerkings gaan deur 'n netfilter, jy kan hake daarin stel. Om dit te doen, moet jy die struktuur waarin die haak gekonfigureer sal word, verklaar. Die belangrikste ding is om die funksie te spesifiseer wat as 'n haak uitgevoer sal word: nfho.hook = icmp_cmd_executor; Ek sal later by die funksie self uitkom.
    Dan stel ek die verwerkingstyd vir die pakket in: NF_INET_PRE_ROUTING spesifiseer om die pakket te verwerk wanneer dit die eerste keer in die kern verskyn. Kan gebruik word NF_INET_POST_ROUTING om die pakkie te verwerk soos dit die kern verlaat.
    Ek stel die filter op IPv4: nfho.pf = PF_INET;.
    Ek gee my haak die hoogste prioriteit: nfho.priority = NF_IP_PRI_FIRST;
    En ek registreer die datastruktuur as die werklike haak: nf_register_net_hook(&init_net, &nfho);
  3. Die laaste funksie verwyder die haak.
  4. Die lisensie is duidelik aangedui sodat die samesteller nie kla nie.
  5. Funksies module_init() и module_exit() stel ander funksies in om die module te inisialiseer en te beëindig.

Herwinning van die loonvrag

Nou moet ons die loonvrag onttrek, dit blyk die moeilikste taak te wees. Die kern het nie ingeboude funksies om met loonvragte te werk nie; jy kan slegs opskrifte van hoërvlakprotokolle ontleed.

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

Wat is besig om te gebeur:

  1. Ek moes addisionele koplêers insluit, hierdie keer om IP- en ICMP-opskrifte te manipuleer.
  2. Ek stel die maksimum lynlengte: #define MAX_CMD_LEN 1976. Hoekom presies dit? Want die samesteller kla daaroor! Hulle het reeds aan my voorgestel dat ek die stapel en hoop moet verstaan, eendag sal ek dit beslis doen en dalk selfs die kode regstel. Ek stel dadelik die reël in wat die opdrag sal bevat: char cmd_string[MAX_CMD_LEN];. Dit moet in alle funksies sigbaar wees; ek sal in meer besonderhede hieroor in paragraaf 9 praat.
  3. Nou moet ons inisialiseer (struct work_struct my_work;) struktureer en verbind dit met 'n ander funksie (DECLARE_WORK(my_work, work_handler);). Ek sal ook in die negende paragraaf praat oor hoekom dit nodig is.
  4. Nou verklaar ek 'n funksie, wat 'n haak sal wees. Die tipe en aanvaarde argumente word gedikteer deur die netfilter, ons stel net belang in skb. Dit is 'n sokbuffer, 'n fundamentele datastruktuur wat alle beskikbare inligting oor 'n pakkie bevat.
  5. Vir die funksie om te werk, sal jy twee strukture en verskeie veranderlikes nodig hê, insluitend twee iterators.
      struct iphdr *iph;
      struct icmphdr *icmph;
    
      unsigned char *user_data;
      unsigned char *tail;
      unsigned char *i;
      int j = 0;
  6. Ons kan met logika begin. Vir die module om te werk, is geen ander pakkies as ICMP Echo nodig nie, so ons ontleed die buffer met behulp van ingeboude funksies en gooi alle nie-ICMP en nie-Echo pakkies uit. Keer terug NF_ACCEPT beteken aanvaarding van die pakket, maar jy kan ook pakkette laat val deur terug te keer 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;
      }

    Ek het nie getoets wat sal gebeur sonder om die IP-opskrifte na te gaan nie. My minimale kennis van C sê vir my dat daar sonder bykomende kontrole iets verskrikliks sal gebeur. Ek sal bly wees as jy my hiervan afraai!

  7. Noudat die pakket presies die tipe is wat jy nodig het, kan jy die data onttrek. Sonder 'n ingeboude funksie moet jy eers 'n wyser kry na die begin van die loonvrag. Dit word op een plek gedoen, jy moet die wyser na die begin van die ICMP-kopskrif neem en dit na die grootte van hierdie kop skuif. Alles gebruik struktuur icmph: user_data = (unsigned char *)((unsigned char *)icmph + (sizeof(icmph)));
    Die einde van die kopskrif moet ooreenstem met die einde van die loonvrag in skb, daarom verkry ons dit met behulp van kernmiddele uit die ooreenstemmende struktuur: tail = skb_tail_pointer(skb);.

    Kerndop oor ICMP

    Die foto is gesteel vandaar, kan jy meer lees oor die socket buffer.

  8. Sodra jy wysers na die begin en einde het, kan jy die data in 'n string kopieer cmd_string, kontroleer dit vir die teenwoordigheid van 'n voorvoegsel run: en gooi die pakkie óf weg as dit ontbreek, óf skryf die reël weer oor en verwyder hierdie voorvoegsel.
  9. Dit is dit, nou kan jy 'n ander hanteerder bel: schedule_work(&my_work);. Aangesien dit nie moontlik sal wees om 'n parameter na so 'n oproep deur te gee nie, moet die lyn met die opdrag globaal wees. schedule_work() sal die funksie wat met die geslaagde struktuur geassosieer word in die algemene tou van die taakskeduleerder plaas en voltooi, sodat jy nie kan wag vir die opdrag om te voltooi nie. Dit is nodig omdat die haak baie vinnig moet wees. Andersins is jou keuse dat niks sal begin nie of jy sal 'n kernpaniek kry. Vertraging is soos die dood!
  10. Dit is dit, jy kan die pakket aanvaar met 'n ooreenstemmende terugkeer.

Bel 'n program in gebruikersruimte

Hierdie funksie is die mees verstaanbare. Sy naam is ingegee DECLARE_WORK(), die tipe en aanvaarde argumente is nie interessant nie. Ons neem die lyn met die opdrag en gee dit heeltemal na die dop. Laat hom met ontleding, soek na binaries en alles anders.

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. Stel die argumente op 'n reeks stringe argv[]. Ek sal aanvaar dat almal weet dat programme eintlik op hierdie manier uitgevoer word, en nie as 'n deurlopende lyn met spasies nie.
  2. Stel omgewingsveranderlikes. Ek het slegs PATH ingevoeg met 'n minimum stel paaie, met die hoop dat hulle almal reeds gekombineer is /bin с /usr/bin и /sbin с /usr/sbin. Ander paaie maak selde saak in die praktyk.
  3. Klaar, kom ons doen dit! Kernfunksie call_usermodehelper() toegang aanvaar. pad na die binêre, verskeidenheid van argumente, verskeidenheid van omgewingsveranderlikes. Hier neem ek ook aan dat almal die betekenis van die deurgee van die pad na die uitvoerbare lêer as 'n aparte argument verstaan, maar jy kan vra. Die laaste argument spesifiseer of daar gewag moet word vir die proses om te voltooi (UMH_WAIT_PROC), proses begin (UMH_WAIT_EXEC) of glad nie wag nie (UMH_NO_WAIT). Is daar nog iets UMH_KILLABLE, Ek het nie daarna gekyk nie.

vergadering

Die samestelling van kernmodules word deur die kernmaakraamwerk uitgevoer. Gebel make binne 'n spesiale gids gekoppel aan die kernweergawe (hier gedefinieer: KERNELDIR:=/lib/modules/$(shell uname -r)/build), en die ligging van die module word na die veranderlike oorgedra M in die argumente. Die icmpshell.ko en skoon teikens gebruik hierdie raamwerk heeltemal. IN obj-m dui die objeklêer aan wat in 'n module omgeskakel sal word. Sintaksis wat hermaak main.o в icmpshell.o (icmpshell-objs = main.o) lyk nie vir my baie logies nie, maar so be it.

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

Ons versamel: make. Laai tans: insmod icmpshell.ko. Klaar, jy kan kontroleer: sudo ./send.py 45.11.26.232 "date > /tmp/test". As jy 'n lêer op jou masjien het /tmp/test en dit bevat die datum waarop die versoek gestuur is, wat beteken jy het alles reg gedoen en ek het alles reg gedoen.

Gevolgtrekking

My eerste ervaring met kernontwikkeling was baie makliker as wat ek verwag het. Selfs sonder ervaring wat in C ontwikkel het, met die fokus op samestellerwenke en Google-resultate, kon ek 'n werkende module skryf en voel soos 'n kernkraker, en terselfdertyd 'n script-kiddie. Daarbenewens het ek na die Kernel Newbies-kanaal gegaan, waar ek aangesê is om te gebruik schedule_work() in plaas daarvan om te bel call_usermodehelper() binne in die haak self en hom beskaam, tereg 'n bedrogspul vermoed. 'n Honderd reëls kode het my ongeveer 'n week se ontwikkeling in my vrye tyd gekos. 'n Suksesvolle ervaring wat my persoonlike mite oor die oorweldigende kompleksiteit van stelselontwikkeling vernietig het.

As iemand instem om 'n kode-oorsig op Github te doen, sal ek dankbaar wees. Ek is redelik seker ek het baie dom foute gemaak, veral wanneer ek met snare gewerk het.

Kerndop oor ICMP

Bron: will.com

Voeg 'n opmerking