type
status
date
slug
summary
tags
category
icon
password
In this blog, we'll explore how to insert a simple Windows API MessageBox to a PE-format executable before it starts.

Step 1: Locating the MessageBoxA Function Address

Our journey begins with finding the address of the MessageBoxA function in the virtual memory during runtime, here I used x86dbg, a powerful x86/x64 debugger. The address I found for my application is 0x76B28270, please note that this address isn't fixed and may change after a reboot.
To find the address in x86dbg, use this command:

Step 2: View the Text Section Header in the PE File

The text section of the PE file, which is executable by default, will house our new code. So, let's seek an empty space within this section for our MessageBox.
In this demonstration, I used a open sourced PE tool to view the text section header in the PE header. The memory block for the code section in my application spans from 0x400 to 0x400 + 0xFA00. Our code can be put in any empty space within this block. For this blog, assume 0xFC80 as the starting position of the code we insert.
notion image

Step 3: Implementing the Message Box to Insert

For this blog, we'll simply create an empty message box via the Windows API MessageBox, then return to the original entry point of our application. The original entry point of my application is 0x11028, and my image base is 0x400000. You can also retrieve this information from the PE optional header using your preferred PE tool.
Here's the code we're going to inject: we push all the arguments into the stack frame, call the MessageBoxA function, then jump back to the original entry point when it returns. Note that MessageBoxA is one of the Windows API and it follows the stdcall calling convention, which means the function callee is responsible for cleaning up the stack before it returns.

Step 4: Understanding CALL and JMP Instructions

Before we proceed, it's crucial to understand that the CALL and JMP instructions within the same EXE/DLL calculate the target address using a relative addressing mode. The Effective Address (EA) is computed using the formula:
  • The Next Instruction Address is the address immediately succeeding the CALL or JMP instruction.
  • The Relative Offset is the value provided with the CALL or JMP instruction.
Why do such instructions using relative addressing mode? Relative addressing refers to specifying an address relative to the current position in code (often relative to the next instruction). This way, if the entire code block gets moved in memory, as it often does when a DLL or an EXE gets loaded, the relative addresses stay valid without requiring any modifications, because they still point to the same place relative to the rest of the code.

Step 5: Calculating the Relative Offset

Now that we're familiar with the effective address, we need to calculate the relative offset that goes into the hardcoding of the instruction. Keep in mind, the address where we inserted our code in the text section is 0xFC80. However, this is the offset of our code in the file buffer, and we need to compute the actual address our code would have during runtime. The formula is:
Note that the virtual offset of text section has been found in step 2.
Finally, we can now calculate the relative offset for the CALL and JMP instructions:
For the CALL instruction:
For the JMP instruction:
There you go! Now you can put the binary hardcode we computed in step 3-5 into the PE file using your favorite hex editor (I used WinHex).
notion image

Step 6: Updating the Entry Point in the PE Header

After injecting our code, we also need to modify the entry point field in the PE header to point to our newly added code. This field tells the operating system where to start executing code when the program is launched.
You may also want to disable ASLR flag in DLL characteristics, this technique randomizes the image base every time the application starts:
notion image
notion image
 

Step 7: We did it!

With the final modification, you've successfully added a new MessageBox to your executable file. Launch your application, and you should be greeted with an empty message box before the original program starts.
notion image

Step 8: Automate the Process with C++

Now let’s re-do all the steps above, but this time we will do it with C++, note that I used some utility functions & classes in the code below, the implementation of these functions are irrelevant.
IAT TableHow C/C++ Compiler Generate Assembly Code For Switch Statement
Zack Yang
Zack Yang
Just a humble bounty hunter🥷
公告
type
status
date
slug
summary
tags
category
icon
password
Hey there, welcome to my blog 👋
-- 这个博客写些什么 --
定期技术分享🤖
不定期发疯文学🤡