I apologise for the length of this post, though I have taken the time to make it as readable as I can and included my ideas and thinking where possible so this post might be useful to other readers later down the track.
For context, I've started trying to learn some reverse engineering. After reading as much as I could find on the subject of reversing C++ and playing around with some trivial examples (that I wrote and compiled myself), I thought I would try my hand at a program I'm familiar with but don't have the source code for.
Loading that Win32 C++ program (compiled with Visual Studio if that helps) into IDA Pro I see it has the following WinMain function:
; int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
__stdcall WinMain(x, x, x, x) proc near
sub esp, 0BCh
mov eax, off_7CA000 ; A pointer to some other string
push ebx ; char (IDA generated comment)
lea ecx, [esp+0C0h+var_B4]
push ecx ; int (IDA generated comment)
mov ecx, offset unk_8C16C0 ; size_t (IDA generated comment)
... ; local variable init starts, including:
mov [esp+0C4h+var_B4], offset aSomeString ; "Some String"
mov [esp+0C4h+var_AC], eax
... ; local variable init finishes
call sub_407530
mov dword_8C16C4, offset off_8C16BC
mov dword_8C16C8, offset sub_242CE0
call sub_4AF9C0
... ; rest of WinMain(..)
Although the order of the ops is a little strange compared to what I have seen (local vars are initialised in between pushing a pointer onto the stack and ECX being initialised for use in a later sub routine call), I guess I have the following:
var_B4
is pushed onto the stackoffset unk_8C16C0
is placed into ECX- Local vars are initialised (where we find out
var_B4
is achar*
) - The sub routine
sub_407530
is called
So perhaps offset unk_8C16C0
is a this
pointer for some global class (global because we're so early on in WinMain) and sub_407530
is a __thiscall
to a member function of the class pointed to by offset unk_8C16C0
that takes a single char*
parameter.
Checking the cross references to offset unk_8C16C0
I see two, so perhaps one is a constructor and the other a destructor. One of them is:
sub_687C30 proc near ; DATA XREF: .rdata:006BF608
var_C = dword ptr -0Ch
var_4 = dword ptr -4
mov eax, large fs:0
push 0FFFFFFFFh
push offset SEH_687C30
push eax
mov large fs:0, esp
mov ecx, offset unk_8C16C0 ; this
mov [esp+0Ch+var_4], 0
call Concurrency::details::_ReaderWriterLock::_ReaderWriterLock(void)
push offset sub_6BA2B0 ; void (__cdecl *)()
call _atexit
mov ecx, [esp+10h+var_C]
mov large fs:0, ecx
add esp, 10h
retn
endp
Great, my suspicions seem validated, offset unk_8C16C0
seems to point to a global reader_writer_lock object, the above block initialises it and registers sub_6BA2B0
(the only other reference to offset unk_8C16C0
) to run on exit
via a call to _atexit
, so that is likely our destructor.
But it's at this point I start to have some doubts about my suspicions ... the above block is referring to a details
namespace, which upon a little more research seems to be more related to the internals of reader_writer_lock. I'm also not sure why this initialisation block is so complicated. And finally, the only reference to this block is in the executables read only data section which itself has no references, so I feel like I'm at a bit of a dead end here.
Nevertheless, moving forward to the call to sub_407530
in WinMain, I see:
; int __thiscall sub_407530(size_t, int)
sub_407530 proc near ; CODE XREF: WinMain(x,x,x,x)
arg_0 = dword ptr 8
push ebp
mov ebp, esp
push esi
push 37628 ; size_t
mov esi, ecx
call operator new(uint)
add esp, 4
test eax, eax
jz short loc_407550
mov ecx, eax
call sub_406B30
jmp short loc_407552
loc_407550: ; CODE XREF: sub_407530+15
xor eax, eax
loc_407552: ; CODE XREF: sub_407530+1E
mov [esi], eax
mov dword_DC32B8, eax
mov eax, [ebp+arg_0]
mov ecx, [esi]
push eax ; int
push esi ; size_t
call sub_407360
pop esi
pop ebp
retn 4
sub_407530 endp
The first half seems straight forward enough, 37628 bytes are allocated for a new object and sub_406B30
is a constructor for that object:
sub_406B30 proc near ; CODE XREF: sub_407530+19
xor eax, eax
push esi
mov esi, ecx
mov dword ptr [esi], offset const Blah1::Blah2::`vftable'
mov [esi+10h], eax
mov [esi+8], eax
mov [esi+0Ch], eax
mov dword ptr [esi+4], offset const Something::List<Blah1::Blah2::Blah3,-1>::`vftable'
mov [esi+24h], eax
...
Back to sub_407530(size_t, int)
, from loc_407552
down it's not clear to me what's happening:
- The newly created object seems to be replacing the _ReaderWriterLock object (whose reference was moved to ESI earlier in the sub-routine), which doesn't make sense to me. It certainly doesn't seem like this would be a member function as I suspected.
- The new object (as a
this
pointer via ECX), an explicit pointer to the new object, and thechar*
from WinMain are being passed to a new sub routine. I don't understand why two pointers to the same object would be passed, although not incredibly important on its own it adds to my doubts.
I'm sure I'm interpreting this incorrectly, so this brings me to my question:
Broadly speaking, is my thinking on track? Are there any obvious errors in my analysis so far?
More specifically, what is happening with offset unk_8C16C0
/ sub_687C30
at the start, and how is loc_407552
in sub_407530(size_t, int)
related and interacting with it? Thanks for taking the time to read.