NationalSecurityAgency / ghidra

Ghidra is a software reverse engineering (SRE) framework
https://www.nsa.gov/ghidra
Apache License 2.0
50.85k stars 5.8k forks source link

How do I deal with inherited methods from the second base class in multiple inheritance #6800

Closed XdotCore closed 2 weeks ago

XdotCore commented 1 month ago

I have this class called MainMenuScreen, and it inherits from MenuFlowPageHandler2 and IEventListener. The vftable for the IEvent Listener is placed after the vftable and inherited fields from MenuFlowPageHandler2, and has a method called RecieveEvent that is overridden in MainMenuScreen.

MainMenuScreen's RecieveEvent has the pointer offset to the vftable of IEventListener (most likely because it was called with it cast to an IEventListener) and causes the access of members to be off in the decompilation. I was tipped off to this mainly because of the negative indexer to access a field before the IEventListener vftable.

Is there a way to tell the ghidra decompiler that the pointer is of MainMenuScreen class but offset to the IEventListener vftable?

Example:

void __thiscall MainMenuScreen::RecieveEvent(MainMenuScreen *this,Event *event,NuEventData *param_2)

{
  bool bVar1;
  bool uVar2;
  GUI2MenuEntry *quitWindowsEntry;

  bVar1 = (*event->vftablePtr->Equals)(event,&AbleToQuitEvent);
  if ((bVar1) &&
     (quitWindowsEntry = (this->MainMenuScreen_data).selectLevelMenuEntry, // here
     quitWindowsEntry != (GUI2MenuEntry *)0x0)) {
    GUI2MenuEntry::SetText(quitWindowsEntry,PCSTRING_QUITWINDOWS);
  }
  bVar1 = (*event->vftablePtr->Equals)(event,&Event_01994248);
  if ((bVar1) && ((param_2->NuEventData_data).int_0x4 == 5)) {
    (this->MainMenuScreen_data).comingSoonMenuEntry = (GUI2MenuEntry *)0x0; // here
    DAT_01993913 = 1;
  }
  uVar2 = (*event->vftablePtr->Equals)(event,&Event_0199420c);
  if (uVar2) {
    FUN_008fc4e0((int)&this[-1].MainMenuScreen_data.field29_0xdc); // here
  }
  return;
}
ghidra007 commented 1 month ago

Can you share the contents of MainMenuScreen's class structure? Does it contain fields for both inherited vftables? Were your structures manually generated or with the RTTI script or something else? Are you able to share the binary?

XdotCore commented 1 month ago

It was generated by the RTTI script, and no, I cannot share the binary as it is a paid game.

Here is a screenshot of the class structure image It contains the vftable and fields for the first inherited class, then the vftable and fields for the second inherited class, then the fields for itself, with its new virtual functions in the first vftable, if that helps.

ghidra007 commented 1 month ago

What is at offset 4? It is cut off. Is it data or a vbtable? I'm sorry for slow responses. I'm out most of this week but checking in once per day. If you are able to share the contents of the RTTI structures for the MainMenuScreen class that would help too. You mentioned the function was called with a cast to IEventListener. Do you think that is correctly cast? Can you share image of the calling function?

XdotCore commented 1 month ago

At offset 4 it is a data. The inheritance structure is CSListLink<> -> GUI2PageHandler -> FlowPageHandler2 -> MainMenuScreen and IEventListener -> MainMenuScreen defined as MainMenuScreen : FlowPageHandler2, IEventListener.

The structure of the MainMenuScreen written out is

struct MainMenuScreen : FlowPageHandler2, IEventLister {
    void** vftable_for_FlowPageHandler2;
    CSListLink<class_GUI2PageHandler>_data CSListLink<class_GUI2PageHandler>_data;
    GUI2PageHandler_data GUI2PageHandler_data;
    void** vftable_for_IEventListener;
    IEventListener_data IEventListener_data;
    MainMenuScreen_data MainMenuScreen_data;
}

Note: FlowPageHandler2 does not have any data or vftable of its own GUI2PageHandler's is

struct MainMenuScreen : CSListLink<class_GUI2PageHandler> {
    void** vftable;
    CSListLink<class_GUI2PageHandler>_data CSListLink<class_GUI2PageHandler>_data;
    GUI2PageHandler_data GUI2PageHandler_data;
}

and IEventListener's is

struct IEventListener {
    void** vftable;
    IEventListener_data IEventListenerData;
}

As you can notice, MainMenuScreen is GUI2PageHandler's struct + IEventListener's struct + MainMenuScreen_data.

The function RecieveEvent is inherited from IEventListener and the virtual ptr is placed and overriden in vftable_for_IEventListener. I do not know yet where the function is being called but since the base classes function requires a IEventListener*, the function will be called with one, and the overriden function will accept one. Due to this and the IEventListener struct being offset within the MainMenuScreen struct, the MainMenuScreen* "this" argument is also offset to where the IEventListener struct is positioned.

I have confirmed this behavior using hooking to the RecieveEvent struct with mirrored versions of the structs in my project, and analyzing which vftable is at 0x0 with or without static casting.

I have made a repository to hopefully explain what is going on better by example: https://github.com/XdotCore/TestMultipleInheritance.

ryanmkurtz commented 1 month ago

Thanks for the example

ghidra007 commented 1 month ago

Here is how to update your function to fix the -1 offset issue:

You need to turn off the thiscall convention for the ReceiveEvent method and just replace it with stdcall. Then from the GUI, you can create the adjusted pointer be right clicking on the parameter in the decompiler (might have to add an extra param after changing the calling convention and also make sure it is a pointer), and select "Adjust Pointer Offset". Then in the dialog, enter "IEventListener" for data-type, "0x1c" for offset, and anything you want for the name.

XdotCore commented 1 month ago

Thanks so much, I got it working!

I used the "Adjust Pointer Offset" option to generate the type. I set the data-type to MainMenuScreen with offset 0x1c. Because I am keeping it as thiscall, it errored because you can't retype the this of a thiscall, but it still made the type which is what I needed. Then I edited the function by turning on "Use Custom Storage" and set the "this" type to the generated type, and now it works!

ghidra007 commented 2 weeks ago

Thanks so much, I got it working!

I used the "Adjust Pointer Offset" option to generate the type. I set the data-type to MainMenuScreen with offset 0x1c. Because I am keeping it as thiscall, it errored because you can't retype the this of a thiscall, but it still made the type which is what I needed. Then I edited the function by turning on "Use Custom Storage" and set the "this" type to the generated type, and now it works!

Glad you got it working!