Disassembling (reverse engineering) ‘Crunch WinS’

Amey Chavan
11 min readSep 23, 2021

--

In this post we will be disassembling our own desktop application that I implemented while following C++ Win32 API. The application runs as administrator & capable of listing all the logon sessions on Windows system, called “Crunch WinS”, meaning Crunch Windows Sessions..!

It extracts the logon sessions on Windows OS including different users at given time as well as several system-level sessions such as Window Manager, associated DWM (Desktop Window Manager), the logon server (i.e. the logged on computer itself), and so on..!

The purpose of reverse engineering own application will help understanding our thought process behind the disassembled code of given binary/executable. Additionally, we have the original source code of our app, so it’s easy to verify the understanding against the actual logic. I believe this experience will benefit giving better confidence about analysing the third-party binary/executables where the original source code is un-likely available.

Before reading further if you’re un-familiar with assembly language, I’d encourage to be familiar with basics assembly instructions, different CPU registers & how those registers are used in programs. Many resources are available regarding assembly. It is worth mentioning that different assemblers (like flat assembler, MASM, NASM etc) may have variations in converting assembly code into the executable machine code, likely to different compilers do (like GCC, LLVM (Clang), MSVC etc). Our application was built using MSVC compiler in Visual Studio on Windows.

Reverse engineering tool

To reverse engineer the binary/executable of application, we need ‘Disassembler’. A disassembler is the computer program that translates machine language into assembly language—the inverse operation to that of an assembler. A disassembler differs from de-compiler, which targets a high-level language rather than the assembly language.

The list of reverse engineering tools is always evolving; but here listed some of them :

  1. The Interactive Disassembler (or IDA), by Hex-Rays.
  2. Ghidra, by NSA.
  3. Binary Ninja, by Vector 35 Inc.
  4. Radare2, by pancake & the community.

Feel free to grab a tool your choice. Here I’m using IDA free version for our ‘Crunch WinS.exe’; just a personal preference :-)

Analysing ‘Crunch WinS.exe’ with IDA

Once opened this sample file in IDA, we will be landed to the graph view of main subroutine from where the program execution begins. If the application was complied with some great optimizations and/or applied some packers like UPX, then we need to unpack & find the main() function on our own by understanding flow of assembly instructions, in that case it may not necessarily named as main..! But our use case is much simpler :

Graph view of main() subroutine in IDA

Graph view gives better visual overview about flow of program but hitting <space> key will toggle us to text view of same assembly instructions in corresponding subroutine :

Text view of main() subroutine in IDA

To begin with the main() subroutine, we have three parameters namely argc, argv & envp. The first two parameters : argc (means ‘argument count’) & argv (means ‘argument vector’) are common but the third parameter envp (means ‘an array of pointers to environment variables’) is a Microsoft extension to the ANSI C standard.

Looking at above assembly instructions, the value of ESI register (32-bit long Extended Source Index) points to arguments list (commented as ArgList), is pushed on the stack if any command line arguments supplied to program. After that, the variable ConsoleTitle’s offset holding our application name Crunch WinS is also pushed on the stack. Then call to SetConsoleTitleA() function will pop the application name from the stack & use it to set the title of console window.

The subsequent call goes to GetConsoleWindow() function that returns the HWND (window handle) in the EAX register (a 32-bit long accumulator register & EAX register is commonly used to store the function’s return value), and then the MOV instruction copies this returned value from EAX source register to ESI destination register.

The next two instructions push the value 3 & value of ESI on the stack. These two values will be popped & used by ShowWindow() function that takes two arguments: the first is of type HWND and other’s type is int, to control how the window will be shown. From function’s documentation we can confirm the value 3 belongs to constant SW_SHOWMAXIMIZED/SW_MAXIMIZE means the application’s console will be a maximized window.

Because of ShowWindow() function had popped HWND value from the stack, another PUSH instruction pushes the same HWND value stored in ESI and then SetForegroundWindow() function set the window to foreground specified by HWND (again, function popped off HWND value from the stack). At this point, the console window of application should be displayed in maximized state.

