Kjarnorkusprengja yfir ICMP

Kjarnorkusprengja yfir ICMP

TL; DR: Ég er að skrifa kjarnaeiningu sem mun lesa skipanir úr ICMP-hleðslunni og framkvæma þær á þjóninum, jafnvel þó að SSH þinn hrynji. Fyrir þá óþolinmóðustu er allur kóðann GitHub.

Varúð! Reyndir C forritarar eiga á hættu að springa í blóðtár! Ég gæti jafnvel haft rangt fyrir mér í hugtökum, en öll gagnrýni er vel þegin. Færslan er ætluð þeim sem hafa mjög grófa hugmynd um C forritun og vilja skoða innra hluta Linux.

Í athugasemdum við mitt fyrsta grein nefndi SoftEther VPN, sem getur líkt eftir sumum „venjulegum“ samskiptareglum, sérstaklega HTTPS, ICMP og jafnvel DNS. Ég get ímyndað mér að aðeins sá fyrsti af þeim virki, þar sem ég er mjög kunnugur HTTP(S), og ég þurfti að læra jarðgangagerð yfir ICMP og DNS.

Kjarnorkusprengja yfir ICMP

Já, árið 2020 lærði ég að þú getur sett handahófskenndan farm í ICMP pakka. En betra er seint en aldrei! Og þar sem eitthvað er hægt að gera í því, þá þarf að gera það. Þar sem ég nota oftast skipanalínuna í daglegu lífi, þar á meðal í gegnum SSH, kom hugmyndin um ICMP skel upp í huga minn fyrst. Og til þess að setja saman algjört bullshield bingó ákvað ég að skrifa það sem Linux mát á tungumáli sem ég hef aðeins grófa hugmynd um. Slík skel mun ekki sjást á listanum yfir ferla, þú getur hlaðið henni inn í kjarnann og hún verður ekki á skráarkerfinu, þú munt ekki sjá neitt grunsamlegt á listanum yfir hlustunartengi. Hvað getu þess varðar, þá er þetta fullgildur rootkit, en ég vonast til að bæta það og nota það sem þrautavara þegar hleðslumeðaltalið er of hátt til að skrá þig inn í gegnum SSH og keyra a.m.k. echo i > /proc/sysrq-triggertil að endurheimta aðgang án þess að endurræsa.

Við tökum textaritli, grunnforritunarkunnáttu í Python og C, Google og sýndarmynd sem þú nennir ekki að leggja undir hnífinn ef allt bilar (valfrjálst - staðbundin VirtualBox/KVM/o.s.frv.) og við skulum fara!

Viðskiptavinahlið

Mér fannst ég þurfa að skrifa handrit með um 80 línum fyrir skjólstæðinginn en það var vingjarnlegt fólk sem gerði það fyrir mig allt verkið. Kóðinn reyndist óvænt einfaldur og passaði í 10 marktækar línur:

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

Handritið tekur tvö rök, heimilisfang og farm. Áður en sending er send er lykill á undan farminum run:, við munum þurfa það til að útiloka pakka með handahófi hleðslu.

Kjarninn krefst réttinda til að búa til pakka, þannig að handritið verður að keyra sem ofurnotandi. Ekki gleyma að gefa framkvæmdarheimildir og setja upp sjálft scapy. Debian er með pakka sem heitir python3-scapy. Nú geturðu athugað hvernig þetta virkar allt.

Keyrir og gefur út skipunina
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!

Svona lítur þetta út í sniffernum
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

Burðargeta í svarpakkanum breytist ekki.

Kjarnaeining

Til að byggja inn Debian sýndarvél þarftu að minnsta kosti make и linux-headers-amd64, restin mun koma í formi ósjálfstæðis. Ég mun ekki gefa upp allan kóðann í greininni; þú getur klónað hann á Github.

Uppsetning króka

Til að byrja með þurfum við tvær aðgerðir til að hlaða einingunni og til að losa hana. Aðgerðin fyrir affermingu er ekki nauðsynleg, en þá rmmod það mun ekki virka; einingin verður aðeins afhlaðin þegar slökkt er á henni.

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

