ここにはシステムに関するプログラムのコードを載せています。
プログラムがVMware上で実行されているかを検出します。exist命令でシステムファイルを調査すれば簡単じゃん、と思って作ってみましたが上手く動作しませんでした。 メッセージが何かブロックされている気がしたので調べてみたところWOW64のリダイレクト機能とやらが原因だそうです。 それを一時的に無効にしてからexistを使用することでドライバのシステムファイルを調査することができました。 レジストリを調査したり、実機でしか検知できない例外を発生させたり、検出方法はさまざまですが、ここではvmmouse.sysドライバの検出をしました。
2. Wow64DisableWow64FsRedirection で WOW64 のリダイレクト機能を無効化する。
3. exist でポインティングデバイスのシステムファイルを確認する。
4. Wow64DisableWow64FsRedirection で WOW64 のリダイレクト機能を元に戻す。
このソースコードでは一連の動作をモジュール化しました。このプログラムは VMware Player しか検出できません。 他のサンドボックスを検出することも可能です。気になる方は参考文献の"VRT"やその他検出方法を書いたサイトをご覧ください。
/*-------------------- ここからモジュール --------------------*/ #module #uselib "kernel32.dll" #func GetEnvironmentVariable "GetEnvironmentVariableA" str, var, int #func Wow64DisableWow64FsRedirection "Wow64DisableWow64FsRedirection" sptr // DetectVMware ... VMware Player の検出を試みる // [返り値] -1=失敗 / 0=VMware以外で実行 / 1=Vmwareで実行 #defcfunc DetectVMware obj = dirinfo(3) + "\\drivers\\vmmouse.sys" // まずは検出を試みる exist obj if strsize != -1 : return 1 // 環境変数取得 sdim var, 256 GetEnvironmentVariable "PROCESSOR_ARCHITEW6432", var, 256 if var == "" : return -1 // WOW64のリダイレクトを一時的に無効化 OldValue = 0 Wow64DisableWow64FsRedirection OldValue // 検出を試みる exist obj if strsize != -1 : ret = 1 : else : ret = 0 // 元に戻す Wow64DisableWow64FsRedirection OldValue return ret #global /*-------------------- ここまでモジュール --------------------*/ screen 0, 640, 480, 4 : title "Sample" // VMware Player を検出 switch DetectVMware() case -1 : mes "検出に失敗しました。" : swbreak case 0 : mes "VMware上で実行されていません。" : swbreak case 1 : mes "VMware上で実行されています。" : swbreak swend stop
Wow64DisableWow64FsRedirection で WOW64 のリダイレクトを無効化したあと、処理が終わったらすぐに元に戻しましょう。
※不正な操作を行うプログラムの作成は刑法第168条の2第1項により禁止されています。
VRT: How does malware know the difference between the virtual world and the real world?
.NET File.Exists doesn't work in Windows\System32\Drivers folder? - Stack Overflow
Wow64DisableWow64FsRedirection function (Windows)
【HSP3】WOW64のファイル・レジストリのリダイレクトを一時無効にするサンプル | Codetter(こーどったー)β
[HSPBC] Tips
DLL Magic を呼び出してプログラムが使用しているDLLを隠蔽します。DLL Magic はコマンドラインから簡単にDLLを隠蔽することができるツールです。 ほとんど使い道の無いコードになってしまいました。DLLをマッピングさせて一から書く予定でしたが何しろ危険なコードになりそうだったので主要な操作はツールに任せました。 本当にHSPからは何もしていませんが、一応流れを書いておきます。
2. DLLMagic にDLL名とPIDを渡して起動する。
DLLMagic はこちらからダウンロードできます。 同じディレクトリに入れて実行するように注意してください。DLLMagic の内部の仕組みはここでは説明しません。
#uselib "user32.dll" #func GetWindowThreadProcessId "GetWindowThreadProcessId" sptr, sptr #define HideDLL "hspinet.dll" ; 隠蔽するDLL名 // hspinet の隠蔽を確かめるため読み込んでおく #uselib HideDLL #func b64encode b64encode $202 // hspinet を確認 sdim out, 64 : in = "HideProc" b64encode out, in, -1 mes "----- hspinet 動作確認 -----" mes in + " -> " + out mes "----------------------------" mes "" + HideDLL + " を隠蔽します。" mes "予めダンプファイル等から " + HideDLL + " が読み込まれていることを確認してください。" button "隠蔽", *Hide GetWindowThreadProcessId hwnd, varptr(pid) stop *Hide exec "DLLMagic.exe " + HideDLL + " " + str(pid) stop
実際に隠蔽できているかを確認してみました。動作させた環境は Windows 7 でしたので、タスクマネージャからプロセスをダンプして、結果をツールで見てみました。


