TL; DR: Мен ICMP пайдалы жүктемесінен пәрмендерді оқитын және SSH бұзылса да серверде орындайтын ядро модулін жазып жатырмын. Ең шыдамсыз үшін барлық код .
Назар аударыңыз! Тәжірибелі C бағдарламашылары қанды көз жасын төгу қаупін тудырады! Мен терминология жағынан да қателесуім мүмкін, бірақ кез келген сынға құлақ асады. Бұл жазба C бағдарламалауын тек негізгі түсінігі бар және оның астарынан үңілгісі келетіндерге арналған. Linux.
Менің бірінші пікірімдегі түсініктемелерде кейбір «тұрақты» протоколдарға, атап айтқанда HTTPS, ICMP және тіпті DNS-ге еліктейтін SoftEther VPN-ді атады. Мен олардың біріншісі ғана жұмыс істейтінін елестете аламын, өйткені мен HTTP(S)-мен өте жақсы таныспын және ICMP және DNS арқылы туннель жасауды үйренуім керек болды.

Иә, мен 2020 жылы ICMP пакеттеріне кез келген пайдалы жүктемелерді енгізуге болатынын білдім. Бірақ кеш болғаны жақсы, еш болмағаннан гөрі! Егер бұл туралы бірдеңе істеуге болатын болса, онда ол жасалуы керек. Мен күнделікті жұмысымда, соның ішінде SSH арқылы командалық жолды көбінесе қолданатындықтан, ICMP қабығы идеясы бірінші болып ойға келді. Ал толықтай Bullshield бингосын құру үшін мен оны модуль ретінде жазуды шештім. Linux Менің тілімде бұл шамамен түсінікті. Мұндай қабық процестер тізімінде көрінбейді, оны ядроға жүктеуге болады және файлдық жүйеде орналаспайды, және сіз тыңдау порттарының тізімінде күдікті ештеңе көрмейсіз. Бұл мүмкіндіктері бойынша толыққанды руткит, бірақ мен оны жетілдіріп, жүктеу орташасы 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:, ол кездейсоқ пайдалы жүктемелері бар пакеттерді алып тастау үшін қажет болады.
Ядро пакеттерді жасау үшін артықшылықтарды қажет етеді, сондықтан скриптті root ретінде іске қосу қажет болады. Орындау рұқсаттарын беруді және 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 жүгіру: Сәлем, әлем
0010 21!
Data: 72756e3a48656c6c6f2c20776f726c6421
[Ұзындығы: 17]
2-кадр: сымда 59 байт (472 бит), wlp59s472 интерфейсінде түсірілген 1 байт (0 бит), id 0
Интернет протоколының 4 нұсқасы, Src: 45.11.26.232, Dst: 192.168.0.240
Интернетті басқару хабарламасының протоколы
Түрі: 0 (жаңғырық (пинг) жауап)
Код: 0
Бақылау қосындысы: 0xde03 [дұрыс]
[Бақылау қосындысының мәртебесі: Жақсы]
Идентификатор (BE): 0 (0x0000)
Идентификатор (LE): 0 (0x0000)
Реттік нөмірі (BE): 0 (0x0000)
Реттік нөмірі (LE): 0 (0x0000)
[Сұраныс шеңбері: 1]
[Жауап беру уақыты: 19.094 мс]
Деректер (17 байт)
0000 72 75 6e 3a 48 65 6c 6c 6f 2c 20 77 6f 72 6c 64 жүгіру: Сәлем, әлем
0010 21!
Data: 72756e3a48656c6c6f2c20776f726c6421
[Ұзындығы: 17]
^C2 пакеттері түсірілді
Жауап бумасындағы пайдалы жүктеме өзгермейді.
Ядро модулі
Виртуалды машинаны құрастыру үшін 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);Бұл жерде не болып жатыр:
- Модульдің өзін және желі сүзгісін басқару үшін екі тақырып файлы тартылады.
- Барлық операциялар желілік сүзгіден өтеді, оған ілмектерді орнатуға болады. Ол үшін ілмек конфигурацияланатын құрылымды жариялау керек. Ең бастысы - ілмек ретінде орындалатын функцияны көрсету:
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); - Соңғы функция ілгекті алып тастайды.
- Компилятор шағымданбауы үшін лицензия анық көрсетілген.
- Функциялар
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;
}Не болып жатыр:
- Бұл жолы IP және ICMP тақырыптарын өңдеу үшін қосымша тақырып файлдарын қосуға тура келді.
- Мен жолдың максималды ұзындығын орнаттым:
#define MAX_CMD_LEN 1976. Неліктен дәл бұл? Өйткені компилятор бұл туралы шағымданады! Олар маған стек пен үйінді түсінуім керек екенін айтты, бір күні мен мұны міндетті түрде жасаймын және тіпті кодты түзетемін. Мен бірден пәрменді қамтитын жолды орнатамын:char cmd_string[MAX_CMD_LEN];. Ол барлық функцияларда көрінуі керек, мен бұл туралы толығырақ 9-тармақта айтатын боламын. - Енді біз инициализациялауымыз керек (
struct work_struct my_work;) құрылымы және оны басқа функциямен байланыстыру (DECLARE_WORK(my_work, work_handler);). Бұл не үшін қажет екендігі туралы мен тоғызыншы абзацта айтайын. - Енді мен ілмек болатын функцияны жариялаймын. Түрі мен қабылданған аргументтерді желі сүзгісі белгілейді, бізді тек қызықтырады
skb. Бұл розетка буфері, пакет туралы барлық қолжетімді ақпаратты қамтитын негізгі деректер құрылымы. - Функция жұмыс істеуі үшін сізге екі құрылым және бірнеше айнымалылар, соның ішінде екі иератор қажет болады.
struct iphdr *iph; struct icmphdr *icmph; unsigned char *user_data; unsigned char *tail; unsigned char *i; int j = 0; - Біз логикадан бастай аламыз. Модуль жұмыс істеуі үшін ICMP Echo-дан басқа пакеттер қажет емес, сондықтан біз кірістірілген функцияларды пайдаланып буферді талдаймыз және барлық ICMP емес және жаңғырық емес пакеттерді лақтырамыз. Қайту
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 туралы ең аз білімім қосымша тексерулерсіз қорқынышты нәрсе болуы мүмкін екенін айтады. Мені осыдан тайдырсаңыз, қуанамын!
- Енді пакет сізге қажет нақты түрге ие болғандықтан, деректерді шығарып алуға болады. Кірістірілген функция болмаса, алдымен пайдалы жүктеменің басына көрсеткішті алу керек. Бұл бір жерде орындалады, көрсеткішті ICMP тақырыбының басына апарып, оны осы тақырыптың өлшеміне жылжыту керек. Барлығы құрылымды пайдаланады
icmph:user_data = (unsigned char *)((unsigned char *)icmph + (sizeof(icmph)));
Тақырыптың соңы пайдалы жүктеменің соңына сәйкес келуі керекskb, сондықтан біз оны сәйкес құрылымнан ядролық құралдарды пайдалана отырып аламыз:tail = skb_tail_pointer(skb);.
Сурет ұрланған , розетка буфері туралы көбірек оқуға болады. - Басы мен аяғына көрсеткіштер болғаннан кейін деректерді жолға көшіруге болады
cmd_string, префикстің бар-жоғын тексеріңізrun:және, егер ол жоқ болса, пакетті тастаңыз немесе осы префиксті алып тастап, жолды қайта жазыңыз. - Міне, енді сіз басқа өңдеушіге қоңырау шала аласыз:
schedule_work(&my_work);. Мұндай қоңырауға параметрді беру мүмкін болмайтындықтан, пәрмені бар жол ғаламдық болуы керек.schedule_work()тапсырылған құрылыммен байланысты функцияны тапсырма жоспарлаушының жалпы кезегіне орналастырады және пәрменнің аяқталуын күтпеуге мүмкіндік береді. Бұл қажет, себебі ілмек өте жылдам болуы керек. Әйтпесе, сіздің таңдауыңыз - ештеңе басталмайды немесе сіз ядролық дүрбелең аласыз. Кешігу өліммен тең! - Міне, сіз пакетті сәйкес қайтарыммен қабылдай аласыз.
Пайдаланушы кеңістігіндегі бағдарламаны шақыру
Бұл функция ең түсінікті. Оның аты берілді 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);
}- Аргументтерді жолдар массивіне орнатыңыз
argv[]. Бағдарламалар бос орындармен үздіксіз сызық ретінде емес, шын мәнінде осылай орындалатынын бәрі біледі деп ойлаймын. - Ортаның айнымалы мәндерін орнатыңыз. Мен олардың барлығы біріктірілген деп үміттеніп, ең аз жолдар жиынтығымен ғана PATH енгіздім
/binс/usr/binи/sbinс/usr/sbin. Басқа жолдар практикада сирек маңызды. - Дайын, жасайық! Ядро функциясы
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
барлығы: icmpshell.ko
icmpshell.ko:main.c
-C $(KERNELDIR) M=$(PWD) модульдерін жасаңыз
таза:
-C $(KERNELDIR) M=$(PWD) таза жасаңыз
Біз жинаймыз: make. Жүктелуде: insmod icmpshell.ko. Дайын, тексеруге болады: sudo ./send.py 45.11.26.232 "date > /tmp/test". Құрылғыңызда файл болса /tmp/test және онда сұрау жіберілген күн бар, яғни сіз бәрін дұрыс жасадыңыз және мен бәрін дұрыс жасадым.
қорытынды
Ядролық дамудағы алғашқы тәжірибем мен күткеннен әлдеқайда оңай болды. Компилятордың кеңестері мен Google нәтижелеріне назар аудара отырып, C тілінде даму тәжірибесі болмаса да, мен жұмыс модулін жазып, өзімді ядро хакері және сонымен бірге сценарийші бала сияқты сезіне алдым. Сонымен қатар, мен Kernel Newbies арнасына бардым, онда маған пайдалану керектігін айтты schedule_work() қоңырау шалудың орнына call_usermodehelper() ілмектің өзінде және оны ұятқа қалдырды, алаяқтықтан дұрыс күдіктенді. Жүз жолдық код маған бос уақытымда бір аптаға жуық дамуға жұмсалды. Жүйені дамытудың күрделілігі туралы менің жеке мифімді жойған сәтті тәжірибе.
Егер біреу Github-та кодты тексеруге келіссе, мен ризамын. Мен көптеген ақымақ қателіктер жібергеніме сенімдімін, әсіресе жіптермен жұмыс істегенде.
Ақпарат көзі: www.habr.com