Hvað er í gangi hér:

  1. Tvær hausskrár eru dregnar inn til að vinna með eininguna sjálfa og netsíuna.
  2. Allar aðgerðir fara í gegnum netsíu, þú getur sett króka í hana. Til að gera þetta þarftu að lýsa yfir uppbyggingunni þar sem krókurinn verður stilltur. Mikilvægast er að tilgreina aðgerðina sem verður framkvæmd sem krókur: nfho.hook = icmp_cmd_executor; Ég kem að aðgerðinni sjálfri síðar.
    Síðan stillti ég vinnslutíma pakkans: NF_INET_PRE_ROUTING tilgreinir að vinna úr pakkanum þegar hann birtist fyrst í kjarnanum. Getur verið notað NF_INET_POST_ROUTING til að vinna úr pakkanum þegar hann fer út úr kjarnanum.
    Ég stillti síuna á IPv4: nfho.pf = PF_INET;.
    Ég gef króknum mínum hæsta forgang: nfho.priority = NF_IP_PRI_FIRST;
    Og ég skrái gagnaskipulagið sem raunverulegan krók: nf_register_net_hook(&init_net, &nfho);
  3. Lokaaðgerðin fjarlægir krókinn.
  4. Leyfið er skýrt tilgreint svo að þýðandinn kvarti ekki.
  5. Aðgerðir module_init() и module_exit() stilltu aðrar aðgerðir til að frumstilla og loka einingunni.

Að sækja farminn

Nú þurfum við að taka út farmið, þetta reyndist erfiðasta verkefnið. Kjarninn hefur ekki innbyggðar aðgerðir til að vinna með hleðslu; þú getur aðeins flokkað hausa á samskiptareglum á hærra stigi.

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

Hvað er að gerast:

  1. Ég þurfti að hafa fleiri hausskrár með, í þetta sinn til að vinna með IP og ICMP hausa.
  2. Ég stillti hámarkslínulengd: #define MAX_CMD_LEN 1976. Af hverju nákvæmlega þetta? Vegna þess að þýðandinn kvartar yfir því! Þeir hafa þegar stungið upp á því við mig að ég þurfi að skilja stafla og haug, einhvern tíma mun ég örugglega gera þetta og kannski jafnvel leiðrétta kóðann. Ég setti strax línuna sem mun innihalda skipunina: char cmd_string[MAX_CMD_LEN];. Það ætti að vera sýnilegt í öllum aðgerðum; ég mun ræða þetta nánar í 9. mgr.
  3. Nú þurfum við að frumstilla (struct work_struct my_work;) byggja upp og tengja það við aðra aðgerð (DECLARE_WORK(my_work, work_handler);). Ég ætla líka að tala um hvers vegna þetta er nauðsynlegt í XNUMX. mgr.
  4. Nú lýsi ég yfir fall, sem verður krókur. Tegund og samþykkt rök eru ráðist af netsíunni, við höfum aðeins áhuga á skb. Þetta er socket buffer, grundvallargagnauppbygging sem inniheldur allar tiltækar upplýsingar um pakka.
  5. Til að aðgerðin virki þarftu tvær mannvirki og nokkrar breytur, þar á meðal tvær endurtekningar.
      struct iphdr *iph;
      struct icmphdr *icmph;
    
      unsigned char *user_data;
      unsigned char *tail;
      unsigned char *i;
      int j = 0;
  6. Við getum byrjað á rökfræði. Til að einingin virki, þarf enga pakka aðra en ICMP Echo, þannig að við þáttum biðminni með því að nota innbyggðar aðgerðir og hentum út öllum pökkum sem ekki eru ICMP og ekki Echo. Til baka NF_ACCEPT þýðir samþykki á pakkanum, en einnig er hægt að sleppa pakka með því að skila 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;
      }

    Ég hef ekki prófað hvað mun gerast án þess að athuga IP-hausana. Lágmarksþekking mín á C segir mér að án frekari athugana hlýtur eitthvað hræðilegt að gerast. Ég mun vera ánægður ef þú dregur mig frá þessu!

  7. Nú þegar pakkinn er nákvæmlega af þeirri gerð sem þú þarft geturðu dregið gögnin út. Án innbyggðrar aðgerðar þarftu fyrst að fá vísbendingu á upphaf farms. Þetta er gert á einum stað, þú þarft að fara með bendilinn í byrjun ICMP haussins og færa hann í stærðina á þessum haus. Allt notar uppbyggingu icmph: user_data = (unsigned char *)((unsigned char *)icmph + (sizeof(icmph)));
    Endir haussins verða að passa við endann á farminum inn skb, þess vegna fáum við það með því að nota kjarnorkutæki úr samsvarandi uppbyggingu: tail = skb_tail_pointer(skb);.

    Kjarnorkusprengja yfir ICMP

    Myndinni var stolið þess vegna, þú getur lesið meira um socket buffer.

  8. Þegar þú hefur bent á upphaf og lok geturðu afritað gögnin í streng cmd_string, athugaðu hvort forskeyti sé til staðar run: og annað hvort fargaðu pakkanum ef hann vantar eða skrifaðu línuna aftur og fjarlægðu þetta forskeyti.
  9. Það er það, nú geturðu hringt í annan stjórnanda: schedule_work(&my_work);. Þar sem ekki verður hægt að senda færibreytu í slíkt símtal verður línan með skipuninni að vera alþjóðleg. schedule_work() mun setja aðgerðina sem tengist samþykktu uppbyggingunni í almennu biðröð verkefnaáætlunar og ljúka, sem gerir þér kleift að bíða ekki eftir að skipuninni ljúki. Þetta er nauðsynlegt vegna þess að krókurinn verður að vera mjög hraður. Annars er val þitt að ekkert byrjar eða þú munt fá kjarna læti. Seinkun er eins og dauði!
  10. Það er það, þú getur tekið við pakkanum með tilheyrandi skilum.

