Search code examples
assemblyreverse-engineeringcalling-conventionghidra

Get informations from decompiled ASM


I am willing to modify camera coordinate on a little 3D game. I have been able to find three functions, one for each axes. Let's call them CameraX, CameraY and CameraZ. I have been working only with the first one, when I found out that I was missing something.

Here are ASM instructions, got from Ghidra :

                             *************************************************************
                             *                           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

I know that :
- My executable is 32 bits, made in Delphi.
- x is the new x-axis value.

My goal is to use this function with an injected dll. I came to this :

typedef int (__stdcall *_CameraX)(int x);
_CameraX CameraX = (_CameraX)0x04789E0;

But no way, I am having an "access violation" on the line 004789fb FSTP dword ptr [DAT_00871218]. This is the first use of x value. So I guess it is the wrong type.

Here is what I understood :
- Since the original program is written in Delphi, __pascal calling convention came in mind. Since it is deprecated in Visual studio, I am using __stdcall. I assume that, since there is only one argument* it would not make any difference.
- x was always a large number. I would have picked long if it was not in 32 bits.
- I don't really know for the return type. I picked int because the caller function make a test al, al just after the call.

* : Ghidra is telling me there is only one argument, but if I was listening to myself, there would be 2 :

        004789e0 53              PUSH       EBX
        004789e1 56              PUSH       ESI
...
...
        00478c53 5e              POP        ESI
        00478c54 5b              POP        EBX
        00478c55 c3              RET

So here are my questions :
- Is there really only one argument ?
- Which calling convention should I use if the right one is __pascal, since it is unusable with Visual Studio ? (If there is more than one argument)
- What is the way to retrieve the return value ?
- Why have we "invented" so many calling convention ? Why don't we all use __cdecl, for example ? Why some use Right to Left, when some others read from Left to Right ? Is there any differences ?

I am pretty sure that some informations are lacking, would the pseudocode generated by Ghidra be usefull ?

Edit :

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) {
  // ...

Edit 2 :

Here is the complete ASM code : https://pastebin.com/UiGGEju1 and here is Ghidra generated pseudo code : https://pastebin.com/1Fc48k1g

so I guess I was wrong : it wasn't the line I thought that was causing this issue but this one : 00478c3d 89 46 1c MOV dword ptr [ESI + 0x1c],x but it's still my x value that is causing it.

What I don't understand is "why" : x is an int (32 bits) and the program is trying to store it as a dword (32 bits) in [ESI + 0x1c]. Is it possible that the program could not resolve where/what is x ? (like if I was calling the function with no argument)

By the way this question : "- Why have we "invented" so many calling convention ? Why don't we all use __cdecl, for example ? Why some use Right to Left, when some others read from Left to Right ? Is there any differences ?" has not been answered and really intrigue me, if you have the explication, I would be glad to listen to it !


Solution

  • This looks like it's using the Borland register calling convention (also known as Borland fastcall):

    • It takes EAX, EDX then ECX as first three parameters, respectively;
    • The 4th and up arguments go in the stack;
    • The callee must preserve the EBX, ESI, EDI, and EBP registers;
    • EAX is used as a return method for 32-bit integers;
    • ...and some other rules that don't apply here.

    You can see your function saves the EBX and ESI registers and also does not use EDI nor EBP. It also uses EAX as the first parameter x and returns the result in EAX also, as per convention.

    There's no equivalent calling convention for Borland fastcall in modern Visual Studio. You'll probably rely on using inline assembly as a workaround on calling this Delphi function.

    This workaround would probably work using a temporary Microsoft fastcall function pointer as it also uses registers for the first parameters, except for EAX, which we'll have to pass using inline assembly, something like (not sure of the code syntax, treat it as pseudo-code):

    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;
    }
    

    Note that if this method works, you're gonna need to invert the next two parameters (for functions with 2 or 3 arguments), because Microsoft's fastcall expects ECX then EDX in that order. For 4 or more arguments, I assume some stack work would be needed.

    Another approach would be to use a naked function, I assume it would look something like this:

    __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)
       }
    }
    

    For future purposes, if you use this method with another function with more than 1 argument, you'll need to use RET n where n is number of arguments * 4. For 3 arguments or more, you'll also have to push the arguments into the stack like with the former method.