Nuklear Shell iwwer ICMP

Nuklear Shell iwwer ICMP

TL; DR: Ech schreiwen e Kernelmodul deen d'Befehle vun der ICMP Notzlaascht liest an se op de Server ausféieren, och wann Är SSH klappt. Fir déi Ongedëllegst ass de ganze Code github.

Opgepasst! Erlieft C Programméierer riskéieren a Blutt Tréinen auszebriechen! Ech hu vläicht souguer falsch an der Terminologie, awer all Kritik ass wëllkomm. De Post ass geduecht fir déi, déi eng ganz rau Iddi iwwer C Programméiere hunn a wëllen an d'Innere vu Linux kucken.

An de Kommentaren op meng éischt Artikel SoftEther VPN ernimmt, deen e puer "normale" Protokoller mimiéiere kann, besonnesch HTTPS, ICMP a souguer DNS. Ech ka mir virstellen datt nëmmen déi éischt vun hinnen funktionnéieren, well ech ganz vertraut mat HTTP(S) sinn, an ech hu missen Tunnel iwwer ICMP an DNS léieren.

Nuklear Shell iwwer ICMP

Jo, am Joer 2020 hunn ech geléiert datt Dir eng arbiträr Notzlaascht an ICMP Pakete kënnt asetzen. Awer besser spéit wéi ni! A well do eppes ka gemaach ginn, da muss et gemaach ginn. Zënter datt ech a mengem Alldag am meeschten d'Kommandozeil benotzen, och iwwer SSH, ass d'Iddi vun enger ICMP Shell fir d'éischt a mengem Kapp komm. A fir e komplette Bullshield-Bingo ze sammelen, hunn ech decidéiert et als Linux-Modul an enger Sprooch ze schreiwen, vun där ech nëmmen eng graff Ahnung hunn. Sou eng Shell wäert net an der Lëscht vun de Prozesser sichtbar sinn, Dir kënnt et an de Kernel lueden an et wäert net am Dateiesystem sinn, Dir gesitt näischt verdächteg an der Lëscht vun den Nolauschtersporten. Wat seng Fäegkeeten ugeet, ass dëst e vollwäertege Rootkit, awer ech hoffen et ze verbesseren an et als Shell vum leschten Auswee ze benotzen wann de Load Average ze héich ass fir iwwer SSH aloggen an op d'mannst auszeféieren. echo i > /proc/sysrq-triggerZougang ouni Neistart ze restauréieren.

Mir huelen engem Text Redakter, Basis programméiere Kompetenzen am Python an C, Google an virtuell déi Dir egal ënner dem Messer setzen wann alles brécht (optional - lokal VirtualBox / KVM / etc) a loosst eis goen!

Client Säit

Et huet mir geschéngt datt ech fir de Client Deel e Skript mat ongeféier 80 Zeilen schreiwen muss, awer et waren léif Leit déi et fir mech gemaach hunn all Aarbecht. De Code huet sech onerwaart einfach erausgestallt, passt an 10 bedeitend Linnen:

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

De Skript hëlt zwee Argumenter, eng Adress an eng Notzlaascht. Virun der Sendung gëtt d'Notzlaascht vun engem Schlëssel virausgesat run:, mir brauchen et fir Packagen mat zoufälleg Notzlaascht auszeschléissen.

De Kernel erfuerdert Privilegien fir Packagen ze kreéieren, sou datt de Skript als Superuser lafen muss. Vergiesst net d'Ausféierungsrechter ze ginn an d'Scapy selwer z'installéieren. Debian huet e Package genannt python3-scapy. Elo kënnt Dir kucken wéi et alles funktionnéiert.

Lafen an ausginn de Kommando
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!

Esou gesäit et am Sniffer aus
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

D'Notzlaascht am Äntwertpaket ännert sech net.

Kernel Modul

Fir an enger Debian virtueller Maschinn ze bauen braucht Dir op d'mannst make и linux-headers-amd64, de Rescht wäert kommen a Form vun Ofhängegkeeten. Ech wäert net de ganze Code am Artikel ubidden; Dir kënnt et op Github klone.

Hook Setup

Fir unzefänken, brauche mir zwou Funktiounen fir de Modul ze lueden an ze entlueden. D'Funktioun fir Ausluede ass net erfuerderlech, awer dann rmmod et funktionnéiert net; de Modul gëtt nëmmen ausgeschalt wann et ausgeschalt gëtt.

