Skip to content

Commit 2aa09a2

Browse files
committed
added trend micro writeups
1 parent 1f97e90 commit 2aa09a2

File tree

15 files changed

+796
-0
lines changed

15 files changed

+796
-0
lines changed

2015-09-26-trendmicro/README.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Writeup TrendMicro CTF 2015
2+
3+
Uczestniczyliśmy (Shalom, msm, Rev, other019, nazywam i graszka22) w TrendMicro 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+
* Zadania były podzelone na nietypowe kategorie, niemniej same zadania specjalnie nie odbiegały od standardu. Utrudniało to jednak wybranie "swoich" zadań. Organizatorzy nie chcieli pewnie, żeby ich klienci widzieli że w konkursie są kategorie typu "binary exploitation" czy "web hacking" więc nazwali je bp. "analysis-offensive"
8+
* Organizatorzy zrobili zadanie za 100p z krzyżówką gdzie wszystkie pytania dotyczyły ich firmy, np. "jak nazywa sie CEO", "w którym roku powstała", "jak sie nazywa produkt do..." ;]
9+
10+
# Spis treści:
11+
12+
* [Calculator (ppc/Programming 100)](calculator)
13+
* [Colors (ppc/Programming 200)](colors)
14+
* [Captcha (ppc/Misc 300)](captcha)
15+
* [RSA (crypto 100)](rsa)
16+
* [AES (crypto 200)](aes)
17+
18+
* [Maze (ppc 300)](maze)
19+
* [Offensive 100 (Web?/Analysis-offensive 100)](offensive100)
20+
* [Defense 100(?/Analysis-defensive 100)](defense100)
21+
* [Other 100 (?/Analysis-other 100)](other100)
22+
23+
24+
# Zakończenie
25+
26+
Zachęcamy do komentarzy/pytań/czegokolwiek.

