Powershell

【Powershell】WindowsAPIの呼び出し

今日はPowershellでWindowsAPIを呼び出す方法を解説していきます。特にサイバーセキュリティ関連の業務に従事している人やこれから従事したいと考える人にとっては、WindowsAPIの使い方を学習することでトロイの木馬の一種であるキーロガーやマルウェアに感染した際の画面制御の動きの理解に繋がってきます。これを機に基礎的な構文を学習しておきましょう。

WindowsAPIを呼び出すサンプルコード

今回解説していくサンプルコードは以下の通りです。今回はWindowsAPIの基本の理解に重点を置いているので内容は極めて簡単でわかりやすいものにしてあります。現時点ではわからないことがあってもこの後丁寧に解説していくので安心してくださいね。

サンプルコード

$Signature = @'
[DllImport("user32.dll", CharSet=CharSet.Auto, ExactSpelling=true)] 
public static extern short GetAsyncKeyState(int virtualKeyCode); 
'@

$WinAPI = Add-Type -MemberDefinition $signature -Name 'Win32' -Namespace WinFunctions -PassThru
 
while($true) {
    Start-Sleep -Milliseconds 100

    for($ascii = 9; $ascii -le 254; $ascii++) {
        $state = $API::GetAsyncKeyState($ascii)
        if ($state -eq -32767) {
            Write-Host $state
        }
    }
}

Add-Typeコマンドレット

PowershellからWindowsAPIを利用するにはAdd-Typeコマンドレットを用いて利用するWindowsAPIが含まれているdllファイルをインポートする必要があります。今回はGetAsyncKeyStateという関数を使いますが、この関数はuser32.dllに含まれておりサンプルコードの以下の部分でインポートを行っています。

$Signature = @'
[DllImport("user32.dll", CharSet=CharSet.Auto, ExactSpelling=true)] 
public static extern short GetAsyncKeyState(int virtualKeyCode); 
'@

$WinAPI = Add-Type -MemberDefinition $signature -Name 'Win32' -Namespace API -PassThru

先頭の「$Signature = @'~'@」の部分はヒアドキュメントと言って、1行だけでなく@(アットマーク)に囲まれている複数行の文字列を変数に代入することができます。

Powershellのヒアドキュメント

PS C:\Users\owner\Desktop> $a = @'
Line1
This is a test message.
Line3
'@

PS C:\Users\owner\Desktop> Write-Host $a
Line1
This is a test message.
Line3

Add-TypeコマンドレットのMemberDefinitionオプションにWindowsAPIを利用するためのC#のコードを指定する必要があるのですが、このようにヒアドキュメントを利用することでオプションの部分にヒアドキュメントの結果を代入した変数を指定するだけで良くなりスッキリします。

Dllmport属性

[DllImport("user32.dll", CharSet=CharSet.Auto, ExactSpelling=true)] 

続いてヒアドキュメントの中身の記述についての解説です。WindowsAPIを呼び出す時に多用される書き方なのでここで形をある程度覚えてしまいましょう。DllImportの引数については以下の表の通り指定しています。

引数
第一引数user32.dll
CharsetCharSet.Auto
ExactSpellingtrue

第一引数にはインポートするDllのファイル名を指定しますが、既に述べた通りサンプルで利用するGetAsyncKeyState関数はuser32.dllに含まれているのでここではuser32.dllを指定します。以下Microsoftの公式サイトの要件の表から確認できます。

https://learn.microsoft.com/ja-jp/windows/win32/api/winuser/nf-winuser-getasynckeystate

第二引数ではGetAsyncKeyState関数をuser32.dllから検索する際の文字コードの指定を行います。WindowsAPIには文字コードがANSIとUnicode版の2種類(MessageBoxA,MessageBoxWなど)あったりするので利用する関数に応じて明示的に文字コードを指定する方がより安全です。ただ正直この部分については筆者があまり詳しくないこともあり今回はCharset.Autoを指定しています。

第三引数ではGetAsyncKeyState関数をuser32.dllから検索する際、関数名が名前の文字列と完全一致したときのみ使用するかどうかを指定します。例えばWinAPIのMessageBox関数にはMessageBox、MessageBoxA、MessageBoxWなどがありますが、第二引数で指定された文字コードと第三引数で指定された完全一致を許容するかどうかの設定に応じて、関数をPowershellセッションに追加します。

public static extern short GetAsyncKeyState(int virtualKeyCode); 

続いてuser32.dll中のどのWindowsAPIを利用するのかを指定します。publicやstatic、externなどの修飾子やshortなどの戻り値については他サイトで解説が充実していますのでそちらを参考にしてください。

$WinAPI = Add-Type -MemberDefinition $signature -Name 'Win32' -Namespace WinFunctions -PassThru

では今回のメインテーマであるWindowsAPIをPowershellセッションに追加する部分を見ていきましょう。Add-Typeコマンドのオプションの意味は以下の通りです。

オプション意味
-MemberDefinitionクラスの新しいプロパティまたはメソッドを指定する
-Name作成するクラス名を指定します
-Namespace名前空間を指定します
-PassThru追加されたクラスやメソッドを持つオブジェクトを返します

-MemberDefinitionには追加するクラスやメソッドを記述したコードを指定します。既に$signature変数に今回利用するメソッドを記述してあるので$signatureを指定します。

今回は-PathThruオプションを指定しているのでPowershellセッションにWinAPIを追加するのと同時にオブジェクトも生成されますが、もし-PathThruオプションを指定しない場合は-Nameと-Namespaceオプションで指定した値に応じて以下のようにメソッドを呼び出します。

PS C:\Users\owner\Desktop> $Source = @"
public static int Add(int a, int b){ return (a + b); }
"@

PS C:\Users\owner\Desktop> Add-Type -MemberDefinition $Source -Name 'Test' -Namespace TestClass

PS C:\Users\owner\Desktop> [TestClass.Test]::Add(3, 5)
8

GetAsyncKeyState関数を呼び出す

では実際に呼び出す部分のサンプルを見ていきましょう。whileやfor文など基礎的な内容はここでは解説を省略しますので必要に応じて他サイトを調べてみてください。

GetAsyncKeyState関数のサンプルコード

 while($true) {
    Start-Sleep -Milliseconds 100
    $state = $WinAPI::GetAsyncKeyState(0x41)
    if ($state -eq -32767) {
        Write-Host "Button A is pressed."
    }
}

GetAsyncKeyState関数で押されたキーの情報を取得しますが、以下のMicrosoft公式サイトのパラメーターの部分にある通り仮想キーコードを使って値を指定する必要があります。サンプルではAが押下されたかどうかを判定するため、仮想キーコード表の0x41(これは16進数表記なので10進数で表記する場合は65になります)を引数にしています。

GetAsyncKeyStateのパラメータ
https://learn.microsoft.com/ja-jp/windows/win32/api/winuser/nf-winuser-getasynckeystate

仮想キーコード
https://learn.microsoft.com/ja-jp/windows/win32/inputdev/virtual-key-codes

これをwhile文のループで回しキーボードのAボタンをの入力受付を行います。試しにこのプログラムを実行してからAを4回押下すると、以下の通りAボタンが押下されましたと表示されます。これで正しく呼び出せることが確認できました。

PS C:\Users\owner\Desktop> C:\Users\owner\Desktop\無題1.ps1
Button A is pressed.
Button A is pressed.
Button A is pressed.
Button A is pressed.

-Powershell