Reverse Engineering of a Parameterless C Function with Return Value in 32-bit

Reverse Engineering of a Parameterless C Function with Return Value in 32-bit

Below is a simple example of a C function that does not take any parameters but returns an integer value. Additionally, I provide step-by-step instructions for writing and viewing its assembly code using Visual Studio 2022.

C Function

#include <stdio.h>

// Definition of a parameterless function with a return value
int GetMagicNumber()
{
    // Return a fixed integer as an example
    return 42; // You can choose any value you want to return
}

int main() {
    // Call the GetMagicNumber function and print the result
    int magicNumber = GetMagicNumber();
    printf("The magic number is: %d\n", magicNumber);
    return 0;
}

How to View the Disassembly Code of This Function in VS2022:

  1. Open Visual Studio 2022, then create a new C project (for example, a console application).

  2. Copy the above code into your source file (usually named main.c or another .c file you named).

  3. Set a breakpoint: To conveniently view the assembly code of a specific function, click next to the line number on the return 42; statement inside the GetMagicNumber function to set a breakpoint. This allows you to pause here while running the program in debug mode, enabling you to view the assembly code at this location.

  4. Start debug mode: Press F5 or click on “Debug” -> “Start Debugging” in the menu bar. The program will run and pause at the breakpoint you set.

  5. View the assembly code

    32-bit debug version

