[PR] この広告は3ヶ月以上更新がないため表示されています。
ホームページを更新後24時間以内に表示されなくなります。

システム関連 - ソースコード
システム関連 - catsのホームページ
目次

ここにはシステムに関するプログラムのコードを載せています。

1 VMware Player上で動作しているか検出する
2 DLLMagic を使用して呼び出し中のDLLを隠蔽する
3 プログラムのデバッグを検知する
4 【WinXP】他プロセスにDLLインジェクションを行う
5 【32bit】他プロセスのAPIをInline Function Hooking(detours)でフックする
6 【32bit】他プロセスのAPIをInline Function Hooking(detours)でフックする - その2
1.VMware Player上で動作しているか検出する
ソースコード

 プログラムがVMware上で実行されているかを検出します。exist命令でシステムファイルを調査すれば簡単じゃん、と思って作ってみましたが上手く動作しませんでした。 メッセージが何かブロックされている気がしたので調べてみたところWOW64のリダイレクト機能とやらが原因だそうです。 それを一時的に無効にしてからexistを使用することでドライバのシステムファイルを調査することができました。 レジストリを調査したり、実機でしか検知できない例外を発生させたり、検出方法はさまざまですが、ここではvmmouse.sysドライバの検出をしました。

1. GetEnvironmentVariable で PROCESSOR_ARCHITEW6432 の内容を取得する。
2. Wow64DisableWow64FsRedirection で WOW64 のリダイレクト機能を無効化する。
3. exist でポインティングデバイスのシステムファイルを確認する。
4. Wow64DisableWow64FsRedirection で WOW64 のリダイレクト機能を元に戻す。

このソースコードでは一連の動作をモジュール化しました。このプログラムは VMware Player しか検出できません。 他のサンドボックスを検出することも可能です。気になる方は参考文献の"VRT"やその他検出方法を書いたサイトをご覧ください。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/*-------------------- ここからモジュール --------------------*/
#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

2.DLL Magic を使用して呼び出し中のDLLを隠蔽する
ソースコード

 DLL Magic を呼び出してプログラムが使用しているDLLを隠蔽します。DLL Magic はコマンドラインから簡単にDLLを隠蔽することができるツールです。 ほとんど使い道の無いコードになってしまいました。DLLをマッピングさせて一から書く予定でしたが何しろ危険なコードになりそうだったので主要な操作はツールに任せました。 本当にHSPからは何もしていませんが、一応流れを書いておきます。

1. GetWindowThreadProcessId でPIDを取得する。
2. DLLMagic にDLL名とPIDを渡して起動する。

DLLMagic はこちらからダウンロードできます。 同じディレクトリに入れて実行するように注意してください。DLLMagic の内部の仕組みはここでは説明しません。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#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 でしたので、タスクマネージャからプロセスをダンプして、結果をツールで見てみました。

figure:Dumped Image Before Hiding a DLL figure:Dumped Image After Hiding a DLL

隠蔽前には hspinet.dll の存在が確認できますが、隠蔽後には確認できません。DLLの隠蔽に成功したようです。
不正な操作を行うプログラムの作成は刑法第168条の2第1項により禁止されています。

参考文献

DLL Magic : Command-line Tool to Hide DLL in Windows Process

3.プログラムのデバッグを検知する
ソースコード

 プログラムのデバッグの防止は、ゲームのチートを防止したり、プログラム内部の知られてはいけない情報を保護したりするのに必要な技術です。 HSPからデバッグを防ぐ最も簡単なコードを書きました。(いくらかのゲーム製作者は既に知っているでしょう。) 特に、デバッグはプログラムの途中からでも開始できるため、常にデバッグを監視しておくことをおすすめします。

1
2
3
4
5
6
7
8
#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を使います。

1
2
3
4
5
6
7
8
#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++を使用しました。

参考文献

たのしいバイナリの歩き方
アナライジング・マルウェア - フリーツールを使った感染事案対処

4.【WinXP】他プロセスにDLLインジェクションを行う
ソースコード

 他のプロセスを介して自由な処理を行わせる手段に、DLLインジェクションという方法があります。 これはDLLを他プロセスにロードさせる(実際には LoadLibrary を他プロセスから使用する)手法です。 今回ここに書く方法は、CreateRemoteThread 関数を使う方法で、これはWin Vist等の中でも特に64bitの環境では使用できません。(今後機会があればそちらについても書くつもりです。) このコードは Windows XP より前のHSP対応OSで使用できます。(テスト環境は WinXPSP3x86 です。)

1. OpenProcess でプロセスを操作する準備をする。
2. VirtualAllocEx でDLLのパスを書き込む準備をする。
3. WriteProcessMemory でDLLのパスを書き込む。
4. LoadLibrary と GetProcAddredd で LoadLibrary 関数のアドレスを得る。(そのまま取得できないので)
5. 4のアドレスの関数を CreateRemoteThread で1のプロセスに対して3のパスを引数として呼び出す。

まず、DLLのコードをC言語で書きます。今回は「危ない動作の実験」ということで「evil.dll」にします。DLLが読み込まれるとCUI入出力画面に「I am here.」と表示します。

1
2
3
4
5
6
7
8
9
#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マシンからファイル移動時に消してしまったと思われます。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#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本体が消される可能性があります。 実行するときは実行ファイルにしてからか、セキュリティソフトを一時的に無効にしてから実行することを推奨します。以下が実行結果です。

figure:The Object Process Before Injecting It figure:The Object Process After Injecting It

