
TL; DR:SSHがクラッシュした場合でも、ICMPペイロードからコマンドを読み取り、サーバー上で実行するカーネルモジュールを作成しています。最もせっかちなことに、すべてのコードはgithubにあります。
注意!経験豊富なCプログラマーは、血の涙に飛び込む危険を冒します!用語でも間違っているかもしれませんが、どんな批判も歓迎します。この投稿は、Cプログラミングの最も大まかなアイデアを持っていて、Linuxの内部を調べたい人を対象としています。
私の最初の記事へのコメントでSoftEther VPNについて言及しました。これは、一部の「通常の」プロトコル、特にHTTPS、ICMP、さらにはDNSを模倣できます。私はHTTP(S)に非常に精通しており、ICMPとDNSを介したトンネリングを学ぶ必要があったため、最初の作業を想像することしかできません。
はい、2020年に、ICMPパケットに任意のペイロードを挿入できることを学びました。しかし、決して遅くなるよりはましです!そして、あなたはそれについて何かをすることができるので、あなたはそれをする必要があります。私の日常生活では、SSH経由を含め、ほとんどの場合コマンドラインを使用するため、ICMPシェルのアイデアが最初に頭に浮かびました。そして、完全なでたらめなビンゴをまとめるために、私は大まかな考えしか持っていない言語で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()
スクリプトは、アドレスとペイロードの2つの引数を取ります。送信する前に、ペイロードの前にキー
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でクローンを作成できます。
フックのセットアップ
まず、モジュールをロードするためとアンロードするための2つの関数が必要です。アンロード機能は必須ではありませんが、機能しなくなり、
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);
何が起きてる:
- モジュール自体とネットフィルターを操作するために、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); - .
- , .
-
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 == '\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;
}
何が起こっていますか:
- 今回は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- 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. ! - , .
この機能は最も簡単です。その名前はで指定されまし
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, .
カーネルモジュールの構築は、kernelmakeフレームワークを介して行われます。
makeカーネルバージョン(ここで定義:)KERNELDIR:=/lib/modules/$(shell uname -r)/buildにリンクされた特別なディレクトリ内で呼び出され、モジュールの場所がM引数の変数に渡されます。icmpshell.koとcleanターゲットは、このフレームワークを完全に使用します。Inobj-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
makeinsmod icmpshell.kosudo ./send.py 45.11.26.232 "date > /tmp/test"/tmp/test
結論
核工学に関する私の最初の経験は、私が予想していたよりもはるかに単純でした。コンパイラのヒントとGoogleの出力に焦点を当てた、C開発の経験がなくても、動作するモジュールを記述して、カーネルハッカーのように感じると同時に、スクリプトの子供であるように感じることができました。さらに、私はKernel Newbiesチャンネルに行きました。そこでは、フック自体の内部
schedule_work()を呼び出す代わりに使用するように言われcall_usermodehelper()、詐欺を疑って私を恥じました。100行のコードは、空き時間に約1週間の開発コストがかかりました。システム開発の圧倒的な複雑さについての私の個人的な神話を破壊した成功した経験。
誰かがgithubでコードレビューを行うことに同意した場合、私は感謝します。特に文字列を扱うとき、私は多くの愚かな間違いを犯したと確信しています。