Now the call goes to subroutine sub_401050, which is most likely a function defined somewhere. We will get back to this subroutine shortly.

After calling subroutine sub_401050, the variable aPressEnterRetu’s offset holding string as \n\n(*) Press ENTER/RETURN to exit.. was pushed on the stack. Then a subroutine sub_401010 was called, that’ll most likely use this string. The string seem like some kind of message to the user, so subroutine sub_401010 should be printf() or wprintf() function in C-language, expanding that subroutine verifies this :

Subroutine sub_401010 is actually wprintf_s(), i.e. printf_s() function with wide character support

Now the wprintf_s() function or subroutine sub_401010 printed the string to console. Adding value 4 to the ESP register (32-bit long Extended Stack Pointer) means to clean up the stack space. The stack grows downwards in memory, incrementing ESP means clearing the stack. Then call to getwchar() function was to read an input character (supporting 16-bit Unicode) otherwise program will terminate immediately without waiting for anything. The main() function should return status to the operating system and as we know EAX register holds the function’s return value, doing XOR operation to EAX by itself will set it to zero (0) so the return value is zero (0). Then the ESI is restored by popping off the stack and function returns.

Now we can assume the general flow of main() function would be like :

  1. Set the title of console window.
  2. Get HWND for application’s console window.
  3. Show window as maximized on user’s screen using HWND.
  4. Call some arbitrary subroutine sub_401050 to perform some operation. (Next we’ll explore this!)
  5. Print a message to the user and wait for some single character user input before terminate.

Here is the original definition of main() function which resembles our assumptions above :

main() function implementation

In the above code, the line number 11 has a call to show_details() function which should be our un-explored subroutine sub_401050..!

Exploring ‘sub_401050’ subroutine or ‘show_details()’ function

This subroutine or function is bit more involved so it will be better if we understand the disassembled code along with original code otherwise people who are not much familiar with assembly may little get confused. We will explain small chunk of original code and it’s corresponding assembly embedded in same image side by side. Please keep in mind that disassemblers usually generate lot of assembly, it’s our responsibility to understand logic as better as possible.

Beginning of show_details() function implementation

On the left side of assembly, the yellow highlighted label loc_401076 forms a loop that basically zero-out the value at address location pointed by EAX at that time. This similar to what the function call SecureZeroMemory() (that expands to RtlSecureZeroMemory() function) do on the line 4 of original code at right side.

Also on the line 3 of original code there declared an array of type WCHAR (a data type supporting the 16-bit Unicode character) of size 257. This value came from the constant UNLEN (means username length) expands to 256 and actually the usage was (UNLEN + 1) = 257. When comparing this thing with assembly on left, the LEA instruction (Load Effective Address) loads the base address of size of array being created in EAX and on the next line, the MOV instruction assigned 101h (a hexadecimal value, equals 257 in decimal) to that address. So pushing this value of EAX to stack indicates the length/size of some buffer. Again, the LEA instruction loads the base address of actual array in EAX and that will also pushed on the stack for future use. Notice that the compiler may perform some intelligent tricks during compilation. That’s why in assembly we zero-out the values first, then specifying the buffer size & then allocate array memory; but in original code, we first allocate array memory, then zero-out the values & then specifying the buffer size.

Next we understand what those two respective stack values pcbBuffer & lpBuffer observed in assembly above will belong to.

GetUserNameW() function usage

The original code on right side calls the GetUserNameW() function that requires two arguments: the first is lpBuffer which contains logon name of user & other is pcbBuffer i.e. the size of the lpBuffer itself; the return value of this function is zero (0), if it fails. As specified in the if condition, if the function fails on the line 8, it just again call to the SecureZeroMemory() function on the line 10 to clear the userName argument i.e. lpBuffer & simply return on the line 11.

On the left side or in assembly, the GetUserNameW() subroutine was called which will surely use these two previously pushed stack values denoted by pcbBuffer & lpBuffer respectively. Next instruction will do a TEST on EAX (the TEST instruction internally perform bitwise AND operation without writing result to destination, which is EAX itself, rather it updates flags).

