pythonでシェルコード実行

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』を手に取ってその周辺の重要な説明部分にも目を通すことを勧める。

kernel32 = ctypes.windll.kernel32
スポンサーリンク

write_memory関数について

ファイルシステムに触れることなくシェルコードを実行するには、

  • シェルコードを保持するためのバッファをメモリの中に作る
  • そしてそのメモリを指すポインタを作る(ctypesモジュール)

 

write_memory()でバッファの内容をメモリに書き込む。

def write_memory(buf):
    length = len(buf)

    kernel32.VirtualAlloc.restype = ctypes.c_void_p
    ptr = kernel32.VirtualAlloc(None, length, 0x3000, 0x40)

    kernel32.RtlMoveMemory.argtypes = (
        ctypes.c_void_p,
        ctypes.c_void_p,
        ctypes.c_size_t)
    kernel32.RtlMoveMemory(ptr, buf, length)
    return ptr

Virtualallocは呼び出し側プロセスの仮想アドレス空間内のページ領域を、予約またはコミットする。

virtualalloc APIを介して直接作成される大きな動的メモリ割り当てに使用される。)

引数は「割り当てたい領域の開始アドレス(NULLなら勝手に決まる)」、「領域のサイズ(バイト)」、「allocation type」、「アクセス保護のタイプ」。

 

割り当てのタイプは、通常 MEM_COMMIT | MEM_RESERVE (0x3000) であり、予約と割り当てを同時に行う。

 

アクセス保護のタイプ0x40は、ページのコミット済み領域に対する実行、読み取り専用、または読み取り/書き込みアクセスを有効にする。

 

RtlMoveMemoryで、ソースメモリブロックの内容をコピー先のメモリブロックにコピーする。

今回のコードでは、コピー元がbuf、コピー先がptr
(この関数がすることはバッファの内容をメモリに書き込むことであった。)

 

俺の理解ではこのイラスト:

メモリ割り当てのイラスト

 

32ビットマシン、64ビットマシンの両方でメモリアドレスの解釈に関して問題なく動作するために、VirtualAllocrestypeでその返り値のデータ型を、RtlMoveMemoryargtypesで引数のデータ型をそれぞれ(Cと互換性のある型ですよ!と)明記する。

kernel32.VirtualAlloc.restype = ctypes.c_void_p

これでVirtualAllocの返り値の方はCのvoid*(ポインタ変数の一種(詳しくはここ)。

 

kernel32.RtlMoveMemory.argtypes = (
        ctypes.c_void_p,
        ctypes.c_void_p,
        ctypes.c_size_t
)

RtlMoveMemoryに与える引数は二つのポインタオブジェクトと一つのサイズオブジェクト。

 

まぁ最終的には、write_memory関数はシェルコードのあるメモリの部分、を指し示すポインタを返す。

シェルコードをバッファからメモリにコピーして、メモリのコピーされたその部分を指すポインタptr

run関数について

def run(shellcode):
    buf = ctypes.create_string_buffer(shellcode)
    ptr = write_memory(buf)
    shell_func = ctypes.cast(ptr, ctypes.CFUNCTYPE(None))
    shell_func()

create_string_bufferは前も出てきたけど、可変長の文字バッファを作成する。

 

ctypes.castはその通り、C言語におけるキャストの役割。

 

ポインタptrををCFUNCTYPEという関数型に変換する。

そのおかげで、pythonで普通に関数を呼ぶみたいにshell_func()で実行できる。

ctypes.windll.kernel32についてほんのちょっと

class ctypes.WinDLL(name, mode=DEFAULT_MODE, handle=None, use_errno=False, use_last_error=False, winmode=0)

このクラスのインスタンスはロードされた共有ライブラリをあらわす。

だからctypes.windllWinDLLインスタンスを作る。

参考リンク

 

(私情)

今日の夜Sandbox Detectionやろう思ったのですが、まぁ色々あって、Sandbox Detectionはコードが長そうだし、計画変更。

明日やる方がいいなって思って明日やります。

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