int GetMagicNumber(){00C31790 push ebp00C31791 mov ebp,esp00C31793 sub esp,0C0h00C31799 push ebx00C3179A push esi00C3179B push edi00C3179C mov edi,ebp00C3179E xor ecx,ecx00C317A0 mov eax,0CCCCCCCCh00C317A5 rep stos dword ptr es:[edi]00C317A7 mov ecx,offset _EFF9DB1D_C Reverse Engineering of a Parameterless C Function with Return Value”>GetMagicNumber@cpp (0C3C008h)00C317AC call @__CheckForDebuggerJustMyCode@4 (0C3132Ah)00C317B1 nop// Return a fixed integer as an examplereturn 42; // You can choose any value you want to return00C317B2 mov eax,2Ah}00C317B7 pop edi00C317B8 pop esi00C317B9 pop ebx00C317BA add esp,0C0h00C317C0 cmp ebp,esp00C317C2 call __RTC_CheckEsp (0C3124Eh)00C317C7 mov esp,ebp00C317C9 pop ebp00C317CA ret— No source file —————————

The disassembly code shows the underlying implementation details of the functionGetMagicNumber. Below are some explanations of this assembly code, which help understand how the C function works at a low level:

int GetMagicNumber()
{
00C31790  push        ebp          ; Save the current value of ebp register to the stack
00C31791  mov         ebp,esp      ; Copy the current value of esp register to ebp, setting a new stack frame
00C31793  sub         esp,0C0h     ; Allocate space on the stack for local variables (here 0xC0 bytes)
00C31799  push        ebx          ; Save the value of ebx register
00C3179A  push        esi          ; Save the value of esi register
00C3179B  push        edi          ; Save the value of edi register
00C3179C  mov         edi,ebp      ; Copy the value of ebp to edi
00C3179E  xor         ecx,ecx      ; Clear ecx register
00C317A0  mov         eax,0CCCCCCCCh; Set eax register to 0x0CCCCCCC for debugging uninitialized local variables
00C317A5  rep stos    dword ptr es:[edi] ; Use rep stos instruction to repeatedly write the value of eax to the space starting from edi for initializing local variables in debug mode
00C317A7  mov         ecx,offset _EFF9DB1D_C Reverse Engineering of a Parameterless C Function with Return Value">GetMagicNumber@cpp (0C3C008h)
00C317AC  call        @__CheckForDebuggerJustMyCode@4 (0C3132Ah) ; Call a check function, possibly related to debugging
00C317B1  nop                     ; No operation instruction, usually for alignment or debugging

    // Return a fixed integer as an example
    return 42; // You can choose any value you want to return
00C317B2  mov         eax,2Ah      ; Move the immediate value 42 (hexadecimal 0x2A) into eax register, preparing it as the return value

}
00C317B7  pop         edi          ; Restore the value of edi register
00C317B8  pop         esi          ; Restore the value of esi register
00C317B9  pop         ebx          ; Restore the value of ebx register
00C317BA  add         esp,0C0h     ; Release the previously allocated stack space
00C317C0  cmp         ebp,esp      ; Compare the values of ebp and esp to ensure they match (for debugging check)
00C317C2  call        __RTC_CheckEsp (0C3124Eh) ; Runtime check to ensure esp is correct, also for debugging purposes
00C317C7  mov         esp,ebp      ; Restore the original stack pointer
00C317C9  pop         ebp          ; Restore the original base pointer
00C317CA  ret                     ; Return to the caller, with the return value in eax

Key Point Analysis:

  • Function Prologue: Includes push ebp, mov ebp,esp, sub esp,0C0h and other instructions to establish a new stack frame.

  • Protecting Register State: The original values of registers (ebx, esi, edi) are saved using push instructions to prevent changes during function execution from affecting the external environment.

  • Return Value Handling: The return value is stored in the eax register, which is the number 42.

  • Function Epilogue: Restores the saved registers and stack frame, and returns to the caller using the ret instruction.

int main() {00C318C0 push ebp00C318C1 mov ebp,esp00C318C3 sub esp,0CCh00C318C9 push ebx00C318CA push esi00C318CB push edi00C318CC lea edi,[ebp-0Ch]00C318CF mov ecx,300C318D4 mov eax,0CCCCCCCCh00C318D9 rep stos dword ptr es:[edi]00C318DB mov ecx,offset _EFF9DB1D_C Reverse Engineering of a Parameterless C Function with Return Value”>GetMagicNumber@cpp (0C3C008h)00C318E0 call @__CheckForDebuggerJustMyCode@4 (0C3132Ah)00C318E5 nop// Call the GetMagicNumber function and print the resultint magicNumber = GetMagicNumber();00C318E6 call GetMagicNumber (0C3136Bh)00C318EB mov dword ptr [magicNumber],eaxprintf(“The magic number is: %d\n”, magicNumber);00C318EE mov eax,dword ptr [magicNumber]00C318F1 push eax00C318F2 push offset string “The magic number is: %d\n” (0C37B30h)00C318F7 call _printf (0C310D2h)00C318FC add esp,8return 0;00C318FF xor eax,eax}00C31901 pop edi00C31902 pop esi00C31903 pop ebx00C31904 add esp,0CCh00C3190A cmp ebp,esp00C3190C call __RTC_CheckEsp (0C3124Eh)00C31911 mov esp,ebp00C31913 pop ebp00C31914 ret— No source file ———————–

The assembly code you provided shows the implementation details of the main function, including the process of calling the GetMagicNumber function and printing its return value. Below is an explanation of this assembly code:

Assembly Code Analysis

int main() {
00C318C0  push        ebp          ; Save the current value of ebp register to the stack
00C318C1  mov         ebp,esp      ; Copy the current value of esp register to ebp, setting a new stack frame
00C318C3  sub         esp,0CCh     ; Allocate space on the stack for local variables (here 0xCC bytes)
00C318C9  push        ebx          ; Save the value of ebx register
00C318CA  push        esi          ; Save the value of esi register
00C318CB  push        edi          ; Save the value of edi register
00C318CC  lea         edi,[ebp-0Ch];  Calculate the address of [ebp-0xC] and load it into edi
00C318CF  mov         ecx,3        ; Set ecx to 3 for initializing unused stack space
00C318D4  mov         eax,0CCCCCCCCh; Set eax register to 0x0CCCCCCC for debugging uninitialized local variables
00C318D9  rep stos    dword ptr es:[edi] ; Use rep stos instruction to repeatedly write the value of eax to the space starting from edi for initializing local variables in debug mode
00C318DB  mov         ecx,offset _EFF9DB1D_C Reverse Engineering of a Parameterless C Function with Return Value">GetMagicNumber@cpp (0C3C008h)
00C318E0  call        @__CheckForDebuggerJustMyCode@4 (0C3132Ah) ; Call a check function, possibly related to debugging
00C318E5  nop                     ; No operation instruction, usually for alignment or debugging

    // Call the GetMagicNumber function and print the result
    int magicNumber = GetMagicNumber();
00C318E6  call        GetMagicNumber (0C3136Bh)  ; Call the GetMagicNumber function
00C318EB  mov         dword ptr [magicNumber],eax; Store the return value of GetMagicNumber (in eax) into the local variable magicNumber

    printf("The magic number is: %d\n", magicNumber);
00C318EE  mov         eax,dword ptr [magicNumber] ; Load the value of magicNumber into eax register
00C318F1  push        eax          ; Push the value of magicNumber onto the stack as the second parameter for printf
00C318F2  push        offset string "The magic number is: %d\n" (0C37B30h) ; Push the address of the format string onto the stack as the first parameter
00C318F7  call        _printf (0C310D2h) ; Call printf function to output text and the value of magicNumber
00C318FC  add         esp,8        ; Clean up the stack, removing the two parameters passed to printf

    return 0;
00C318FF  xor         eax,eax      ; Clear the eax register, indicating main function returns 0

}
00C31901  pop         edi          ; Restore the value of edi register
00C31902  pop         esi          ; Restore the value of esi register
00C31903  pop         ebx          ; Restore the value of ebx register
00C31904  add         esp,0CCh     ; Release the previously allocated stack space
00C3190A  cmp         ebp,esp      ; Compare the values of ebp and esp to ensure they match (for debugging check)
00C3190C  call        __RTC_CheckEsp (0C3124Eh) ; Runtime check to ensure esp is correct, also for debugging purposes
00C31911  mov         esp,ebp      ; Restore the original stack pointer
00C31913  pop         ebp          ; Restore the original base pointer
00C31914  ret                     ; Return to the operating system, with the return value in eax

Key Point Summary:

  • Function Prologue: Establishes a new stack frame and allocates space for local variables using push ebp, mov ebp,esp, and sub esp,0CCh instructions.

  • Protecting Register State: The original values of registers (ebx, esi, edi) are saved using a series of push instructions to prevent their state from being modified and affecting the external environment.

  • Calling the GetMagicNumber Function: The call GetMagicNumber instruction is used to call this function, and the return value is stored in the local variable magicNumber.

  • Calling the printf Function: Prepares parameters (such as the value of magicNumber and the format string), then calls _printf to output the information.

  • Function Epilogue: Restores the saved register state and stack frame, and returns to the operating system using the ret instruction, with the value in the eax register (in this case, 0) as the program’s exit status code.

32-bit release version

— C:\Users\14371\source\repos\C Reverse Engineering of a Parameterless C Function with Return Value\GetMagicNumber.cpp ————-// Call the GetMagicNumber function and print the resultint magicNumber = GetMagicNumber();printf(“The magic number is: %d\n”, magicNumber);009D1040 push 2Ah009D1042 push offset string “The magic number is: %d\n” (09D2100h)009D1047 call printf (09D1010h)009D104C add esp,8return 0;009D104F xor eax,eax}009D1051 ret

The assembly code snippet shows the process of calling the GetMagicNumber function and printing its return value in the main function. Compared to the previous detailed explanation, this assembly code is more concise and directly pushes the return value of GetMagicNumber (42 or hexadecimal 0x2A) as an immediate value onto the stack, rather than explicitly calling the GetMagicNumber function. This may be because the compiler has inlined the implementation of GetMagicNumber during compilation or directly used its return value during optimization.

Assembly Code Analysis

// Call the GetMagicNumber function and print the result
int magicNumber = GetMagicNumber();
009D1040  push        2Ah          ; Push the immediate value 42 (0x2A) onto the stack, replacing the call to GetMagicNumber and obtaining its return value
009D1042  push        offset string "The magic number is: %d\n" (09D2100h)  ; Push the address of the format string onto the stack
009D1047  call        printf (09D1010h)  ; Call printf function to output text and the value of magicNumber
009D104C  add         esp,8        ; Clean up the stack, removing the two parameters passed to printf

return 0;
009D104F  xor         eax,eax      ; Clear the eax register, indicating main function returns 0
}
009D1051  ret                     ; Return to the operating system, with the return value (in this case, 0)

Key Point Summary:

  • Simplified Handling: The assembly code here does not explicitly call the GetMagicNumber function, but directly pushes the return value (42) as an immediate value (push 2Ah) onto the stack. This may be due to compiler optimization, which directly replaced the call to GetMagicNumber with a constant.

  • Calling the printf Function: First, the value of magicNumber (which is 42) and the address of the format string are pushed onto the stack, and then the printf function is called to output the information.

  • Stack Cleanup: The add esp,8 instruction is used to manually adjust the stack pointer to clear the two parameters pushed onto the stack for the printf call (each parameter occupies 4 bytes of space).

  • Function Epilogue: The xor eax,eax instruction sets the eax register to 0, indicating that the main function ends normally, and the ret instruction returns to the operating system.

This optimization strategy can reduce the overhead of function calls and improve program execution efficiency, especially when the function body is very simple. However, this also means that you will not see the original function call structure in the disassembly code unless you disable the compiler’s optimization options.

Leave a Comment