-2

So I decided to try my hand at Ghidra decompiling / disassembling a simple c++ class with a virtual function and a non virtual function. However; the decompilation confused me a bit. Below, is both my source and my decompilation. What I do not understand is why the call to the non virtual function consists of parameters such as the printed string and these 3 other weird parameters. What are these other parameters? I can only assume one them is the "This" pointer ? If so, what are the other two?

compiled on visual studio 2k17 x64 release no pdb

Source

#include <iostream>
#include <stdio.h>

class Person
{
public:
    Person(int val)
    {
        myval = val;
    }

    void PersonFunction()
    {
        printf("this is a person func/n");
    }

    virtual void PersonFunction2()
    {
        printf("this is a person func2/n");
    }

protected:
    int myval = 5;
};

int main(int argc, char** argv)
{
    Person * person = new Person(10);
    std::cout << "Hello World!\n";
    person->PersonFunction();
    person->PersonFunction2();
}

decompiled

undefined8
FUN_140001080(undefined8 param_1,undefined8 param_2,undefined8 param_3,undefined8 param_4)

{
  code **ppcVar1;
  
  ppcVar1 = (code **)operator_new(0x10);
  *ppcVar1 = (code *)Person::vftable;
  *(undefined4 *)(ppcVar1 + 1) = 10;
  FUN_1400010e0((longlong *)cout_exref);
  FUN_140001010("this is a person func/n",param_2,param_3,param_4); #what is going on here.. why 3 params?
  (**(code **)*ppcVar1)(ppcVar1); # this, i assume is the virtual function call passing in this ptr
  return 0;
}

// furthermore inside FUN140001010
void FUN_140001010(undefined8 param_1,undefined8 param_2,undefined8 param_3,undefined8 param_4)

{
  undefined8 uVar1;
  undefined8 *puVar2;
  undefined8 local_res10;
  undefined8 local_res18;
  undefined8 local_res20;
  
  local_res10 = param_2;
  local_res18 = param_3;
  local_res20 = param_4;
  uVar1 = __acrt_iob_func(1);
  puVar2 = (undefined8 *)FUN_140001000();
  __stdio_common_vfprintf(*puVar2,uVar1,param_1,0,&local_res10);
  return;
}

Can anyone explain whats going on with the function that takes my string and three params? What are the params? Why pass the string?

efel
  • 1,054
  • 3
  • 14
  • 29
  • How did you compile it? – Captain Giraffe Mar 06 '21 at 02:01
  • visual studio 2k17 - release x64, no pdb – efel Mar 06 '21 at 02:05
  • What does the code look like that is using the `Person` class? That decompiled output does not appear to match the code you have shown, so I suspect it belongs to other code you have not shown yet. – Remy Lebeau Mar 06 '21 at 07:10
  • The [x64 calling convention](https://learn.microsoft.com/en-us/cpp/build/x64-calling-convention) passes the first 4 arguments through registers. Unlike x86 code, where arguments are actively pushed onto the stack, there's nothing in x64 code that serves as a hint to Ghidra to determine whether those 4 registers are arguments to a function. Try your experiment when compiling to x86 and things will look much more sane. – IInspectable Mar 06 '21 at 11:33
  • Alternatively, work your way back starting at `FUN_140001010` (presumably the virtual function). It only uses `param_1`, so you can adjust its function signature by removing the remaining arguments. Once you commit this, the changes should start trickling back to `FUN_140001080`. I'm not sure Ghidra will auto-update this signature, but surely will adjust the function call by no longer passing all 4, but only the first argument. – IInspectable Mar 06 '21 at 11:39
  • i added a lot more detail to my question. I believe FUN)140001010 is a non virtual function. The virtual function call is the next one where it calls from the vftable. – efel Mar 07 '21 at 00:43
  • True. Looks like `FUN_140001010` is a `printf` wrapper, translating the variable arguments list into a `va_list`, and then forwarding it to the CRT. Still, the main point being: Microsoft's x64 calling convention makes it near impossible to tell, how many arguments are passed into a function. That information isn't directly reflected in the generated machine code, unlike x86. – IInspectable Mar 07 '21 at 07:40

1 Answers1

0

So it seems like the MSVC++ compiler just optimized and inlined printf directly into main. This is what printf looks like under the hood with vaargs:

_Check_return_opt_
_CRT_STDIO_INLINE int __CRTDECL printf(
    _In_z_ _Printf_format_string_ char const* const _Format,
    ...)
#if defined _NO_CRT_STDIO_INLINE
;
#else
{
    int _Result;
    va_list _ArgList;
    __crt_va_start(_ArgList, _Format);
    _Result = _vfprintf_l(stdout, _Format, NULL, _ArgList);
    __crt_va_end(_ArgList);
    return _Result;
}
#endif

_Check_return_opt_
_CRT_STDIO_INLINE int __CRTDECL _vfprintf_l(
    _Inout_  FILE*       const _Stream,
    _In_z_   char const* const _Format,
    _In_opt_ _locale_t   const _Locale,
             va_list           _ArgList
    )
#if defined _NO_CRT_STDIO_INLINE
;
#else
{
    return __stdio_common_vfprintf(_CRT_INTERNAL_LOCAL_PRINTF_OPTIONS, _Stream, _Format, _Locale, _ArgList);
}
#endif
efel
  • 1,054
  • 3
  • 14
  • 29