2015-09-26-trendmicro/aes/README.md

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
## AES (crypto, 200p)
2+
3+
### PL Version
4+
`for ENG version scroll down`
5+
6+
Zadanie polegało na odzyskaniu wektora inicjalizacyjnego IV dla szyfru AES na podstawie znajomości wiadomości, części klucza oraz części zaszyfrowanego tekstu. Dane były przekazane za pomocą pomazanego zdjęcia kodu:
7+
8+
![](./q.png)
9+
10+
Wynika z nich że dysponujemy:
11+
12+
* Częścią klucza `5d6I9pfR7C1J` z brakującymi ostatnimi 2 bajtami
13+
* Wiadomością `The message is protected by AES!`
14+
* Fragmentem zaszyfrowanego tekstu `fe000000000000000000000000009ec3307df037c689300bbf2812ff89bc0b49` (przez 0 oznaczam padding nieznanych elementów)
15+
16+
Pierwszy krokiem, po zapoznaniu się z zasadą działania szyfrowania AES w zadanej konfiguracji, było odzyskanie całego klucza. Warto zauważyć że nasza wiadomość stanowi 2 bloki dla szyfru, każdy po 16 bajtów:
17+
18+
The message is p
19+
rotected by AES!
20+
21+
A szyfrowanie odbywa się blokami, więc nasz zaszyfrowany tekst także możemy podzielić na bloki:
22+
23+
fe000000000000000000000000009ec3
24+
307df037c689300bbf2812ff89bc0b49
25+
26+
Do szyfrowania pierwszego bloku AES używa wektora IV oraz klucza, ale do szyfrowania kolejnego bloku użyty jest tylko poprzedni zaszyfrowany blok oraz klucz. Dodatkowo szyfrowane odbywa się bajt po bajcie co oznacza, że deszyfrowanie 1 bajtu 2 bloku wymaga znajomości jedynie klucza oraz 1 bajtu 1 bloku.
27+
28+
To oznacza, że dla danych:
29+
30+
XX000000000000000000000000000000
31+
YY000000000000000000000000000000
32+
33+
Deszyfrowanie za pomocą poprawnego klucza pozwoli uzyskać poprawnie odszyfrowany 16 bajt wiadomości (licząc od 0), niezależnie od wektora IV.
34+
W związku z tym próbujemy przetestować wszystkie możliwości ostatnich 2 znaków klucza, sprawdzając dla których deszyfrowany tekst zawiera odpowiednie wartości w drugim bloku na pozycjach na których w pierwszym bloku mamy ustawione poprawne wartości (pierwszy bajt oraz dwa ostatnie):
35+
36+
KEY = "5d6I9pfR7C1J"
37+
IV = "0000000000000000"
38+
39+
def valid_key(correct_bytes, decrypted):
40+
for byte_tuple in correct_bytes:
41+
if decrypted[byte_tuple[0]] != byte_tuple[1]:
42+
return False
43+
return True
44+
45+
def break_key(key_prefix, encoded_message_part, correct_bytes):
46+
final_key = ""
47+
encrypted = encoded_message_part
48+
for missing1 in range(0, 256):
49+
key = key_prefix + chr(missing1)
50+
for missing2 in range(0, 256):
51+
real_key = key + chr(missing2)
52+
decrypted = decrypt(real_key, IV, binascii.unhexlify(encrypted))
53+
if valid_key(correct_bytes, decrypted):
54+
final_key = real_key
55+
return final_key
56+
57+
real_key = break_key(KEY, "fe000000000000000000000000009ec3307df037c689300bbf2812ff89bc0b49", [(16, "r"), (30, "S"), (31, "!")])
58+
59+
Uzyskujemy w ten sposób klucz: `5d6I9pfR7C1JQt`
60+
61+
Wektor IV którego poszukujemy służy do szyfrowania 1 bloku i opiera się na podobnej zasadzie jak szyfrowanie kolejnych bloków przedstawione wyżej - pierwszy bajt pierwszego bloku zależy od pierwszego bajtu wektora IV, drugi od drugiego itd. Żeby móc w takim razie odzyskać wektor IV potrzebujemy znać pierwszy blok zaszyfrowanej wiadomości. W tym celu stosujemy zabieg identyczny jak powyżej, ale tym razem próbujemy dopasować kolejne bajty zaszyfrowanego pierwszego bloku wiadomości, sprawdzając kiedy deszyfrowanie daje nam poprawnie deszyfrowany bajt z drugiego bloku:
62+
63+
IV = "0000000000000000"
64+
message = "The message is protected by AES!"
65+
ciphertext = ""
66+
encrypted = "00000000000000000000000000000000307df037c689300bbf2812ff89bc0b49"
67+
data = binascii.unhexlify(encrypted)
68+
for position in range(16): # going through first block
69+
encrypted_sub = list(data)
70+
for missing in range(0, 256):
71+
encrypted_sub[position] = chr(missing) #encrypted message with single byte in first block set to tested value
72+
decrypted = decrypt(real_key, IV, "".join(encrypted_sub))
73+
if decrypted[position + 16] == message[position + 16]:
74+
print("%d %d" % (position, missing))
75+
print(decrypted[position + 16])
76+
ciphertext += chr(missing)
77+
print(binascii.hexlify(ciphertext))
78+
79+
Co daje nam: `fe1199011d45c87d10e9e842c1949ec3` i jest to pierwszy zakodowany blok.
80+
81+
Ostatnim krokiem jest odzyskanie wektora IV. Robimy to identycznym schematem, tym razem testujemy kolejne bajty wektora IV sprawdzając kiedy deszyfrowanie daje nam poprawnie odszyfrowane wartości z 1 bloku:
82+
83+
iv_result = ""
84+
encrypted = "fe1199011d45c87d10e9e842c1949ec3"
85+
for position in range(16):
86+
iv = list(IV)
87+
for missing in range(0, 256):
88+
iv[position] = chr(missing) # IV with single byte set to tested value
89+
decrypted = decrypt(real_key, "".join(iv), binascii.unhexlify(encrypted))
90+
if decrypted[position] == message[position]:
91+
print("%d %d" % (position, missing))
92+
iv_result += chr(missing)
93+
print(iv_result)
94+
95+
Co daje nam `Key:rVFvN9KLeYr6` więc zgodnie z treścią zadania flagą jest `TMCTF{rVFvN9KLeYr6}`
96+
97+
### ENG Version
98+
99+
The task was to recover initialization vector IV for AES cipher based on knowledge of the message, part of the key and part of ciphertext. The data were proviede as a photo of crossed-out code:
100+
101+
![](./q.png)
102+
103+
From this we can get:
104+
105+
* Part of the key: `5d6I9pfR7C1J` with missing 2 bytes
106+
* Message: `The message is protected by AES!`
107+
* Part of ciphertext: `fe000000000000000000000000009ec3307df037c689300bbf2812ff89bc0b49` (0s in the first block are missing part)
108+
109+
First step, after reading about AES in given configuration, was to extract the whole ciper key. It is worth noting that our message is separated into 2 blocks for this cipher, each with 16 bytes:
110+
111+
The message is p
112+
rotected by AES!
113+
114+
And the cipher works on blocks, so our ciphertext can also be split into blocks:
115+
116+
fe000000000000000000000000009ec3
117+
307df037c689300bbf2812ff89bc0b49
118+
119+
For encoding the first block AES uses IV vector and the key, but to encode second block only previous block and the key is used. On top of that the cipher works byte-by-byte which means that deciphering 1 byte of 2 block requires knowledge only of the key and of the 1 byte of 1 block.
120+
121+
It means that for input:
122+
123+
XX000000000000000000000000000000
124+
YY000000000000000000000000000000
125+
126+
Deciphering usign a proper key will give us properly decoded 16th byte (counting from 0), regardless of IV vector used.
127+
Therefore, we test all possible values for the missing 2 key characters, testing for which of them the decipered text has proper values in the second block on the positions where in the first block we have proper values (first byte and last two bytes):
128+
129+
KEY = "5d6I9pfR7C1J"
130+
IV = "0000000000000000"
131+
132+
def valid_key(correct_bytes, decrypted):
133+
for byte_tuple in correct_bytes:
134+
if decrypted[byte_tuple[0]] != byte_tuple[1]:
135+
return False
136+
return True
137+
138+
def break_key(key_prefix, encoded_message_part, correct_bytes):
139+
final_key = ""
140+
encrypted = encoded_message_part
141+
for missing1 in range(0, 256):
142+
key = key_prefix + chr(missing1)
143+
for missing2 in range(0, 256):
144+
real_key = key + chr(missing2)
145+
decrypted = decrypt(real_key, IV, binascii.unhexlify(encrypted))
146+
if valid_key(correct_bytes, decrypted):
147+
final_key = real_key
148+
return final_key
149+
150+
real_key = break_key(KEY, "fe000000000000000000000000009ec3307df037c689300bbf2812ff89bc0b49", [(16, "r"), (30, "S"), (31, "!")])
151+
152+
This way we get the key: `5d6I9pfR7C1JQt`
153+
154+
IV vector we are looking for is used to encode 1 block and it is used on the same principle as encoding next blocks decribed above - encoded 1 byte of 1 block depends on 1 byte of 1 block of IV vector, 2 depends on 2 etc. Therefore, to be able to get the IV vector we need to know the whole first encoded block. To get it we use a very similar approach as the one we used to get the key, but this time we test bytes of the encoded 1 block, checking which value after decoding gives us properly decoded byte from 2 block:
155+
156+
IV = "0000000000000000"
157+
message = "The message is protected by AES!"
158+
ciphertext = ""
159+
encrypted = "00000000000000000000000000000000307df037c689300bbf2812ff89bc0b49"
160+
data = binascii.unhexlify(encrypted)
161+
for position in range(16): # going through first block
162+
encrypted_sub = list(data)
163+
for missing in range(0, 256):
164+
encrypted_sub[position] = chr(missing) #encrypted message with single byte in first block set to tested value
165+
decrypted = decrypt(real_key, IV, "".join(encrypted_sub))
166+
if decrypted[position + 16] == message[position + 16]:
167+
print("%d %d" % (position, missing))
168+
print(decrypted[position + 16])
169+
ciphertext += chr(missing)
170+
print(binascii.hexlify(ciphertext))
171+
172+
Which gives us: `fe1199011d45c87d10e9e842c1949ec3` and this is the encoded 1 block.
173+
174+
Last step is to recover IV vector. We use the same principle, this time testing IV vector bytes, checking when deciphering gives us properly decoded values from 1 block:
175+
176+
iv_result = ""
177+
encrypted = "fe1199011d45c87d10e9e842c1949ec3"
178+
for position in range(16):
179+
iv = list(IV)
180+
for missing in range(0, 256):
181+
iv[position] = chr(missing) # IV with single byte set to tested value
182+
decrypted = decrypt(real_key, "".join(iv), binascii.unhexlify(encrypted))
183+
if decrypted[position] == message[position]:
184+
print("%d %d" % (position, missing))
185+
iv_result += chr(missing)
186+
print(iv_result)
187+
188+
Which gives us: `Key:rVFvN9KLeYr6` so according to the task rules the flag is `TMCTF{rVFvN9KLeYr6}`

