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_B4is pushed onto the stack
offset unk_8C16C0is placed into ECX
- Local vars are initialised (where we find out
- The sub routine
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
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 ...
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
thispointer via ECX), an explicit pointer to the new object, and the
char*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
sub_407530(size_t, int) related and interacting with it? Thanks for taking the time to read.