Solution
On commence par regarder la datasheet :
On a donc affaire à un servo-moteur qui positionne une aiguille sur différents caractères. La liste montre qu’il s’agit d’un flag. On suppose donc que la capture est faite durant l’affichage du flag. Un autre point essentiel à noter est :
On voit que l’impulsion nécessaire pour positionner le servo à un certain angle est d’une durée totale de 20ms, avec un front haut qui va encoder l’angle avec un minimum de 0.6 ms pour -90°, jusqu’à 2.4ms pour +90°. Je note qu’on a une amplitude de 1.8ms pour 180°, ce qui va simplifier les calculs.
Vous noterez que dans toute la datasheet, on parle systématiquement des bornes -90° et +90°. Comme on le verra, c’est au minimum trompeur, et à mon avis erroné. Les organisateurs ont indiqué que c’était l’épreuve la plus concernée par l’ouverture de tickets, et je n’en suis pas étonné.
J’ouvre ensuite la capture VCD. Je passe le prologue du fichier, et je regarde les premiers enregistrements:
#5290 1!
#5489 0!
#7290 1!
#7489 0!
#9291 1!
#9489 0!
#11291 1!
#11490 0!
#13291 1!
#13490 0!
#15292 1!
#15490 0!
#17292 1!
Intuitivement, je comprends que le premier élément de chaque ligne est un template, et que le deuxième est un changement de front. Ainsi, à 5290, la valeur passe à 1, et à 5489, la valeur passe à 0. Je regarde les écarts entre les différents changements de front :
199 # Passage à 1
1801 # Passage à 0
199 # Passage à 1
1802 # Passage à 0
198 # Passage à 1
La somme de deux lignes donne 2000, ce qui est cohérent avec une séquence de 20ms pour un cycle, dont
2 ms de front haut. Ça donnerait une valeur assez à droite du cadran, du côté des caractères FCS{}
,
ce qui est cohérent avec le début d’un flag (un F
).
J’observe aussi que la même valeur est répétée 100 fois. J’en déduis que le servo-moteur est maintenu dans la même position pendant 2s au total, le temps de repérer le caractère vers lequel pointe l’aiguille.
Voilà donc la façon dont je vais traiter les données:
- je charge les données et garde les différences entre deux timestamps consécutifs.
- je ne garde que les fronts haut (
!1
). - je fais la moyenne des 100 valeurs consécutives (pas très utile au final, mais mes problèmes à venir m’ont encouragé à le faire car je soupçonnais des problèmes de précision).
- je retranche les 0.6 ms correspondant à l’angle -90°.
- sachant que j’ai 19 caractères positionnés sur un arc de 180°, chaque caractère couvre un ange de 180/19. Je divise donc la valeur que j’ai obtenue à l’étape précédente par cet angle, et ça me donne l’index du caractère.
Et ça me donne : FCSC{S2C927_02500AS_902AS2_4B71D2C7}
. Sauf que…
ça ne valide pas. Je fais quelques modifications, c’est à ce moment là
que j’ajoute la moyenne des 100 valeurs plutôt qu’un échantillon simple.
Quelques caractères changent, mais ça ne valide toujours pas.
J’observe que selon mes méthodes de calculs, le résultat «bagotte» autour des valeurs 2, 3 ou 5, 6, 7. Il y a donc quelque chose que je rate.
Le moment Eureka ? C’est lorsque je me suis rendu compte que j’avais une valeur négative dans mes résultats intermédiaires (j’utilise des angles compris entre 0 et 180° plutôt que -90 à +90°). En creusant, je me rends compte qu’un des fronts haut dure 0.55ms (rappelons que la datasheet annonçait un minimum de 60ms !).
Se pourrait-il que le caractère 0
ne démarre pas à l’angle -90° mais un
peu avant ? Dans cette hypothèse, le caractère _
dépasserait aussi de
son côté ? Mettons qu’on ajoute 5° de chaque côté, on aurait alors 19
caractères à répartir sur 190° et là, on aurait un angle parfaitement juste
de 10° pour chaque caractère ? Trop juste pour être faux !
Je modifie alors mon script (voir ci-dessous) sur cette base, et cette fois-ci, le flag obtenu est accepté !
$ python solve.py
[196.48, 178.38, 208.66, 178.49, 219.16, 207.71, 86.21, 177.78, 146.56, 85.69, 127.6, 238.22, 64.85, 85.57, 115.56, 54.46, 65.61, 157.99, 209.26, 239.09, 146.35, 64.9, 85.95, 157.98, 207.73, 86.81, 238.63, 106.51, 167.36, 125.43, 77.22, 186.28, 87.13, 177.02, 128.52, 229.55, 229.57, 229.56, 229.58, 229.59, 229.55, 229.51, 229.58, 229.57, 229.57, 229.61, 229.6, 229.6, 229.6, 229.61, 229.59]
[141.48, 123.38, 153.66, 123.49000000000001, 164.16, 152.71, 31.209999999999994, 122.78, 91.56, 30.689999999999998, 72.6, 183.22, 9.849999999999994, 30.569999999999993, 60.56, -0.5399999999999991, 10.61, 102.99000000000001, 154.26, 184.09, 91.35, 9.900000000000006, 30.950000000000003, 102.97999999999999, 152.73, 31.810000000000002, 183.63, 51.510000000000005, 112.36000000000001, 70.43, 22.22, 131.28, 32.129999999999995, 122.02000000000001, 73.52000000000001, 174.55, 174.57, 174.56, 174.58, 174.59, 174.55, 174.51, 174.58, 174.57, 174.57, 174.61, 174.6, 174.6, 174.6, 174.61, 174.59]
19 10.0
[14.148, 12.338, 15.366, 12.349, 16.416, 15.271, 3.1209999999999996, 12.278, 9.156, 3.069, 7.26, 18.322, 0.9849999999999994, 3.0569999999999995, 6.056, -0.053999999999999916, 1.061, 10.299000000000001, 15.425999999999998, 18.409, 9.135, 0.9900000000000005, 3.095, 10.297999999999998, 15.273, 3.181, 18.363, 5.151000000000001, 11.236, 7.043000000000001, 2.222, 13.128, 3.2129999999999996, 12.202000000000002, 7.352000000000001, 17.455000000000002, 17.457, 17.456, 17.458000000000002, 17.459, 17.455000000000002, 17.451, 17.458000000000002, 17.457, 17.457, 17.461000000000002, 17.46, 17.46, 17.46, 17.461000000000002, 17.459]
[14, 12, 15, 12, 16, 15, 3, 12, 9, 3, 7, 18, 1, 3, 6, 0, 1, 10, 15, 18, 9, 1, 3, 10, 15, 3, 18, 5, 11, 7, 2, 13, 3, 12, 7, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17]
FCSC{S3C937_13601AS_913AS3_5B72D3C7}}}}}}}}}}}}}}}}
Pour moi, la datasheet reste trompeuse.
Script Python
#!/usr/bin/env python3
alpha='0123456789ABCDFS{}_'
# Load values & compute differences
with open('mechanical-display.vcd', 'r') as input:
prec = 0
values = list()
for l in input.read().split('\n'):
if not l.startswith('#'):
continue
value = int(l.split(" ")[0].strip('#'))
values.append(value-prec)
prec = value
# Keep only the 1's duration
values = values[0::2]
# Average of the 100 values (100 * 20ms => 2s for each command)
# for i in range(len(values)//100):
# print(f'{i=}: {values[i*100:i*100+100]}')
values = [ sum(values[i*100:i*100+100])/100 for i in range(len(values)//100) ]
print(values)
# Substract 0.6 ms (base 0°)
values = [ x-55 for x in values ]
print(values)
# Divide by the number of character on the 190° arc
angle = 190/len(alpha)
print(len(alpha), angle)
values = [ x/angle for x in values ]
print(values)
values = [ round(x) for x in values ]
print(values)
# Convert to a flag :)
values = [ '?' if x<0 else '?' if x>=len(alpha) else alpha[x] for x in values ]
values = ''.join(values)
print(values)