Að hringja í forrit í notendarými

Þessi aðgerð er skiljanlegast. Nafn þess var gefið upp DECLARE_WORK(), gerð og viðurkennd rök eru ekki áhugaverð. Við tökum línuna með skipuninni og sendum hana alfarið í skelina. Leyfðu honum að takast á við þáttun, leit að tvöföldum og öllu öðru.

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. Stilltu rökin á fylki strengja argv[]. Ég geri ráð fyrir að allir viti að forrit eru í raun keyrð á þennan hátt, en ekki sem samfelld lína með bilum.
  2. Stilltu umhverfisbreytur. Ég setti aðeins PATH inn með lágmarks sett af slóðum, í von um að þær væru allar þegar sameinaðar /bin с /usr/bin и /sbin с /usr/sbin. Aðrar leiðir skipta sjaldan máli í reynd.
  3. Búið, við skulum gera það! Kernel virka call_usermodehelper() tekur við inngöngu. slóð að tvöfaldanum, fylki af rökum, fylki umhverfisbreyta. Hér geri ég líka ráð fyrir að allir skilji merkingu þess að senda slóðina að keyrsluskránni sem sérstök rök, en þú getur spurt. Síðasta rökin tilgreina hvort bíða eigi eftir að ferlinu ljúki (UMH_WAIT_PROC), ferli byrjun (UMH_WAIT_EXEC) eða alls ekki bíða (UMH_NO_WAIT). Er eitthvað fleira UMH_KILLABLE, ég skoðaði það ekki.

Þing

Samsetning kjarnaeininga fer fram í gegnum kjarnagerðina. Hringt make inni í sérstakri möppu sem er tengd við kjarnaútgáfuna (skilgreint hér: KERNELDIR:=/lib/modules/$(shell uname -r)/build), og staðsetning einingarinnar er send til breytunnar M í rökum. icmpshell.ko og hrein markmið nota þennan ramma algjörlega. IN obj-m gefur til kynna hlutskrána sem verður breytt í einingu. Setningafræði sem endurgerir main.o в icmpshell.o (icmpshell-objs = main.o) lítur ekki mjög rökrétt út fyrir mér, en svo sé.

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

Við söfnum: make. Hleður: insmod icmpshell.ko. Búið, þú getur athugað: sudo ./send.py 45.11.26.232 "date > /tmp/test". Ef þú ert með skrá á vélinni þinni /tmp/test og það inniheldur dagsetninguna sem beiðnin var send, sem þýðir að þú gerðir allt rétt og ég gerði allt rétt.

Ályktun

Fyrsta reynsla mín af kjarnorkuþróun var miklu auðveldari en ég bjóst við. Jafnvel án reynslu af því að þróa í C, með áherslu á vísbendingar um þýðanda og Google niðurstöður, gat ég skrifað vinnueiningu og mér fannst ég vera kjarnahakkari, og á sama tíma handritsbarn. Að auki fór ég á Kernel Newbies rásina, þar sem mér var sagt að nota schedule_work() í stað þess að hringja call_usermodehelper() inni í króknum sjálfum og skammaði hann, réttilega grunaður um svindl. Hundrað línur af kóða kostaði mig um viku af þróun í frítíma mínum. Árangursrík reynsla sem eyðilagði persónulega goðsögn mína um yfirþyrmandi flókið kerfisþróun.

Ef einhver samþykkir að endurskoða kóðann á Github, mun ég vera þakklátur. Ég er nokkuð viss um að ég gerði mörg heimskuleg mistök, sérstaklega þegar ég vann með strengi.

Kjarnorkusprengja yfir ICMP

Heimild: www.habr.com

Bæta við athugasemd