Gamba la nyuklia juu ya ICMP

Gamba la nyuklia juu ya ICMP

TL; DR: Ninaandika moduli ya kernel ambayo itasoma amri kutoka kwa upakiaji wa ICMP na kuzitekeleza kwenye seva hata kama SSH yako itaanguka. Kwa wasio na subira zaidi, kanuni zote ni github.

Tahadhari Watengenezaji programu wenye uzoefu wa C wana hatari ya kutokwa na machozi ya damu! Huenda hata nikakosea katika istilahi, lakini ukosoaji wowote unakaribishwa. Chapisho limekusudiwa wale ambao wana wazo mbaya sana la upangaji wa C na wanataka kuangalia ndani ya Linux.

Katika maoni yangu ya kwanza Ibara ya ilitaja SoftEther VPN, ambayo inaweza kuiga itifaki zingine za "kawaida", haswa HTTPS, ICMP na hata DNS. Ninaweza kufikiria ya kwanza tu yao kufanya kazi, kwa kuwa ninaifahamu sana HTTP(S), na ilinibidi nijifunze kushughulikia ICMP na DNS.

Gamba la nyuklia juu ya ICMP

Ndio, mnamo 2020 nilijifunza kuwa unaweza kuingiza malipo ya kiholela kwenye pakiti za ICMP. Lakini bora kuchelewa kuliko kamwe! Na kwa kuwa kitu kinaweza kufanywa juu yake, basi kinahitaji kufanywa. Kwa kuwa katika maisha yangu ya kila siku mimi hutumia safu ya amri mara nyingi, pamoja na kupitia SSH, wazo la ganda la ICMP lilikuja akilini mwangu kwanza. Na ili kukusanya bingo kamili ya bullshield, niliamua kuiandika kama moduli ya Linux katika lugha ambayo nina wazo mbaya tu. Kamba kama hiyo haitaonekana kwenye orodha ya michakato, unaweza kuipakia kwenye kernel na haitakuwa kwenye mfumo wa faili, hautaona chochote cha tuhuma kwenye orodha ya bandari za kusikiliza. Kwa upande wa uwezo wake, hii ni mizizi kamili, lakini ninatumai kuiboresha na kuitumia kama ganda la mwisho wakati Wastani wa Mzigo ni wa juu sana kuingia kupitia SSH na kutekeleza angalau. echo i > /proc/sysrq-triggerkurejesha ufikiaji bila kuwasha tena.

Tunachukua kihariri cha maandishi, ujuzi wa msingi wa kupanga programu katika Python na C, Google na mtandaoni ambayo hujali kuweka chini ya kisu ikiwa kila kitu kitavunjika (hiari - VirtualBox ya ndani / KVM / nk) na twende!

Upande wa mteja

Ilionekana kwangu kuwa kwa sehemu ya mteja itabidi niandike hati iliyo na mistari kama 80, lakini kulikuwa na watu wema ambao walinifanyia hivyo. kazi yote. Nambari hiyo iligeuka kuwa rahisi bila kutarajia, inafaa katika mistari 10 muhimu:

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

Hati inachukua hoja mbili, anwani na mzigo wa malipo. Kabla ya kutuma, mzigo wa malipo unatanguliwa na ufunguo run:, tutaihitaji ili kuwatenga vifurushi vilivyo na malipo ya nasibu.

Kernel inahitaji marupurupu kuunda vifurushi, kwa hivyo hati italazimika kuendeshwa kama mtumiaji mkuu. Usisahau kutoa ruhusa za utekelezaji na usakinishe scapy yenyewe. Debian ina kifurushi kinachoitwa python3-scapy. Sasa unaweza kuangalia jinsi yote inavyofanya kazi.

Kuendesha na kutoa amri
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!

Hivi ndivyo inavyoonekana katika mnusaji
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

Mzigo wa malipo katika kifurushi cha majibu haubadilika.

Moduli ya Kernel

Ili kuunda kwenye mashine ya kawaida ya Debian utahitaji angalau make ΠΈ linux-headers-amd64, wengine watakuja kwa namna ya utegemezi. Sitatoa nambari nzima kwenye kifungu; unaweza kuiweka kwenye Github.

Mpangilio wa ndoano

Kuanza, tunahitaji kazi mbili ili kupakia moduli na kuipakua. Kazi ya kupakua haihitajiki, lakini basi rmmod haitafanya kazi; moduli itapakuliwa tu wakati imezimwa.

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