隠蔽前には hspinet.dll の存在が確認できますが、隠蔽後には確認できません。DLLの隠蔽に成功したようです。
不正な操作を行うプログラムの作成は刑法第168条の2第1項により禁止されています。
DLL Magic : Command-line Tool to Hide DLL in Windows Process
プログラムのデバッグの防止は、ゲームのチートを防止したり、プログラム内部の知られてはいけない情報を保護したりするのに必要な技術です。 HSPからデバッグを防ぐ最も簡単なコードを書きました。(いくらかのゲーム製作者は既に知っているでしょう。) 特に、デバッグはプログラムの途中からでも開始できるため、常にデバッグを監視しておくことをおすすめします。
#uselib "kernel32.dll" #cfunc IsDebuggerPresent "IsDebuggerPresent" if ( IsDebuggerPresent() ) { mes "On Debugger." } else { mes "Not On Debugger." } stop
IsDebuggerPresentで検知するだけでは、静的解析やレジスタの書き換えにより突破されてしまいます。静的解析への対策として、コード自体の難読化を行うことを強く推奨します。
レジスタの書き換えについては、更に高度なデバッガ検知もしようと思ったのですが、HSPからのVirtualAllocが上手くいかない(機械語の実行)ので今後成功したら載せます。
[追記]HSPからの機械語呼び出しに成功したのでコードを書きます。
方法としては、PEB構造体のNtGlobalFlagsを取得するのですが、インタプリタのHSPは機械語を呼び出す命令が無いのでVirtualProtectとcallfuncを使います。
#uselib "kernel32.dll" #func VirtualProtect "VirtualProtect" var, int, int, var #define xdim(%1, %2) dim %1, %2 : VirtualProtect %1, %2*4, 0x40, dummy dummy = 0 xdim code, 4 code(0) = 0x0018a164, 0x408b0000, 0x68408b30, 0x000000c3 mes callfunc(dummy, varptr(code), 0) stop
もし112(0x70)が表示されたらデバッグされています。デバッグされていない場合は0が表示されます。
この機械語をアセンブリ言語で表すと
MOV EAX, DWORD PTR fs:24
MOV EAX, DWORD PTR [EAX+48]
MOV EAX, DWORD PTR [EAX+104]
RET
です。機械語への翻訳はVisual Studio 2013のC++を使用しました。
たのしいバイナリの歩き方
アナライジング・マルウェア - フリーツールを使った感染事案対処
他のプロセスを介して自由な処理を行わせる手段に、DLLインジェクションという方法があります。 これはDLLを他プロセスにロードさせる(実際には LoadLibrary を他プロセスから使用する)手法です。 今回ここに書く方法は、CreateRemoteThread 関数を使う方法で、これはWin Vist等の中でも特に64bitの環境では使用できません。(今後機会があればそちらについても書くつもりです。) このコードは Windows XP より前のHSP対応OSで使用できます。(テスト環境は WinXPSP3x86 です。)
2. VirtualAllocEx でDLLのパスを書き込む準備をする。
3. WriteProcessMemory でDLLのパスを書き込む。
4. LoadLibrary と GetProcAddredd で LoadLibrary 関数のアドレスを得る。(そのまま取得できないので)
5. 4のアドレスの関数を CreateRemoteThread で1のプロセスに対して3のパスを引数として呼び出す。
まず、DLLのコードをC言語で書きます。今回は「危ない動作の実験」ということで「evil.dll」にします。DLLが読み込まれるとCUI入出力画面に「I am here.」と表示します。
#include <windows.h> #include <stdio.h> BOOL WINAPI DllMain( HINSTANCE inst, DWORD reason, LPVOID reserved ) { if ( reason == DLL_PROCESS_ATTACH ) { printf("I am here.\n"); } return TRUE; }
これをgccでdllとしてコンパイルし、evil.dllにします。(ここからダウンロードできます。) このdllが読み込まれたかを確認するには、CUIベースのソフトウェアを適当に対象にします。 今回はHSPで#runtime "hsp3cl"を使用した、ただのコンソール画面をインジェクションの対象とします。 次のコードは対象にevil.dllを読み込ませるものです。_PID_と_libPath_はそれぞれ対象のプロセスID、読み込ませるDLLの絶対パスにしてください。 なぜかというと、実際にDLLを呼び出すのは対象プロセスであり、DLLのパスは対象プロセスのカレントディレクトリに依存するからです。 (逆に言えば、デスクトップにあるevil.dllはカレントディレクトリがデスクトップのコマンドプロンプトに読み込ませられる。) このソースコードでは一連の動作をモジュール化しました。
[追記]定数の定義の大きな誤りを修正しました。(XPマシンからファイル移動時に消してしまったと思われます。)
#define _PID_ 0 #define _libPath_ dirinfo($10000) + "\\evil.dll" /*-------------------- ここからモジュール --------------------*/ #module #const PROCESS_CREATE_THREAD 2 #const PROCESS_VM_OPERATION 0x08 #const PROCESS_VM_WRITE 0x0020 #const MEM_COMMIT 0x1000 #const PAGE_READWRITE 4 #uselib "kernel32.dll" #cfunc OpenProcess "OpenProcess" int, int, int #cfunc VirtualAllocEx "VirtualAllocEx" int, int, int, int, int #func WriteProcessMemory "WriteProcessMemory" int, int, str, int, int #func CreateRemoteThread "CreateRemoteThread" int, int, int, int, int, int, int #cfunc LoadLibrary "LoadLibraryA" str #cfunc GetProcAddress "GetProcAddress" int, str // Inject ... 他プロセスにDLLインジェクションを行う // p1 : 対象のプロセスID // p2 : 読み込ませるDLLの絶対パス #deffunc Inject int PID, str libPath_ libPath = libPath_ pathSize = strlen(libPath) + 1 // プロセスをオープン proc = OpenProcess( PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | PROCESS_VM_WRITE, 0, PID ) // 対象プロセスに、DLLのパスを書き込む分のメモリを確保する remoteLibPath = VirtualAllocEx( proc, 0, pathSize, MEM_COMMIT, PAGE_READWRITE ) // 対象プロセスのメモリにDLLのパスを書き込む WriteProcessMemory proc, remoteLibPath, libPath, pathSize, 0 // LoadLibraryA 関数のアドレスを取得する funcPtr = GetProcAddress( LoadLibrary("kernel32.dll"), "LoadLibraryA" ) // LoadLibraryA を呼び出させる CreateRemoteThread proc, 0, 0, funcPtr, remoteLibPath, 0, 0 return #global /*-------------------- ここまでモジュール --------------------*/ Inject _PID_, _libPath_ stop
これをエディタから実行すると、セキュリティソフトによりHSP本体が消される可能性があります。 実行するときは実行ファイルにしてからか、セキュリティソフトを一時的に無効にしてから実行することを推奨します。以下が実行結果です。


