从反编译的 ASM 中获取信息
Get informations from decompiled ASM
我愿意在一个3D小游戏上修改相机坐标。我已经能够找到三个函数,每个轴一个。
我们称它们为 CameraX、CameraY 和 CameraZ。当我发现我遗漏了一些东西时,我一直只使用第一个。
这是从 Ghidra 获得的 ASM 指令:
*************************************************************
* FUNCTION
*************************************************************
undefined1 __register CameraX (undefined2 x)
undefined1 AL:1 <RETURN>
undefined2 AX:2 x
undefined1 Stack[-0x14] local_14 XREF[8]: 00478abe (*) ,
00478aca (*) ,
00478b44 (*) ,
00478b53 (*) ,
00478bb3 (*) ,
00478bbf (*) ,
00478c1d (*) ,
00478c29 (*)
undefined4 Stack[-0x18] local_18 XREF[4]: 00478ae8 (R) ,
00478b6e (R) ,
00478bdd (R) ,
00478c47 (R)
undefined4 Stack[-0x1c] local_1c XREF[4]: 00478ae1 (R) ,
00478b67 (R) ,
00478bd6 (R) ,
00478c40 (R)
undefined4 Stack[-0x20] local_20 XREF[8]: 00478ace (*) ,
00478ada (R) ,
00478b57 (*) ,
00478b60 (R) ,
00478bc3 (*) ,
00478bcf (R) ,
00478c2d (*) ,
00478c39 (R)
undefined8 Stack[-0x28] local_28 XREF[4,2]: 004789ec (*) ,
004789f5 (*) ,
00478b06 (*) ,
00478b0f (*) ,
004789f1 (W) ,
00478b0b (W)
undefined4 Stack[-0x2c] local_2c XREF[1]: 00478b40 (*)
CameraX XREF[1]: FUN_0047a280:0047a291 (c)
004789e0 53 PUSH EBX
004789e1 56 PUSH ESI
004789e2 83 c4 e0 ADD ESP ,-0x20
004789e5 8b f0 MOV ESI ,x
004789e7 e8 68 9b CALL FUN_00462554 undefined FUN_00462554()
fe ff
004789ec 89 04 24 MOV dword ptr [ESP ]=> local_28 ,x
004789ef 33 c0 XOR x,x
004789f1 89 44 24 MOV dword ptr [ESP + local_28 +0x4 ],x
04
004789f5 df 2c 24 FILD qword ptr [ESP ]=> local_28
004789f8 dc 66 08 FSUB qword ptr [ESI + 0x8 ]
004789fb d9 1d 18 FSTP dword ptr [DAT_00871218 ] = ??
12 87 00
...
...
...
00478c4e 8b c3 MOV x,EBX
00478c50 83 c4 20 ADD ESP ,0x20
00478c53 5e POP ESI
00478c54 5b POP EBX
00478c55 c3 RET
我知道:
- 我的可执行文件是 32 位的,在 Delphi.
中生成
- x 是新的 x 轴值。
我的目标是将此函数与注入的 dll 一起使用。我来到这里 :
typedef int (__stdcall *_CameraX)(int x);
_CameraX CameraX = (_CameraX)0x04789E0;
但没办法,我在 004789fb FSTP dword ptr [DAT_00871218]
线上有一个 "access violation"。这是 x 值的第一次使用。所以我猜是错误的类型。
这是我的理解:
- 由于原始程序是用 Delphi 编写的,因此想到了 __pascal 调用约定。由于它在 Visual studio 中已弃用,我正在使用 __stdcall。我假设,因为只有一个参数*,所以不会有任何区别。
- x 总是一个很大的数字。如果不是 32 位,我会选择 long。
- 我真的不知道 return 类型。我选择 int 是因为调用者函数在调用后立即生成 test al, al
。
* : Ghidra 告诉我只有一个参数,但如果我自己听的话,会有 2 个:
004789e0 53 PUSH EBX
004789e1 56 PUSH ESI
...
...
00478c53 5e POP ESI
00478c54 5b POP EBX
00478c55 c3 RET
所以这是我的问题:
- 真的只有一个参数吗?
- 如果正确的调用约定是 __pascal,我应该使用哪个调用约定,因为它不能用于 Visual Studio? (如果参数不止一个)
- 检索 return 值的方法是什么?
- 为什么我们 "invented" 有这么多调用约定?例如,为什么我们不都使用 __cdecl?为什么有些人使用从右到左,而有些人从左到右阅读?有什么不同吗?
我很确定缺少一些信息,Ghidra 生成的伪代码有用吗?
编辑:
int CameraX(int x)
{
undefined4 *puVar1;
uint uVar2;
undefined4 unaff_EBX;
int iVar3;
float10 in_ST0;
undefined4 local_20;
undefined4 local_1c;
undefined4 local_18;
undefined local_14 [12];
uVar2 = FUN_00462554();
DAT_00871218 = (float)(ulonglong)uVar2 - (float)*(double *)(x + 8);
iVar3 = CONCAT31((int3)((uint)unaff_EBX >> 8),1);
if (*(float *)(x + 0x6c) < DAT_00871218) {
// ...
编辑 2:
这是完整的 ASM 代码:
https://pastebin.com/UiGGEju1
这是 Ghidra 生成的伪代码:
https://pastebin.com/1Fc48k1g
所以我想我错了:我认为这不是导致这个问题的原因,而是这一行:00478c3d 89 46 1c MOV dword ptr [ESI + 0x1c],x
但它仍然是我的 x 值导致的。
我不明白的是 "why":x 是一个 int(32 位),程序试图将它存储为 [ESI + 0x1c] 中的双字(32 位)。程序是否可能无法解析 where/what is x ? (就像我不带参数调用函数一样)
顺便提一下这个问题:“- 为什么我们 "invented" 有这么多调用约定?为什么我们不都使用 __cdecl,例如?为什么有些人使用从右到左,当其他一些人是从左到右阅读的?有什么不同吗?
还没有人回答我真的很感兴趣,如果你有解释,我很乐意听听!
这看起来像是在使用 Borland 寄存器调用 约定(也称为 Borland fastcall
):
- 前三个参数分别为
EAX
、EDX
、ECX
;
- 第 4 个及以上的参数入栈;
- 被调用者必须保留
EBX
、ESI
、EDI
和 EBP
寄存器;
EAX
用作 32 位整数的 return 方法;
- ...以及一些不适用于此处的其他规则。
您可以看到您的函数保存了 EBX
和 ESI
寄存器,也没有使用 EDI
和 EBP
。它还使用 EAX
作为第一个参数 x
并且 return 也是 EAX
中的结果,按照惯例。
现代 Visual Studio 中没有 Borland fastcall
的等效调用约定。您可能会依赖使用内联汇编作为调用此 Delphi 函数的解决方法。
此解决方法可能会使用临时 Microsoft fastcall
函数指针,因为它还使用寄存器作为第一个参数,EAX
除外,我们必须使用内联汇编传递它,类似(不确定代码语法,将其视为伪代码):
typedef void (__fastcall *_CameraX)();
_CameraX CameraXTemp = (_CameraX)0x04789E0;
int CameraX(int x) {
int ret;
__asm mov eax, x
CameraXTemp();
__asm mov ret, eax
return ret;
}
请注意,如果此方法有效,您将需要反转接下来的两个参数(对于具有 2 个或 3 个参数的函数),因为 Microsoft 的 fastcall
期望 ECX
然后 EDX
按照这个顺序。对于 4 个或更多参数,我假设需要一些堆栈工作。
另一种方法是使用 naked 函数,我认为它看起来像这样:
__declspec(naked) int __stdcall CameraX(int x)
{
__asm
{
mov eax, [esp + 4] // x
push ebx
mov ebx, 0x04789E0
call ebx
pop ebx
ret 4 // 1 argument in this __stdcall function (1 * 4)
}
}
为了将来的目的,如果您将此方法与另一个具有多个参数的函数一起使用,则需要使用 RET n
,其中 n
是 number of arguments * 4
。对于 3 个或更多参数,您还必须像前一种方法一样将参数压入堆栈。
我愿意在一个3D小游戏上修改相机坐标。我已经能够找到三个函数,每个轴一个。 我们称它们为 CameraX、CameraY 和 CameraZ。当我发现我遗漏了一些东西时,我一直只使用第一个。
这是从 Ghidra 获得的 ASM 指令:
*************************************************************
* FUNCTION
*************************************************************
undefined1 __register CameraX (undefined2 x)
undefined1 AL:1 <RETURN>
undefined2 AX:2 x
undefined1 Stack[-0x14] local_14 XREF[8]: 00478abe (*) ,
00478aca (*) ,
00478b44 (*) ,
00478b53 (*) ,
00478bb3 (*) ,
00478bbf (*) ,
00478c1d (*) ,
00478c29 (*)
undefined4 Stack[-0x18] local_18 XREF[4]: 00478ae8 (R) ,
00478b6e (R) ,
00478bdd (R) ,
00478c47 (R)
undefined4 Stack[-0x1c] local_1c XREF[4]: 00478ae1 (R) ,
00478b67 (R) ,
00478bd6 (R) ,
00478c40 (R)
undefined4 Stack[-0x20] local_20 XREF[8]: 00478ace (*) ,
00478ada (R) ,
00478b57 (*) ,
00478b60 (R) ,
00478bc3 (*) ,
00478bcf (R) ,
00478c2d (*) ,
00478c39 (R)
undefined8 Stack[-0x28] local_28 XREF[4,2]: 004789ec (*) ,
004789f5 (*) ,
00478b06 (*) ,
00478b0f (*) ,
004789f1 (W) ,
00478b0b (W)
undefined4 Stack[-0x2c] local_2c XREF[1]: 00478b40 (*)
CameraX XREF[1]: FUN_0047a280:0047a291 (c)
004789e0 53 PUSH EBX
004789e1 56 PUSH ESI
004789e2 83 c4 e0 ADD ESP ,-0x20
004789e5 8b f0 MOV ESI ,x
004789e7 e8 68 9b CALL FUN_00462554 undefined FUN_00462554()
fe ff
004789ec 89 04 24 MOV dword ptr [ESP ]=> local_28 ,x
004789ef 33 c0 XOR x,x
004789f1 89 44 24 MOV dword ptr [ESP + local_28 +0x4 ],x
04
004789f5 df 2c 24 FILD qword ptr [ESP ]=> local_28
004789f8 dc 66 08 FSUB qword ptr [ESI + 0x8 ]
004789fb d9 1d 18 FSTP dword ptr [DAT_00871218 ] = ??
12 87 00
...
...
...
00478c4e 8b c3 MOV x,EBX
00478c50 83 c4 20 ADD ESP ,0x20
00478c53 5e POP ESI
00478c54 5b POP EBX
00478c55 c3 RET
我知道:
- 我的可执行文件是 32 位的,在 Delphi.
中生成
- x 是新的 x 轴值。
我的目标是将此函数与注入的 dll 一起使用。我来到这里 :
typedef int (__stdcall *_CameraX)(int x);
_CameraX CameraX = (_CameraX)0x04789E0;
但没办法,我在 004789fb FSTP dword ptr [DAT_00871218]
线上有一个 "access violation"。这是 x 值的第一次使用。所以我猜是错误的类型。
这是我的理解:
- 由于原始程序是用 Delphi 编写的,因此想到了 __pascal 调用约定。由于它在 Visual studio 中已弃用,我正在使用 __stdcall。我假设,因为只有一个参数*,所以不会有任何区别。
- x 总是一个很大的数字。如果不是 32 位,我会选择 long。
- 我真的不知道 return 类型。我选择 int 是因为调用者函数在调用后立即生成 test al, al
。
* : Ghidra 告诉我只有一个参数,但如果我自己听的话,会有 2 个:
004789e0 53 PUSH EBX
004789e1 56 PUSH ESI
...
...
00478c53 5e POP ESI
00478c54 5b POP EBX
00478c55 c3 RET
所以这是我的问题:
- 真的只有一个参数吗?
- 如果正确的调用约定是 __pascal,我应该使用哪个调用约定,因为它不能用于 Visual Studio? (如果参数不止一个)
- 检索 return 值的方法是什么?
- 为什么我们 "invented" 有这么多调用约定?例如,为什么我们不都使用 __cdecl?为什么有些人使用从右到左,而有些人从左到右阅读?有什么不同吗?
我很确定缺少一些信息,Ghidra 生成的伪代码有用吗?
编辑:
int CameraX(int x)
{
undefined4 *puVar1;
uint uVar2;
undefined4 unaff_EBX;
int iVar3;
float10 in_ST0;
undefined4 local_20;
undefined4 local_1c;
undefined4 local_18;
undefined local_14 [12];
uVar2 = FUN_00462554();
DAT_00871218 = (float)(ulonglong)uVar2 - (float)*(double *)(x + 8);
iVar3 = CONCAT31((int3)((uint)unaff_EBX >> 8),1);
if (*(float *)(x + 0x6c) < DAT_00871218) {
// ...
编辑 2:
这是完整的 ASM 代码: https://pastebin.com/UiGGEju1 这是 Ghidra 生成的伪代码: https://pastebin.com/1Fc48k1g
所以我想我错了:我认为这不是导致这个问题的原因,而是这一行:00478c3d 89 46 1c MOV dword ptr [ESI + 0x1c],x
但它仍然是我的 x 值导致的。
我不明白的是 "why":x 是一个 int(32 位),程序试图将它存储为 [ESI + 0x1c] 中的双字(32 位)。程序是否可能无法解析 where/what is x ? (就像我不带参数调用函数一样)
顺便提一下这个问题:“- 为什么我们 "invented" 有这么多调用约定?为什么我们不都使用 __cdecl,例如?为什么有些人使用从右到左,当其他一些人是从左到右阅读的?有什么不同吗? 还没有人回答我真的很感兴趣,如果你有解释,我很乐意听听!
这看起来像是在使用 Borland 寄存器调用 约定(也称为 Borland fastcall
):
- 前三个参数分别为
EAX
、EDX
、ECX
; - 第 4 个及以上的参数入栈;
- 被调用者必须保留
EBX
、ESI
、EDI
和EBP
寄存器; EAX
用作 32 位整数的 return 方法;- ...以及一些不适用于此处的其他规则。
您可以看到您的函数保存了 EBX
和 ESI
寄存器,也没有使用 EDI
和 EBP
。它还使用 EAX
作为第一个参数 x
并且 return 也是 EAX
中的结果,按照惯例。
现代 Visual Studio 中没有 Borland fastcall
的等效调用约定。您可能会依赖使用内联汇编作为调用此 Delphi 函数的解决方法。
此解决方法可能会使用临时 Microsoft fastcall
函数指针,因为它还使用寄存器作为第一个参数,EAX
除外,我们必须使用内联汇编传递它,类似(不确定代码语法,将其视为伪代码):
typedef void (__fastcall *_CameraX)();
_CameraX CameraXTemp = (_CameraX)0x04789E0;
int CameraX(int x) {
int ret;
__asm mov eax, x
CameraXTemp();
__asm mov ret, eax
return ret;
}
请注意,如果此方法有效,您将需要反转接下来的两个参数(对于具有 2 个或 3 个参数的函数),因为 Microsoft 的 fastcall
期望 ECX
然后 EDX
按照这个顺序。对于 4 个或更多参数,我假设需要一些堆栈工作。
另一种方法是使用 naked 函数,我认为它看起来像这样:
__declspec(naked) int __stdcall CameraX(int x)
{
__asm
{
mov eax, [esp + 4] // x
push ebx
mov ebx, 0x04789E0
call ebx
pop ebx
ret 4 // 1 argument in this __stdcall function (1 * 4)
}
}
为了将来的目的,如果您将此方法与另一个具有多个参数的函数一起使用,则需要使用 RET n
,其中 n
是 number of arguments * 4
。对于 3 个或更多参数,您还必须像前一种方法一样将参数压入堆栈。