Nini kinaendelea hapa:

  1. Faili mbili za vichwa huvutwa ili kuendesha moduli yenyewe na kichujio cha wavu.
  2. Shughuli zote hupitia netfilter, unaweza kuweka ndoano ndani yake. Ili kufanya hivyo, unahitaji kutangaza muundo ambao ndoano itaundwa. Jambo muhimu zaidi ni kutaja kazi ambayo itatekelezwa kama ndoano: nfho.hook = icmp_cmd_executor; Nitafika kwenye shughuli yenyewe baadaye.
    Kisha nikaweka wakati wa usindikaji wa kifurushi: NF_INET_PRE_ROUTING inabainisha kusindika kifurushi kinapoonekana kwanza kwenye kernel. Inaweza kutumika NF_INET_POST_ROUTING kusindika pakiti inapotoka kwenye kernel.
    Niliweka kichungi kwa IPv4: nfho.pf = PF_INET;.
    Ninaipa ndoano yangu kipaumbele cha juu zaidi: nfho.priority = NF_IP_PRI_FIRST;
    Na mimi husajili muundo wa data kama ndoano halisi: nf_register_net_hook(&init_net, &nfho);
  3. Kazi ya mwisho huondoa ndoano.
  4. Leseni imeonyeshwa wazi ili mkusanyaji asilalamike.
  5. Kazi module_init() ΠΈ module_exit() weka vitendaji vingine vya kuanzisha na kusitisha moduli.

Inarejesha mzigo

Sasa tunahitaji kutoa mzigo wa malipo, hii iligeuka kuwa kazi ngumu zaidi. Kernel haina vitendaji vilivyojumuishwa vya kufanya kazi na mizigo; unaweza tu kuchanganua vichwa vya itifaki za kiwango cha juu.

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

Nini kinaendelea:

  1. Ilinibidi nijumuishe faili za vichwa vya ziada, wakati huu ili kudhibiti vichwa vya IP na ICMP.
  2. Niliweka urefu wa juu wa mstari: #define MAX_CMD_LEN 1976. Kwa nini hasa hii? Kwa sababu mkusanyaji analalamika juu yake! Tayari wamenipendekeza kuwa ninahitaji kuelewa fungu na lundo, siku moja bila shaka nitafanya hivi na labda hata kusahihisha nambari. Mara moja niliweka mstari ambao utakuwa na amri: char cmd_string[MAX_CMD_LEN];. Inapaswa kuonekana katika utendaji wote; Nitazungumza juu ya hili kwa undani zaidi katika aya ya 9.
  3. Sasa tunahitaji kuanzisha (struct work_struct my_work;) muundo na kuiunganisha na kazi nyingine (DECLARE_WORK(my_work, work_handler);) Nitazungumza pia kwa nini hii ni muhimu katika aya ya tisa.
  4. Sasa ninatangaza kazi, ambayo itakuwa ndoano. Aina na hoja zinazokubalika zinaagizwa na kichujio cha mtandao, tunavutiwa tu nazo skb. Hii ni bafa ya tundu, muundo msingi wa data ambao una taarifa zote zinazopatikana kuhusu pakiti.
  5. Ili chaguo hili lifanye kazi, utahitaji miundo miwili na vigezo kadhaa, ikiwa ni pamoja na virudia viwili.
      struct iphdr *iph;
      struct icmphdr *icmph;
    
      unsigned char *user_data;
      unsigned char *tail;
      unsigned char *i;
      int j = 0;
  6. Tunaweza kuanza na mantiki. Ili moduli ifanye kazi, hakuna pakiti zingine isipokuwa ICMP Echo zinazohitajika, kwa hivyo tunachanganua bafa kwa kutumia vitendaji vilivyojumuishwa na kutupa pakiti zote zisizo za ICMP na zisizo za Echo. Rudi NF_ACCEPT inamaanisha kukubalika kwa kifurushi, lakini pia unaweza kuacha vifurushi kwa kurudisha 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;
      }

    Sijajaribu kitakachotokea bila kuangalia vichwa vya IP. Ufahamu wangu mdogo wa C unaniambia kuwa bila ukaguzi wa ziada, kitu kibaya kitatokea. Nitafurahi ikiwa utanizuia kwa hili!

  7. Sasa kwa kuwa kifurushi ni cha aina halisi unayohitaji, unaweza kutoa data. Bila kazi iliyojengwa, kwanza unapaswa kupata pointer hadi mwanzo wa mzigo wa malipo. Hii imefanywa katika sehemu moja, unahitaji kuchukua pointer hadi mwanzo wa kichwa cha ICMP na uhamishe kwa ukubwa wa kichwa hiki. Kila kitu kinatumia muundo icmph: user_data = (unsigned char *)((unsigned char *)icmph + (sizeof(icmph)));
    Mwisho wa kichwa lazima ulingane na mwisho wa upakiaji ndani skb, kwa hivyo tunaipata kwa kutumia njia za nyuklia kutoka kwa muundo unaolingana: tail = skb_tail_pointer(skb);.

    Gamba la nyuklia juu ya ICMP

    Picha iliibiwa hivyo, unaweza kusoma zaidi kuhusu bafa ya tundu.

  8. Mara tu ukiwa na viashiria vya mwanzo na mwisho, unaweza kunakili data kwenye mfuatano cmd_string, angalia ikiwa kuna kiambishi awali run: na, ama tupa kifurushi ikiwa hakipo, au andika tena mstari, ukiondoa kiambishi awali hiki.
  9. Ni hivyo, sasa unaweza kupiga simu kidhibiti kingine: schedule_work(&my_work);. Kwa kuwa haitawezekana kupitisha parameter kwa simu hiyo, mstari na amri lazima iwe ya kimataifa. schedule_work() itaweka kazi inayohusishwa na muundo uliopitishwa kwenye foleni ya jumla ya mpangaji wa kazi na imekamilika, huku kuruhusu usisubiri amri ili kukamilisha. Hii ni muhimu kwa sababu ndoano lazima iwe haraka sana. Vinginevyo, chaguo lako ni kwamba hakuna kitu kitaanza au utapata hofu ya kernel. Kuchelewa ni kama kifo!
  10. Hiyo ndiyo yote, unaweza kukubali kifurushi na kurudi sawa.