#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 leeft hei:

  1. Zwee Headerdateien ginn eragezunn fir de Modul selwer an den Netfilter ze manipuléieren.
  2. All Operatiounen ginn duerch en Netfilter, Dir kënnt Haken dran setzen. Fir dëst ze maachen, musst Dir d'Struktur erklären an där den Haken konfiguréiert gëtt. Déi wichtegst Saach ass d'Funktioun ze spezifizéieren déi als Hook ausgefouert gëtt: nfho.hook = icmp_cmd_executor; Ech kommen méi spéit op d'Funktioun selwer.
    Da setzen ech d'Veraarbechtungszäit fir de Package: NF_INET_PRE_ROUTING spezifizéiert fir de Package ze veraarbecht wann et fir d'éischt am Kernel erschéngt. Kann benotzt ginn NF_INET_POST_ROUTING fir de Paket ze veraarbecht wéi et aus dem Kernel erausgeet.
    Ech hunn de Filter op IPv4 gesat: nfho.pf = PF_INET;.
    Ech ginn meng Hook déi héchst Prioritéit: nfho.priority = NF_IP_PRI_FIRST;
    An ech registréieren d'Datenstruktur als den aktuellen Hook: nf_register_net_hook(&init_net, &nfho);
  3. Déi lescht Funktioun läscht den Haken.
  4. D'Lizenz ass kloer uginn sou datt de Compiler net beschwéiert.
  5. Functions module_init() и module_exit() aner Funktiounen setzen fir de Modul ze initialiséieren an ofzeschléissen.

D'Notzlaascht zréckzéien

Elo musse mir d'Notzlaascht extrahéieren, dëst huet sech als déi schwieregst Aufgab erausgestallt. De Kernel huet keng agebaute Funktiounen fir mat Notzlaascht ze schaffen; Dir kënnt nëmmen Header vun méi héije Protokoller parséieren.

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

Waat leeft:

  1. Ech hu missen zousätzlech Headerdateien enthalen, dës Kéier fir IP an ICMP Header ze manipuléieren.
  2. Ech setzen déi maximal Linn Längt: #define MAX_CMD_LEN 1976. Firwat genau dat? Well de Compiler sech doriwwer beschwéiert! Si hu mir scho virgeschloen datt ech de Stack an de Koup muss verstoen, iergendwann wäert ech dat definitiv maachen a vläicht souguer de Code korrigéieren. Ech setzen direkt d'Linn déi de Kommando enthält: char cmd_string[MAX_CMD_LEN];. Et soll an all Funktiounen siichtbar sinn; Ech wäert iwwer dëst méi am Detail am Paragraph 9 schwätzen.
  3. Elo musse mir initialiséieren (struct work_struct my_work;) Struktur a verbënnt se mat enger anerer Funktioun (DECLARE_WORK(my_work, work_handler);). Ech wäert och schwätzen iwwer firwat dat néideg ass am néngten Paragraph.
  4. Elo erklären ech eng Funktioun, déi en Haken wäert sinn. Den Typ an akzeptéiert Argumenter ginn vum Netfilter diktéiert, mir sinn nëmmen interesséiert skb. Dëst ass e Socket-Puffer, eng fundamental Datestruktur déi all verfügbar Informatioun iwwer e Paket enthält.
  5. Fir d'Funktioun ze schaffen, braucht Dir zwou Strukturen a verschidde Variabelen, dorënner zwee Iteratoren.
      struct iphdr *iph;
      struct icmphdr *icmph;
    
      unsigned char *user_data;
      unsigned char *tail;
      unsigned char *i;
      int j = 0;
  6. Mir kënne mat der Logik ufänken. Fir datt de Modul funktionnéiert, gi keng Päck ausser ICMP Echo gebraucht, also parse mir de Puffer mat agebaute Funktiounen an werfen all net-ICMP an net-Echo Päckchen eraus. Retour NF_ACCEPT heescht Akzeptanz vum Package, awer Dir kënnt och Packagen erofsetzen andeems Dir zréckkoum 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;
      }

    Ech hunn net getest wat geschitt ouni d'IP Header ze kontrolléieren. Meng minimal Kenntnisser vun C seet mir, datt ouni zousätzlech Kontrollen, eppes schrecklech ass gebonnen ze geschéien. Ech wäert frou sinn wann Dir mech dovunner ofschléissen!

  7. Elo datt de Package vun der exakter Aart ass, déi Dir braucht, kënnt Dir d'Donnéeën extrahéieren. Ouni eng agebaute Funktioun, musst Dir als éischt e Pointer op den Ufank vun der Notzlaascht kréien. Dëst gëtt op enger Plaz gemaach, Dir musst de Pointer op den Ufank vum ICMP Header huelen an op d'Gréisst vun dësem Header réckelen. Alles benotzt Struktur icmph: user_data = (unsigned char *)((unsigned char *)icmph + (sizeof(icmph)));
    D'Enn vum Header muss mam Enn vun der Notzlaascht passen skb, dofir kréien mir et mat nukleare Mëttelen aus der entspriechender Struktur: tail = skb_tail_pointer(skb);.

    Nuklear Shell iwwer ICMP

    D'Bild gouf geklaut vun hei, Dir kënnt méi iwwer de Socketbuffer liesen.

  8. Wann Dir Hiweiser op den Ufank an Enn hutt, kënnt Dir d'Donnéeën an eng String kopéieren cmd_string, kontrolléieren et fir d'Präsenz vun engem Präfix run: an, entweder de Pak ewechzegeheien wann et vermësst, oder schreiwen der Linn erëm, ewechzehuelen dësem Präfix.
  9. Dat ass et, elo kënnt Dir en aneren Handler uruffen: schedule_work(&my_work);. Well et net méiglech ass e Parameter un esou en Uruff ze passéieren, muss d'Linn mam Kommando global sinn. schedule_work() wäert d'Funktioun, déi mat der passéierter Struktur assoziéiert ass, an d'allgemeng Schlaang vum Taskplaner setzen a komplett, wat Iech erlaabt net op de Kommando ze waarden fir ze kompletéieren. Dëst ass néideg, well den Haken ganz séier muss sinn. Soss ass Äre Choix datt näischt ufänkt oder Dir kritt eng Kernel Panik. Verzögerung ass wéi den Doud!
  10. Dat ass et, Dir kënnt de Package mat engem entspriechende Retour akzeptéieren.

