3

Why is Ghidra interpreting this incorrectly? The example is very simple.

In this stack:

0019FF58  $-8      0019FF58        00000002     LOCAL 2
0019FF5C  $-4      0019FF5C        00000001     LOCAL 1
0019FF60  $ ==>    0019FF60        0019FF80     OLD EBP
0019FF64  $+4      0019FF64      | 00401025     return to layout.00401025 from layout.sub_40102C
0019FF68  $+8      0019FF68      | 00000041     PARAM 3
0019FF6C  $+C      0019FF6C      | 0000BABE     PARAM 2
0019FF70  $+10     0019FF70      | 0000CAFE     PARAM 1

Ghidra obtains:

undefined4        Stack[0x4]:4   param_1                                 XREF[1]:     00401040 (R)   
undefined4        Stack[0x8]:4   param_2                                 XREF[1]:     00401043 (R)   
undefined4        Stack[0xc]:4   param_3                                 XREF[1]:     00401046 (R)   
undefined4        Stack[-0x8]:4  local_8                                 XREF[1]:     00401032 (W)   
undefined4        Stack[-0xc]:4  local_c                                 XREF[1]:     00401039 (W)  
00401032 C745FC01000000            MOV        dword ptr [EBP  + local_8 ],0x1
00401039 C745F802000000            MOV        dword ptr [EBP  + local_c ],0x2
00401040 8B5D08                    MOV        EBX ,dword ptr [EBP  + param_1 ]
00401043 8B4D0C                    MOV        ECX ,dword ptr [EBP  + param_2 ]
00401046 FF7510                    PUSH       dword ptr [EBP  + param_3 ]

Whereas IDA correctly gets:

.text:0040102C var_8           = dword ptr -8
.text:0040102C var_4           = dword ptr -4
.text:0040102C arg_0           = dword ptr  8
.text:0040102C arg_4           = dword ptr  0Ch
.text:0040102C arg_8           = dword ptr  10h
.text:00401032                 mov     [ebp+var_4], 1
.text:00401039                 mov     [ebp+var_8], 2
.text:00401040                 mov     ebx, [ebp+arg_0]
.text:00401043                 mov     ecx, [ebp+arg_4]
3
  • what calling conventions are Ghidra and IDA using in your example
    – mumbel
    Mar 27, 2020 at 0:53
  • Default ones, Ghidra reports " __stdcall" and I suppose IDA too. In any case, the call is pretty standard to be identified by the tool, with a RET 0xC Mar 27, 2020 at 1:51
  • 1
    see this question - github.com/NationalSecurityAgency/ghidra/issues/998
    – R4444
    Mar 27, 2020 at 15:18

1 Answer 1

7

TL;DR:

  • This is not an error from Ghidra. The values are just a naming convention, and the real instructions are correctly disassembled.
  • Ghidra assigns variable names based on the function entry point, and displays offsets based on that.
  • It seems Ghidra behaviour is like this to have a universal way to assign names, independently from the compiler.

As pointed by R4444, Ghidra shows variable offsets relative to the entry stack-pointer and are not frame-based offsets.

Herein, Ghidra assigns variable names based on ESP (or corresponding stack pointer) at the time the function is entered, without considering the coming PUSH EBP, basically following this:

0019FF58  $-C      0019FF58        00000002     LOCAL 2
0019FF5C  $-8      0019FF5C        00000001     LOCAL 1
0019FF60  $-4      0019FF60        0019FF80     will store the OLD EBP
0019FF64  $ ==>    0019FF64      | 00401025     return to layout.00401025 from layout.sub_40102C
0019FF68  $+4      0019FF68      | 00000041     PARAM 1
0019FF6C  $+8      0019FF6C      | 0000BABE     PARAM 2
0019FF70  $+C      0019FF70      | 0000CAFE     PARAM 3

This is how Ghidra obtained the values:

Stack[0x4]  -> param_1
Stack[0x8]  -> param_2
Stack[0xc]  -> param_3
Stack[-0x8] -> local_8
Stack[-0xc] -> local_c

It must be considered that this is just a variable naming, the actual instruction is addressing the data with the correct offset. If we navigate to one of the offensive instructions, we can see that Ghidra provides the correct instruction at the bottom right corner, in this case, EBP-4 for the named variable local_8 ([-0x8]):

Ghidra stack-pointer relative to entry

This default Ghidra behavior can be modified permanently via: Edit > Tool Options > Listing Fields > Operands Field > Markup Stack Variable References, and then Ghidra will display:

     undefined4        Stack[0x4]:4   param_1                                 XREF[1]:     00401040 (R)   
     undefined4        Stack[0x8]:4   param_2                                 XREF[1]:     00401043 (R)   
     undefined4        Stack[0xc]:4   param_3                                 XREF[1]:     00401046 (R)   
     undefined4        Stack[-0x8]:4  local_8                                 XREF[1]:     00401032 (W)   
     undefined4        Stack[-0xc]:4  local_c                                 XREF[1]:     00401039 (W)   

00401032 C745FC01000000            MOV        dword ptr [EBP  + -0x4 ]=> local_8 ,0x1
00401039 C745F802000000            MOV        dword ptr [EBP  + -0x8 ]=> local_c ,0x2
00401040 8B5D08                    MOV        EBX ,dword ptr [EBP  + 0x8 ]=> param_1
00401043 8B4D0C                    MOV        ECX ,dword ptr [EBP  + 0xc ]=> param_2
00401046 FF7510                    PUSH       dword ptr [EBP  + 0x10 ]=> param_3

This is the REASON for the values mismatch and the HOW the values are obtained, but WHY is Ghidra naming variables based on the function entry? @emteere explains that:

The choice of stack variable offsets based on the frame variable versus stack variables based on the stack pointer can cause some confusion. What it allows is ignoring the stack frame variable and just creating references to the stack wherever they occur however they occur. There are many examples of the stack pointer loaded into alternate registers without a frame, so a universal base of the stack at entry seemed like a good choice and less confusing when there is and isn't a frame pointer in two different functions. When debug information is loaded the conversion to SP at entry needs to be done. In addition, many compilers have gotten rid of the use of a stack frame register all together.

So, I imagine that the explanation is that one normally would wish to have frame-based variable naming, like seen in IDA, at least for some of the most extended architectures/compilers. However, Ghidra names variables with a general policy, and they decided to harmonise behaviour of different architectures/compilers by offseting variables based on the stack-pointer at the time the function is entered.


Sources:

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.