概述
所有分析基于 X-VERSE-X 的 chusanApp.exe0x400000
仅供学习参考
State Machine 架构
整体结构
CHUNITHM 用了一套层级式的 state machine 来管理启动流程
Root StateMachine
├─ State 0: PowerOn
│ ├─ State 0: PowerOnLoad (XML asset loading)
│ └─ State 1: PowerOnTest (hardware diagnostics)
└─ State 1: Initialize
├─ State 0: InitializeMecha (device tests)
└─ State 1: InitializeCollab (network + dist server)
State Machine Base Class
函数StateMachineBase_ctor @ 0xA33E10
反编译签名长这样
StateMachineBase* __thiscall StateMachineBase_ctor(
StateMachineBase* this,
int parentSM,
int stateCount // ← the key parameter
)
核心逻辑
this->field_5 = parentSM;
this->field_6 = stateCount; // Stored at this+0x18
// ... initialization of factory registry ...
从 xref 推断出的 state transition 逻辑
bool StateMachine::IsComplete() {
return (this->currentState >= this->stateCount);
}
当 currentState >= stateCount 时
分析方法
Step 1: 字符串
第一步是在 IDA Pro 里搜索已知的 state 名称字符串
0x18E9B18: "PowerOnTest"
0x18E9BBC: "InitializeMecha"
0x18E9BD0: "InitializeCollab"
0x1C3A200: ".?AV?$FactoryConstructor@VPowerOnTest@projGame@@..."
0x1C3A328: ".?AV?$FactoryConstructor@VInitializeMecha@projGame@@..."
0x1C3A398: ".?AV?$FactoryConstructor@VInitializeCollab@projGame@@..."
RTTI 字符串暴露了 FactoryConstructor<StateClass> 的 template 实例化
Step 2: xrefs 分析
对字符串 "PowerOnTest" 做 xref0xD9D5B3 附近找到了用法
d9d5b3 push 1 ; state index
d9d5b5 mov ecx, esi ; this = PowerOn SM
d9d5b7 call sub_44C05A ; factory registration function
push <index>; mov ecx, <sm>; call <register_func> 这个 pattern 就是 factory registration 的特征 —— 每个 child state 在构建期间都会把自己注册到 parent SM 里
Step 3: Constructor 追踪
从 0xD9D5B3 往上追PowerOnSM_ctor @ 0xD9D4400xD9D46A 处的 constructor prologue
d9d46a push 2 ; ← stateCount = 2
d9d46c push [ebp+arg_0] ; parentSM
d9d46f call j_StateMachineBase_ctor
反编译出来是这样
int __thiscall PowerOnSM_ctor(int this, int a2, int a3) {
j_StateMachineBase_ctor(a2, 2); // ← stateCount hardcoded as immediate
*this = &earth::StateMachine<projGame::PowerOn>::`vftable';
// ... vtable setup, member initialization ...
return this;
}
state count 是以 immediate value 的形式 push 进去的push 2
Step 4: Copyright 定位
其实这个自己并没有找到
反编译上下文
char __thiscall InitializeCollab_Execute(_DWORD* this) {
// ... device checks ...
if (!IsCopyrightWarningAcknowledged_thunk()) {
return 0; // Block progression
}
// ... proceed with initialization ...
this[5] = 2; // Transition to next state
return 1;
}
顺着 thunk 找到目标函数 @ 0x7D4CA0
7d4ca0 xor al, al ; return 0
7d4ca2 retn
char IsCopyrightWarningAcknowledged() {
return 0; // Always returns "not acknowledged"
}
这是个永远返回 false 的 stub
Patch 分析
Patch 1: PowerOn State Count
Patch0xD9D46BPowerOnSM_ctor 里6A 02push 26A 01push 1
; BEFORE:
d9d46a push 2 ; stateCount = 2 (PowerOnLoad, PowerOnTest)
d9d46c push [ebp+arg_0]
d9d46f call j_StateMachineBase_ctor
; AFTER:
d9d46a push 1 ; stateCount = 1 (PowerOnLoad only)
d9d46c push [ebp+arg_0]
d9d46f call j_StateMachineBase_ctor
PowerOnLoadcurrentState 自增到 11 >= 1 成立>= 而不是 ==
Patch 2: PowerOnTest Factory Registration NOP
Patch0xD9D5B3PowerOn_ctor 里6A 01 8B CE E8 9E EA 6A FF 全替换成 NOP
; BEFORE:
d9d5b3 push 1 ; state index = 1
d9d5b5 mov ecx, esi ; this = PowerOn SM
d9d5b7 call sub_44C05A ; RegisterStateFactory(1, PowerOnTest)
d9d5bc mov eax, esi
; AFTER:
d9d5b3 nop ; 9 bytes of NOP
d9d5bc mov eax, esi
这个 patch 和 Patch 1 是冗余的
Patch 3: Initialize State Count
Patch0xD9DF5BInitializeSM_ctor 里6A 02 改成 6A 00
; BEFORE:
d9df5a push 2 ; stateCount = 2 (InitializeMecha, InitializeCollab)
d9df5c push [ebp+arg_0]
d9df5f call j_StateMachineBase_ctor
; AFTER:
d9df5a push 0 ; stateCount = 0 (no states)
d9df5c push [ebp+arg_0]
d9df5f call j_StateMachineBase_ctor
stateCount = 0 的时候currentState = 00 >= 0 立刻成立
Patch 4: Initialize Factory Registrations NOP
Patch0xD9E0A8Initialize_ctor 里
; BEFORE:
d9e0a8 push 0 ; state index = 0
d9e0aa mov ecx, esi
d9e0ac call j_InitializeMecha_factoryReg ; 9 bytes total
d9e0b1 push 1 ; state index = 1
d9e0b3 mov ecx, esi
d9e0b5 call j_InitializeCollab_factoryReg ; 9 bytes total
d9e0ba mov eax, esi
; AFTER:
d9e0a8 nop ; 18 bytes of NOP
d9e0ba mov eax, esi
两个 factory registration 恰好相邻
Patch 5: Copyright Bypass
Patch0x7D4CA0IsCopyrightWarningAcknowledged32 C0xor al, alB0 01mov al, 1
; BEFORE:
7d4ca0 xor al, al ; AL = 0 (false)
7d4ca2 retn
; AFTER:
7d4ca0 mov al, 1 ; AL = 1 (true)
7d4ca2 retn
验证与测试
静态验证
指令长度合计
Patch 1: 1 字节 (0xD9D46B: 02 → 01)
Patch 2: 9 字节 (0xD9D5B3: push+mov+call → NOP×9)
Patch 3: 1 字节 (0xD9DF5B: 02 → 00)
Patch 4: 18 字节 (0xD9E0A8: 2×(push+mov+call) → NOP×18)
Patch 5: 2 字节 (0x7D4CA0: xor al,al → mov al,1)
所有被 NOP 掉的区域都包含完整的 call 指令
动态测试
结果stateCount = 0 和 stateCount = 1 的情况都能优雅地处理
技术附录
A. Patch 数据
Patch 1 @ 0xD9D46B:
Original: 02
Patched: 01
Patch 2 @ 0xD9D5B3:
Original: 6A 01 8B CE E8 9E EA 6A FF
Patched: 90 90 90 90 90 90 90 90 90
Patch 3 @ 0xD9DF5B:
Original: 02
Patched: 00
Patch 4 @ 0xD9E0A8:
Original: 6A 00 8B CE E8 DC C5 6A FF 6A 01 8B CE E8 B3 BB 67 FF
Patched: 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
Patch 5 @ 0x7D4CA0:
Original: 32 C0
Patched: B0 01
B. State Machine Vtable 结构
PowerOn SM vtable @ 0x18D3xxx
struct StateMachine_PowerOn_vtable {
void* dtor;
void* Execute;
void* OnEnter;
void* OnExit;
void* GetStateName;
// ... additional virtual methods ...
};
State 执行流
while (!sm->IsComplete()) {
sm->vtable->Execute(sm);
sm->currentState++;
}
C. Factory Registration 机制
sub_44C05Asub_D9D190
void __thiscall RegisterStateFactory(
StateMachine* parentSM,
int stateIndex,
FactoryConstructor* factory
);
效果是在 parentSM->factoryRegistry[stateIndex] 里添加一个条目
D. Patch 应用代码
void ApplyEarlyPatches() {
DWORD oldProtect;
// Patch 1: PowerOn state count 2 → 1
VirtualProtect((void*)0xD9D46B, 1, PAGE_EXECUTE_READWRITE, &oldProtect);
*(BYTE*)0xD9D46B = 0x01;
VirtualProtect((void*)0xD9D46B, 1, oldProtect, &oldProtect);
// Patch 2: NOP PowerOnTest factory registration
VirtualProtect((void*)0xD9D5B3, 9, PAGE_EXECUTE_READWRITE, &oldProtect);
memset((void*)0xD9D5B3, 0x90, 9);
VirtualProtect((void*)0xD9D5B3, 9, oldProtect, &oldProtect);
// Patch 3: Initialize state count 2 → 0
VirtualProtect((void*)0xD9DF5B, 1, PAGE_EXECUTE_READWRITE, &oldProtect);
*(BYTE*)0xD9DF5B = 0x00;
VirtualProtect((void*)0xD9DF5B, 1, oldProtect, &oldProtect);
// Patch 4: NOP both Initialize factory registrations
VirtualProtect((void*)0xD9E0A8, 18, PAGE_EXECUTE_READWRITE, &oldProtect);
memset((void*)0xD9E0A8, 0x90, 18);
VirtualProtect((void*)0xD9E0A8, 18, oldProtect, &oldProtect);
// Patch 5: Copyright check xor al,al → mov al,1
VirtualProtect((void*)0x7D4CA0, 2, PAGE_EXECUTE_READWRITE, &oldProtect);
*(BYTE*)0x7D4CA0 = 0xB0;
*(BYTE*)0x7D4CA1 = 0x01;
VirtualProtect((void*)0x7D4CA0, 2, oldProtect, &oldProtect);
}
在 DllMain 的 DLL_PROCESS_ATTACH 里调用VirtualProtect 临时把内存页标记为可写
E. 版本可移植性
对于未来版本
DWORD FindPowerOnCtor() {
// Search for "PowerOn" string
DWORD strAddr = FindString("PowerOn");
// Find xrefs to string
DWORD* xrefs = GetXrefs(strAddr);
// Locate constructor via xref analysis
return LocateConstructor(xrefs);
}
// Pattern: push 2; push [ebp+arg]; call <ctor>
BYTE pattern[] = {0x6A, 0x02, 0xFF, 0x75, '?', 0xE8, '?', '?', '?', '?'};
DWORD addr = AOBScan(pattern, sizeof(pattern));
bool ValidatePowerOnCtor(DWORD addr) {
// Verify vtable assignment follows constructor call
if (*(BYTE*)(addr + 5) != 0x8B) return false; // mov ecx, esi
if (*(BYTE*)(addr + 10) != 0xC7) return false; // mov [esi], vtable
return true;
}
F. 已知局限性
所有 patch 用的都是硬编码的绝对地址
此外
另外
总结
修改 control flow 的 metadata
鸣谢
- tangent90 协助我了很多前备知识
。 - CaochengQWQ 协助了很多测试和游戏知识
以及出给我了他的 chupico 手台, 。 - 所有的群 u 们
。