Kupigia simu programu katika nafasi ya mtumiaji

Kazi hii ndiyo inayoeleweka zaidi. Jina lake lilitolewa DECLARE_WORK(), aina na hoja zinazokubalika hazipendezi. Tunachukua mstari na amri na kuipitisha kabisa kwenye shell. Acha ashughulike na uchanganuzi, kutafuta jozi na kila kitu kingine.

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. Weka hoja kwa safu ya mifuatano argv[]. Nitafikiria kuwa kila mtu anajua kuwa programu zinatekelezwa kwa njia hii, na sio kama safu inayoendelea na nafasi.
  2. Weka vigezo vya mazingira. Niliingiza PATH pekee na seti ya chini ya njia, nikitumai kuwa zote tayari zimeunganishwa /bin с /usr/bin и /sbin с /usr/sbin. Njia zingine sio muhimu sana katika mazoezi.
  3. Imekamilika, tuifanye! Utendaji wa Kernel call_usermodehelper() inakubali kuingia. njia ya binary, safu ya hoja, safu ya anuwai ya mazingira. Hapa pia nadhani kuwa kila mtu anaelewa maana ya kupitisha njia ya faili inayoweza kutekelezwa kama hoja tofauti, lakini unaweza kuuliza. Hoja ya mwisho inabainisha iwapo tusubiri mchakato ukamilike (UMH_WAIT_PROC), mchakato kuanza (UMH_WAIT_EXEC) au usisubiri kabisa (UMH_NO_WAIT) Je, kuna wengine zaidi UMH_KILLABLE, sikuiangalia.

Mkutano

Mkusanyiko wa moduli za kernel hufanywa kupitia mfumo wa kutengeneza kernel. Imeitwa make ndani ya saraka maalum iliyofungwa kwa toleo la kernel (imefafanuliwa hapa: KERNELDIR:=/lib/modules/$(shell uname -r)/build), na eneo la moduli hupitishwa kwa kutofautisha M katika hoja. icmpshell.ko na shabaha safi hutumia mfumo huu kabisa. KATIKA obj-m inaonyesha faili ya kitu ambayo itabadilishwa kuwa moduli. Sintaksia inayorudisha nyuma main.o Π² icmpshell.o (icmpshell-objs = main.o) haionekani kuwa yenye mantiki sana kwangu, lakini iwe hivyo.

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

Tunakusanya: make. Inapakia: insmod icmpshell.ko. Umemaliza, unaweza kuangalia: sudo ./send.py 45.11.26.232 "date > /tmp/test". Ikiwa una faili kwenye mashine yako /tmp/test na ina tarehe ambayo ombi lilitumwa, ambayo inamaanisha ulifanya kila kitu sawa na nilifanya kila kitu sawa.

Hitimisho

Uzoefu wangu wa kwanza na maendeleo ya nyuklia ulikuwa rahisi zaidi kuliko nilivyotarajia. Hata bila uzoefu wa kukuza katika C, nikizingatia vidokezo vya mkusanyaji na matokeo ya Google, niliweza kuandika moduli ya kufanya kazi na kujisikia kama mdukuzi wa kernel, na wakati huo huo mtoto wa hati. Kwa kuongezea, nilienda kwa chaneli ya Kernel Newbies, ambapo niliambiwa nitumie schedule_work() badala ya kupiga simu call_usermodehelper() ndani ya ndoano yenyewe na aibu yake, sawa suspecting kashfa. Laini mia za msimbo zilinigharimu takriban wiki moja ya maendeleo katika wakati wangu wa bure. Uzoefu uliofanikiwa ambao uliharibu hadithi yangu ya kibinafsi kuhusu ugumu mkubwa wa ukuzaji wa mfumo.

Ikiwa mtu atakubali kufanya ukaguzi wa nambari kwenye Github, nitashukuru. Nina hakika nilifanya makosa mengi ya kijinga, haswa wakati wa kufanya kazi na kamba.

Gamba la nyuklia juu ya ICMP

Chanzo: mapenzi.com

Kuongeza maoni