Skip to content

Commit e63f4be

Browse files
committed
Adding hacklu and ekoparty writeups
1 parent f1a848c commit e63f4be

File tree

7 files changed

+343
-0
lines changed

7 files changed

+343
-0
lines changed

2015-10-20 hacklu/README.md

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Writeup Hack.lu CTF 2015
2+
3+
Uczestniczyliśmy (msm, Rev, Shalom, other019, nazywam i pp) w Hack.lu CTF, i znowu spróbujemy opisać zadania z którymi walczyliśmy (a przynajmniej te, które pokonaliśmy).
4+
5+
Ogólne wrażenia:
6+
- CTF w środku tygodnia = bardzo słaby pomysł bo wszyscy w pracy albo w szkole więc na CTFa zostaje tylko kilka godzin...
7+
8+
Opisy zadań po kolei.
9+
10+
# Spis treści / Table of contents:
11+
* Module Loader 100+10
12+
* PHP Golf 75+70
13+
* Stackstuff 150+80
14+
* Bashful 200+40
15+
* Creative Cheating 150+60
16+
* Checkcheckcheck 150+80
17+
* Perl Golf 75+50
18+
* Teacher's Pinboard 352+100
19+
* Secret Library 200+70
20+
* Grading Board 300+80
21+
* [GuessTheNumber (ppc 150+80)](ppc150_guess_the_number)
22+
* GuessTheNumber 150+80
23+
* Salt 200+90
24+
* Dr. Bob 150+80
25+
26+
# Zakończenie
27+
28+
Zachęcamy do komentarzy/pytań/czegokolwiek.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
## GuessTheNumber (ppc, 150+80p)
2+
3+
The teacher of your programming class gave you a tiny little task: just write a guess-my-number script that beats his script. He also gave you some hard facts:
4+
he uses some LCG with standard glibc LCG parameters
5+
the LCG is seeded with server time using number format YmdHMS (python strftime syntax)
6+
numbers are from 0 up to (including) 99
7+
numbers should be sent as ascii string
8+
You can find the service on school.fluxfingers.net:1523
9+
10+
### PL
11+
[ENG](#eng-version)
12+
13+
Pierwsze podejście do zadania polegało na implementacji opisanego w zadaniu LCG i próbie dopasowania wyników do zgadywanki z serwera. Jednak bardzo szybko uznaliśmy, że możliwości jest bardzo dużo i nie ma sensu ich analizować skoro da się to zadanie wykonać dużo prościej.
14+
Do zgadnięcia mamy zaledwie 100 liczb pod rząd a rozdzielczość zegara na serwerze to 1s. W związku z tym uznaliśmy, że prościej i szybciej będzie po prostu oszukiwać w tej grze :)
15+
16+
Wiemy że liczby generowane są przez LCG co oznacza, że dla danego seeda liczby do zgadnięcia są zawsze takie same. W szczególności jeśli dwóch użytkowników połączy się w tej samej sekundzie to wszystkie 100 liczb do zgadnięcia dla nich będzie takie samo. Dodatkowo serwer zwraca nam liczbę której oczekiwał jeśli się pomylimy.
17+
18+
Nasze rozwiązanie jest dość proste:
19+
20+
* Uruchamiamy 101 wątków, które w tej samej sekundzie łączą się z docelowym serwerem.
21+
* Synchronizujemy wątki tak, żeby wszystkie zgadywały jedną turę w tej samej chwili a następnie czekały aż wszystkie skończą.
22+
* W każdej turze wszystkie wątki oprócz jednego czekają na liczbę do wysłania.
23+
* W każdej iteracji jeden wątek "poświęca się" wysyłając -1 jako odpowiedź, a następnie odbiera od serwera poprawną odpowiedź i informuje o niej pozostałe wątki.
24+
* W efekcie co turę "tracimy" jeden wątek, ale wszystkie pozostałe przechodzą do następnej tury podając poprawną odpowiedź.
25+
26+
Każdy wątek realizuje poniższy kod:
27+
28+
```python
29+
max = 101
30+
threads = Queue()
31+
correct_values = Queue()
32+
init_barier = threading.Barrier(max)
33+
init_barier_seeds = threading.Barrier(max)
34+
bar = [threading.Barrier(max - i) for i in range(max)]
35+
seeds = set()
36+
37+
38+
def worker(index):
39+
threads.get()
40+
init_barier.wait()
41+
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
42+
s.connect(("school.fluxfingers.net", 1523))
43+
initial_data = str(s.recv(4096))
44+
seeds.add(parse_seed(initial_data))
45+
init_barier_seeds.wait()
46+
if len(seeds) != 1: # make sure we all start with the same seed, otherwise quit
47+
threads.task_done()
48+
return
49+
for i in range(max):
50+
bar[i].wait() #wait on the barrier for all other threads
51+
if i == index: # suicide thread
52+
value = -1
53+
else:
54+
value = correct_values.get()
55+
s.send(bytes(str(value) + "\n", "ASCII"))
56+
data = str(s.recv(4096))
57+
print("thread " + str(index) + " iteration " + str(i) + " " + data)
58+
if "wrong" in data.lower():
59+
correct = re.compile("'(\d+)'").findall(data)[0]
60+
for j in range(max - i - 1): # tell everyone what is the right number
61+
correct_values.put(correct)
62+
break
63+
threads.task_done()
64+
```
65+
66+
Kompletny skrypt dostępny [tutaj](guess.py)
67+
68+
Uruchamiamy skrypt i dostajemy:
69+
```
70+
thread 98 iteration 97 b'Correct! Guess the next one!\n'
71+
thread 99 iteration 97 b'Correct! Guess the next one!\n'
72+
thread 100 iteration 97 b'Correct! Guess the next one!\n'
73+
thread 98 iteration 98 b"Wrong! You lost the game. The right answer would have been '38'. Quitting."
74+
thread 99 iteration 98 b'Correct! Guess the next one!\n'
75+
thread 100 iteration 98 b'Correct! Guess the next one!\n'
76+
thread 99 iteration 99 b"Wrong! You lost the game. The right answer would have been '37'. Quitting."
77+
thread 100 iteration 99 b"Congrats! You won the game! Here's your present:\nflag{don't_use_LCGs_for_any_guessing_competition}"
78+
```
79+
80+
`flag{don't_use_LCGs_for_any_guessing_competition}`
81+
82+
### ENG version
83+
84+
Initial attempt for this task was to implement described LCG and trying to match the output for the results on the server. But we instantly decided that there are too many possibilities and there is no point in wasting time for analysis when we can do it much easier.
85+
We need to guess only 100 numbers in a row and the clock resolution on the server is just 1s. So we decided that it will be better and faster just to cheat the game :)
86+
87+
We know that the numbers are generated with LCG which means that for given seed the numbers are always the same. In particular, if two users connect at the same time the 100 numbers to guess will be identical. On top of that the server returns the expected number if we make a mistake.
88+
89+
Our solution was quite simple:
90+
91+
* Run 101 threads, which will connect to the server at the same time.
92+
* Synchronize the threads so that they all execute a single turn and the wait for the rest.
93+
* In each turn all threads but one are waiting for the number to send.
94+
* In each turn one thread "sacrifices himself" sending -1 as answer, and the collects the correct number form server response and informs rest of the threads about it.
95+
* As a result in each turn we "lose" one thread but all the others pass to the next round.
96+
97+
Eaach thread executes:
98+
99+
```python
100+
max = 101
101+
threads = Queue()
102+
correct_values = Queue()
103+
init_barier = threading.Barrier(max)
104+
init_barier_seeds = threading.Barrier(max)
105+
bar = [threading.Barrier(max - i) for i in range(max)]
106+
seeds = set()
107+
108+
109+
def worker(index):
110+
threads.get()
111+
init_barier.wait()
112+
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
113+
s.connect(("school.fluxfingers.net", 1523))
114+
initial_data = str(s.recv(4096))
115+
seeds.add(parse_seed(initial_data))
116+
init_barier_seeds.wait()
117+
if len(seeds) != 1: # make sure we all start with the same seed, otherwise quit
118+
threads.task_done()
119+
return
120+
for i in range(max):
121+
bar[i].wait() #wait on the barrier for all other threads
122+
if i == index: # suicide thread
123+
value = -1
124+
else:
125+
value = correct_values.get()
126+
s.send(bytes(str(value) + "\n", "ASCII"))
127+
data = str(s.recv(4096))
128+
print("thread " + str(index) + " iteration " + str(i) + " " + data)
129+
if "wrong" in data.lower():
130+
correct = re.compile("'(\d+)'").findall(data)[0]
131+
for j in range(max - i - 1): # tell everyone what is the right number
132+
correct_values.put(correct)
133+
break
134+
threads.task_done()
135+
```
136+
137+
Complete script is [here](guess.py).
138+
139+
We run the script and we get:
140+
```
141+
thread 98 iteration 97 b'Correct! Guess the next one!\n'
142+
thread 99 iteration 97 b'Correct! Guess the next one!\n'
143+
thread 100 iteration 97 b'Correct! Guess the next one!\n'
144+
thread 98 iteration 98 b"Wrong! You lost the game. The right answer would have been '38'. Quitting."
145+
thread 99 iteration 98 b'Correct! Guess the next one!\n'
146+
thread 100 iteration 98 b'Correct! Guess the next one!\n'
147+
thread 99 iteration 99 b"Wrong! You lost the game. The right answer would have been '37'. Quitting."
148+
thread 100 iteration 99 b"Congrats! You won the game! Here's your present:\nflag{don't_use_LCGs_for_any_guessing_competition}"
149+
```
150+
151+
`flag{don't_use_LCGs_for_any_guessing_competition}`
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
from queue import Queue
2+
import socket
3+
import threading
4+
import re
5+
6+
7+
def parse_seed(data):
8+
pattern = re.compile(".*(\d{2})\\.(\d{2})\\.(\d{4}).*(\d{2}):(\d{2}):(\d{2})", re.MULTILINE | re.DOTALL)
9+
seed = pattern.findall(data)[0]
10+
return int(str.format("{2}{1}{0}{3}{4}{5}", *seed))
11+
12+
13+
max = 101
14+
threads = Queue()
15+
correct_values = Queue()
16+
init_barier = threading.Barrier(max)
17+
init_barier_seeds = threading.Barrier(max)
18+
bar = [threading.Barrier(max - i) for i in range(max)]
19+
seeds = set()
20+
21+
22+
def worker(index):
23+
threads.get()
24+
init_barier.wait()
25+
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
26+
s.connect(("school.fluxfingers.net", 1523))
27+
initial_data = str(s.recv(4096))
28+
seeds.add(parse_seed(initial_data))
29+
init_barier_seeds.wait()
30+
if len(seeds) != 1: # make sure we all start with the same seed, otherwise quit
31+
threads.task_done()
32+
return
33+
for i in range(max):
34+
bar[i].wait() #wait on the barrier for all other threads
35+
if i == index: # suicide thread
36+
value = -1
37+
else:
38+
value = correct_values.get()
39+
s.send(bytes(str(value) + "\n", "ASCII"))
40+
data = str(s.recv(4096))
41+
print("thread " + str(index) + " iteration " + str(i) + " " + data)
42+
if "wrong" in data.lower():
43+
correct = re.compile("'(\d+)'").findall(data)[0]
44+
for j in range(max - i - 1): # tell everyone what is the right number
45+
correct_values.put(correct)
46+
break
47+
threads.task_done()
48+
49+
50+
def conn():
51+
for i in range(max):
52+
thread = threading.Thread(target=worker, args=[i])
53+
thread.daemon = True
54+
thread.start()
55+
for i in range(max):
56+
threads.put(i)
57+
threads.join()
58+
59+
60+
conn()

