2021年7月13日火曜日

FRIDAでKERNEL32.DLLのSearchPathWフック

(注)これは、FRIDAのIRC/Telegramチャンネルに寄せられたユーザーの課題に答えるための、簡単なブログ記事です。 

今回は、SearchPathWのWINAPIにフックすることにします。
 
1
2
3
4
5
6
7
8
DWORD SearchPathW(
  LPCWSTR lpPath,
  LPCWSTR lpFileName,
  LPCWSTR lpExtension,
  DWORD   nBufferLength,
  LPWSTR  lpBuffer,
  LPWSTR  *lpFilePart
);

この場合、最も意味のある引数を取得したいと考えており、この場合は2番目の引数であるlpFileNameを取得します。必要であれば、残りのフィールドから情報を抽出することも可能です。 この例では、次のようなプログラムを使用しています。

 
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// searchPathCpp.cpp : This file contains the 'main' function. Program execution begins and ends there.
//

#include <iostream>
#include <Windows.h>

int main()
{
    TCHAR lpBuffer[MAX_PATH];
    LPWSTR *lpFilePart{};
    DWORD result;

    result = SearchPath(NULL, L"c:\\windows\\", NULL, MAX_PATH, lpBuffer, lpFilePart);
    std::cout << "SearchPath retval: " << result;
}

c:/windowsのパスが存在するかどうかを確認するだけです。コンパイルして、アタッチしてみましょう。 

 frida -f searchPathCpp.exe
[Local::searchPathCpp.exe]-> searchPathPtr = Module.getExportByName("KERNELBASE.DLL", "SearchPathW") 
"0x76fc02f0" 
[Local::searchPathCpp.exe]-> Interceptor.attach(searchPathPtr, { onEnter: function (args) { console.log(args[1].readUtf 16String()); } }) {} 
[Local::searchPathCpp.exe]-> %resume 

 順を追って説明します。 KERNELBASE.DLLのSearchPathWのポインタを取り出す

[Local::searchPathCpp.exe]-> searchPathPtr = Module.getExportByName("KERNELBASE.DLL", "SearchPathW") 
"0x76fc02f0" 

InterceptorのonEnterフックを入力する
1
2
3
4
5
Interceptor.attach(searchPathPtr, { 
        onEnter: function (args) { 
                console.log(args[1].readUtf16String()); 
        } 
});

アプリを再開するために「%resume」と入力すると、次のような出力が得られます

[Local::searchPathCpp.exe]-> %resume Search
Path retval: 11c:\windows\ 

 他に質問があれば、Twitterで @entdark_ に連絡してください。

2021年7月12日月曜日

FRIDAでC++のstd::string

私たちにとって非常に興味深いのは、文字列を読み取る機能です。しかし、文字列の表現方法が異なるため、FRIDAのreadUtf8String/readCString組み込み関数を使用するだけでは、文字列を読み取ることができない場合があります。例えば、WindowsのUNICODE_STRINGは構造体で次のように定義されています。
 typedef struct _UNICODE_STRING {  
  USHORT Length;  
  USHORT MaximumLength;  
  PWSTR Buffer;  
 } UNICODE_STRING, *PUNICODE_STRING;  
パースする一般的な文字列型は、C++のstd::stringです。同様の概念は、後にSwift.Stringのデータ型でも見られます。std::stringのLSB(Least Significant Bit)には、短い文字列(22バイト以下)の場合は0が、長い文字列の場合は1が格納されます。長い文字列の場合、取得したい文字列へのポインタは、接続されているプロセスのProcess.pointerSizeの2倍の値が格納されます。この知識を試して、文字列を取得する方法を確認するために、この簡単なプログラムを見てみましょう。
 #include <iostream>  
 void print_std_string(std::string arg_1)  
 {  
      std::cout << arg_1 << std::endl;  
 }  
 int  
 main(void)  
 {  
      std::string my_string = "FRIDA is great, you should check it out at frida.re";  
      print_std_string(my_string);  
      return 0;  
 }  
このプログラムは、単にprint_std_string(std::string arg_1)関数を呼び出して、画面に表示しているだけです。このようにして、std::stringのパラメータを取得し、検査することが簡単にできます。FRIDAのREPLでこのプログラムを起動し、バイナリ上でModule.enumerateExportsSync()を実行すると、名前がつぶれていることに気づきます。しかし、テスト関数に選んだ名前のおかげで、_Z16print_std_stringNSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEEという名前のつぶれた関数を見つけることができます。これが、Interceptor.attachを使用したい関数です。
 Interceptor.attach(Module.getExportByName(null, '_Z16print_std_stringNSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEE'), {  
      onEnter (args) {  
           const LSB = args[0].readU8() & 1;  
           console.log('LSB: ' + LSB);  
           const stdString = args[0].add(Process.pointerSize*2).readPointer().readUtf8String();  
           console.log("std::string: " + stdString);  
      }  
 });  
そして、この小さなスクリプトを実行すると、次のような出力が得られます。 

 LSB: 1 std::string: FRIDA is great, you should check it out at frida.re 
[Local::a.out]-> Process terminated 

このテストはclang++ 12.0.0を使用して行われており、小さな文字列を格納するためにユニオンを実装しているGCCなどのコンパイラでは、メモリのレイアウトが異なる可能性があることに留意する必要があります。

FRIDAでHTTPSENDREQUESTEXのLPINTERNET_BUFFERSA構造体の解析

ソースはこちら https://azurda.github.io/posts/lpinternet_buffersa_struct_parse/