20260729

0332睡到0756睡到1030睡到1114,睡觉好舒服,梦到有了能心照不宣的队友。

1
2
        /** Gets the method identified by the given name and parameter count. */
        method<T extends Il2Cpp.Method.ReturnType>(name: string, parameterCount?: number): Il2Cpp.Method<T>;

frida-il2cpp-bridge 的 method() 只支持按名字 + 参数数量筛选,没有直接按参数类型 overload 的 API。

1
2
3
public static AbilityCompatibility GetAbilityCompatibility(TSKBattleNote attackNote, TSKBattleNote targetNote, TeamType attackTeam) { }

public static AbilityCompatibility GetAbilityCompatibility(int attribute_1, int attribute_2, TeamType attackTeam) { }

所以这个要怎么办呢。

这样:

1
2
3
4
5
6
    const method = TSKBattleUtility.methods.find(
      (m) =>
        m.name === "GetAbilityCompatibility" &&
        m.parameters[0].type.name === "TSKBattleNote" &&
        m.parameters[1].type.name === "TSKBattleNote"
    );

1
2
3
[23:00:05] args[8] kind (TSKBattleAttack.AttackType) = 0x1
...
[23:00:09] args[8] kind (TSKBattleAttack.AttackType) = 0x21500000004

有时正常而有时发狂的enum,下面明显是4,但有了很怪的前缀。原因是用 arg.toString() 打印 NativePointer,所以该用toInt32()?但enum又无法根据type直接看出来..

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
    const TSKBattleUtility = Il2Cpp.domain
      .assembly("Assembly-CSharp")
      .image.class("TSKBattleUtility");
    const GetAbilityCompatibility = TSKBattleUtility.methods.find(
      (m) =>
        m.name === "GetAbilityCompatibility" &&
        m.parameters[0].type.name === "TSKBattleNote" &&
        m.parameters[1].type.name === "TSKBattleNote"
    );
    if (!GetAbilityCompatibility) {
      log("GetAbilityCompatibility not found");
      return;
    }
    log(GetAbilityCompatibility.toString());
    const result = GetAbilityCompatibility?.invoke(attack, defence, teamType);
    log(`GetAbilityCompatibility result = ${result?.toString()}`);

这样能调用游戏内的函数,但其实如果只是想监听想知道数值的话直接trace会更方便。而且一开始log时[00:04:54] GetAbilityCompatibility result = None,我还以为我invoke失败了,后来才发现是因为返回值就是None:

1
2
3
4
5
enum AbilityCompatibility {
  None = 0,
  Week = 1,
  Resist = 2,
}

不过trace时也会有噪音,我可能只想知道TSKBattleCalculationManager.CaluculationNormalDamageTSKBattleUtility.GetAbilityCompatibility的情况,但后者也有可能在别的地方被调用到,会污染log..

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
          fVar15 = (float)TSKBattleCalculationManager$$FluctuationOffset(0);
          fVar16 = (float)TSKBattleCalculationManager$$RushOffset(rushCount,attack,*compatibility,0)
          ;
          critical = TSKBattleNote$$GetAttack(attack,0,0);
          fVar17 = (float)TSKBattleCalculationManager$$AttributeOffset
                                    (*compatibility,attack,beforeRushCount,0);
          fVar18 = (float)TSKBattleCalculationManager$$CriticalOffset
                                    (*isCritical != '\0',attack,param_10,beforeRushCount,0);
          fVar19 = (float)TSKBattleCalculationManager$$DownOffset(defence,attack,0);
          if (*(int *)(System.Math_TypeInfo + 0xe0) == 0) {
            FUN_180297050();
          }
          dVar20 = ceil((double)((float)critical * fVar17 * fVar18 * skillValue * fVar16 * fVar15 *
                                fVar19));

TSKBattleCalculationManager.CaluculationNormalDamage中上述这些变量都是和涉及到最终伤害的数据,一开始还想自己invoke来算,现在想来还是trace可能更方便,进入TSKBattleCalculationManager.CaluculationNormalDamage时全局变量设计个flag,然后trace后面的函数就行了?

用il2cpp和ghidra时可以直接看offset然后

1
2
3
    const teamPtr = attack.handle.add(0x28).readPointer();
    const team = new Il2Cpp.Object(teamPtr);
    const teamType = team.handle.add(0x28).readS32();

如果只用frida动态分析的话可能还得打印class然后field(’team')

1
2
3
[01:00:13] enter TSKBattleCalculationManager.FluctuationOffset
[01:00:13] FluctuationOffset (CaluculationNormalDamage) = 0x215f3268a50
[01:00:13] {"pc":"0x7fff353a5430","sp":"0x2142f7ef08","rax":"0x215f3268a50","rcx":"0x11","rdx":"0x0","rbx":"0x0","rsp":"0x2142f7ef08","rbp":"0x2142f7f289","rsi":"0x215a0e6c0f4","rdi":"0x2158a42b240","r8":"0xd16a77a6","r9":"0x2130007e180","r10":"0x30","r11":"0x2142f7ee40","r12":"0x17","r13":"0x17","r14":"0x0","r15":"0x2158da52b40","rip":"0x7fff353a5430"}

float TSKBattleCalculationManager$$FluctuationOffset(void)没能通过trace得到正确返回值,打印log(JSON.stringify(this.context));也没看到像是正确的值,它似乎给的是"rax":"0x215f3268a50",而float不存在这里?Windows x64 ABI决定float存在XMM0。ghidra中return时也是180c2df37 f3 0f 58 05 ... ADDSS XMM0,dword ptr [DAT_183b16444],但我好像获取不到它。

1
2
        const f = new NativeFunction(method.virtualAddress, "float", []);
        console.log(f());

直接调用测试得到的结果:

1
2
3
4
5
6
1.0091999769210815
1.0003000497817993
1.0069999694824219
1.003999948501587
1.0053000450134277
1.003600001335144

基本可以说明[wiki]中的1.00倍~1.01倍(100%~101%)の範囲内で変動する。是对的;也说明这里的retval是有问题的。所以是frida-il2cpp-bridge的问题吗?

frida-il2cpp-bridge的某个pr中作者有写:

1
2
3
4
5
The main reason why I didn't use `Interceptor.attach` is because Frida doesn't support reading/writing floating-point values (`System.Single`, `System.Double`) - such values live in different CPU registers, and Frida doesn't expose them (yet). So, the only option was `Interceptor.replace`.

Ref: https://github.com/frida/frida/issues/266#issuecomment-294729705

However, a lot of time passed since that, and Frida might have implemented the support for it.

似乎是这个问题,但也不太像。。一翻log翻到了更诡异的:

1
2
3
[23:30:11] args[4] skillValue (System.Single) = 4.320000171661377
...
[23:30:11] [CaluculationNormalDamage]: baseAttack=2747 attack=5659(ignore charge) critical=2350 beforeRushCount=2 skillValue=2.802596928649634e-45

怎么同一时间还能不同值的,我急了。288+144=432,说明前者的skillValue肯定是读对了,但后面是怎么回事。

前者是直接function dumpArgs(method: Il2Cpp.Method, args: InvocationArguments) {,后者是function parseArgument(arg: NativePointer, typeName: string): unknown {,本质都是value = intBitsToFloat(arg.toString());,所以是后面读错寄存器了?为什么前面能是正确的??

…这里是我的问题 const skillValue = parseArgument(args[4], "float");被我写成 const skillValue = parseArgument(args[3], "float");了,生气了。。反正参数中是能正确读float的。明天再说吧。

Licensed under CC BY-NC-SA 4.0
使用 Hugo 构建
主题 StackJimmy 设计