I am trying to learn RE and I decided to explore this field by reverse engineering a simple C++ program, which I post here:


#include <iostream>

int main()
    int value;
    std::cout << "Hello Word";
    std::cin >> value;

    return 0;

I compiled it using MSVC compiler (VS 2017, x86 build). I wanted to understand the assembly language behind it.

For RE I use Ghidra tool. I am currently focused only on understanding the assembly part, rather than Decompiled part, because that one makes less sense to me at the moment, but mayber if I understand the Assembly part, I would be able to decode the decompiled output.


                     // .text 
                     // ram: 00401000-00401f9a
                     *                          FUNCTION                          *
                     int __cdecl main(void)
     int               EAX:4          <RETURN>
     int               EAX:4          value                                   XREF[1]:     00401032(W)  
     char *            Stack[-0x8]:4  hello_string                            XREF[2]:     0040100d(W), 
     undefined1        Stack[-0xc]:1  local_c                                 XREF[1]:     00401021(*)  
                     .text$mn                                        XREF[3]:     0040012c(*), 00400204(*), 
                     main                                                         __scrt_common_main_seh:00401500(
00401000 55              PUSH       EBP
00401001 8b ec           MOV        EBP,ESP
00401003 83 ec 08        SUB        ESP,0x8
00401006 a1 04 30        MOV        EAX,[___security_cookie]                         = BB40E64Eh
         40 00
0040100b 33 c5           XOR        EAX,EBP
0040100d 89 45 fc        MOV        dword ptr [EBP + hello_string],EAX
00401010 8b 0d 54        MOV        ECX,dword ptr [->MSVCP140.DLL::std::cout]        = 000027ec
         20 40 00
00401016 e8 25 00        CALL       std::operator<<<struct_std::char_traits<char>_>  basic_ostream<char,struct_std::c
         00 00
0040101b 8b 0d 4c        MOV        ECX,dword ptr [->MSVCP140.DLL::std::cin]         = 0000284a
         20 40 00
00401021 8d 45 f8        LEA        EAX=>local_c,[EBP + -0x8]
00401024 50              PUSH       EAX
00401025 ff 15 34        CALL       dword ptr [->MSVCP140.DLL::std::basic_istream<
         20 40 00
0040102b 8b 4d fc        MOV        ECX,dword ptr [EBP + hello_string]
0040102e 33 c0           XOR        EAX,EAX
00401030 33 cd           XOR        ECX,EBP
00401032 e8 fe 02        CALL       @__security_check_cookie@4                       undefined @__security_check_cook
         00 00
00401037 8b e5           MOV        ESP,EBP
00401039 5d              POP        EBP
0040103a c3              RET

I am trying to follow the assembly code line by line. I understand some basics of the registers mentioned there and stack allocation, but I get lost during the dword ptr MOV instructions and then that CALL instruction.

if anyone could please elaborate to me what is happening actually there, I will start to better understand how to approach assembly code when doing RE tasks.

  • 1
    what command line did you pass to msvc cl.exe ? do you use a project ? and built it with default ? or did you copy paste correctly there appears to be some thing amiss in the paste – blabb Sep 10 at 7:21

The first two lines are function prologue and you will see them at the beginning of almost every function:

PUSH       EBP
MOV        EBP,ESP

They basically save the current EBP value and then store current ESP into EBP, so that the local variables and function arguments can be accessed via EBP (ESP has to change when you allocate the memory on the stack). In this case, function arguments passed via the stack will be accessed by [RBP + offset] while local variables by [RBP - offset].

The next line is about memory allocation - function reserves 8 bytes for its own use:

SUB        ESP,0x8

In the next three lines, there is a protection against buffer overflow.

MOV        EAX,[___security_cookie]
XOR        EAX,EBP
MOV        dword ptr [EBP + hello_string],EAX

Here [EBP + hello_string] contains a random value when ASLR is turned on. The value stored there will likely be different during each program execution and that will make it hard for potential attacker to predict it - if he fails to, it will be detected later on in the line with CALL @__security_check_cookie@4.

It's important to note here, that the local variable (offset) that you named hello_string doesn't have anything in common with the "Hello Word" string - it's there only for preventing exploits.

Now, let's skip the next two lines for a moment - we'll come back to them later on.

So, now we have:

LEA        EAX=>local_c,[EBP + -0x8]
PUSH       EAX

The first line passes the global cin object as a first parameter to the operator function. It's because it is the __thiscall convention which (source)

is the default calling convention used by C++ member functions that do not use variable arguments [...] with the this pointer being passed via register ECX, and not on the stack, on the x86 architecture.

Then, the pointer to the local variable value is passed and the >> operator member function of cin is being called.

The remaining lines:

MOV        ECX,dword ptr [EBP + hello_string]
XOR        EAX,EAX
XOR        ECX,EBP
CALL       @__security_check_cookie@4       
MOV        ESP,EBP
POP        EBP

set EAX (which will contain function return value) to 0, then checks whether a buffer overflow occured and then restores the ESP value before allocating space for local variables and the EBP value from the beginning of the function (EBP has to be preserved, otherwise other functions could change it and then ESP would be changed to the wrong value).

There are two lines not analysed yet:

CALL       std::operator<<<struct_std::char_tra

They are problematic for two reasons:

  1. The second argument to the << is not being passed (though it's required).
  2. As you may notice, contrary to the cin case, non-member function is being called, so the first argument should have been passed via stack and not as in the __thiscall convention:
    1. >> for (cin, int) is member function: source
    2. << for (cout, const char*) is non-member function: source.

As you can see in the disassembly posted by @blabb, these two arguments are indeed passed via the stack, while in yours - only one is passed and not by the stack.

So, it seems that this code doesn't work - it doesn't even reference the string you want to be printed.


I think that in this case, hello_string is, in fact, referring to the security cookie storage location.

in 0040100d the stack cookie is saved to the stack. You can Read more here


It looks there's something wrong with your code, it looks like there's no parameter for the operator<<.

Can you try to add either a '\n' on the string or use << std::endl and see if the generated code is different?


By default __security_cookie is not enabled for all functions
msvc vs 2017 does not insert __security_check for the code in question
either with optimizations or without optimizations

i had to force it with #pragma

source , commandline for compiling and disassembly as below


:/>dir /b si*

:/>type simp.cpp
#include <iostream>
#pragma strict_gs_check(on) <<< had to force stricter security checks
int main() {
    int value;
    std::cout << "Hello Word";
    std::cin >> value;
    return 0;

\compiled and linked with

:/>cl /Zi /W4 /analyze /Od /GS /EHsc simp.cpp /link /release
Microsoft (R) C/C++ Optimizing Compiler Version 19.16.27025.1 for x86
Copyright (C) Microsoft Corporation.  All rights reserved.

Microsoft (R) Incremental Linker Version 14.16.27025.1
Copyright (C) Microsoft Corporation.  All rights reserved.


Hello Word



:/>cdb -c "uf simp!main;q" simp.exe | awk "/Reading/,/quit/"
0:000> cdb: Reading initial command 'uf simp!main;q'
013a1190 55              push    ebp
013a1191 8bec            mov     ebp,esp
013a1193 83ec08          sub     esp,8
013a1196 a16c904201      mov     eax,dword ptr [simp!__security_cookie (0142906c)]
013a119b 33c5            xor     eax,ebp
013a119d 8945fc          mov     dword ptr [ebp-4],eax
013a11a0 68f0214101      push    offset simp!__xt_z+0x10 (014121f0)
013a11a5 6830ab4201      push    offset simp!std::cout (0142ab30)
013a11aa e861010000      call    simp!std::operator<<<std::char_traits<char> > (013a1310)
013a11af 83c408          add     esp,8
013a11b2 8d45f8          lea     eax,[ebp-8]
013a11b5 50              push    eax
013a11b6 b9b8aa4201      mov     ecx,offset simp!std::cin (0142aab8)
013a11bb e8b0280000      call    simp!std::basic_istream<char,std::char_traits<char> >::operator>> (013a3a70)
013a11c0 33c0            xor     eax,eax
013a11c2 8b4dfc          mov     ecx,dword ptr [ebp-4]
013a11c5 33cd            xor     ecx,ebp
013a11c7 e8e63a0200      call    simp!__security_check_cookie (013c4cb2)
013a11cc 8be5            mov     esp,ebp
013a11ce 5d              pop     ebp
013a11cf c3              ret

Your Answer