左側(インジェクション前)はプログラムが停止している状態ですが、このプロセスに対してevil.dllをDLLインジェクションすると、右側(インジェクション後)のようになりました。
読み込まれたevil.dllのDllMainが実行され、prinf("I am here.\n");の部分が実行されたのです。
今回はソフトウェア開発などにはほとんど関係無いコードでしたが、興味深い内容だったので書きました。セキュリティ系のソフトウェアを開発される方は参考までに。
不正な操作を行うプログラムの作成は刑法第168条の2第1項により禁止されています。
アナライジング・マルウェア - フリーツールを使った感染事案対処
プロセスのAPI関数の挙動を変更する方法(インジェクション)として、Inline Function Hooking(detours)というものがあります。 C言語などではMicrosoftがライブラリ化しており、このインジェクションが非常に簡単に使えるようになっています。 HSPでできるかなーと思って調べてみたのがきっかけです。 ここで重要な話なのですが、このInline Function Hookingについて、ライブラリなしで実際に動作するコードを探しても見つかりませんでした。 以下で説明するコードは理論から勝手に組み立てたものなので、実際のdetoursとは異なることをしているかもしれません。 詳しい方で、間違いを発見された場合は教えてください。 また、このプログラムはMessageBoxと同じ構造を持つ呼び出し部しか検出できません。 したがって、これが使用できる関数は非常に限られています。 このコードで理論を見てもらい、後は自分の使いたい形に改造してもらったら、と思っています。 とはいえ、実際にAPI呼び出しに迂回路を作ることには成功していますので、その点は安心してください。 また、HSPは32bitなので、32bitの場合を考慮しています。64bitでは関数の呼び出し構造が全く違うのでそもそも動きません。 さて、この方法では、APIを呼び出したときの機械語を書き換え、自分のコードに迂回路(detours)を作ります。 detoursにEIPが移り、コードを終了すると、元のAPIを呼び出した場所へ戻ります。 下の図は変更前と変更後のMessageBoxA関数呼び出しコードです。

