Binary Pulsar

技術寄りなセキュリティの話題について書きます

ReflectionによりPowerShellでWindows APIを呼び出す手法

概要

近年盛んであるPowerShellを悪用した攻撃の根幹を成す技術の一つが、PowerShellによるWindows APIプログラミングであることは間違いないでしょう。PowerShellではその豊富な機能により、Windows APIを複数の手法で呼び出すことが可能であるため、カーネルエクスプロイトやメモリ上の機密情報の取得などが可能です。本記事では、Windows APIの呼び出しを可能とする技法のひとつである、Reflectionという機能を用いたWindows APIの呼び出し手法を取り上げます。

PowerShellとReflection

Reflectionは情報工学における技術の一つであり、プログラミングにおいては「自分自身の構造を書き換える機能」を指します。PowerShellにもReflectionが機能として実装されており、Reflectionにより新たな型を定義したりプログラム内部の情報を参照することが可能です。

PSReflect

Reflectionを活用してWindows APIを呼び出すためのPowerShellスクリプトが、Matt Graeber氏によって公開されています。

github.com

スクリプトの詳細についてブログの一記事としてまとめるにはなかなか骨が折れるため、詳細は割愛します。

PSReflectの機能は以上に公開されているスクリプトのうち、PSReflect.psm1に実装されています。スクリプトには様々な関数が実装されていますが、明示的に使う関数はNew-InMemoryModule関数とAdd-Win32Type関数の2つのみです。New-InMemoryModule関数はメモリ空間にモジュールを新たに定義する関数です。Add-Win32Type関数は、New-InMemoryModule関数により定義されたモジュールを用いて、ReflectionによりWindows APIPowerShellに追加する機能を持つ関数です。

PSReflectを利用してWindows APIを呼び出す

それではPSReflectを用いてWindows APIを呼び出す手順を示します。本記事ではuser32.dllに実装されているMessageBox関数を取り上げて、Windows APIを呼び出す手順を示します。

実践に移る前に、MessageBox関数というWindows APIの実装を調べましょう。Windows APIの多くはMicrosoft公式Webサイトに、公式文書として実装を参照することが可能です。(公式文書として公開されていないUndocumented APIと呼ばれているWindows APIが存在しますが、本記事の内容を超えるため触れません。)Microsoftの公式文書は様々な国の言語で公開されています。本記事では英語に馴染みがない読者を想定して、日本語版がある公式文書に関しては日本語版の公式文書のリンクを掲載しますが、言語である英語が最も情報の更新が早く情報が充実しているため、可能であれば英語版を読むべきでしょう。MessageBox関数の公式文書は以下のWebサイトに公開されています。

MessageBox 関数

MessageBox関数の実装は以下の通りです。

int MessageBox(
  HWND    hWnd,
  LPCTSTR lpText,
  LPCTSTR lpCaption,
  UINT    uType
);

Windows APIの関数を利用するには、それぞれの引数の意味とその型をどの型として定義すればよいかを理解する必要があります。以上に記載のWindows API関数の公式文書の内容では、引数の型がC#PowerShellでのプログラミングとは異なるため、それぞれ対応する方に置き換える必要があります。Windows API関数の公式文書に記載の型と、C#PowerShellでのプログラミングにおける型との対応に関しては、以下のMicrosoft公式文書に記載されています。

プラットフォーム呼び出しによるデータのマーシャリング | Microsoft Docs

公式文書によると、HWND型はハンドルを指定する引数の型であるためSystem.IntPtr型に対応します。LPCTSTR型はSystem.String型またはSystem.Text.StringBuilder型であるため、本記事ではSystem.String型を採用します。UINT型はSystem.UInt32型に対応します。 それではMessageBox関数の引数について解説しましょう。まず、第一引数のhWndですが、メッセージボックスを立ち上げるプロセスのハンドルを整数の数値で指定する引数です。親プロセスとして立ち上げる場合は0を指定します。次に、第二引数のlpTextですが、この引数にはメッセージボックスに表示する文言を文字列型の引数として指定する引数です。第三引数のlpCaptionはメッセージボックスのキャプションの文言を指定する引数であり、第二引数と同じく文字列型の値をしてします。最後に、第四引数のuTypeですが、この引数には「メッセージボックスの書式を指定する整数」を指定します。メッセージボックスの書式については、先述のMessageBox関数の公式文書に記載されています。例えばuTypeを「2」と指定すると「YesとNoとIgnoreが選択肢として表示されるメッセージボックス」が、「4」と指定すると「YesとNoが選択肢として表示されるメッセージボックス」が表示されます。

まずはPSReflect.psm1から、スクリプトに定義されている関数をインポートします。

PS C:\> Import-Module .\PSReflect.psm1
PS C:\>

続けて、PSReflect.psm1からインポートしたNew-InMemoryModule関数により、PowerShellのメモリ空間にモジュールを定義します。ModuleNameオプションに任意の名前を値として与えてNew-InMemoryModule関数を実行することにより、任意の名前のモジュールを定義することが可能です。本記事ではモジュール名をWinApiとして、変数Moduleに定義します。

PS C:\> $Module = New-InMemoryModule -ModuleName WinApi
PS C:\>

次に型情報を配列型の変数として定義します。関数毎に配列が多変数の一要素として、以下の書式で定義します。

(func <DLL名> <Windows API関数名> (<返り値の型>) @(<第一引数の型>, <第二引数の型>, ...))

MessageBox関数の場合は、例として以下のように変数Funcdefに定義します。

PS C:\> $Fundef = @( (func user32 MessageBox ([int]) @([IntPtr], [String], [String], [int]) ) )
PS C:\>

関数の型情報を定義したら、Add-Win32Type関数によりWindows APIを読み込みます。Add-Win32Type関数のModuleオプションに先ほどNew-InMemoryModule関数で定義したモジュールを、Namespaceオプションには任意の名前で名前空間を設定します。本記事の例では、名前空間の名前をWinDLLとして、Windows APIの読み込みを変数Typesに定義します。

PS C:\> $Types = $Fundef | Add-Win32Type -Module $Module -Namespace 'WinDLL'
PS C:\>

続けて、変数Typesでの定義で読み込んだWindows APIから、利用したDLLの名前を指定します。今回の例として取り上げているMessageBox関数というWindows APIは、user32.dllに実装されているため、以下のようにして変数User32に定義します。

PS C:\> $User32 = $Types['user32']
PS C:\>

最後に、変数User32に定義したuser32.dllを読み込んだ空間からMessageBox関数を呼び出すことにより、メッセージボックスを起動することが可能です。

PS C:\> $User32::MessageBox(0, 'Hello, world!', 'PopUp', 2) | Out-Null
PS C:\>

以下の図に例を示します。

f:id:binary-pulsar:20181003120256p:plain

まとめ

本記事では、近年のPowerShellの攻撃で用いられている根幹をなす技術である、Windows APIの呼び出しの手法の一つを取り上げました。Windows APIの呼び出しについてはその他にも複数の手法が存在し、いずれの手法も攻撃スクリプトに活用されています。その他の手法によるWindows APIの呼び出しについては、またの機会に取り上げます。