返回
顶部

references:

here is a demo source file

here is the compiled binary

it is a x86 exe

the main function of it is sub_402A60

image-20250312170515951

virtual function table of these three class begin from 40AB34

you can see two _purecall function in father class:

image-20250312170856393

they're corresponding to these two lines in source code:

image-20250312170930334

normally, only virtual function in base class have this function defined, here is the code of _purecall

//
// purevirt.cpp
//
//      Copyright (c) Microsoft Corporation. All rights reserved.
//
// The _purecall handler, called by compiler-generated code when a pure virtual
// call occurs.
//
#include <vcruntime_internal.h>
#include <stdlib.h>



extern "C" extern _purecall_handler __pPurecall;



extern "C" int __cdecl _purecall()
{
    _purecall_handler const purecall_handler = _get_purecall_handler();
    if (purecall_handler != nullptr)
    {
        purecall_handler();

        // The user-registered purecall handler should not return, but if it does,
        // continue with the default termination behavior.
    }

    abort();
}

extern "C" _purecall_handler __cdecl _set_purecall_handler(
    _purecall_handler const new_handler
    )
{
    return __crt_fast_decode_pointer(
        __crt_interlocked_exchange_pointer(
            &__pPurecall,
            __crt_fast_encode_pointer(new_handler)));
}

extern "C" _purecall_handler __cdecl _get_purecall_handler()
{
    return __crt_fast_decode_pointer(__crt_interlocked_read_pointer(&__pPurecall));
}

in the main function, we can see sub_4010E1 is being called for allocate 4h bytes memory

this is the implementation of this function, it is just a wrapper of malloc:

void *__cdecl sub_402CB0(size_t Size)
{
  void *v2; // [esp+0h] [ebp-4h]

  while ( 1 )
  {
    v2 = malloc(Size);
    if ( v2 )
      break;
    if ( !callnewh(Size) )
    {
      if ( Size == -1 )
        sub_401357();
      sub_4010DC();
    }
  }
  return v2;
}

then, the new allocated memory is pass either to sub_40125D or sub_401316, depends on the rand() return value, these two function is actually the Dog and Cat class constructor

in IDA, we can see that this memory will finally be assigned with the correspond class virtual function table:

image-20250312172218511

image-20250312172230109

after class constructor call, member function walk will be called, we can not see the actual function address being called, it is called from a register:

image-20250312172630519

so v5 is now the class object, based on the constructor function code, we know *v5 is virtual function table, and 8h offset of it is the 3rd function in vftable, which is depends on the which class is this vftable belongs to, it is decided in runtime, it will either call dog's walk or cat's walk

because neither Dog nor Cat implement move function, so the last entry in the vftable of all three class is the same

now let's define their structure:

image-20250312173921333

then we need to create a structure of every vfptr of them

image-20250312174318212

then we set the vfptr to the correct type for these three class

image-20250312174531254

then we set set for variable in IDA, we can either set v5 to dogs* or cats*

image-20250312175008507