2015-10-22-ekoparty/README.md

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Writeup Ekoparty CTF 2015
2+
3+
Uczestniczyliśmy (msm, Rev, Shalom, other019, nazywam i pp) w Ekoparty CTF, i znowu spróbujemy opisać zadania z którymi walczyliśmy (a przynajmniej te, które pokonaliśmy).
4+
5+
Ogólne wrażenia:
6+
7+
Opisy zadań po kolei.
8+
9+
# Spis treści/Table of contents:
10+
11+
* Slogans (trivia 50)
12+
* Banner (trivia 70)
13+
* Mr Anderson (trivia 80)
14+
* SSL Attack (trivia 90)
15+
* Pass Check (web 50)
16+
* Crazy JSON (web 200)
17+
* Rand DOOM (web 300)
18+
* SCYTCRYPTO (crypto 50)
19+
* [XOR Crypter (crypto 200)](crpto_200_xorcrypter)
20+
* Patch me (reverse 50)
21+
* Malware (reverse 200)
22+
* Olive (misc 50)
23+
24+
25+
# Zakończenie
26+
27+
Zachęcamy do komentarzy/pytań/czegokolwiek.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
## XOR Crypter (crypto 200p)
2+
3+
Description: The state of art on encryption, can you defeat it?
4+
CjBPewYGc2gdD3RpMRNfdDcQX3UGGmhpBxZhYhFlfQA=
5+
6+
### PL
7+
[ENG](#eng-version)
8+
9+
Cały kod szyfrujący jest [tutaj](shiftcrypt.py).
10+
Szyfrowanie jest bardzo proste, aż dziwne że zadanie było za 200 punktów. Szyfrowanie polega na podzieleniu wejściowego tekstu na 4 bajtowe kawałki (po dodaniu paddingu jeśli to konieczne, aby rozmiar wejścia był wielokrotnością 4 bajtów), rzutowanie ich na inta a następnie wykonywana jest operacja `X xor X >>16`. Jeśli oznaczymy kolejnymi literami bajty tego inta uzyskujemy:
11+
12+
`ABCD ^ ABCD >> 16 = ABCD ^ 00AB = (A^0)(B^0)(C^A)(D^B) = AB(C^A)(D^B)`
13+
14+
Jak widać dwa pierwsze bajty są zachowywane bez zmian a dwa pozostałe bajty są xorowane z tymi dwoma niezmienionymi. Wiemy także że xor jest operacją odwracalną i `(A^B)^B = A` możemy więc odwrócić szyfrowanie dwóch ostatnich bajtów xorując je jeszcze raz z pierwszym oraz drugim bajtem (pamiętając przy tym o kolejności bajtów)
15+
16+
```python
17+
data = "CjBPewYGc2gdD3RpMRNfdDcQX3UGGmhpBxZhYhFlfQA="
18+
decoded = base64.b64decode(data)
19+
blocks = struct.unpack("I" * (len(decoded) / 4), decoded)
20+
output = ''
21+
for block in blocks:
22+
bytes = map(ord, struct.pack("I", block))
23+
result = [bytes[0] ^ bytes[2], bytes[1] ^ bytes[3], bytes[2], bytes[3]]
24+
output += "".join(map(chr, result))
25+
print(output)
26+
```
27+
28+
W wyniku czego uzyskujemy flagę: `EKO{unshifting_the_unshiftable}`
29+
30+
### ENG version
31+
32+
33+
Cipher code is [here](shiftcrypt.py).
34+
The cipher is actually very simple, it was very strange that the task was worth 200 point. The cipher splits the input text in 4 byte blocks (after adding padding if necessary so that the input is a multiply of 4 bytes), casting each block to integer and the performing `X xor X >>16`. If we mark each byte of the single block with consecutive alphabet letters we get:
35+
36+
`ABCD ^ ABCD >> 16 = ABCD ^ 00AB = (A^0)(B^0)(C^A)(D^B) = AB(C^A)(D^B)`
37+
38+
As can be notices, first two bytes are unchanged and last two are xored with those two unchanged. We also know that xor is reversible and `(A^B)^B = A` so we can revert the cipher of the last two bytes by xoring them again with first and second byte (keeping in mind the byte order).
39+
40+
```python
41+
data = "CjBPewYGc2gdD3RpMRNfdDcQX3UGGmhpBxZhYhFlfQA="
42+
decoded = base64.b64decode(data)
43+
blocks = struct.unpack("I" * (len(decoded) / 4), decoded)
44+
output = ''
45+
for block in blocks:
46+
bytes = map(ord, struct.pack("I", block))
47+
result = [bytes[0] ^ bytes[2], bytes[1] ^ bytes[3], bytes[2], bytes[3]]
48+
output += "".join(map(chr, result))
49+
print(output)
50+
```
51+
52+
As a result we get: `EKO{unshifting_the_unshiftable}`
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import struct
2+
import sys
3+
import base64
4+
5+
if len(sys.argv) != 2:
6+
print "Usage: %s data" % sys.argv[0]
7+
exit(0)
8+
9+
data = sys.argv[1]
10+
padding = 4 - len(data) % 4
11+
if padding != 0:
12+
data = data + "\x00" * padding
13+
14+
result = []
15+
blocks = struct.unpack("I" * (len(data) / 4), data)
16+
for block in blocks:
17+
result += [block ^ block >> 16]
18+
19+
output = ''
20+
for block in result:
21+
output += struct.pack("I", block)
22+
23+
print base64.b64encode(output)

README.md

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# CTF writeups from P4 Team
22

3+
* [2015.10.18 **Ekoparty CTF 2015** (28th place / 356 teams)](2015-10-22-ekoparty)
4+
* [2015.10.18 **Hack.lu CTF 2015** (17th place / 248 teams)](2015-10-20 hacklu)
35
* [2015.10.18 **Hitcon CTF 2015** (22th place / 382 teams)](2015-10-18-hitcon)
46
* [2015.10.10 **ASIS CTF Finals 2015** (17th place / 197 teams)](2015-10-10-asisfin)
57
* [2015.10.02 **Defcamp CTF Qualification 2015** (17th place / 378 teams)](2015-10-02-dctf)

0 commit comments

Comments
 (0)