Then LEA instruction will calculate the address with respect to ESP & store into EAX. The JNZ instruction (Jump if Not Zero) will make a jump to loc_4010C6 if the zero-flag is not set, based on TEST operation performed previously. Otherwise if the zero-flag is set then the instructions in yellow highlighted block loc_4010A6 starts executing. In short, the block loc_4010A6 will do the SecureZeroMemory() function logic & return back to the caller function main() by restoring the previous function frame, called as function epilogue.

Exploration of loc_4010C6

The above one is likely easy to understand. In the code on right side, we got a print statement on the line 14, then a function call to Sleep() on the line 17, which require milliseconds as argument & the value passed is 3000 (i.e. 3 seconds in total). The left side or in assembly has same sequence which will perform few push operations and then call to subroutine sub_401010 which prints the message we know. The call to Sleep() function in assembly looks interesting. First the function is pointed by EDI register (32-bit long Extended Destination Index), then after adjusting ESP, the hexadecimal value 0BB8h pushed on stack and that means value 3000 in decimal..! Next instruction calls EDI means the Sleep() function & will use 3000 milliseconds value on stack as argument.

One more thing to observe is show_fake_progress() function call on the line 18 of right side. This is just a function that prints the progress bar in console window and because of many floating point numbers used in this function, we see some different assembly instructions used like XORPS (Bitwise Logical XOR of Packed Single Precision Floating-Point Values). It is not that important to understand its logic, since printing the progress bar is just nice visuals. But still here’s the disassembly & original code side by side :

show_fake_progress() function implementation

At this point we have fair idea of how the CPU registers used with assembly instructions to perform operations described in source code..! But we haven’t finished the show_details() function yet, so here’s the remaining disassembly versus original code :

show_details() function’s remaining definition

In the source code or right side of above image, we see the user being asked for a choice between yes/no (y/n) to display or not logon sessions information. But if we observe the if/else conditional blocks there, the program will always print the sessions info doesn’t matter what user provide as input choice..! Likewise before, the two more function calls should be noticed here.

The first one is extract_sessions() function calls, which basically iterates over all the logon sessions available and display the result in console after user provided the input choice (y/n). If we see the line numbers 26 and 46 on the left side or in assembly has calls to subroutine sub_4012F0 which actually means the calls to extract_sessions() function (to catch it better, I manually added comments for both calls on assembly lines 26 and 46).

The second one is clearInputStreamStdin() function calls, which contains a loop and it basically clears the STDIN (or standard input stream) otherwise library functions getwchar() or getchar() may not work as expected terminating the program immediately without waiting for anything. This happens because these two library functions read a character from input stream and if the input stream is not empty then it will read first character from stream without waiting for user’s input. Here’s the disassembly versus original code for clearInputStreamStdin() function :

clearInputStreamStdin() function implementation

Things to notice here is how the two condition values are evaluated in source code versus in assembly. In the while loop of source code, first we’re comparing with ‘\n’ (aka newline) character. So the line number 4 in assembly, this character is represented as 0Ah in hexadecimal, that equals value 10 in decimal, and the ASCII value of ‘\n’ character is actually 10..!

Next condition uses the constant EOF (stands for End Of File) which usually equal to -1. So the line number 11 in assembly is representing this -1 as 0FFFFFFFFh which is correct because if all bits of 32-bit integer is set then it becomes equal to -1..!

The instructions like CWD/CDQ/CQO used to convert Word to Doubleword or to convert Doubleword to Quadword.

Execution

Finally the below pictures provide better idea behind how our application executes :

1} Progress bar before extracting logon sessions (just a nice visual)
2} User being asked for choice (y/n) to show sessions
3} Displaying extracted sessions from the system

I’d encourage you to build project in Visual Studio (as Release). Then open generated binary/executable in disassembler of your choice & try to analyse the extract_sessions() function..! If you’re interested in full code, please go through my GitHub repo.

I hope you enjoyed the post. Feedback is always welcome. Thank you..!

Amey Chavan

--

--

Amey Chavan
Amey Chavan

Written by Amey Chavan

Passionate about programming, Software Engineering & gaming... 😃 GitHub/LinkedIn/Twitter: apchavan