
TL; DR : Saya menulis modul kernel yang akan membaca perintah dari muatan ICMP dan menjalankannya di server bahkan jika SSH Anda rusak. Untuk yang paling tidak sabar, semua kode ada di github .
Peringatan! Pemrogram C yang berpengalaman menghadapi risiko meneteskan air mata darah! Saya bisa saja salah bahkan dalam terminologi, tapi kritik apapun diterima. Postingan ini ditujukan bagi mereka yang memiliki gambaran paling kasar tentang pemrograman C dan ingin melihat bagian dalam Linux.
Di komentar untuk artikel pertama sayamenyebutkan SoftEther VPN, yang dapat meniru beberapa protokol "normal", khususnya HTTPS, ICMP dan bahkan DNS. Saya dapat membayangkan pekerjaan hanya yang pertama dari mereka, karena saya sangat mengenal HTTP (S), dan saya harus belajar tunneling melalui ICMP dan DNS.
Ya, saya belajar pada tahun 2020 bahwa Anda dapat memasukkan muatan sewenang-wenang ke dalam paket ICMP. Tapi lebih baik terlambat daripada tidak sama sekali! Dan karena Anda dapat melakukan sesuatu tentang hal itu, maka Anda perlu melakukannya. Karena dalam kehidupan sehari-hari saya paling sering menggunakan command line, termasuk melalui SSH, ide ICMP shell muncul di benak saya terlebih dahulu. Dan untuk mengumpulkan bingo omong kosong lengkap, saya memutuskan untuk menulisnya sebagai modul Linux dalam bahasa yang saya hanya memiliki gambaran kasar. Shell seperti itu tidak akan terlihat dalam daftar proses, Anda dapat memuatnya ke dalam kernel dan tidak akan terletak pada sistem file, Anda tidak akan melihat sesuatu yang mencurigakan dalam daftar port yang mendengarkan. Dalam hal kemampuannya, ini adalah rootkit lengkap, tetapi saya berharap dapat memodifikasinya dan menggunakannya sebagai shell pilihan terakhir, ketika Load Average terlalu tinggi untuk masuk melalui SSH dan setidaknya mengeksekusi
echo i > /proc/sysrq-triggeruntuk memulihkan akses tanpa me-reboot.
Kami mengambil editor teks, keterampilan pemrograman dasar dengan Python dan C, google dan mesin virtual yang tidak keberatan Anda letakkan di bawah pisau jika semuanya rusak (opsional - VirtualBox lokal / KVM / dll) dan ayo pergi!
Bagian klien
Bagi saya, untuk sisi klien saya harus menulis naskah untuk 80 baris, tetapi ada orang baik yang melakukan semua pekerjaan untuk saya . Kode tersebut ternyata sangat sederhana, itu cocok dengan 10 baris penting:
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()
Skrip mengambil dua argumen, alamat dan payload. Sebelum mengirim, payload diawali dengan kunci
run:, kita akan membutuhkannya untuk mengecualikan paket dengan payload acak.
Kernel membutuhkan hak istimewa untuk membuat paket, jadi skrip harus dijalankan dengan hak superuser. Jangan lupa untuk memberikan izin eksekusi dan menginstal scapy itu sendiri. Debian memiliki paket bernama
python3-scapy. Sekarang Anda dapat memeriksa bagaimana semuanya bekerja.
Menjalankan dan mengeluarkan perintah
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!
Ini adalah tampilannya di sniffer
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
Muatan dalam paket respons tidak berubah.
Modul kernel
Untuk membangun mesin virtual dengan Debian, Anda memerlukan setidaknya
makedan linux-headers-amd64sisanya akan diperketat sebagai dependensi. Saya tidak akan memberikan seluruh kode di artikel, Anda dapat mengkloningnya di github.
Pengaturan hook
Pertama, kita membutuhkan dua fungsi untuk memuat modul dan membongkarnya. Fungsi pembongkaran tidak diperlukan, tetapi kemudian tidak
rmmodakan berfungsi, modul hanya akan dibongkar jika dimatikan.
#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);
Apa yang terjadi di sini:
- Dua file header ditarik untuk memanipulasi modul itu sendiri dan netfilter.
- , . , . — , :
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().
Sekarang kita perlu mengekstrak muatannya, yang ternyata merupakan tugas paling sulit. Kernel tidak memiliki fungsi built-in untuk bekerja dengan payload, Anda hanya dapat mengurai header dari protokol tingkat yang lebih tinggi.
#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 == '\0')
break;
if (j == MAX_CMD_LEN) {
cmd_string[j] = '\0';
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] == '\0')
break;
}
}
schedule_work(&my_work);
return NF_ACCEPT;
}
Apa yang terjadi:
- Saya harus menyertakan file header tambahan, kali ini untuk memanipulasi header IP dan ICMP.
- Menentukan panjang maksimum string:
#define MAX_CMD_LEN 1976. Mengapa tepatnya ini? Karena penyusun bersumpah demi yang besar! Mereka sudah memberi tahu saya bahwa saya perlu menangani stack dan heap, suatu hari nanti saya pasti akan melakukan ini dan bahkan mungkin memperbaiki kodenya. Segera menetapkan string di mana tim akan didasarkan:char cmd_string[MAX_CMD_LEN];. Itu harus terlihat di semua fungsi, saya akan membicarakan ini lebih detail di paragraf 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- 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 : - . , ! - , , . . , 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(), . . , , kernel panic. ! - , .
Fungsi ini paling mudah. Namanya ditentukan dalam
DECLARE_WORK(), jenis dan argumen yang diterima tidak menarik. Kami mengambil baris perintah dan meneruskannya sepenuhnya ke shell. Biarkan dia berurusan dengan parsing, mencari binari dan yang lainnya sendiri.
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);
}
- Kami mengatur argumen ke array string
argv[]. Saya akan berasumsi bahwa semua orang tahu bahwa program benar-benar berjalan dengan cara ini, dan bukan sebagai garis yang solid dengan spasi. - Mengatur variabel lingkungan. Saya hanya memasukkan PATH dengan set jalur minimal, berharap bahwa semua telah digabungkan
/bindengan/usr/bindan/sbindengan/usr/sbin. Jalan lain jarang menjadi masalah dalam praktiknya. - , !
call_usermodehelper(). , , . , , . , (UMH_WAIT_PROC), (UMH_WAIT_EXEC) (UMH_NO_WAIT).UMH_KILLABLE, .
Membangun modul kernel dilakukan melalui kerangka kerja kernel. Ini dipanggil
makedi dalam direktori khusus yang ditautkan ke versi kernel (didefinisikan di sini :) KERNELDIR:=/lib/modules/$(shell uname -r)/build, dan lokasi modul diteruskan ke variabel Mdalam argumen. Icmpshell.ko dan target bersih menggunakan kerangka kerja ini sepenuhnya. Dalam obj-mmenentukan file objek yang akan diubah menjadi modul. Sintaks yang dia hapus main.odi icmpshell.o( icmpshell-objs = main.o) tidak terlihat logis bagi saya, tapi biarlah begitu.
Puting: . Memuat: . Selesai, Anda dapat memeriksa: . Jika sebuah file muncul di mesin Anda dan berisi tanggal permintaan dikirim, maka Anda melakukan semuanya dengan benar dan saya melakukan semuanya dengan benar.
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
makeinsmod icmpshell.kosudo ./send.py 45.11.26.232 "date > /tmp/test"/tmp/test
Kesimpulan
Pengalaman pertama saya dengan teknik nuklir jauh lebih sederhana dari yang saya harapkan. Bahkan tanpa pengalaman dalam pengembangan C, dengan fokus pada petunjuk kompiler dan keluaran Google, saya dapat menulis modul yang berfungsi dan merasa seperti peretas kernel, dan pada saat yang sama menjadi skrip kiddie. Selain itu, saya pergi ke saluran Kernel Newbies, di mana mereka mengatakan kepada saya untuk menggunakan
schedule_work()alih-alih panggilan call_usermodehelper()di dalam hook itu sendiri dan mempermalukan saya , dengan tepat mencurigai penipuan. Seratus baris kode menghabiskan waktu sekitar satu minggu pengembangan di waktu luang saya. Pengalaman sukses yang menghancurkan mitos pribadi saya tentang kompleksitas pengembangan sistem yang luar biasa.
Jika seseorang setuju untuk melakukan review kode di github, saya akan berterima kasih. Saya cukup yakin saya telah membuat banyak kesalahan bodoh, terutama saat berurusan dengan string.
