From b11373e7e04004dcd0996536b66d200af7610658 Mon Sep 17 00:00:00 2001 From: Malfurious Date: Thu, 5 May 2022 01:56:39 -0400 Subject: angstromCTF 2022 results Signed-off-by: Malfurious --- scores.txt | 1 + 1 file changed, 1 insertion(+) 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) -- cgit v1.2.3 From e2f6f7d9ee2a5eb46030b90c3939985a56f3a5a6 Mon Sep 17 00:00:00 2001 From: Malfurious Date: Sat, 7 May 2022 20:52:36 -0400 Subject: Writeup angstromCTF 2022 / Auth Skip Signed-off-by: Malfurious --- docs/writeups/angstromCTF_2022/Auth_Skip.txt | 61 ++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 docs/writeups/angstromCTF_2022/Auth_Skip.txt 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}.`); +}); -- cgit v1.2.3 From 69002ed19f3c239d9ed86da4e52ec5363279ddf0 Mon Sep 17 00:00:00 2001 From: Malfurious Date: Sat, 7 May 2022 20:56:22 -0400 Subject: Writeup angstromCTF 2022 / baby3 Signed-off-by: Malfurious --- docs/writeups/angstromCTF_2022/baby3.txt | 43 ++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 docs/writeups/angstromCTF_2022/baby3.txt 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} -- cgit v1.2.3 From aeb7ef3dba2dc1524a795b5faac72fa1f0472897 Mon Sep 17 00:00:00 2001 From: Malfurious Date: Sat, 7 May 2022 22:28:30 -0400 Subject: Writeup angstromCTF 2022 / uninspired Signed-off-by: Malfurious --- docs/writeups/angstromCTF_2022/uninspired.txt | 150 ++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 docs/writeups/angstromCTF_2022/uninspired.txt 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; +} -- cgit v1.2.3 From 8456a85a083c7cbc957e6a9176c0c7a608b63283 Mon Sep 17 00:00:00 2001 From: Malfurious Date: Sat, 7 May 2022 22:52:52 -0400 Subject: Writeup angstromCTF 2022 / whatsmyname Signed-off-by: Malfurious --- docs/writeups/angstromCTF_2022/whatsmyname.txt | 115 +++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 docs/writeups/angstromCTF_2022/whatsmyname.txt 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 +#include +#include + +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; +} -- cgit v1.2.3