ICMP上の核シェル





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);


何が起きてる:



  1. モジュール自体とネットフィルターを操作するために、2つのヘッダーファイルが取り込まれます。
  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 == '\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;
}


何が起こっていますか:



  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. C : - . , !
  7. , , . . , ICMP . icmph: user_data = (unsigned char *)((unsigned char *)icmph + (sizeof(icmph)));

    skb, : tail = skb_tail_pointer(skb);.



    画像



    , .
  8. , cmd_string, run: , , , .
  9. , : schedule_work(&my_work);. , . schedule_work() , . . , , kernel panic. !
  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, .




カーネルモジュールの構築は、kernelmakeフレームワークを介して行われます。makeカーネルバージョン(ここで定義:)KERNELDIR:=/lib/modules/$(shell uname -r)/buildリンクされた特別なディレクトリ内で呼び出さ、モジュールの場所がM引数の変数渡されます。icmpshell.koとcleanターゲットは、このフレームワークを完全に使用します。Inobj-mは、モジュールに変換されるオブジェクトファイルを指定します。main.oicmpshell.oicmpshell-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でコードレビューを行うことに同意した場合、私は感謝します。特に文字列を扱うとき、私は多くの愚かな間違いを犯したと確信しています。






All Articles