2015-09-26-trendmicro/aes/q.png

1.08 MB
Loading
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
## Calculator (ppc/Programming, 100p)
2+
3+
### PL Version
4+
`for ENG version scroll down`
5+
6+
Zadanie polegało na połączeniu się za pomocą NC z podanym serwerem. Serwer podawał na wejściu działania i oczekiwał na ich rozwiązania. Należało rozwiązać kilkadziesiąt przykładów pod rząd aby uzyskać flagę. Działania przychodzące z serwera miały postać:
7+
8+
`eight hundred ninety nine million, one hundred sixty eight thousand eleven - 556226 * ( 576 - 21101236 ) * 948 - ( 29565441 + thirty six ) * 182,745 - 6,124,792 + CMLXXVI - 647 =`
9+
10+
Na co serwer w odpowiedzi oczekiwał na: `11121023402232863`
11+
12+
Zadanie rozwiązaliśmy wykorzystując parser liczb słownych, parser liczb rzymskich oraz pythonową funkcję `eval()`.
13+
Same transformacje są raczej trywialne i łatwie do znalezienia w internecie, reszta solvera to:
14+
15+
def solve(data):
16+
fixed = data.replace(",", "") #turn 3,200 into 3200
17+
fixed = " " + fixed #ensure word boundary on the left
18+
romans = re.findall("[^\d=\\-\\+/\\*\\(\\)\s]+", fixed)
19+
for romanNumber in romans:
20+
try:
21+
number = str(fromRoman(romanNumber))
22+
fixed = re.sub(r"\b%s\b" % romanNumber, number, fixed)
23+
except:
24+
pass
25+
literals = re.findall("[^\d=\\-\\+/\\*\\(\\)]+", fixed)
26+
for literal in sorted(literals, key=lambda x: len(x), reverse=True):
27+
if literal != ' ' and literal != "":
28+
try:
29+
number = str(text2int(literal))
30+
fixed = re.sub(r"\b%s\b" % literal.strip(), number, fixed)
31+
except:
32+
pass
33+
return eval(fixed[:-2]) #omit " ="
34+
35+
Czyli w skrócie:
36+
37+
* Usuwamy przecinki będące separatorami tysiąców
38+
* Zamieniamy wszystkie znalezione liczby rzymskie na arabskie
39+
* Zamieniamy wszystkie znalezione literały na liczby arabskie (uwaga: trzeba zamieniać od tych najdłuższych, żeby np. zamiana "one" nie była plikowana do "fifty one")
40+
* Usuwamy znak `=` z końca
41+
* Ewaluujemy wyrażenie
42+
43+
Po kilkudziesieciu przykładach dostajemy: `Congratulations!The flag is TMCTF{U D1D 17!}`
44+
45+
### ENG Version
46+
47+
The challenge was to connect to a server via NC. Server was providing equations and was waiting for their solutions. We had to solve few dozens consecutively in order to get the flag. The equations were for example:
48+
49+
`eight hundred ninety nine million, one hundred sixty eight thousand eleven - 556226 * ( 576 - 21101236 ) * 948 - ( 29565441 + thirty six ) * 182,745 - 6,124,792 + CMLXXVI - 647 =`
50+
51+
And server was expecting a solution: `11121023402232863`
52+
53+
We solved this using literal nubmbers parser, roman numbers parser and python `eval()` function.
54+
The parsers are trivial and easy to find on the internet, the rest was:
55+
56+
def solve(data):
57+
fixed = data.replace(",", "") #turn 3,200 into 3200
58+
fixed = " " + fixed #ensure word boundary on the left
59+
romans = re.findall("[^\d=\\-\\+/\\*\\(\\)\s]+", fixed)
60+
for romanNumber in romans:
61+
try:
62+
number = str(fromRoman(romanNumber))
63+
fixed = re.sub(r"\b%s\b" % romanNumber, number, fixed)
64+
except:
65+
pass
66+
literals = re.findall("[^\d=\\-\\+/\\*\\(\\)]+", fixed)
67+
for literal in sorted(literals, key=lambda x: len(x), reverse=True):
68+
if literal != ' ' and literal != "":
69+
try:
70+
number = str(text2int(literal))
71+
fixed = re.sub(r"\b%s\b" % literal.strip(), number, fixed)
72+
except:
73+
pass
74+
return eval(fixed[:-2]) #omit " ="
75+
76+
So in short:
77+
78+
* We remove thousands separator `,`
79+
* We turn all roman numbers into integers
80+
* We turn all literal numbers into integers (notice: you need to replace starting from longest numbers so that for example replacing "one" doesn't affect "fifty one")
81+
* We remove `=` from the end
82+
* We evaluate the expression
83+
84+
After mutiple examples we finally get:`Congratulations!The flag is TMCTF{U D1D 17!}`

0 commit comments

Comments
 (0)