Rufft e Programm am Benotzerraum

Dës Funktioun ass déi verständlech. Säin Numm gouf uginn DECLARE_WORK(), den Typ an akzeptéiert Argumenter sinn net interessant. Mir huelen d'Linn mam Kommando a passéieren se ganz an d'Schuel. Loosst hien sech mat Parsing beschäftegen, Sich no Binären an alles anescht.

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. Setzt d'Argumenter op eng Array vu Saiten argv[]. Ech huelen un datt jidderee weess datt Programmer tatsächlech esou ausgefouert ginn, an net als kontinuéierlech Linn mat Raum.
  2. Set Ëmfeld Variablen. Ech hunn nëmmen PATH mat engem Minimum Set vu Weeër agebaut, an der Hoffnung datt se all scho kombinéiert sinn /bin с /usr/bin и /sbin с /usr/sbin. Aner Weeër selten an der Praxis wichteg.
  3. Fäerdeg, loosst eis et maachen! Kernel Funktioun call_usermodehelper() akzeptéiert Entrée. Wee zum Binär, Array vun Argumenter, Array vun Ëmfeldvariablen. Hei huelen ech och un datt jiddereen d'Bedeitung versteet fir de Wee an d'ausführbar Datei als separat Argument ze passéieren, awer Dir kënnt froen. Dat lescht Argument spezifizéiert ob bis de Prozess ofgeschloss ass (UMH_WAIT_PROC), Prozess ufänken (UMH_WAIT_EXEC) oder guer net waarden (UMH_NO_WAIT). Gëtt et e puer méi UMH_KILLABLE, Ech hunn et net gekuckt.

Assemblée

D'Assemblée vu Kernelmoduler gëtt duerch de Kernel Make-Framework ausgefouert. Geruff make an engem speziellen Verzeechnes verbonne mat der Kernel Versioun (definéiert hei: KERNELDIR:=/lib/modules/$(shell uname -r)/build), an de Standort vum Modul gëtt un d'Variabel iwwerginn M an den Argumenter. D'icmpshell.ko a propper Ziler benotzen dëse Kader ganz. IN obj-m weist d'Objetdatei un, déi an e Modul ëmgewandelt gëtt. Syntax déi remakes main.o в icmpshell.o (icmpshell-objs = main.o) gesäit fir mech net ganz logesch aus, mee sou ass et.

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

Mir sammelen: make. Lueden: insmod icmpshell.ko. Fäerdeg, Dir kënnt kontrolléieren: sudo ./send.py 45.11.26.232 "date > /tmp/test". Wann Dir e Fichier op Ärer Maschinn hutt /tmp/test an et enthält den Datum wou d'Ufro geschéckt gouf, dat heescht datt Dir alles richteg gemaach hutt an ech alles richteg gemaach hunn.

Konklusioun

Meng éischt Erfahrung mat nuklear Entwécklung war vill méi einfach wéi ech erwaart. Och ouni Erfahrung am C z'entwéckelen, konzentréieren op Compiler Hiweiser a Google Resultater, konnt ech en Aarbechtsmodul schreiwen a fille wéi e Kernel Hacker, a gläichzäiteg e Skript Kiddie. Zousätzlech sinn ech op de Kernel Newbies Kanal gaang, wou ech gesot hunn ze benotzen schedule_work() amplaz ze ruffen call_usermodehelper() am Haken selwer an huet him geschummt, mat Recht e Bedruch verdächtegt. Honnert Zeilen Code kascht mech ongeféier eng Woch Entwécklung a menger Fräizäit. Eng erfollegräich Erfahrung déi mäi perséinleche Mythos iwwer déi iwwerwältegend Komplexitéit vun der Systementwécklung zerstéiert huet.

Wann iergendeen averstanen ass eng Code review op Github ze maachen, wäert ech dankbar sinn. Ech si ganz sécher datt ech vill blöd Feeler gemaach hunn, besonnesch wann ech mat Saiten schaffen.

Nuklear Shell iwwer ICMP

Source: will.com

Setzt e Commentaire