変更後のルーチンは一度Evilを呼び出す必要があるので、サイズが大きくなります。 そのため、この後ろにあるコードを上書きしないように、先に確保しておいた領域Detoursに元の呼び出しコードを退避しておきます。 今回のコードの流れを以下に書きました。
2. 引数のpushからretまでの機械語を対象プロセスに確保した領域に退避。(ReadProcessMemory + VirtualAllocEx + WriteProcessMemory)
3. 2でcallのアドレスは相対アドレスなので、移動先から元の値までの相対アドレスに変更する。
4. 迂回用ルーチンを元の関数に上書きする。
5. 対象プロセスがその関数を呼び出そうとしたら、用意したコードが実行される。
今回は、MessageBoxAを呼び出した際に、Sleep(1000);を実行させます。 Sleep(1000);を実行させるコードは以下のアセンブリです。
mov ebp, esp
push 0x3e8
mov edx, [Sleep]
call edx
mov esp, ebp
pop ebp
ret
また、機械語組み立て時は関数のアドレスがわからないので、movで一度移動して呼び出すようにしています。 これにより、後からmovのオペランドに関数アドレスを上書きするだけで変更できます。 このソースコードでは一部の動作をモジュール化しました。
/*-------------------- ここからモジュール --------------------*/ #module #uselib "kernel32.dll" #cfunc VirtualAllocEx "VirtualAllocEx" int, int, int, int, int #func WriteProcessMemory "WriteProcessMemory" int, int, int, int, int #func ReadProcessMemory "ReadProcessMemory" int, int, int, int, int #define MEM_COMMIT 0x1000 #define PAGE_EXECUTE_READWRITE 0x40 #const SIZEOF_PREPAREMENT 5 #const SIZEOF_CALL_NEWFUNCTION 16 /* 機械語を割り当てる */ #defcfunc AllocMachineCode int hProcess, var code, int size,\ local p_code, local bytes_set // 指定プロセスに領域を確保 p_code = VirtualAllocEx(hProcess, 0, size, MEM_COMMIT, PAGE_EXECUTE_READWRITE) if p_code == 0 : return 0 // コードを書き込み WriteProcessMemory hProcess, p_code, varptr(code), size, varptr(bytes_set) if bytes_set == 0 : return 0 return p_code /* APIをInline Function Hookingでフックする */ #deffunc HookAPI int hProcess, int OldFunction, int NewFunction,\ local bytes_set, local bytes,\ local p_call_pointer, local call_pointer,\ local call_OldFunction, local sizeof_call_OldFunction,\ local p_call_OldFunction, local call_NewFunction p_OldFunction = OldFunction p_NewFunction = NewFunction // フック先関数が5バイトの準備領域を持っているか確認 sdim bytes, 5 ReadProcessMemory hProcess, p_OldFunction, varptr(bytes), 5, varptr(bytes_set) if lpeek(bytes, 0) != 0x8b55ff8b || peek(bytes, 4) != 0xec : return 0 // 元の機械語をpushから全て取得 sdim bytes, 4 sdim call_OldFunction, 128 sizeof_call_OldFunction = 0 repeat 32 ReadProcessMemory hProcess, p_OldFunction+5+4*cnt, varptr(bytes), 4, varptr(bytes_set) if bytes_set == 0 : continue if lpeek(bytes, 0) == 0xCCccCCcc : break lpoke call_OldFunction, 4*cnt, lpeek(bytes, 0) sizeof_call_OldFunction += 4 loop // 元の機械語を退避する領域を対象プロセスに確保 p_call_OldFunction = VirtualAllocEx(hProcess, 0, 8, MEM_COMMIT, PAGE_EXECUTE_READWRITE) if p_call_OldFunction == 0 : return 0 // callのアドレスを検出 repeat sizeof_call_OldFunction, 1 if peek(call_OldFunction, sizeof_call_OldFunction-cnt) == 0xe8 { ; found call if peek(call_OldFunction, sizeof_call_OldFunction-cnt-1) != 0xe8 { ; not in address p_call_pointer = sizeof_call_OldFunction-cnt+1 call_pointer = lpeek(call_OldFunction, p_call_pointer) break } } loop if call_pointer == 0 : return 0 // callのアドレスを正しい相対アドレスに修正 call_pointer += p_OldFunction - p_call_OldFunction + SIZEOF_PREPAREMENT lpoke call_OldFunction, p_call_pointer, call_pointer // 元の機械語(上書き領域)を確保した領域に退避 WriteProcessMemory hProcess, p_call_OldFunction, varptr(call_OldFunction), sizeof_call_OldFunction, varptr(bytes_set) // p_NewFunctionをcallしてcall_OldFunctionにjmpする機械語 ; 0: ba 78 56 34 12 mov edx, 0x12345678 ; 5: ff d2 call edx ; 7: ba 78 56 34 12 mov edx, 0x12345678 ; c: ff e2 jmp edx dim call_NewFunction, 4 call_NewFunction(0) = 0x000000ba call_NewFunction(1) = 0xbad2ff00 call_NewFunction(2) = 0x00000000 call_NewFunction(3) = 0x0000e2ff lpoke call_NewFunction, 1, p_NewFunction lpoke call_NewFunction, 8, p_call_OldFunction // p_NewFunction呼び出しルーチンを対象プロセスに書き込む WriteProcessMemory hProcess, p_OldFunction+SIZEOF_PREPAREMENT, varptr(call_NewFunction), SIZEOF_CALL_NEWFUNCTION, varptr(bytes_set) if bytes_set == 0 : return 0 return 1 #global /*-------------------- ここまでモジュール --------------------*/ #uselib "user32.dll" #func MessageBox "MessageBoxA" int, sptr, sptr, int // フック対象 #uselib "kernel32.dll" #cfunc OpenProcess "OpenProcess" int, int, int #func Sleep "Sleep" int #define PROCESS_ALL_ACCESS 0x001F0FFF // [テスト]自分を変更する hProcess = -1;OpenProcess(PROCESS_ALL_ACCESS, 0, /* ここにPID */) /*-------------------- ; 以下のコードを実行する push ebp ; とりあえずEBPは保存 mov ebp, esp push 0x3e8 ; 引数は1000 mov edx, [Sleep] ; Sleepを call edx ; 呼び出す mov esp, ebp pop ebp ret --------------------*/ dim code, 10 code(0) = 0x68e58955 code(1) = 0x000003e8 code(2) = 0xFFFFFFba code(3) = 0x89d2ffFF code(4) = 0x90c35dec lpoke code, 9, varptr(Sleep) p_NewMessageBox = AllocMachineCode(hProcess, code, 40) // HSPのvarptrは外部関数のアドレスを取得してくれる p_MessageBox = varptr(MessageBox) ; GetProcAddress( LoadLibrary("user32.dll"), "MessageBoxA" ) mes strf("OLD : %x", p_MessageBox) mes strf("NEW : %x", p_NewMessageBox) // p_MessageBoxを呼び出すとp_NewMessageBoxへ迂回するように書き換える HookAPI hProcess, p_MessageBox, p_NewMessageBox if stat == 1 { mes "成功しました。" } else { mes "失敗しました。" } // [テスト]MessageBoxAを呼び出すと先にSleepが実行される MessageBox 0, "The quick brown fox jumps over the lazy dog", "Hello, world!", 0 stop
フックを元に戻す関数は作っていません。(これだけでも3日かかったから許してください。)
また、少し改造すれば、元のAPIを実行させないようにもできます。(理論を理解されている方にとっては簡単ですよね。)
例えばOpenProcessで自分のハンドルがpushされている場合にリターンするなどすれば、チート対策に使えます。
そのためには64bitのも対応する必要がありますが、また暇なときに挑戦しようと思います。
※不正な操作を行うプログラムの作成は刑法第168条の2第1項により禁止されています。
3. Direct3D Hooking :: IT & 잡동사니
リバースエンジニアリングバイブル ~コード再創造の美学~
前のセクションでInline Function Hookingを作りましたが、そのコードはMessageBoxのように呼び出し直後にcallする構造の関数にだけ有効でした。 今回は、できるだけ多くの関数に適応できるような方法をとりました。 Detoursについてさらに調べると、このライブラリでは関数のcallに割り込むのではなく、関数の先頭をjmpに変更して自由なコードに飛ばしていました。 C言語などでは関数アドレスを戻すことができますが、残念ながらそれが使えないHSPで実装しなくてはなりません。 そこで、次のようなアルゴリズムを考えました。
2. 対象の関数の先頭をjmpにし、自由な機械語に飛ばす。
3. 基本的には機械語列からそのままスタックポインタを使ってリターンする。
4. 元の関数を継続して実行したい場合は、1で退避したバイト列を関数の先頭へ復元するような機械語に飛ばす。
5. 必要に応じてこの操作を繰り返す。
つまり、基本的に自分の機械語にジャンプして、元の関数は実行せずにリターンします。 元の関数を実行する場合、jmpに置き換わった先頭部分を復元するような機械語を実行してからリターン(ジャンプ)します。 これを利用して、対象プロセスでOpenProcessが実行され、引数のPIDが自分のものであればNULLを返すようなフックを作ってみます。 (これならチート対策にかなり有効ですね。) 以下はPIDが自分のもの(myPID)と一致したらNULLを返し、そうでなければ関数の先頭を復元します。 (ただし、ebpをpushしないのでesp+0xCによりdwProcessIdを参照しています。)
cmp eax, myPID
jne restore
xor eax, eax
ret
restore:
mov edx, [OpenProcess]
mov DWORD PTR [edx], <originalCode+0>
mov DWORD PTR [edx+0x4], <originalCode+4>
jmp edx
また、機械語組み立て時は関数のアドレスや退避したバイト列がわからないので、movで一度移動して呼び出すようにしています。 これにより、後からmovのオペランドにデータを上書きするだけで変更できます。 jneはnear shortで行うので、相対アドレスです。 このソースコードでは一部の動作をモジュール化しました。
/*-------------------- ここからモジュール --------------------*/ #module #uselib "kernel32.dll" #cfunc OpenProcess "OpenProcess" int, int, int #cfunc VirtualAllocEx "VirtualAllocEx" int, int, int, int, int #func VirtualProtectEx "VirtualProtectEx" int, int, int, int, int #func ReadProcessMemory "ReadProcessMemory" int, int, int, int, int #func WriteProcessMemory "WriteProcessMemory" int, int, int, int, int #cfunc GetLastError "GetLastError" #define PROCESS_ALL_ACCESS 0x1F0FFF #define MEM_COMMIT 0x1000 #define PAGE_EXECUTE_READWRITE 0x40 // プロセスを開く // #defcfunc AccessProcess int dwProcessId return OpenProcess(PROCESS_ALL_ACCESS, 0, dwProcessId) // PutMachineCode ... 対象プロセスで機械語を作成する // p1[int] : 対象プロセス // p2[var] : 機械語を含む変数 // p3[int] : 機械語のサイズ // [返り値] 0=失敗 / 確保したアドレス:成功 #defcfunc PutMachineCode int hProcess, var code, int size,\ local ptrCode, local setBytes // RWX領域を確保 ptrCode = VirtualAllocEx(hProcess, 0, size, MEM_COMMIT, PAGE_EXECUTE_READWRITE) if ptrCode == 0 : return 0 // 機械語を書き込み WriteProcessMemory hProcess, ptrCode, varptr(code), size, setBytes if stat == 0 : return 0 return ptrCode // GetHookAPI ... 元のコードを取得する // p1[int] : 対象プロセス // p2[int] : 先頭コードを取得する関数 // p3[var] : 元のコードを格納する変数 // [返り値] 0=成功 / 1=失敗 #deffunc GetHookAPI int hProcess, int ptrOldFunction, var originalCode,\ local setBytes // 元のコードを退避 dim originalCode, 2 ReadProcessMemory hProcess, ptrOldFunction, varptr(originalCode), 8, setBytes if stat == 0 : return 1 return 0 // HookAPI ... 関数をフックする // p1[int] : 対象プロセスのハンドル // p2[int] : フックする関数のアドレス // p3[int] : 迂回する関数のアドレス // [返り値] 0=成功 / 1=失敗 #deffunc HookAPI int hProcess, int ptrOldFunction, int ptrNewFunction,\ local jmpCode, local setBytes, local oldProtect /* Jump to 0xFFFFFFFF 0: ba ff ff ff ff mov edx, 0xFFFFFFFF 5: ff e2 jmp edx */ dim jmpCode, 2 jmpCode(0) = 0xFFFFFFba jmpCode(1) = 0x90e2ffFF lpoke jmpCode, 1, ptrNewFunction // 書き込みを許可する(mov用) VirtualProtectEx hProcess, ptrOldFunction, 8, PAGE_EXECUTE_READWRITE, varptr(oldProtect) if stat == 0 : return 1 // OldFunctionの先頭をNewFunctionへのjmpに書き換え WriteProcessMemory hProcess, ptrOldFunction, varptr(jmpCode), 8, setBytes if stat == 0 : return 1 return 0 #global /*-------------------- ここまでモジュール --------------------*/ #uselib "kernel32.dll" #cfunc GetProcessId "GetProcessId" int #cfunc GetCurrentProcess "GetCurrentProcess" #cfunc OpenProcess "OpenProcess" int, int, int #define PROCESS_ALL_ACCESS 0x1F0FFF // 変数初期化 dim originalCode dim procChecker, 10 ptrProcChecker = 0 myPID = 0 // 対象と条件 ptrOldFunction = varptr(OpenProcess) ; OpenProcessのアドレス hProcess = GetCurrentProcess() ; AccessProcess(4724) myPID = GetProcessId(GetCurrentProcess()) ; 自分のPID // 元のコードを取得 GetHookAPI hProcess, ptrOldFunction, originalCode /* dwProcessIdがmyPIDと一致したらNULLを返す そうでなければ戻り値を0にしてOpenProcessへ処理を戻す 0: 8b 44 24 0c mov eax,DWORD PTR [esp+0xc] 4: 3d ff ff ff ff cmp eax,0xFFFFFFFF 9: 75 03 jne 0xe b: 31 c0 xor eax,eax d: c3 ret 0000000e: e: ba ee ee ee ee mov edx,0xEEEEEEEE 13: c7 02 dd dd dd dd mov DWORD PTR [edx],0xDDDDDDDD 19: c7 42 04 cc cc cc cc mov DWORD PTR [edx+0x4],0xCCCCCCCC 20: ff e2 jmp edx */ procChecker(0) = 0x0c24448b procChecker(1) = 0xFFFFFF3d procChecker(2) = 0x310375FF procChecker(3) = 0xEEbac3c0 procChecker(4) = 0xc7EEEEEE procChecker(5) = 0xDDDDDD02 procChecker(6) = 0x0442c7DD procChecker(7) = 0xCCCCCCCC procChecker(8) = 0x0000e2ff lpoke procChecker, 5, myPID lpoke procChecker, 15, ptrOldFunction lpoke procChecker, 21, originalCode(0) lpoke procChecker, 28, originalCode(1) // 機械語として埋め込み ptrProcChecker = PutMachineCode(hProcess, procChecker, 40) mes "PID : " + myPID mes "\nフック前:" mes "自分のプロセスハンドル => " + OpenProcess(PROCESS_ALL_ACCESS, 0, myPID) // 新しいコードへのジャンプで上書き HookAPI hProcess, ptrOldFunction, ptrProcChecker mes "\nフック後:" mes "自分のプロセスハンドル => " + OpenProcess(PROCESS_ALL_ACCESS, 0, myPID) mes "\n他のPIDで実行したあと、自分のPIDで再試行:" mes "適当なプロセスハンドル => " + OpenProcess(PROCESS_ALL_ACCESS, 0, 0) mes "自分のプロセスハンドル => " + OpenProcess(PROCESS_ALL_ACCESS, 0, myPID) stop
フックを元に戻す関数は作っていませんが、退避したバイト列を再度書き込むだけなのでWriteProcessMemoryするだけです。
機械語の部分を正しくかければたぶんwindows api以外を含む全ての関数に対して使えます。
また、64bitでも関数のアドレスが分かればモジュール内のjmp機械語を変更するだけで使えます。(未検証)
ただし、例えば今回の場合、OpenProcessで自分以外が引数に取られたらコードは復元されます。
したがって、一度きりとなるので、関数の先頭が自分の設定したjmp列でなければ、繰り返しこのモジュールを呼び出す必要があります。
これは今後の課題です。
また、すべての関数に使えるというものの、各関数の特性を理解した上で自分で機械語、復元コードを書く必要があります。
そのため、アセンブリを習得した人なら簡単なものの、そうでない人にとっては時間のかかる作業となるでしょう。
※不正な操作を行うプログラムの作成は刑法第168条の2第1項により禁止されています。
OpenProcess function (Windows)
毎回お世話になっているツール:Online x86 and x64 Intel Instruction Assembler