HackDay 2024 | Write-up des challenges de crypto
The following article will describe the solutions to all the crypto challenges from HackDay 2024 CTF - a CTF for which my team and I managed to qualify for the finals !
Baby Cryptography
Statement
1
2
3
4
5
6
Among the exchanges recovered between the Union agents we also saw an encrypted message. He must be talking about something important. That is why we ask you to decipher the communication. We know that the agent knows how to xor and how to count...
Parmi les échanges récupérés entre les agents du Syndicat nous avons aussi vu un message crypté. Il doit surement parler de quelque chose d'important. Nous savons qu'il sait compter et xorer. C'est pourquoi nous vous demandons de déchiffrer la communication.
Déchiffrez le texte ci-joint (il a été base64 pour la portabilité ^^) Formatage du drapeau : HACKDAY{email}
Decrypt the file (which was base64 for portability) FLAG FORMAT : HACKDAY{email}
Author : Chelinka
Points : 100
Solves : 62
Source : You can get the source files on the HackDay GitHub
Analysis of the input file
In this challenge we are given an input secret.txt file wich contains a base64 string. We can decode it using the following python code :
1
2
3
4
5
6
7
from base64 import b64decode
with open("secret.txt") as f_in:
ct = b64decode(f_in.read())
print(ct)
f_in.close()
Here is a part of the result :
1
b'Hdnok%Q7zd9xx>|#\x1a\x1bZvfp6vj|:otx>vNRVQQFRNGGY\x0bXB\x0eI_]^\\C\x0f<\x17\x15\x19cTI\x1d_M%a%,-+!g<&j>?(n;84r&\' 7;x07+0<0+@\x15\rC\x01\x0b\x12\x02[...]'
Solving the chall
According to the statement, we know that “the agent know how to XOR and count”. Moreover, we see that the first character is decodable and the further you go from the beginning of the text, the more it becomes undecodable. We can try to XOR each character with it’s index in the text with the following python code :
1
2
3
4
5
res = b""
for char_index in range(len(ct)):
res += bytes([ct[char_index] ^ char_index])
print(res)
But we get this error :
1
2
3
res += bytes([ct[c] ^ c])
^^^^^^^^^^^^^^^^^^
ValueError: bytes must be in range(0, 256)
Obviously, after a while, we XOR with indexes that are too large, so the XOR result will also be large. So we need to reduce each intermediate res modulo 256 and then we get the following text :
1
b"Hello W0rm3st3r,\n\nHere are the instructions to follow:\n - You are going to use the usual implant to enter the company's virtual enclosure we talked about last time.\n- The implant will be detected after a few days by the Blue Team, which is perfectly normal. This gives us plenty of time to scan the network, as well as the Active Directory behind it.\n- Your only objective is to park in the Starbucks opposite the company and collect the data emitted by the implant.\n- Once you've completed the scan, or stopped the implant, you'll send us the encrypted data to the following address: C2endpoint@requiemC2.thm.htb.rm.fr\n\nLet's get to work!"
According to the flag format, here is the flag : HACKDAY{C2endpoint@requiemC2.thm.htb.rm.fr}
Show me the way
Statement
1
2
3
4
5
6
7
L'agent secret 007 nous a donné un fichier classé et nous a donné le moyen de pouvoir le lire, malheureusement, il n'a pas pu le faire pour éviter tout soupçons au sein du Hack107. Déchiffrez les texte contenu dans le fichier pour y lire le nom du protocole secret !
The secret agent 007 gave us a classified file and gave us the means to read it, unfortunately, he could not do it to avoid any suspicion within the Hack107. Decrypt the text in the file to read the flag!
Récupérez les fichiers et récupérez le nom du protocole activé ! Formatage du drapeau: HACKDAY{PROTOCOLE}
Download the files and get the flag data ! FLAG FORMAT : HACKDAY{PROTOCOL}
Author : Chelinka
Points : 443
Solves : 20
Files analysis
This challenge consist of two files :
encrypt.py: the source of the encryptionsecretmessage.txt: components of the public key, and the ciphertext
The file encrypt.py
After analyzing the source code, I didn’t find anything interesting except:
- We don’t know how primes are generated, except that their generation doesn’t seem random, so the vulnerability may lie in
pandq - Before the encryption, the flag is reversed (
flag = flag[::-1]), but this info is useless as the plaintext is not stereotyped
All in all, this python file doesn’t help that much, but that was predictable based on this message of the chall maker : “Et pour show me the way ça marche sans le chiffrement, c’est du bête RSA derrière normalement” (en: source code is not required for a simple RSA implementation).
The file secretmessage.txt
After analyzing the source code, I’ll now talk about the public key parameters:
About e, the choosen value dosen’t seem to have any weakness (e = 0x10001). Indeed, 0x10001 is a common value and is large enough even four our size of N. We can deduce that the issue has to come from N.
Unfortunetly, about N … this is pretty robust. We are dealing with a 4060-bit long number. I’ve checked quickly its binary/hex representation but I didn’t find anything.
Challenge resolution
Following this inconclusive analysis, I’ve deciced to try factoring N, but after tryed the most of more classical factorization algorithm I’ve gave up this option.
I then turned to intensive reading of RSA write-ups on ctftime and after the sixth pages, I’ve found this write up (thanks jack4818). It exploited a technic I was not familiar with it. It involved examining the representations of N in different bases to find a base where N has a lot of 0s.
So, I analyzed N in differents bases using the str(base=i) method on sage integer. When it came to base 7, here what I obtained:
1
2
sage: N.str(base=7)
'1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000010000000000000001000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000010000000000000000000000000000000000000000000000000000000000331000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000010000110000000000000000000000000000000000000000000010000000000000011000000000000000000000000000000000000000000000000011'
We can decomposeN in power of $7$ like this : \(N = 7^0 \times 1 + 7^1 \times 1 + 7^{51} \times 1 + ... + 7^{1446}\)
Then, we replace $7$ by the indeterminate $x$ thanks to the following sage code :
1
2
3
sage: poly = sum(e * x^i for i,e in enumerate(N.digits(7)))
sage: poly
x^1446 + 3*x^1198 + x^1146 + 3*x^898 + x^835 + x^790 + x^774 + x^724 + 2*x^723 + 3*x^542 + x^535 + 3*x^476 + 3*x^475 + x^474 + x^423 + x^179 + x^118 + x^113 + x^112 + x^67 + x^52 + x^51 + x + 1
We got know a polynomial which evaluated in $x = 7$ is equal to N, thanks to the number of 0s in the N representation, the associated polynomial is easy to factorize. Indeed :
1
2
sage: factor(poly)
(x^723 + 3*x^475 + x^112 + x^51 + 1)*(x^723 + x^423 + x^67 + x + 1)
We now just have to evaluate each of the factors in $x = 7$ in order to get p and q. Now that we have p and q … this is over!
Final script
1
2
3
4
5
6
7
8
9
10
11
12
sage: from Crypto.Util.number import long_to_bytes, bytes_to_long
sage: p = (x^723 + 3*x^475 + x^112 + x^51 + 1)
sage: q = (x^723 + x^423 + x^67 + x + 1)
sage: p = p(x=7)
sage: q = q(x=7)
sage: assert p * q == N
sage: phi = (p-1)*(q-1)
sage: e = 0x10001
sage: d = pow(e, -1, phi)
sage: C = 2565617200166195847141820928636232303959466722932792435323711473965652085371699818644409256990409854048944[...]31
sage: long_to_bytes(pow(C, d, N))[::-1]
b'DS Bandit pwned - Follow protocol EVE - Brute Ninja'
According to the flag format : HACKDAY{EVE}
Again and again and again and again
Statement
1
2
3
4
5
6
7
Nous avons intercepté une communication sortant du serveur de secours du Syndicat. On dirait qu'un membre s'est fait une porte de secours pour pouvoir discuter avec quelqu'un à l'intérieur... Mais que souhaitait-il dire ?
We've intercepted an outgoing communication from the Syndicate's backup server. It looks like a member has made himself an emergency door so he can chat with someone inside... But what did he want to say?
Paraît-il qu'il discutent de `Hack107` et de l'un de leur équipiers... Utilisez cette information pour déchiffrer les trois conversations.
Decrypt the conversation using the fact that they are talking about `Hack107` and one of their team member...
Author : Chelinka
Points : 430
Solves : 22
Files analysis
This challenge consist of 4 file, each file is a conversation given in base 64. After decoding each conversation from base64 the result is just raw bytes.
Solving the challenge
In order to solve this challenge, I’ve guessed that these files was encrypted using OTP (XOR). I’ve also guessed that the same key was used, we are finally in the case of a many time pad :
\[c_1 = p_1 \oplus k\] \[c_2 = p_2 \oplus k\] \[c_3 = p_3 \oplus k\] \[c_4 = p_4 \oplus k\]If we XOR a ciphertext with another one, here is what we get :
\[c_1 \oplus c_2 = (p_1 \oplus k) \oplus (p_2 \oplus k)\] \[c_1 \oplus c_2 = p_1\oplus p_2\]And by XORING each combination possible of ciphertext we can begin to guess a part of a plaintext in order to retrieve the other plaintext and guessing from the new plaintext and so on…
I will not explain in details how I’ve retrieved the flag because that was a long session of plaintext guessing … But here is the plaintext who contain the flag :
1
Perfect! My new address is vale.scafati02@gmail.com and my token is HACKDAY{DO_NOT_USE_SAME_KEY_PLS_ITS_NOT_SAFE}
If you want so usefull documentation about many time pad, here is some interresting doc :
- https://www.thecrowned.org/the-one-time-pad-and-the-many-time-pad-vulnerability
- https://crypto.stackexchange.com/questions/6020/how-to-attack-a-many-time-pad-based-on-what-happens-when-an-ascii-space-is-xor
- https://crypto.stackexchange.com/questions/7992/decryption-many-time-pad
Care the factors !
Statement
1
2
3
Téléchargez le fichier et déchiffrez le fichier chiffré.
Download the file and decrypt the encrypted file.
Author : D0ppl3gang3r
Points : 460
Solves : 22
Files analysis
This challenge consist of two files :
- The source code:
source_thefactors.sage flag_thefactors.txt: two pointsPandQ, anIVand the encrypted flag
The file source_thefactors.sage
After analyzing the source code, we can see that there a function wo take a secret as an input and return the encrypted flag, this function consist of an AES CBC with a random IV and a key derived from the shared_secret.
1
2
3
4
5
6
7
8
9
10
11
def enc_flag(shared_secret):
md5 = hashlib.md5()
md5.update(str(shared_secret).encode())
key = md5.digest()[:16]
iv = os.urandom(16)
cipher = AES.new(key, AES.MODE_CBC, iv)
ciphertext = cipher.encrypt(pad(flag, 16))
output = {}
output['iv'] = iv.hex()
output['flag'] = ciphertext.hex()
return output
So we can deduce quickly that the vulnerability didn’t come from this function. Further down in the file, we can see this code :
1
2
3
4
5
6
7
8
9
10
11
12
p = 1410235279292998784331797202421753874063265295308568058662741299116310072677
A = getA()
B = getB()
Fp = FiniteField(p)
E = EllipticCurve(Fp, [A, B])
P = E.random_element()
n = randint(2**30, 2**60)
Q = n * P
flag = enc_flag(n)
print(f"{P=}")
print(f"{Q=}")
print(f"{flag=}")
This is a classical implementation of the ECDLP
All in all, we “just” have to solve the ECDLP given in the sage file in order to retrieve the flag.
The file secretmessage.txt
This file just contains public values like P, Q, IV and the encrypted flag.
Solving the chall
In order to solve the ECDLP we have to recover the curve parameters. With a little bit of algebra we can easily retrieve A and B from P and Q, here is how I’ve done :
1
2
3
4
5
6
7
8
9
10
def get_a_and_b_from_two_point(x, y, x_, y_, p):
inv_x , inv_y = inverse_mod(x, p) , inverse_mod(y, p)
inv_x_, inv_y_ = inverse_mod(x_, p), inverse_mod(y_, p)
c = (x * inv_x_) % p
b = (((y**2) - (x**3) - (y_**2 * c) + (x_**3 * c)) * inverse_mod((1 - c) % p, p) ) % p
a = ((y_**2 - x_**3 - b) * inverse_mod(x_, p)) % p
return a, b
Now we can build the curve like this using sage :
1
2
3
4
5
6
7
8
9
10
11
P = (406156291172024449433827761031736513098183950832214481256475543523051604042, 937502472800241241676075882016117499207790111193756481427079135615174871684)
Q = (92554882076587701525654416824880284407135974444455993706448015434816328085, 1067245947645250194968549384640439378373660468218406176128671131644883921569)
p = 1410235279292998784331797202421753874063265295308568058662741299116310072677
Fp = FiniteField(p)
A, B = get_a_and_b_from_two_point(P, Q, p)
E = EllipticCurve(Fp, [A, B])
P = E(P)
Q = E(Q)
And we can check for common vulnerability by checking these values :
1
2
3
4
5
6
7
8
9
E_order = E.order()
# Is anomalus ?
print(E_order == p) # False -> No
# Is vulnerable to Pohlig–Hellman ?
E_order_factors = factor(E_order)
print(E_order_factors)
# [2, 3, 7, 43, 349, 409, 6199, 344222059, 58853094821, 162989330351, 284121766519, 940658041742936711869] -> YES
I immediately spotted that the order of this curve has small primes factor. This is great because we’ll be able to solve the ECDLP with the Pohlig-Hellman algorithm !
This algo is based on the following idea : we will solve several discrete logarithm problem in several subgroup and then unify answers over $p$ using CRT. Here is the algorithm :
Pohlig-Hellman algorithm : Illustration taken from https://link.springer.com/book/10.1007/978-1-4939-1711-2
And here is my implentation in SageMath : (Be carefull this code works only if like in our exemple each factor f respect ord(f) = 1, otherwise you’ll need to change g_i and h_i according to the figure above)
1
2
3
4
5
6
7
8
9
10
11
12
E_order_factors = [2, 3, 7, 43, 349, 409, 6199, 344222059, 58853094821, 162989330351, 284121766519, 940658041742936711869]
all_res = []
for fact in E_order_factors:
g_i = P * (E_order / fact)
h_i = Q * (E_order / fact)
current_res = discrete_log(h_i, g_i, g_i.order(), operation='+')
all_res.append(current_res)
print("secret : ", crt(all_res, E_order_factors))
# 1883705452
Now that we have the shared_secret it is really easy :
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
shared_secret = 1883705452
iv = bytes.fromhex("4318aa195451964d2078e230494ef079")
flag = bytes.fromhex("75ae6944d3434c9e96affd40c6137bfe23934fddcc6693bdfdd7a1d542f3464a12abc09d87dd0dc8fd860d666dd2b337")
def dec_flag(shared_secret, iv):
# Key du aes = md5 de shared_secret
md5 = hashlib.md5()
md5.update(str(shared_secret).encode())
key = md5.digest()[:16]
# Chiffrement du flag avec key et iv
cipher = AES.new(key, AES.MODE_CBC, iv)
ciphertext = cipher.decrypt(flag)
print(ciphertext)
# Build le ct et l'iv
output = {}
output['iv'] = iv.hex()
output['flag'] = ciphertext
# Renvoie le ct et l'iv
return output
print(dec_flag(shared_secret, iv))
Flag : HACKDAY{W34k_EC_W1th_P0hlig_H3llm4n_4TT4cK}
Flip The Bit
Statement
1
2
3
4
5
6
Nos équipes ont réussi à identifier un serveur semblant appartenir au SYNDICAT. Malheureusement celui-ci est protégé … Nous devons faire appel à vos services en tant que crack en cybersécurité pour déjouer le système et avoir accès aux informations qu’il contient !
Our teams have successfully identified a server that appears to belong to SYNDICAT. Unfortunately, it is protected... We need to enlist your services as cybersecurity experts to bypass the system and gain access to the information it contains!
Connectez-vous à l'instance avec netcat et trouvez le moyen de vous faire passer pour un administrateur !
Connect to the instance with netcat and find a way to become an administrator !
nc challenges.hackday.fr 50398
Author : Didouad
Points : 454
Solves : 18
Analysis of the statement
The statement tell us we need to bypass the connection system in order to get the flag. The conncetion system is on an instance : nc challenges.hackday.fr 50398.
Analysis of the connection system
After running netcat on the instance, we get the following prompt :
1
2
3
[>A] Name Yourself
[>B] Give your token
>
We deduce we can register ourself or login with a token, here is how the login works :
1
2
3
4
5
6
7
8
9
10
11
Network of SYNDICAT
[>A] Name Yourself
[>B] Give your token
> A
A
> Username : skilo
skilo
> Profession : hacker
hacker
username=skilo&agent=agence&profession=hacker&admin=false&time=1705829072.531827
Token : 8b8441779bb4dd7dd6da8028c576c856fcc5774e912237d96248c34425931cd20ee13eacdad2709068084aca3828105f5b6583712fd1a149964dec46ba1f8620e9a888f59447a2bc1d08cfb4ffed42352b9e813432b652e1b0c07ae4405096c977cf4e8092c281ff5cee1a7557b68f2e
According to the title and the output we immediately notice the possibility of attacking AES CBC with bit flipping. If you don’t know how works this attack, you can search on internet, this is very well explained.
About the token
With this simple python function :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
BLOCK_SIZE = 16
def show_blocks(plaintext, token):
for i in range(0, len(token), BLOCK_SIZE):
print(plaintext[i:i+BLOCK_SIZE], token[i:i+BLOCK_SIZE])
>>> plaintext = b"username=skilooo&agent=agence&profession=hackerr&admin=false&time=1705829266.675812"
>>> token = bytes.fromhex("a56defc95685a44b2e29976e7fc5f3f7b87553c12210baf9955b8c2013ab1937ddb7fff35c0bd87275a2920a45dcfb38c864eea5fbf11313231b2983a7a768eaffa771a3feaf931d80ddedc431efcdd23e2c40c7d14f52670973451869f6f31d3beec2f167d2ca9c1d8325f742bceb31")
>>> show_blocks(plaintext, token)
b'username=skilooo' b'\xa5m\xef\xc9V\x85\xa4K.)\x97n\x7f\xc5\xf3\xf7'
b'&agent=agence&pr' b'\xb8uS\xc1"\x10\xba\xf9\x95[\x8c \x13\xab\x197'
b'ofession=hackerr' b'\xdd\xb7\xff\xf3\\\x0b\xd8ru\xa2\x92\nE\xdc\xfb8'
b'&admin=false&tim' b'\xc8d\xee\xa5\xfb\xf1\x13\x13#\x1b)\x83\xa7\xa7h\xea'
b'e=1705829266.675' b'\xff\xa7q\xa3\xfe\xaf\x93\x1d\x80\xdd\xed\xc41\xef\xcd\xd2'
b'812' b'>,@\xc7\xd1ORg\tsE\x18i\xf6\xf3\x1d'
b'' b';\xee\xc2\xf1g\xd2\xca\x9c\x1d\x83%\xf7B\xbc\xeb1'
We can see that there one more block in the token that in the plaintext, we can guess that the first block of the token is the IV. Having access in input/output to the IV during a bit flipping attack is a good thing because we can avoid destroying a ciphertext block by destroying the IV instead.
Now, let’s try to login we this token :
1
2
3
4
5
6
7
8
9
Network of SYNDICAT
[>A] Name Yourself
[>B] Give your token
> B
B
> Token : a56defc95685a44b2e29976e7fc5f3f7b87553c12210baf9955b8c2013ab1937ddb7fff35c0bd87275a2920a45dcfb38c864eea5fbf11313231b2983a7a768eaffa771a3feaf931d80ddedc431efcdd23e2c40c7d14f52670973451869f6f31d3beec2f167d2ca9c1d8325f742bceb31
a56defc95685a44b2e29976e7fc5f3f7b87553c12210baf9955b8c2013ab1937ddb7fff35c0bd87275a2920a45dcfb38c864eea5fbf11313231b2983a7a768eaffa771a3feaf931d80ddedc431efcdd23e2c40c7d14f52670973451869f6f31d3beec2f167d2ca9c1d8325f742bceb31
username=skilooo&agent=agence&profession=hackerr&admin=false&time=1705829266.675812
You are not admin nor from syndicat !
The output is clear : we have to change admin=true by admin=false and agent=agence by agent=syndicat.
Solving the chall
For this challenge, I don’t have the time to do the full write-up, so I’ll just give some elements to understand the trick below :
- We realize that we need to change
admin=falsetoadmin=trueandagent=agencetoagent=syndicat. - We notice that the entire decrypted token is passed through the Python
.decode()method. - We decide to use the Initialization Vector (IV) to keep the ciphertext unchanged. -> However, there’s a problem: the text to be modified is more than 16 bytes, so we must necessarily change a cipher block other than the IV.
- Stroke of genius: thanks to the Python error generated by
.decode(), we have access to the plaintext byte by byte of the first block.
Example: When we get the error: “can’t decode 0xfa at position 0,” we know that the byte at position 0 is0xfa. We just need to modify the IV at position 0 to get what we want, to then get an error at a more advanced position and continue until we know the entire block.
In summary:
- I edit the first cipher block, so my second plaintext block is what I want.
- I send the token as is to the authentication system; it gives me one by one the problematic bytes of the first plaintext block.
- After having just
decodablechars in block 0 I will know the full plaintext of block 0 - I adjust my IV to put what I want in the first plaintext block.
- It’s flag!
Here is my (horrible) python script :
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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
from pwn import remote, xor
from time import sleep
BLOCK_SIZE = 16
host = "challenges.hackday.fr"
port = 50398
conn = remote(host, port)
username = b"ski"
profession = b"hacker&username=skilo"
def get_token(username, profession):
# Choix du register
res = conn.recvuntil(b">")
# print(res.decode())
conn.sendline(b"A")
# Choix du username
res = conn.recvuntil(b":")
# print(res.decode())
conn.sendline(username)
# Choix de la profession
res = conn.recvuntil(b":")
# print(res.decode())
conn.sendline(profession)
# Choix de la profession
res = conn.recvuntil(b"[>A]")
# print(res.decode())
token = bytes.fromhex(res.split(b"Token :")[1].split(b"\r\n")[0].split(b" ")[1].decode())
payload = res.split(b"Token :")[0].strip().split(b"\r\n")[1]
return (payload, token)
def edit_token(payload, token):
print(len(payload), len(token))
# assert len(payload) + 16 == len(token)
# Parsing tu token raw
iv = token[:BLOCK_SIZE]
token = token[BLOCK_SIZE:]
# Reparsing (plus précis)
expected_block = b"dmin=true&profes"
current_cblock = token[:BLOCK_SIZE]
current_pblock = payload[BLOCK_SIZE:BLOCK_SIZE*2]
# Bit flipping pour edit le deuxieme block
z_0 = xor(current_cblock, current_pblock)
new_cblock = xor(z_0, expected_block)
# Construction du nouveau token
resultat = b""
resultat += iv
resultat += new_cblock
resultat += token[BLOCK_SIZE:]
# Retour du nouveau token
return resultat
def send_token(token):
# Conversion hex + bytes + on conserve l'iv
iv = token[:BLOCK_SIZE]
token = token.hex().encode()
# Envoie du token
conn.sendline(b"B")
conn.sendline(token)
# Reception de la réponse + print dans tous les cas
res = conn.recvuntil(b"Network of SYNDICAT")
print("---------------------------------------------")
print(res)
print("---------------------------------------------")
if b"You are not admin nor from syndicat" in res:
last_chance(bytes.fromhex(token.decode()), res)
exit(1)
if b"can't decode" not in res:
clean_res = res # TODO
return_value = {
"status": "can_decode",
"res": clean_res,
"iv": iv
}
# On récup le byte qui a rendu du non imprimable
error_byte = res.split(b"decode byte ")[1].split(b" ")[0]
error_pos = res.split(b"position ")[1].split(b":")[0]
return_value = {
"status": "cant_decode",
"error_byte": int(error_byte.decode(), 16),
"error_pos": int(error_pos.decode()),
"iv": iv,
"token": token
}
return return_value
def edit_iv(error_infos):
if error_infos["status"] != "cant_decode":
print("Ça n'avait pas échoué")
conn.interactive()
# Parsing des données
iv = error_infos["iv"]
token = error_infos["token"]
char_pos = error_infos["error_pos"]
char_pt = error_infos["error_byte"]
# Infos cools
expected_pt = b"agent=syndicat&a"
z_i = iv[char_pos] ^ char_pt
temp_iv = list(iv)
temp_iv[char_pos] = z_i ^ expected_pt[char_pos]
return bytes(temp_iv)
def last_chance(token, res):
store_v = len(token)
iv = token[:BLOCK_SIZE]
token = token[BLOCK_SIZE:]
current_pblock = res.split(b"Token :")[1].split(b"\r\n")[1][:BLOCK_SIZE]
expected_pt = b"agent=syndicat&a"
assert len(current_pblock) == len(expected_pt)
z = xor(current_pblock, iv)
new_iv = xor(z, expected_pt)
assert len(new_iv) == BLOCK_SIZE
new_token = new_iv + token
print(len(new_iv), len(token), len(new_token))
print(len(new_token))
assert len(new_token) == store_v
print("LAST CHANCE WAS CALLED")
send_token(new_token)
def show_blocks(p_input, token):
for i in range(0, len(token), BLOCK_SIZE):
print(p_input[i:i+BLOCK_SIZE], token[i:i+BLOCK_SIZE])
payload, token = get_token(username, profession)
show_blocks(payload, token)
# exit(1)
new_token = edit_token(payload, token)
while True:
sleep(0.5)
return_value = send_token(new_token)
print(return_value)
new_iv = edit_iv(return_value)
new_token = new_iv + new_token[BLOCK_SIZE:]
Flag : HACKDAY{This_15_SH0wTime}
