summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/writeups/angstromCTF_2022/Auth_Skip.txt61
-rw-r--r--docs/writeups/angstromCTF_2022/baby3.txt43
-rw-r--r--docs/writeups/angstromCTF_2022/uninspired.txt150
-rw-r--r--docs/writeups/angstromCTF_2022/whatsmyname.txt115
-rw-r--r--scores.txt1
5 files changed, 370 insertions, 0 deletions
diff --git a/docs/writeups/angstromCTF_2022/Auth_Skip.txt b/docs/writeups/angstromCTF_2022/Auth_Skip.txt
new file mode 100644
index 0000000..a5a4767
--- /dev/null
+++ b/docs/writeups/angstromCTF_2022/Auth_Skip.txt
@@ -0,0 +1,61 @@
+Clam was doing his angstromCTF flag% speedrun when he ran into the infamous
+timesink known in the speedrunning community as "auth". Can you pull off the
+legendary auth skip and get the flag?
+
+Category: web (40 points)
+Chall author: aplet123
+Writeup author: malfurious
+
+
+
+The website starts on a login page prompting for a username and password, with
+the text "Want flag? Been far? Decided to use? Login first."
+
+As seen in the source code (see below), login supports the username "admin"
+whose password is compared to a string that is randomly generated on each
+request. However, all that is required to view the flag is to navigate to the
+home page with a basic cookie "user=admin", which can be set locally in the
+browser or via a curl command.
+
+> curl -b user=admin https://auth-skip.web.actf.co/
+actf{passwordless_authentication_is_the_new_hip_thing}
+
+
+
+Original source (Javascript): index.js
+--------------------------------------
+const express = require("express");
+const path = require("path");
+const cookieParser = require("cookie-parser");
+
+const app = express();
+const port = Number(process.env.PORT) || 8080;
+
+const flag = process.env.FLAG || "actf{placeholder_flag}";
+
+app.use(express.urlencoded({ extended: false }));
+app.use(cookieParser());
+
+app.post("/login", (req, res) => {
+ if (
+ req.body.username !== "admin" ||
+ req.body.password !== Math.random().toString()
+ ) {
+ res.status(401).type("text/plain").send("incorrect login");
+ } else {
+ res.cookie("user", "admin");
+ res.redirect("/");
+ }
+});
+
+app.get("/", (req, res) => {
+ if (req.cookies.user === "admin") {
+ res.type("text/plain").send(flag);
+ } else {
+ res.sendFile(path.join(__dirname, "index.html"));
+ }
+});
+
+app.listen(port, () => {
+ console.log(`Server listening on port ${port}.`);
+});
diff --git a/docs/writeups/angstromCTF_2022/baby3.txt b/docs/writeups/angstromCTF_2022/baby3.txt
new file mode 100644
index 0000000..2c1d3a7
--- /dev/null
+++ b/docs/writeups/angstromCTF_2022/baby3.txt
@@ -0,0 +1,43 @@
+This program doesn't do anything.
+
+Category: re (40 points)
+Chall author: preterite
+Writeup author: malfurious
+
+
+
+As described, this challenge offers an ELF binary that has no observable effect
+when run. However, disassembly of its main function shows the flag string
+being constructed in memory via several mov instructions. Of course, the flag
+is not read before returning.
+
+ 0x00001139 55 push rbp
+ 0x0000113a 4889e5 mov rbp, rsp
+ 0x0000113d 4883ec40 sub rsp, 0x40
+ 0x00001141 64488b042528. mov rax, qword fs:[0x28]
+ 0x0000114a 488945f8 mov qword [canary], rax
+ 0x0000114e 31c0 xor eax, eax
+ 0x00001150 48b861637466. movabs rax, 0x686d657b66746361 ; 'actf{emh'
+ 0x0000115a 48ba70616964. movabs rdx, 0x657a656d64696170 ; 'paidmeze'
+ 0x00001164 488945c0 mov qword [var_40h], rax
+ 0x00001168 488955c8 mov qword [var_38h], rdx
+ 0x0000116c 48b8726f646f. movabs rax, 0x72616c6c6f646f72 ; 'rodollar'
+ 0x00001176 48ba73746f6d. movabs rdx, 0x74656b616d6f7473 ; 'stomaket'
+ 0x00001180 488945d0 mov qword [var_30h], rax
+ 0x00001184 488955d8 mov qword [var_28h], rdx
+ 0x00001188 48b868697363. movabs rax, 0x6c6c616863736968 ; 'hischall'
+ 0x00001192 48ba656e6765. movabs rdx, 0x6f6d615f65676e65 ; 'enge_amo'
+ 0x0000119c 488945e0 mov qword [var_20h], rax
+ 0x000011a0 488955e8 mov qword [var_18h], rdx
+ 0x000011a4 c745f0677573. mov dword [var_10h], 0x7d737567 ; 'gus}'
+ 0x000011ab c645f400 mov byte [var_ch], 0
+ 0x000011af b800000000 mov eax, 0
+ 0x000011b4 488b55f8 mov rdx, qword [canary]
+ 0x000011b8 64482b142528. sub rdx, qword fs:[0x28]
+ ┌─< 0x000011c1 7405 je 0x11c8
+ │ 0x000011c3 e868feffff call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
+ │ ; CODE XREF from main @ 0x11c1
+ └─> 0x000011c8 c9 leave
+ 0x000011c9 c3 ret
+
+actf{emhpaidmezerodollarstomakethischallenge_amogus}
diff --git a/docs/writeups/angstromCTF_2022/uninspired.txt b/docs/writeups/angstromCTF_2022/uninspired.txt
new file mode 100644
index 0000000..cc1d6c6
--- /dev/null
+++ b/docs/writeups/angstromCTF_2022/uninspired.txt
@@ -0,0 +1,150 @@
+clam has no more inspiration :( maybe help him get some?
+
+Category: re (100 points)
+Chall author: aplet123
+Writeup author: malfurious
+
+
+
+Software RE
+-----------
+We are given an executable ELF binary, and RE reveals two functions of interest:
+main and print_flag.
+
+The main function will read a line of text from input and make various
+assertions about it, exiting if any fail. Eventually, on success, the user
+supplied string is passed to the print_flag function. print_flag uses the data
+to derive the flag string, then prints it of course.
+
+I didn't bother reversing the print_flag function, as main gives us a seemingly
+simple puzzle to solve in order to satify its requirements. Those requirements
+being:
+
+ - Input must be 10 characters
+ - Each character must be numeric ('0' - '9')
+ - The value of each character (digit) will increment a corresponding index
+ in an array ("count array")
+ - The final count array values must match the original input in sequence
+
+Therefore, we must supply a 10-digit number where each positional digit
+(starting from the left) is equal to the quantity of that positional value in
+the number overall. For example, consider this potential answer:
+
+ 9 0 0 0 0 0 0 0 0 0
+ ^ ^
+ zero position nine position
+
+The value of 9 in the zero position indicates that there must be a total of 9
+zeroes in the overall value, which there are. However, this is not a correct
+anwser, as there is a zero in the nine position while the actual number of nines
+is 1 (the one in the zero position).
+
+
+
+Numeric puzzle
+--------------
+Let's try to fixup the previous example to arrive at the correct solution. If
+we correct the nines position, there is now one fewer zeros, which would need
+its own correction, but there are other cascading issues as well.
+
+ 9 0 0 0 0 0 0 0 0 1 # wrong number of zeroes and ones
+ 8 0 0 0 0 0 0 0 0 1 # wrong number of eights, nines, ones
+ 8 1 0 0 0 0 0 0 1 0 # wrong number of zeroes and ones
+ ...
+
+Not 100% confident that there even was a solution, the team decided to try to
+rule out sets of potential solutions by reasoning our way to a contradiction
+from some given starting point (as you can see above, for the example of
+zero->nine).
+
+We decided to start by varying the value of zero, starting with zero set to
+nine. The reason for this being that maximizing the number of zeroes in the
+number should in theory minimize the additional values we need to fit in the
+remaining digits. If the nines position has a value of 1, for example, then
+that would imply that there must be nine of some other value elsewhere in the
+answer. Furthermore, a two in the nines position is impossible, since there
+aren't enough places to satify the requirements imposed by two floating nine
+digits.
+
+Using this process, we eliminated all solutions where the zero position is
+9, 8, or 7, however converged on a possible solution for zero->six.
+
+ 6 0 0 0 0 0 0 0 0 0 # wrong zeroes, sixes
+ 6 0 0 0 0 0 1 0 0 0 # wrong zeroes, ones
+ 6 1 0 0 0 0 1 0 0 0 # wrong zeroes, ones
+ 6 2 0 0 0 0 1 0 0 0 # wrong zeroes, ones, twos
+ 6 2 1 0 0 0 1 0 0 0 # all positions correct!
+
+It is unknown to the team whether there are other correct solutions. Since
+this string is used to derive the flag, it's likely that this is the only one.
+However, if the other solutions (if they exist) are somehow congruent under the
+logic of the print_flag function, then the puzzle should still hold.
+
+
+
+Solution
+--------
+> ./uninspired
+there's no more inspiration :(
+6210001000
+yay I'm inspired now, have a flag :)
+actf{ten_digit_numbers_are_very_inspiring}
+
+
+
+Decompiled main function (C/Ghidra)
+-----------------------------------
+undefined8 main(void)
+{
+ size_t str_len;
+ long idx;
+ undefined8 retval;
+ char *inp_ptr;
+ char user_input [10];
+ char user_input_end [6];
+ int buffer [4];
+ char c;
+
+ inp_ptr = user_input;
+ puts("there\'s no more inspiration :(");
+ fgets(user_input,0x10,stdin);
+ str_len = strcspn(user_input,"\n");
+ /* Read user input, fixup newline/null termination
+ Length of string must == 10 */
+ user_input[(int)str_len] = '\0';
+ if ((int)str_len == 10) {
+ /* zero buffer */
+ buffer = (undefined [16])0x0;
+ do {
+ /* foreach char in user_input */
+ c = *inp_ptr;
+ if (9 < (byte)(c - 0x30U)) {
+ /* characters must be numeric (0-9) */
+ puts("I don\'t like your inspiration :(");
+ return 1;
+ }
+ inp_ptr = inp_ptr + 1;
+ /* increment the selected position in 'buffer' */
+ buffer[(char)(c - 0x30U)] = buffer[(char)(c - 0x30U)] + 1;
+ } while (inp_ptr != user_input_end);
+ idx = 0;
+ do {
+ /* foreach char in user_input */
+ if (buffer[idx] != user_input[idx] + -0x30) {
+ /* buffer indicies must match input string values */
+ puts("that\'s not good inspiration :(");
+ return 1;
+ }
+ idx = idx + 1;
+ } while (idx != 10);
+ puts("yay I\'m inspired now, have a flag :)");
+ print_flag(user_input);
+ retval = 0;
+ }
+ else {
+ /* strlen != 10 */
+ puts("that\'s not very inspiring :(");
+ retval = 1;
+ }
+ return retval;
+}
diff --git a/docs/writeups/angstromCTF_2022/whatsmyname.txt b/docs/writeups/angstromCTF_2022/whatsmyname.txt
new file mode 100644
index 0000000..9fc3fd7
--- /dev/null
+++ b/docs/writeups/angstromCTF_2022/whatsmyname.txt
@@ -0,0 +1,115 @@
+Can you guess my name?
+
+Category: pwn (50 points)
+Chall author: JoshDaBosh
+Writeup author: malfurious
+
+
+
+The problem gives us an ELF binary, it's source code, and a netcat endpoint
+hosting the service. The program asks us for our name, then asks us to guess
+it's name, which is initialized to random data gathered from /dev/urandom.
+
+All character buffers are allocated to 48 bytes in size, and all input functions
+properly bound memory to avoid buffer overruns. However, after we enter our
+name, the program prints it back to us with a greeting. Because the two name
+strings are adjacent in memory, with the user's name coming first, we can leak
+the random service name by filling the user's name buffer completely to avoid
+its NULL terminator.
+
+Take note that the output is appended with a "!" character. I missed this
+initially, and spent far too long trying to debug the situation.
+
+Our guess is compared to the real service name with strncmp(name, guess, 48) == 0.
+
+Our guess is read by the binary with scanf("%48s[^\n]", guess). The '[^\n]'
+portion should cause reading to stop at the first newline. However, this is
+redundant with how %s works anyway. '%s' stops reading its input at _any_
+whitespace, including:
+
+ 0x20 space
+ 0x09 horizontal tab
+ 0x0a newline
+ 0x0b vertical tab
+ 0x0c feed
+ 0x0d carriage return
+
+This means that sending the correct guess would be impossible on any run where
+these 6 bytes were present in the random data. On a lucky run (lacking any of
+the whitespace chars), sending the leaked data will result in the flag being
+printed.
+
+actf{i_c0uld_be_l0nely_with_y0u_a21f8611c74b}
+
+
+
+Solution (Python/sploit)
+------------------------
+#!/usr/bin/sploit
+from sploit.payload import Payload
+from sploit.until import contains
+io.logonwrite = True
+
+pref = b'Nice to meet you, '
+size = 48
+
+io.write(Payload().rep(b'A', size)())
+
+io.readuntil(contains, pref)
+io.read(size) # our name
+leak = io.read(size)[:-1] # [-1] to strip the trailing '!'
+
+io.writeline(leak)
+
+
+
+Original source (C): whatsmyname.c
+----------------------------------
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+static void generate_name(char *str)
+{
+ FILE *file = fopen("/dev/urandom","r");
+ fgets(str, 48, file);
+ fclose(file);
+}
+
+int main(){
+ char yourName[48];
+ char myName[48];
+
+ char guess[48];
+
+ setbuf(stdout, NULL);
+
+ generate_name(myName);
+
+ printf("Hi! What's your name? ");
+
+ int n = read(0, yourName, 48);
+ if (yourName[n-1] == '\n') yourName[n-1] = '\x00';
+
+ printf("Nice to meet you, %s!\n", yourName);
+
+ puts("Guess my name and you'll get a flag!");
+
+ scanf("%48s[^\n]", guess);
+
+ if (strncmp(myName, guess, 48) == 0){
+ char flag[128];
+
+ FILE *file = fopen("flag.txt","r");
+ if (!file) {
+ puts("Error: missing flag.txt.");
+ exit(1);
+ }
+
+ fgets(flag, 128, file);
+ puts(flag);
+ }
+
+ puts("Bye!");
+ return 0;
+}
diff --git a/scores.txt b/scores.txt
index 7a35f34..6e772d2 100644
--- a/scores.txt
+++ b/scores.txt
@@ -19,3 +19,4 @@ N1CTF 2021 136 110 /601
Metasploit Community CTF 2021 1300 22 /265 (727)
picoCTF 2022 13100 140 /7794
+angstromCTF 2022 2111 80 /1179 (1319)