گلوله هسته ای بر فراز ICMP

گلوله هسته ای بر فراز ICMP

TL؛ DR: من در حال نوشتن یک ماژول هسته هستم که دستورات را از بار ICMP خوانده و روی سرور اجرا می کند حتی اگر SSH شما خراب شود. برای بی حوصله ترین، همه کد است گیتهاب.

احتیاط! برنامه نویسان باتجربه C خطر اشک ریختن در خون را دارند! حتی ممکن است در اصطلاح اشتباه کنم، اما هر انتقادی پذیرفته می شود. این پست برای کسانی است که ایده بسیار تقریبی از برنامه نویسی C دارند و می خواهند به درون لینوکس نگاه کنند.

در نظرات اولین من مقاله به SoftEther VPN اشاره کرد که می تواند برخی از پروتکل های "عادی"، به ویژه HTTPS، ICMP و حتی DNS را تقلید کند. من می توانم تصور کنم که تنها اولین آنها کار می کند، زیرا من با HTTP(S) بسیار آشنا هستم و باید تونل سازی را از طریق ICMP و DNS یاد می گرفتم.

گلوله هسته ای بر فراز ICMP

بله، در سال 2020 متوجه شدم که می توانید یک بار دلخواه را در بسته های ICMP وارد کنید. دیر رسیدن بهتر از هرگز نرسیدن است! و از آنجایی که می توان کاری در مورد آن انجام داد، پس باید انجام شود. از آنجایی که در زندگی روزمره من اغلب از خط فرمان استفاده می کنم، از جمله از طریق SSH، ابتدا ایده پوسته ICMP به ذهن من خطور کرد. و به منظور مونتاژ یک بینگو کامل، تصمیم گرفتم آن را به عنوان یک ماژول لینوکس به زبانی بنویسم که فقط ایده ای تقریبی از آن دارم. چنین پوسته ای در لیست فرآیندها قابل مشاهده نخواهد بود، می توانید آن را در هسته بارگذاری کنید و در سیستم فایل قرار نخواهد گرفت، هیچ چیز مشکوکی را در لیست پورت های گوش دادن نخواهید دید. از نظر قابلیت‌هایش، این یک روت کیت کامل است، اما امیدوارم زمانی که میانگین بار برای ورود از طریق SSH و اجرای حداقل زیاد است، آن را بهبود بخشیده و به عنوان آخرین راه حل از آن استفاده کنم. echo i > /proc/sysrq-triggerبرای بازیابی دسترسی بدون راه اندازی مجدد

ما یک ویرایشگر متن، مهارت های برنامه نویسی اولیه در پایتون و سی، گوگل و مجازی اگر همه چیز خراب شد (اختیاری - محلی 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:، برای حذف بسته هایی با بارهای تصادفی به آن نیاز خواهیم داشت.

هسته برای ساخت بسته‌ها به امتیازاتی نیاز دارد، بنابراین اسکریپت باید به‌عنوان superuser اجرا شود. فراموش نکنید که مجوزهای اجرا را بدهید و خود اسکاپی را نصب کنید. دبیان بسته ای به نام دارد 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، بقیه به شکل وابستگی در می آیند. من کل کد را در مقاله ارائه نمی کنم، می توانید آن را در 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. اکنون یک تابع را اعلام می کنم که یک قلاب خواهد بود. نوع و آرگومان های پذیرفته شده توسط netfilter دیکته می شود، ما فقط به آن علاقه داریم 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 چه اتفاقی می افتد. حداقل دانش من از C به من می گوید که بدون بررسی های اضافی، چیز وحشتناکی اتفاق می افتد. خوشحال می شوم اگر من را از این موضوع منصرف کنید!

  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 داخل یک دایرکتوری خاص که به نسخه هسته گره خورده است (در اینجا تعریف شده است: KERNELDIR:=/lib/modules/$(shell uname -r)/build، و مکان ماژول به متغیر منتقل می شود M در استدلال ها icmpshell.ko و اهداف تمیز به طور کامل از این چارچوب استفاده می کنند. که در 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 و حاوی تاریخ ارسال درخواست است، یعنی شما همه چیز را درست انجام دادید و من همه چیز را درست انجام دادم.

نتیجه

اولین تجربه من در زمینه توسعه هسته ای بسیار ساده تر از آن چیزی بود که انتظار داشتم. حتی بدون تجربه توسعه در C، تمرکز بر نکات کامپایلر و نتایج گوگل، من توانستم یک ماژول کار بنویسم و ​​مانند یک هکر هسته، و در عین حال یک بچه اسکریپت احساس کنم. علاوه بر این، من به کانال Kernel Newbies رفتم که به من گفتند استفاده کنم schedule_work() به جای تماس گرفتن call_usermodehelper() درون خود قلاب قرار گرفت و او را شرمنده کرد و به درستی به کلاهبرداری مشکوک شد. صد خط کد برای من حدود یک هفته برای توسعه وقت آزادم هزینه دارد. تجربه موفقی که افسانه شخصی من را در مورد پیچیدگی بسیار زیاد توسعه سیستم نابود کرد.

اگر کسی موافقت کند که یک بررسی کد در Github انجام دهد، سپاسگزار خواهم بود. من تقریباً مطمئن هستم که اشتباهات احمقانه زیادی مرتکب شده ام، به خصوص هنگام کار با سیم.

گلوله هسته ای بر فراز ICMP

منبع: www.habr.com

اضافه کردن نظر