株式会社Ninjastars 技術研究部

「もっと楽しく安全なゲームの世界を創る」

Frida入門 for Windows

株式会社Ninjastarsセキュリティエンジニアの一瀬です。

今日は前回の記事でも取り上げたFridaについてWindowsでの利用方法を解説させていただきたいと思います。

Fridaとは何か
FridaはJavaScriptを用いて各種アプリケーションを調査・解析できる動的解析ツールです。

対象OSとしてWindows,MacOS,GNU/Linux,iOS,Android等といった幅広いプラットフォームで使用することができます。

AndroidiOS端末内にサーバーを立ててホストOSからアタッチして解析するなども可能です。

Fridaのセットアップ
Fridaのインストールはpip(Pythonのパッケージインストール用コマンド)を用いて以下のように簡単にできます。
「pip install frida」

今回やること

www.frida.re


この記事ではFridaのウェブサイトにある上記サンプルで関数フックを用いて処理を書き換えてみたいと思います。

github.com


ビルド済みのファイルが上記GitHubにあるため、「hello.exe」,「hi.exe」をFridaを用いて解析してみます。

①hello.exeの解析
hello.cpp

#include<stdio.h>
#include<windows.h>

 __declspec(noinline) void f(int n)
{
 printf("Number: %d\n",n);
}


 int main(int argc,char *argv)
{
 int i = 0;
 printf("f() is at %p\n", f);

    while(1)
   {
    f(i++);
    Sleep(1);
   }

 return 0;
}

ソースコードからも分かるように実行すると関数fのアドレスが出力され、以降1ミリ秒毎に変数iの値が出力されていきます。

これをFridaを用いてhello.exeにアタッチし、関数fをフックして表示される値を変更してみようと思います。
hello.exeを起動した状態で、以下のPythonスクリプトを実行する(python hook1.py)と表示される値が常に1000で固定されます。

hook1.py

# -*- coding: utf-8 -*-

import frida,sys

session=frida.attach("hello.exe")
jscode="""
Interceptor.attach(Module.findBaseAddress('hello.exe').add(0x1070), {
 onEnter:function(args) {
 args[0] = ptr("1000");
 },
 onLeave:function(retval){
 }
});
"""
script=session.create_script(jscode)

def on_message(message ,data):
    print(message)

script.on("message" , on_message)
script.load()

sys.stdin.read()

hook1.pyの説明

frida.attach("hello.exe")


実行プロセス名"hello.exe"にアタッチします。

プロセス名の代わりにプロセスIDでも可能です。

Interceptor.attach(Module.findBaseAddress('hello.exe').add(0x1070)

「hello.exeのモジュールの開始アドレス+0x1070」をフックすることを意味します。

onEnterは関数開始時

onLeaveは関数終了直後

をフックして任意の処理を挿入可能です。

args[0] = ptr("1000");

引数の1番目のアドレスに1000を代入します。

ここで引数の型などによっては正常に機能しない場合があるので

x86の場合

this.context.eax

......

x64の場合

this.context.rax

....

でそれぞれレジスタの値に直接アクセスできるため、レジスタの値から引数を取得すれば正確な代入や参照が可能です。

②hi.exeの解析
hi.cpp

#include<stdio.h>
#include<windows.h>

 __declspec (noinline) int f(const char * s)
{
 printf("String: %s\n",s);
 return 0;
}

 int main(int argc, char *argv)
{
 const char *s="Testing!";

 printf("f() is at %p\n",f);
 printf("s is at %p\n",s);

    while(1)
   {
    f(s);
    Sleep(1);
   }

}

実行すると関数fと文字列sのアドレスが出力され、以降1ミリ秒毎に"Testing!"が出力されていきます。

Fridaを用いて以下のPythonスクリプトを実行すると"TESTING!"という文字列が"TESTMEPLZ!"に置き換えられ出力されます。

hook2.py

# -*- coding: utf-8 -*-

import frida,sys

session=frida.attach("hi.exe")

jscode="""
var st=Memory.allocUtf8String("TESTMEPLZ!");
Interceptor.attach(Module.findBaseAddress('hi.exe').add(0x1070), {
 onEnter:function(args) {
 args[0]=st;
 },
 onLeave:function(retval){
 }
});
"""

script=session.create_script(jscode)

def on_message(message,data):
    print(message)

script.on("message",on_message)
script.load()
sys.stdin.read()

hook2.pyの説明

var st = Memory.allocUtf8String("TESTMEPLZ!")

メモリを確保してそこに文字列を書き込み、stにはアドレスを返します。

args[0]=st;

引数の1番目のアドレスに先ほど確保したアドレスを代入します。

this.context.rcx=st;

と上述のように直接レジスタの値にアドレスを書き込んでも同じ挙動となります。

次に本来の処理を完全に無視して、WindowsAPIであるMessageBoxを読び出すようにfを関数フックしてみます。

hook3.py

# -*- coding: utf-8 -*-

import frida,sys

session=frida.attach("hi.exe")

jscode="""
function getApiPointer(moduleName,apiName,ret,args) {
 var lib=Module.findExportByName(moduleName,apiName);
 var address=parseInt(lib);
 var functionPointer=new NativePointer("0x"+address.toString(16));
 var func=new NativeFunction(functionPointer,ret,args);
 return func;
}

var message=Memory.allocUtf8String("Hello,World!");
var title=Memory.allocUtf8String("Frida");

Interceptor.attach(Module.findBaseAddress('hi.exe').add(0x1070), {
 onEnter:function(args) {
 var func=getApiPointer('user32','MessageBoxA','int',['int','pointer','pointer','int']);
 func(0,message,title,0);
 return 0;
 },

 onLeave:function(retval){
 }
});
"""

script=session.create_script(jscode)

def on_message(message ,data):
    print(message)

script.on("message",on_message)
script.load()
sys.stdin.read()

実行結果:

fが呼び出されるたびに、標準出力には何も表示されず代わりに以下のメッセージボックスが表示されます。
f:id:Ninjastars:20181224173804p:plain


Fridaではこのように本来の処理とは全く違う処理を注入することもできます。

勿論関数やAPI等をフックすることはFridaでなくても可能ですが、記述の容易さ、機能の豊富さや対応OSの広さなど利点が多数あります。控えめに言っても覚えておいて損はないツールであると思います。

③応用例
最後に、ネイティブAPIをフックしてタスクマネージャーからOllydbgを隠蔽してみました。
(OllydbgとはWindows用のデバッガです。)

    • 動画--


Frida for Windows 応用編


補足:Fridaの動作原理
Windowsの場合

Fridaでアタッチして関数フックする前

f:id:Ninjastars:20181224173834p:plain
関数フック前


Fridaでアタッチして関数フックした後

f:id:Ninjastars:20181224173901p:plain
関数フック後

インジェクションされたDLL

f:id:Ninjastars:20181224173912p:plain
DLL

上記のように指定したアドレスの先頭処理をジャンプ命令に置き換え、ジャンプさせた先でDLLインジェクションした処理を噛ませて各種機能を実現していると思われます。

注意事項
本レポートに記載されている内容を許可されていないソフトウェアで行うと、場合によっては犯罪行為となる可能性があります。そのため、記事の内容を試す際には許可されたソフトウェアに対してのみ実施するようにしてください。

本レポートについて
お問い合せ
E-mail:ichise@ninjastars-net.com

株式会社Ninjastarsエンジニア
一瀬健二郎