左側(インジェクション前)はプログラムが停止している状態ですが、このプロセスに対してevil.dllをDLLインジェクションすると、右側(インジェクション後)のようになりました。 読み込まれたevil.dllのDllMainが実行され、prinf("I am here.\n");の部分が実行されたのです。 今回はソフトウェア開発などにはほとんど関係無いコードでしたが、興味深い内容だったので書きました。セキュリティ系のソフトウェアを開発される方は参考までに。
不正な操作を行うプログラムの作成は刑法第168条の2第1項により禁止されています。

参考文献

アナライジング・マルウェア - フリーツールを使った感染事案対処

5.【32bit】他プロセスのAPIをInline Function Hooking(detours)でフックする
ソースコード

 プロセスのAPI関数の挙動を変更する方法(インジェクション)として、Inline Function Hooking(detours)というものがあります。 C言語などではMicrosoftがライブラリ化しており、このインジェクションが非常に簡単に使えるようになっています。 HSPでできるかなーと思って調べてみたのがきっかけです。 ここで重要な話なのですが、このInline Function Hookingについて、ライブラリなしで実際に動作するコードを探しても見つかりませんでした。 以下で説明するコードは理論から勝手に組み立てたものなので、実際のdetoursとは異なることをしているかもしれません。 詳しい方で、間違いを発見された場合は教えてください。 また、このプログラムはMessageBoxと同じ構造を持つ呼び出し部しか検出できません。 したがって、これが使用できる関数は非常に限られています。 このコードで理論を見てもらい、後は自分の使いたい形に改造してもらったら、と思っています。 とはいえ、実際にAPI呼び出しに迂回路を作ることには成功していますので、その点は安心してください。 また、HSPは32bitなので、32bitの場合を考慮しています。64bitでは関数の呼び出し構造が全く違うのでそもそも動きません。 さて、この方法では、APIを呼び出したときの機械語を書き換え、自分のコードに迂回路(detours)を作ります。 detoursにEIPが移り、コードを終了すると、元のAPIを呼び出した場所へ戻ります。 下の図は変更前と変更後のMessageBoxA関数呼び出しコードです。

figure:How it works

変更後のルーチンは一度Evilを呼び出す必要があるので、サイズが大きくなります。 そのため、この後ろにあるコードを上書きしないように、先に確保しておいた領域Detoursに元の呼び出しコードを退避しておきます。 今回のコードの流れを以下に書きました。

1. 実行させたいコードを機械語として、対象プロセスに書き込む。(VirtualAllocEx + WriteProcessMemory)
2. 引数のpushからretまでの機械語を対象プロセスに確保した領域に退避。(ReadProcessMemory + VirtualAllocEx + WriteProcessMemory)
3. 2でcallのアドレスは相対アドレスなので、移動先から元の値までの相対アドレスに変更する。
4. 迂回用ルーチンを元の関数に上書きする。
5. 対象プロセスがその関数を呼び出そうとしたら、用意したコードが実行される。

今回は、MessageBoxAを呼び出した際に、Sleep(1000);を実行させます。 Sleep(1000);を実行させるコードは以下のアセンブリです。

push ebp
mov ebp, esp
push 0x3e8
mov edx, [Sleep]
call edx
mov esp, ebp
pop ebp
ret

また、機械語組み立て時は関数のアドレスがわからないので、movで一度移動して呼び出すようにしています。 これにより、後からmovのオペランドに関数アドレスを上書きするだけで変更できます。 このソースコードでは一部の動作をモジュール化しました。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
/*-------------------- ここからモジュール --------------------*/
#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 & 잡동사니

リバースエンジニアリングバイブル ~コード再創造の美学~

6.【32bit】他プロセスのAPIをInline Function Hooking(detours)でフックする - その2
ソースコード

 前のセクションでInline Function Hookingを作りましたが、そのコードはMessageBoxのように呼び出し直後にcallする構造の関数にだけ有効でした。 今回は、できるだけ多くの関数に適応できるような方法をとりました。 Detoursについてさらに調べると、このライブラリでは関数のcallに割り込むのではなく、関数の先頭をjmpに変更して自由なコードに飛ばしていました。 C言語などでは関数アドレスを戻すことができますが、残念ながらそれが使えないHSPで実装しなくてはなりません。 そこで、次のようなアルゴリズムを考えました。

1. 対象プロセスの対象の関数の先頭をHSP内の変数に退避する。
2. 対象の関数の先頭をjmpにし、自由な機械語に飛ばす。
3. 基本的には機械語列からそのままスタックポインタを使ってリターンする。
4. 元の関数を継続して実行したい場合は、1で退避したバイト列を関数の先頭へ復元するような機械語に飛ばす。
5. 必要に応じてこの操作を繰り返す。

つまり、基本的に自分の機械語にジャンプして、元の関数は実行せずにリターンします。 元の関数を実行する場合、jmpに置き換わった先頭部分を復元するような機械語を実行してからリターン(ジャンプ)します。 これを利用して、対象プロセスでOpenProcessが実行され、引数のPIDが自分のものであればNULLを返すようなフックを作ってみます。 (これならチート対策にかなり有効ですね。) 以下はPIDが自分のもの(myPID)と一致したらNULLを返し、そうでなければ関数の先頭を復元します。 (ただし、ebpをpushしないのでesp+0xCによりdwProcessIdを参照しています。)

mov eax, DWORD PTR [esp+0xc]
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で行うので、相対アドレスです。 このソースコードでは一部の動作をモジュール化しました。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
/*-------------------- ここからモジュール --------------------*/
#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