1

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 !

Nox
  • 713
  • 7
  • 16
  • You can't really have the fault at that line as that is just writing to global storage which should work. The code seems to use a nonstandard calling sequence since the argument is passed in `eax`. Also that seems to be passed on to `FUN_00462554` via `esi`. – Jester Mar 13 '20 at 13:50
  • Why did you decide that this is pascal calling convention? That doesn't seem right. – interjay Mar 13 '20 at 13:51
  • @interjay: "Based on the Borland Pascal programming language's calling convention, the parameters are pushed on the stack in left-to-right order (opposite of cdecl), and the callee is responsible for removing them from the stack." (from https://en.wikipedia.org/wiki/X86_calling_conventions), it sounded to me like it was it, could you please tell me which point is wrong in this sentence ? Jester: I am clearly having a popup saying "Access violation on address "004789fb"". I would like to show you Ghidra pseudocode for this line (see the last paragraph of the above topic. – Nox Mar 13 '20 at 17:06
  • @Nox You're just saying what the pascal calling convention is. But this is not the calling convention used by this assembly code. – interjay Mar 13 '20 at 17:21
  • @Jester: How do you know it is past by ``eax`` ? – Nox Mar 13 '20 at 17:23
  • @interjay: Can you please tell me if my thinking is right ? - Callee save the stack (004789e0) - Callee clean the stack (00478c54) - I don't know for the reading-convention, but since there is (apparently) one argument, it doesn't matter. So I can use __stdcall. Note that Jester said : "The code seems to use a nonstandard calling sequence since the argument is passed in eax" and the above wikipedia page tells us : "Registers EAX, ECX, and EDX are designated for use within the function. Return values are stored in the EAX register. " – Nox Mar 13 '20 at 17:27
  • Your decompiler says so itself: `undefined2 AX:2 x` and later you can see `004789e5 8b f0 MOV ESI ,x` which is `mov esi, eax` and `eax` has not been set to anything hence its value must be coming from the caller. – Jester Mar 13 '20 at 17:38
  • This is not pascal or stdcall because those pass values through stack and this uses registers. You have to use a compatible calling convention. – interjay Mar 13 '20 at 18:14

1 Answers1

3

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.

Toribio
  • 3,963
  • 3
  • 34
  • 48
  • Thank you for trying to help ! So I tested the first approach : It compiles without any problem, but I am still getting the "Access violation at address 00478C3D in module '...'. Write of address 004789FC." it is the same message that I already got. (please, see my edit). I tried the second one. I couldn't compile with "call 0x04789E0", so I tried by sending the address through argument. It compiles but give me the same message (with different adresses, out of scope, so I did something wrong) – Nox Mar 13 '20 at 19:30
  • 2
    The error line means it's trying to write into `[ESI + 0x1C]`, i.e. `[0x004789E0 - 0x1C]`, which is `0x004789FC‬` (what the error says), `ESI` is holding the start of the original `CameraX` function somehow. Looking at the disassembled function, `ESI` is traced back to the value of the argument `x`, which means you're passing the address of the function to the first function argument itself. This also means that `X` (`EAX`) probably is not what you think it is. It looks like a pointer in the context of the original function. – Toribio Mar 13 '20 at 20:24
  • 1
    `__stdcall` is a caller-pops convention (https://learn.microsoft.com/en-us/cpp/cpp/stdcall?view=vs-2019). You need `ret 4` to clear your arg from the stack after the register-arg function returns. Also, MSVC inline asm unfortunately doesn't support assembling a relative `call` to an absolute target address; you need to do something clunky like putting the absolute address in a register and doing `call ecx` [How to write an absolute target for a near direct relative call/jmp in MASM](https://stackoverflow.com/q/50058523) (ping @Nox) – Peter Cordes Mar 13 '20 at 22:03
  • I have been trying a lot of things with the first method, after checking that I was not wrong about the purpose of x. I still don't have his type (I tried int, *int, and to send a function pointer to an int function with no args). No success. Still, I am sure of what is x supposed to do (I have been able to check it through Cheat engine). Any clues on how to find the type of x ? By the way, why do we `ret 4` instead of `ret 1` ? Do we return the number of bytes ? – Nox Mar 14 '20 at 17:48
  • 1
    Now your stdcall function is broken because it clobbers EBX without saving. Probably push/pop it around the call. `push 0x04789E0` / `call [esp]` (and `pop ecx` cleanup) is another option – Peter Cordes Mar 14 '20 at 18:52