Unix C がちょっと書けるだけの俺による令和最新式 Ghidra のトラブル解決法 - E-MU 1616M CardBus が刺さってる Panasonic Let's Note CF-S10E がスリープからの復帰に失敗する (その 12)
過去回で CtDCIFCE.DLL の呼び出しに COM/OLE Viewer からぶっこ抜いた MIDL を使ってたんだが、ポインタを long で扱ってるから 64bit で死ぬわアレ。
[
odl,
uuid(C3ED4631-2768-43E0-BEBC-1C399989E666),
helpstring("IDevConDevice2 Interface")
]
interface IDevConDevice2 : IUnknown {
[helpstring("method GetNumDevices")]
HRESULT _stdcall GetNumDevices(long* pdwNumDevices);
[helpstring("method GetFirstDevice")]
HRESULT _stdcall GetFirstDevice(long* pdwDevice);
[helpstring("method GetNextDevice")]
HRESULT _stdcall GetNextDevice(
long dwCurrentDevice,
long* pdwNextDevice);
[helpstring("method GetDeviceInfo")]
HRESULT _stdcall GetDeviceInfo(
long dwDevice,
long* pDeviceInfo);
[helpstring("method GetDeviceInfoEx")]
HRESULT _stdcall GetDeviceInfoEx(
long dwDevice,
long* pDeviceInfoEx);
…
LP64 な Unix で長年生きてきたせいでうっかり long だから 32bit と 64bit どちらでもヨシ!ってスルーしてしまってたわガハハ。
Windows は LLP64 とかいうクソの煮凝りなので
- Win32 API であれば
DWORDは 64bit 環境でもDWORD32のエイリアスなので 64bit でDWORD64になるエイリアスが必要ならDWORD_PTRを使う - C 標準なら
longは 64bit 環境でも 32bit integer なので 64bit integer が必要ならlong longかintptr_tを使う
必要があるということ。
Ghidra は型推論がよわよわで ILP32 ならほとんど int と判定されてしまうのも併せて全部コード読まんとならんから気が遠くなってきた。
ひたすらインタフェースの精査
そんじゃ Unko1から順に読んでいくか。
HRESULT Unko1::FUN_0200370a(Unko1 *this,int param_2,int *param_3)
{
HRESULT HVar1;
HRESULT HVar2;
undefined4 extraout_ECX;
undefined8 uVar1;
int local_c;
int local_8;
local_c = 0;
local_8 = 0;
HVar1 = E_FAIL;
if (this->handle != (HANDLE)INVALID_HANDLE) {
uVar1 = FUN_02003566(this->handle,this->unknown2,this->unknown3,param_2,param_2 >> 0x1f,&local_c
);
HVar1 = (HRESULT)uVar1;
if (-1 < HVar1) {
HVar2 = SimpleFactory(extraout_ECX,(int)((ulonglong)uVar1 >> 0x20),local_c,local_8,&GUID_Unko2,
&this);
*param_3 = (int)this;
return HVar2;
}
}
*param_3 = 0;
return HVar1;
}
そんでまたしても謎のローカル変数 extraout_ECX が発生していて SimpleFactory の第 1 引数として渡していて頭を抱える。
こいつは前回 COM インタフェースと GUID の対応を調べるために読んだ GUID からオブジェクトを作る Simple Factory パターンのコードである。
Ghidra の呼出規約の復元は信用するな
まず最初に疑ったのは前回もでてきた呼出規約である。
__stdcallであればthisはスタック渡し__thiscallであればthisはレジスタ渡し
よって __stdcall の場合スタック渡しされた this を ECX レジスタにコピーするのだけど、そのコードが正しく解釈できてない可能性がある。
じゃあいアセンブラ読もうか、Ghidra の逆アセンブルコードって Intel 記法ベースの上に婉曲表現で理解しづらいなこれ。
**************************************************************
* FUNCTION *
**************************************************************
HRESULT __stdcall FUN_0200370a(Unko1 * this, long param_
assume FS_OFFSET = 0xffdff000
HRESULT EAX:4 <RETURN>
Unko1 * Stack[0x4]:4 this XREF[3]: 02003711(R),
...
Unko1::FUN_0200370a XREF[1]: 020013a0(*)
…
0200370c 55 PUSH EBP
0200370d 8b ec MOV EBP,ESP
…
02003711 8b 4d 08 MOV ECX,dword ptr [EBP + this]
…
thisはStack[0x4]:4つまり スタックフレーム + 4 の位置にある 4byte データEAXレジスタのリターンアドレス 4byte が退避されてるからスタックフレームは[EBP + 4]の位置- よって
[EBP + this]は[EBP + 8]の意味、通常ここには第 1 引数が積まれる MOVE ECX, [EBP + 8]つまりスタックに積まれた第 1 引数thisをECXレジスタにコピーしている
ということで推測は正しかった。
そんで SimpleFactory は IUnknown のメンバ関数で __thiscall ちゅうことかと思ったのだが、開けてびっくり __fastcall になっとるんよ。
void __fastcall
FUN_02002e86(undefined4 param_1,int param_2,int param_3,int param_4,GUID *param_5,
undefined4 *param_6)
__thiscall が誤判定されてるだけか思ったが、よくよく調べたらフリー関数で __stdcall な関数みたいなんだわ。
整理すると
Unko1::FUN_0200370aはクラスのメンバ変数だけど COM インタフェースなので__stdcallで宣言されてる- よってコンパイラがスタックに置かれた第 1 引数の
thisを 改めてECXレジスタに置きなおした - Ghidra はそのコードをみて
SimpleFactoryの呼出のために置いたと勘違いし__fastcallと誤判定
ってこと。
それに呼び元で戻り値がをチェックしてるのに戻り値が void になっとるのも整合性がついとらん。
結局のところ SimpleFactory のプロトタイプは __stdcall であり
HRESULT SimpleFactory(undefine4, undefined4, IID*, void**);
が正しいのかなと推測した。
これが x86_64 なら最初の引数 4 個は必ずレジスタ渡しになるから呼出し規約なんて有名無実化してるので誤判定なぞ起きようが無い。 幸運なことに今回は 64bit 版の CtDPROXY.DLL もあるのだから、そっち解析すれば正しい答えが出るよね。
x86_64 でも基本 x86 と同じ手順だからそこの解説は省略させていただく、ただしコンパイラの進化によって以下の違いがあることを知らないと大変だと思われる。
- 最適化ががっつり効いてて
GUID定数が全部インライン展開され .text セクションにそもそも存在しないから、GUID の先頭 64bit でメモリ検索しないと探せない - コンストラクタもデストラクタもすべてインライン展開されている
- もはや inline キーワードは有名無実化しすきあらば性能の為にインライン展開される
- ありとあらゆる最適化により鼻から悪魔が飛び出す
- AddRef/Release での参照カウントの増減は
InterlockedIncrement/InterlockedDecrementを使わずLOCK/UNLOCKによるクリティカルセクションで実装されている
今の子は大変だね(憐憫)、そんで見つかったプロトタイプがこちら。
HRESULT SimpleFactory(undefined8, IID*, void**);
あー引数 3 つで第 1 引数は 64bit でそれが 32bit + 32bit に解釈されて引数 4 つに化けてんだわ、これについてはなぜそうなるかは後で説明する。
32bit 非 thunking 版の方も見てみるとこっちも __fastcall に誤判定されてたけどそこ修正してやると
HRESULT SimpleFactory(undefined4, IID *,void **);
とやはり引数 3 つだったわ、正解出たね。
Thunking すなわち帳尻合わせの実例
前回軽く触れた thinking のサンプルやねこれ、コードを脳内で書き直すと以下のようになる。
#if defined(THUNKING)
typedef DWORD64 THUNK_DWORD_PTR;
#else
typedef DWORD_PTR THUNK_DWORD_PTR;
#endif
extern HRESULT FUN_02003566(HANDLE, THUNK_DWORD_PTR, THUNK_DWORD_PTR, THUNK_DWORD_PTR*);
extern HRESULT SimpleFactory(THUNK_DWORD_PTR, IID*, void**);
class IUnknown {
protected:
DWORD_PTR refcount;
THUNK_DWORD_PTR unknown;
IID iid;
HANDLE handle;
};
class Unko1 : IUnknown {
public:
HRESULT FUN_0200370a(Unko1 *this, DWORD_PTR param_2, DWORD_PTR* param_3) {
HRESULT ret = E_FAIL;
THUNK_DWORD_PTR value = 0;
Unko2 *unko2 = nullptr;
if (this->handle != INVALID_HANDLE) {
ret = FUN_02003566(this->handle, this->unknown, (THUNK_DWORD_PTR)param_2, &value);
if (SUCCEEDED(ret))
ret = SimpleFactory(value, &GUID_Unko2, (void **)&unko2);
}
*param_3 = (DWORD_PTR)unko2;
return ret;
}
...
};
前回 IUnknown の末尾 4byte が未使用だったのが謎だったんだけど、IUnknown::handle も Thunking して 64bit で宣言してるからっぽい。
ただしこいつは DeviceIoControl の第 1 引数に渡すものだからする必要もないし、仮に 64bit 化する必要があるなら INVALID_HANDLE は使えないはず。
このマクロって 32bit では 0xffffffff 64bit なら 0xffffffffffffffff になるから互換性無いんだわ、this->handle != (THUNK_DWORD_PTR)-1 とする必要がある。
しかし LLP64 ってのは 32bit でも 64bit でも long は 32bit だからコードの移行が楽なんて言われてるけど、Thunking が必要なケースになると非常にめんどくせえなこれ。
ところでなぜ Ghidra の疑似 C ソースを修正せずにず脳内で処理したか、それは Ghidra がバグっとるから。
Ghidra は古いバイナリの解析が苦手でバグを踏みまくる
Unko1::FUN_0200370a から呼出されてるもうひとつの関数 FUN_02003566 は DeviceIoControl でドライバを叩くだけのコード、x86_64 だとインライン化され消えてしまう程度のもの。
ただこいつも Ghidra はまともに解釈できないのだ。
/* WARNING: Function: __alloca_probe replaced with injection: alloca_probe */
void FUN_02003566(HANDLE param_1,undefined4 param_2,undefined4 param_3,undefined4 param_4,
undefined4 param_5,undefined4 *param_6)
{
undefined1 local_58 [8];
undefined4 local_50;
undefined4 local_4c;
undefined4 uStack_44;
undefined4 local_40;
undefined4 local_3c;
undefined4 local_38;
undefined4 local_34;
undefined4 local_30;
undefined4 local_2c;
undefined4 local_28;
undefined4 local_24;
undefined4 uStack_1c;
DWORD local_c;
uint canary;
canary = __security_cookie ^ (uint)&stack0xfffffffc;
uStack_1c = 0x2003582;
uStack_44 = 0x2003594;
local_3c = 0;
local_40 = 2;
local_38 = param_2;
local_34 = param_3;
local_30 = param_4;
local_2c = param_5;
local_28 = *param_6;
local_24 = param_6[1];
DeviceIoControl(param_1,0xf7770000,&local_40,0x20,local_58,0x10,&local_c,(LPOVERLAPPED)NULL);
*param_6 = local_50;
param_6[1] = local_4c;
__security_check_cookie(canary ^ (uint)&stack0xfffffffc);
return;
}
こいつも HRESULT 型のステータスコードを返すはずなんだが戻り値の型が void になってしまっているね。
そして SimpleFactory の話にも出てきたけど、正しいプロトタイプは
HRESULT FUN_02003566(HANDLE, THUNK_DWORD_PTR, THUNK_DWORD_PTR, THUNK_DWORD_PTR*);
になるはずなのだが 64bit な引数が 32bit+32bit に化けてしまっている。これは命令が 32bit に分割されてしまってるからである。
たとえば以下のコードが
void foo(undefined8 param_1)
{
undefined8 local_8 = param_1;
が以下のように化けて解析されてしまうケースがある。
void foo(undefined4 param_1, undefined4 param_2)
{
undefined4 local_c = param_2;
undefined4 local_8 = param_1;
movq よりも mov を 2 度呼ぶ方が命令サイズが小さい(ニチャァ)というサイズ最適化がまかり通ってた時代の産物ですな。
今のコンパイラなら SIMD の movlpd 使うからこの手のトラブルは無縁のはずである。
Unko1::FUN_0200370a でも疑似アセンブラ画面では undefined8 local_c なのに
**************************************************************
* FUNCTION *
**************************************************************
HRESULT __stdcall FUN_0200370a(Unko1 * this, long param_
…
undefined8 Stack[-0xc]:8 local_c XREF[3,2]: 02003714(RW),
…
Unko1::FUN_0200370a XREF[1]: 020013a0(*)
…
02003714 83 65 f8 00 AND dword ptr [EBP + local_c],0x0
02003718 83 65 fc 00 AND dword ptr [EBP + local_c+0x4],0x0
…
疑似 C ソース画面では undefined4 local_c と undefined4 uStack_8 に分かれてしまってる。
undefined4 local_c;
undefined4 uStack_8;
local_c = 0;
uStack_8 = 0;
さらに関数呼び出しの為にこの 64bit な変数を引数としてスタックに積むコードも疑似アセンブラでは正しく解釈されてるけど
02003755 ff 75 fc PUSH dword ptr [EBP + local_c+0x4]
02003758 ff 75 f8 PUSH dword ptr [EBP + local_c]
0200375b e8 26 f7 CALL FUN_02002e86 HRESULT FUN_02002e86(longlong pa
ff ff
疑似 C ソース画面ではやっぱり CONCAT44(uStack_8,local_c) つまり 32bit+32bit に化けているのだ。
HVar2 = FUN_02002e86(CONCAT44(uStack_8,local_c),(IID *)&DAT_02001264,&this);
Ghidra が CONCAT44(hi,lo) などというオレオレマクロを何の解説も無くお出ししてきたけど 4byte 同士を結合するという意味だろう、それなら ((undefined8)hi << 32 | lo) でいいだろ…
ローカル変数が化けるのはまだマシな部類で ECX レジスタに置かれた Unko1* this の 64bit なメンバ変数 this->unknown をスタックに積むという以下のコードはもっとヤバい。
02003734 ff 71 0c PUSH dword ptr [ECX + 0xc]
02003737 ff 71 08 PUSH dword ptr [ECX + 0x8]
…
0200373b e8 26 fe CALL FUN_02003566 undefined4 FUN_02003566(HANDLE p
ff ff
クラス Unko1 の継承元である IUnknown は C 構造体で書くと
struct Unko1 {
void* vftbl; /* 0x0 */
DWORD32 refcount; /* 0x4 */
DWORD64 unknown; /* 0x8 */
HANDLE handle; /* 0x10 */
/* unknown 4byte */
}
という定義なので PUSH [ECX + 0xc] PUSH [ECX + 0x8] というコードは this->unknown を 32bit づつに分けてスタックに積んでることがお判りいただけるであろう。
しかし困ったことに疑似 C ソース画面からみると以下のように完全にぶっ壊れて表示されてしまうのだ。
(iVar1 = FUN_02003566(this->handle,CONCAT44(param_2,*(undefined4 *)((int)&this->unknown + 4)),
CONCAT44(&local_c,param_2 >> 0x1f),unaff_ESI), -1 < iVar1)) {
this->unknown4 が CONCAT44(param_2,*(undefined4 *)((int)&this->unknown + 4)) に化けてしまっている、無関係の param_2 どっから出てきたんだ。
これおそらく [ECX + 0xc] が [EBX + 0xc] つまり第 2 引数に化けたんだろうな。
とうことで Ghidra で 32bit+32bit に化けてしまった処理を 64bit に戻そうと試みるのはこのバグが直るまで不可能である。 俺は困ってないのでバグ修正はしないし NSA と関わりたくないのでバグ報告もしないよ…
Ghidra はコンパイラがインジェクションしたコードの解釈があまり得意でない
そして謎のマジックナンバー 0x2003582 0x2003594 なんてのまで現れた。
uStack_1c = 0x2003582;
uStack_44 = 0x2003594;
これは大きなサイズのスタック要求に対して Visual C/C++ は alloca_probe を呼び出しスタックオーバーフローを検知しようとする。
0200357d e8 ae a7 CALL __alloca_probe undefined __alloca_probe(void)
00 00
02003582 8b c4 MOV EAX,ESP
…
0200358f e8 9c a7 CALL __alloca_probe undefined __alloca_probe(void)
00 00
02003594 83 66 04 00 AND dword ptr [ESI + Stack[-0x3c]],0x0
マジックナンバーは __alloca_probe 呼出の次の命令のアドレスですな、つまりこれも Ghidra のバグである。
大きなスタックサイズ要求というのは DeviceIoControl の引数となるローカル変数宣言 local_40 の 32byte(0x20) と local_58 16byte(0x10) である、全然小さいやんけ。
Ghidraは構造体までは復元できないので
- 入力データ
undefined4 local_40;
undefined4 local_3c;
undefined4 local_38;
undefined4 local_34;
undefined4 local_30;
undefined4 local_2c;
undefined4 local_28;
undefined4 local_24;
- 出力データ
undefined1 local_58 [8];
undefined4 local_50;
undefined4 local_4c;
のようにバラバラのローカル変数となってしまうので、以下の構造体を Ghidra に登録する。
/* 入力データ */
typedef struct {
DWORD32 abi_version_lo;
DWORD32 abi_version_hi;
DWORD32 unknown1_lo;
DWORD32 unknown1_hi;
DWORD32 unknown2_lo;
DWORD32 unknown2_hi;
DWORD32 unknown3_lo;
DWORD32 unknown3_hi;
} FUN_02003566_In;
/* 出力データ */
typedef struct struct {
DWORD32 unknown1_lo;
DWORD32 unknown1_hi;
DWORD32 unknown2_lo;
DWORD32 unknown4_hi;
} FUN_02003566_Out;
本当は全部 DWORD64 で定義したいのだが前述の Ghidra の 32bit+32bit バグを踏むから hi/lo で分けて定義している、そんで
local_40の型をFUN_02003566_In名前をinDataにlocal_58の型をFUN_02003566_Out名前をoutDataに
変更した結果がこちら。
void FUN_02003566(HANDLE param_1,DWORD32 param_2_lo,DWORD32 param_2_hi,DWORD32 param_3_lo,
DWORD32 param_3_hi,DWORD32 *param_6)
{
FUN_02003566_Out outData;
FUN_02003566_In inData;
DWORD local_c;
uint canary;
canary = __security_cookie ^ (uint)&stack0xfffffffc;
inData.abi_version_hi = 0;
inData.abi_version_lo = 2;
inData.unknown1_lo = param_2_lo;
inData.unknown1_hi = param_2_hi;
inData.unknown2_lo = param_3_lo;
inData.unknown2_hi = param_3_hi;
inData.unknown3_lo = *param_6;
inData.unknown3_hi = param_6[1];
DeviceIoControl(param_1,0xf7770000,&inData,0x20,&outData,0x10,&local_c,(LPOVERLAPPED)NULL);
*param_6 = outData.unknown3;
param_6[1] = outData.unknown4;
__security_check_cookie(canary ^ (uint)&stack0xfffffffc);
return;
}
よっしゃ謎のマジックナンバー入れてる変数が消えた。
ちなみに DeviceIoControl は Unix でいうところの ioctl で In/Out はドライバ固有なので構造体のメンバがそれぞれ何を意味するのかはデータシートでも手元に無いと判らん。
inData の先頭 8byte はおそらく ABI バージョンだとは想像つくが他はさっぱり、俺はドライバ屋でもハード屋でもないし。
kX Project 2 の Emu10k オープンソースドライバを読めばわかるかもしれないが、あれどうやって調べたんだろうな。
そして戻り値を返すはずなのに返さない問題、こいつは過去回でも解説したのだが Visual C/C++ の /GS オプションすなわち Guard Stack によるスタック破壊からのリターンアドレス書換攻撃を防ぐためのインジェクションコードが悪さをしてるせい。
- リターンアドレスの手前、スタックの底に置かれるカナリア、ローカル変数 canary
- 乱数の入ってるグローバル変数
__security_cookie - カナリア変数が破壊されてないかをチェックする CRT の関数
__security_check_cookie - スタックベースポインタを表す
stack0xfffffffcつまりEBP
こいつらは全部インジェクションコードなので Ghidra が消し込まないとならないのだがそこが盛大にバグっていることで発生する。
戻り値は 8byte(=64bit) のはずなのだが void に誤判定されてるからとりあえず DWORD64 に変更する。
DWORD64 FUN_02003566(HANDLE param_1,DWORD32 param_2_lo,DWORD32 param_2_hi,DWORD32 param_3_lo,
DWORD32 param_3_hi,DWORD32 *param_6)
{
undefined4 extraout_EAX;
undefined4 extraout_EDX;
…
uint canary;
canary = __security_cookie ^ (uint)&stack0xfffffffc;
…
__security_check_cookie(canary ^ (uint)&stack0xfffffffc);
return CONCAT44(extraout_EDX,extraout_EAX);
}
うーん謎の変数 extraout_EDX extraout_EAX が生えてしまった、そりゃ戻り値が 64bit なら EDX と EAX レジスタ使うけどさぁ。
過去回でもやった通り __security_check_cookie をインライン指定すると消えてしまった戻り値が見えるようになる。
return CONCAT44(extraout_EDX,outData.unknown1_lo);
なるほど、outDataの値を返してるようだ、しかし extraout_EDX は消えてくれんな。
俺の第六感が
return CONCAT44(outData.unknown2_hi,outData.unknown1_lo);
なんじゃね?と囁いているのでアセンブラ読むしかないっすね。
020035e7 ff 15 54 CALL dword ptr [->KERNEL32.DLL::DeviceIoControl] = 0000e350
10 00 02
020035ed 8b 47 08 MOV EAX,dword ptr [EDI + outData.unknown3]
020035f0 89 03 MOV dword ptr [EBX],EAX
020035f2 8b 47 0c MOV EAX,dword ptr [EDI + outData.unknown4]
020035f5 89 43 04 MOV dword ptr [EBX + 0x4],EAX
020035f8 8b 07 MOV EAX,dword ptr [EDI]=>outData
020035fa 8d 65 ec LEA ESP=>local_18,[EBP + -0x14]
020035fd 5f POP EDI
020035fe 5e POP ESI
020035ff 5b POP EBX
02003600 8b 4d fc MOV ECX,dword ptr [EBP + canary]
02003603 33 cd XOR ECX,EBP
02003605 e8 ec e9 CALL __security_check_cookie void __security_check_cookie(int
ff ff
assume FS_OFFSET = <UNKNOWN>
0200360a c9 LEAVE
0200360b c2 18 00 RET 0x18
assume FS_OFFSET = 0xffdff000
うーん EDX 触ってねえや、俺の第六感はカス…
そもそも戻り値が 8byte ってのが変なんだよね。
02003732 52 PUSH EDX
02003733 50 PUSH EAX
02003734 ff 71 0c PUSH dword ptr [ECX + 0xc]
02003737 ff 71 08 PUSH dword ptr [ECX + 0x8]
0200373a 56 PUSH ESI
0200373b e8 26 fe CALL FUN_02003566 undefined8 FUN_02003566(HANDLE p
ff ff
02003740 85 c0 TEST EAX,EAX
02003742 7d 08 JGE LAB_0200374c
呼出し前に PUSH EDX して退避してるとはいえ、呼出し後は TEST EAX,EAX だけで EDX レジスタ見てないし HRESULT か BOOL だよなこれ。
HRESULT FUN_02003566(HANDLE param_1,DWORD32 param_2_lo,DWORD32 param_2_hi,DWORD32 param_3_lo,
DWORD32 param_3_hi,DWORD32 *param_6)
{
…
return outData.unknown1_lo;
}
##
そんで上で 64bit バイナリの解析を元に SimpleFactory は __fastcall ではなく __stdcall と断定したのだけど、実は EDX の値参照する謎の条件分岐があるのだ。
IID local_18;
...
local_18.Data1 = param_3->Data1;
local_18.Data2 = param_3->Data2;
local_18.Data3 = param_3->Data3;
local_18.Data4._0_4_ = *(undefined4 *)param_3->Data4;
local_18.Data4._4_4_ = *(undefined4 *)(param_3->Data4 + 4);
iVar2 = FUN_0200b894(&local_18);
piVar2 = FUN_02002821(param_3,param_4,iVar1,(int *)&DAT_0200f8f0);
if ((piVar2 != (int *)0x0) && (iVar1 = FUN_02002d76((int)piVar2), iVar1 == 0)) {
switch(*(undefined4 *)(extraout_EDX + 0xc)) {
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
case 7:
case 8:
case 9:
case 10:
case 0xb:
case 0xc:
case 0xd:
case 0xe:
case 0xf:
piVar2 = (int *)(*(int *)(extraout_EDX + 8) + 4);
*piVar2 = *piVar2 + 1;
break;
case 6:
piVar2 = (int *)(*(int *)(extraout_EDX + 8) + 8);
*piVar2 = *piVar2 + 1;
}
*param_6 = *(undefined4 *)(extraout_EDX + 8);
param_2 = extraout_EDX;
goto LAB_02003474;
}
こいつもアセンブラ読むしかねえな。
02002ec9 50 PUSH EAX
02002eca a5 MOVSD ES:EDI,ESI
02002ecb e8 c4 89 CALL FUN_0200b894 int FUN_0200b894(int * param_1)
00 00
02002ed0 68 f0 f8 PUSH DAT_0200f8f0
00 02
02002ed5 50 PUSH EAX
02002ed6 ff 75 0c PUSH dword ptr [EBP + param_2]
02002ed9 ff 75 08 PUSH dword ptr [EBP + param_1]
02002edc e8 40 f9 CALL FUN_02002821 int * FUN_02002821(int param_1,
ff ff
02002ee1 8b d0 MOV EDX,EAX
02002ee3 85 d2 TEST EDX,EDX
02002ee5 74 35 JZ LAB_02002f1c
02002ee7 52 PUSH EDX
02002ee8 e8 89 fe CALL FUN_02002d76 undefined4 FUN_02002d76(int para
ff ff
02002eed 85 c0 TEST EAX,EAX
02002eef 75 2b JNZ LAB_02002f1c
02002ef1 8b 4a 0c MOV ECX,dword ptr [EDX + 0xc]
02002ef4 83 f9 0f CMP ECX,0xf
02002ef7 8b 42 08 MOV EAX,dword ptr [EDX + 0x8]
02002efa 77 0f JA switchD_02002efc::default
引数を右からスタックに積んで FUN_02002821 を呼び、戻り値 EAX を EDX においてゼロかどうかのテストで条件分岐やってる。
この MOV EDX,EAX を Ghidra は無視して新たな変数 extraout_EDX 生やしちゃってるけど piVar2 だよなぁこれ。
この部分のコードは GUID から対応する IUnknown 実装オブジェクトが生成済かをグローバル変数 DAT_0200f8f0 のリストから探している。
つまりこの SimpleFactory は Object Pooling パターンだけでなく Flyweight パターンないし Singleton でもあるみたい。
そして switch/case 文は GUID を元に Unko15 とそれ以外で処理を分岐してる。
switch (unko->unknown3) {
case 0x0: /* GUID_IUnknown */
case 0x1: /* GUID_Unko1 */
case 0x2: /* GUID_Unko2 */
case 0x3: /* GUID_Unko3 */
case 0x4: /* GUID_Unko4 */
case 0x5: /* GUID_Unko5 */
case 0x7: /* GUID_Unko6 */
case 0x8: /* GUID_Unko7 */
case 0x9: /* GUID_Unko8 */
case 0xa: /* GUID_Unko9 */
case 0xb: /* GUID_Unko10 */
case 0xc: /* GUID_Unko11 */
case 0xd: /* GUID_Unko12 */
case 0xe: /* GUID_Unko13 */
case 0xf: /* GUID_Unko14 */
…
piVar1 = (int *)(unko->unknown2 + 4);
*piVar1 = *piVar1 + 1;
break;
case 6:
piVar1 = (int *)(unko->unknown2 + 8);
*piVar1 = *piVar1 + 1;
break;
case 0x6: /* GUID_Unko15 */
…
break;
}
…
前回 Unko15 には仮想関数テーブルが 2 つ実装されているという話をしたけど、この部分は GUID に応じてメンバのオフセットを変える Thunking 処理ということですな。
要するに多重継承のキャストと同じである。
次回
このままクッソ地味な作業が続くので、先に書換禁止の .text でなく書換可能な .data からインタフェース名がらしきものがボロボロ出てきたからそこ調べるかな。
0200f344 ds "IDrvAdapterMgr" s_IDrvAdapterMgr_0200f344
0200f368 ds "IDrvDevice" s_IDrvDevice_0200f368
0200f38c ds "IDrvProfile" s_IDrvProfile_0200f38c
0200f3b0 ds "IDrvBMixer" s_IDrvBMixer_0200f3b0
0200f3d4 ds "IDrvBMixerLine" s_IDrvBMixerLine_0200f3d4
0200f3f8 ds "ICallbackManager" s_ICallbackManager_0200f3f8
0200f41c ds "IDrvWaveOut" s_IDrvWaveOut_0200f41c
0200f440 ds "IDrvWaveOutStream" s_IDrvWaveOutStream_0200f440
0200f464 ds "IDrvSBLive" s_IDrvSBLive_0200f464
0200f488 ds "IDrvRemote" s_IDrvRemote_0200f488
0200f4ac ds "IDrvFeatureSet" s_IDrvFeatureSet_0200f4ac
0200f4d0 ds "IUnknown" s_IUnknown_0200f4d0
0200f4f4 ds "IDrvALDevice" s_IDrvALDevice_0200f4f4
0200f518 ds "IDrvALContext" s_IDrvALContext_0200f518
0200f560 ds "IDrvBMixerLine2" s_IDrvBMixerLine2_0200f560
02001168 s_IDriver_02001168 ds "IDriver" s_IDriver_02001168
0200161c s_IDrvALContext_0200161c ds "IDrvALContext" s_IDrvALContext_0200161c
02001500 s_IDrvALDevice_02001500 ds "IDrvALDevice" s_IDrvALDevice_02001500
020013b8 s_IDrvDevice_020013b8 ds "IDrvDevice" s_IDrvDevice_020013b8