Recette pour ressortir victorieux d'un tirage au sort

Récit d'une attaque bruteforce sur une lotterie pour gagner des vacances
Blog Cybercrime Informatique Python JCZD

Cet article de vulgarisation explique, sans se perdre dans la terminologie technique, comment la programmation peut vous permettre d’économiser de nombreux clics de souris et pourquoi il vaut parfois mieux donner du pain au vilain petit canard qu’essayer de l’éloigner, pourquoi une moustiquaire est inutile contre un lion. Je vois d’ici les réactions : la masse sera éberluée de découvrir ce qu’on peut faire tandis que ceux qui baignent dans cet environnement s’esclafferont d’un exemple de plus d’un truc “trop facile” (ou au contraire s’offusqueront qu’on puisse leur faire subir ça, parce que quand même c’est pas gentil).

On m’a parlé d’OSI il y a quelques années, et j’ai assisté à une de leurs conférences annuelles dans les bâtiments de l’ONU à Genève tout en donnant un coup de pouce pour la technique audiovisuelle. C’est une très chouette équipe : ils ont des idéaux tout-à-fait honorables et se donnent les moyens de les voir se réaliser. Par ailleurs et qui ne gâche rien au plaisir, je les ai tous trouvés très sympathiques. J’y serai bien retourné, si ne n’est pour diverses difficultés existentielles auxquelles j’ai du faire face et c’est tout naturellement que j’ai vu l’occasion de saisir ma chance lorsque, le 1er décembre 2022, je recevais un sympathique e-mail m’informant de la possibilité de gagner un séjour avec eux : il suffisait de répondre juste et aussi souvent que possible à une question posée chaque jour.

À ce moment, cela faisait 10 ans jour pour jour que je n’avais pas eu l’opportunité de partir en vacances. Que demande le peuple? Par ailleurs, je voyais ici une bonne occasion de montrer par l’exemple qu’on est rarement trop prudent: je décidais donc illico de documenter la façon par laquelle je comptais augmenter mes chances de gagner : vous verrez que c’est un peu comme si j’avais trouvé le moyen de remplir des dizaines de milliers des grilles de bingo dans le même temps qu’un humain normal a besoin pour en remplir deux ou trois1.

Merci d’avoir participé

Les conditions

Les conditions de participation étaient les suivantes :

conditions de participation

On était le premier jour, et quand j’ai vu le formulaire je me suis très rapidement dit qu’il y avait de la place pour une victoire presque assurée : j’ai donc essayé avec une autre adresse e-mail - dans le but de limiter le spam, j’en ai plusieurs - et ça a marché. On pouvait même voter plusieurs fois avec la même adresse, et ceci simplement en repostant les données du formulaire.

Évidemment, rien ne sert de participer si on peut être éliminé au motif que les règles n’ont pas été respectées ; dans le cas qui nous occupe, on s’intéresse en particulier à la définition d’adresse e-mail, ce qui nous plonge directement dans une série de documents forts intéressants et dont je vous propose un extrait ci-dessous.

Ceci nous permet de constater qu’il n’y a aucune limitation liant une personne physique et le nombre d’adresses e-mail que cette personne peut utiliser pour voter : comme plus de votes = plus de chances, on en déduit que plus d’adresses = encore plus de chances.

RFC 2996

3.  Restrictions on email addresses

   Reference documents: RFC 2821 [RFC2821] and RFC 2822 [RFC2822]

   Contemporary email addresses consist of a "local part" separated from
   a "domain part" (a fully-qualified domain name) by an at-sign ("@").
   The syntax of the domain part corresponds to that in the previous
   section.  The concerns identified in that section about filtering and
   lists of names apply to the domain names used in an email context as
   well.  The domain name can also be replaced by an IP address in
   square brackets, but that form is strongly discouraged except for
   testing and troubleshooting purposes.

RFC 2822

3.4. Address Specification

   Addresses occur in several message header fields to indicate senders
   and recipients of messages.  An address may either be an individual
   mailbox, or a group of mailboxes.

Le fun par l’automatisation

Je n’allais évidemment pas passer 24 jours à presser la touche F5 de mon clavier ; par ailleurs, je n’étais pas sûr s’il y avait une limitation technique invisible qui m’aurait limité à un vote par adresse e-mail et par jour. J’aurais également pu bricoler un machin qui appuie sur la touche du clavier à ma place mais j’avais déjà fait quelque chose de similaire plus tôt et dans un autre contexte, il fallait donc trouver quelque chose d’autre et de préférence un peu plus efficace et surtout plus amusant.

Avoir plein d’adresses mail

La première étape était de me créer des adresses e-mail. Beaucoup d’adresses. Pour ça, pas besoin d’ouvrir des dizaines de comptes chez les fournisseurs habituels ou un peu plus exotiques : j’avais la possibilité de créer presque autant d’alias que je voulais pour ma boîte mail, et j’avais déjà - pour une toute autre raison - en projet d’automatiser ça. Le tout me pris peut-être une heure, maximum deux.

Récupération du formulaire

Ceci fait, j’ai ensuite jeté un rapide coup d’oeil au code source de la page et écrit quelques lignes de codes pour isoler avec BeautifulSoup le contenu du formulaire de vote ; j’ai très rapidement compris que l’équipe qui s’occupe du site web n’a pas mon expérience du milieu : alors que j’ai commencé dans les années ‘90 et qu’à cette époque tout était à faire à la main, ceux-là utilisaient des outils qui vous donnent du code “tout cuit” et n’avaient de toute évidence qu’une connaissance théorique des paradigmes sous-jacents. Trouver un champ caché nommé “mechantrobot” et accompagné de la légende “Veuillez laisser ce champ vide” m’a bien fait poiler, ce genre de protection n’ayant qu’une efficacité très limitée.

Code source du formulaire

Dans le rectangle magenta ci-dessous, le contenu de la requête telle que le navigateur l’envoie : il s’agit pour nous de la répliquer au plus proche.

Données POST

Voter juste, ou mieux vaut voter au hasard que ne pas voter du tout

J’ai également créé un fichier contenant la bonne réponse à chaque question : cela était indispensable pour maximiser mes chances de gagner le gros lot (et les autres par la même occasion). À ce moment, je ne savais pas combien il y aurait de possibilités de réponses, s’il n’y aurait toujours qu’une seule bonne réponse, et une longue chaîne de caractères pseudo-aléatoires me laissait penser que peut-être celle-là pouvait changer de temps à autre et que je ne pouvais pas me baser dessus pour mes votes. J’ai également pris en compte le fait que si je n’étais pas sûr de quelle(s) réponse(s) serait la bonne, je pouvais ajouter dans mon fichier une probabilité à chaque réponse (ou groupe de réponses), ce qui influencerait la fréquence à laquelle cette réponse serait donnée.

#!/usr/bin/python
qa = {
	'01 décembre 2022': [
			(1,['Des projets de sciences participatives']),
		],
	'02 décembre 2022': [
			(1,['7']),
		],
	'03 décembre 2022': [
			(1,['2014']),
		],
	'01 janvier 1790': [
			(9,["Je suis presque sûr que c'est ceci"]),
			(1,["Mais j'ai quand même un doute qui me dit qu'une fois sur dix ça pourrait être ça"]),
		],
}}

Pour m’amuser et au cas où je n’avais pas renseigné la réponse à telle ou telle question, j’ai également écrit un bout de script qui voterait pseudo-aléatoirement selon une répartition statistique que je jugeais convenable. En effet, je m’était dit à ce moment (probablement à tort) qu’il me valait mieux voter au hasard que ne rien voter du tout, ceci afin d’utiliser au mieux les jours disponibles. En réalité, j’aurai juste du créer plus d’adresses mail ; en pratique, cette partie du code n’a jamais pas servi : je l’ai surtout écrite pour le challenge d’une bonne répartition2.

L’échec du début

Il était venu l’heure de tester mon script, et c’est là que j’ai eu quelques sueurs froides : bien que ma requête semblait parfaite en tout point, je me retrouvais toujours avec le formulaire vide au lieu du sympathique message “Merci d’avoir parcipé”.

more help

J’ai donc choisi d’utiliser un joker et, malgré le risque de voir quelqu’un d’autre en profiter pour aussi tenter sa chance, j’ai demandé l’avis du public.

an interesting puzzle

C’est sur IRC que j’ai trouvé un habitué du web-scraping qui fut vite emballé par l’idée de résoudre un nouveau problème. Je lui partageai mon code afin qu’il gagne du temps, et nous continuâmes à discuter de nos trouvailles respectives quant au fonctionnement du formulaire et de ses mécanismes de protection. Soudain, Eurêka!! Il m’annonçait avoir trouvé la solution au problème : il fallait impérativement préciser le user-agent dans la requête ; il était donc nécessaire de dire quel navigateur on utilisait, mais on pouvait aussi mettre presque n’importe quoi comme chaîne de caractères pour que ça fonctionne. Comme il venait de m’écrire “baguettes cant web security” j’essayais ça : succès.

baguettes cant web security

Ce petit problème trivial nécessita à peu près autant de temps que tout le reste à résoudre ; non que ce soit techniquement compliqué, mais parce que je ne cherchais pas au bon endroit : il est difficile de prévoir les délais réels lors d’un développement.

le prototype fonctionne

En plus d’avoir trouvé la solution à “mon” problème, Cheaterman me remerciait : “it was an interesting puzzle :-)”. De mon côté, j’en profitais pour remercier OSI pour le concours.

Merci pour le concours

Unicité des votes

Il me fallait maintenant résoudre un autre problème, celui de faire voter chaque adresse une et une seul fois chaque jour, mais également de créer des adresses à la volée si nécessaire et que toute nouvelle adresse vote également pour les jours précédents : le règlement précisait que c’était possible et autorisé, donc pourquoi s’en priver. En même temps, il me fallait également m’assurer que le script continue à tourner en cas d’erreur du réseau ou sur le serveur (quand on envoie autant de requêtes à la fois c’est fréquent : j’ai eu jusqu’à une vingtaine de processus qui tournaient simultanément et envoyaient au total autant de requêtes POST - des votes! - chaque seconde) et, de mon côté, faire de mon mieux pour économiser les ressources de ma machine. Ce fut de loin la partie la plus compliquée du point de vue algorithmique ; le code avec lequel j’ai abouti est avouons-le un peu dégueu mais il fait ce qu’il doit faire.

Encore plus d’adresses mail

Le script tournait tellement bien qu’au bout d’environ une heure seulement, j’avais épuisé mes premières dix mille adresses e-mail (chacune d’elles avait voté pour les 5 premières questions) que j’avais créées grâce à une librairie faite pour générer des tests et qui me donnait des couples nom-prénom. Statistiquement et en estimant qu’il y avait dix mille autres participants humains qui ne voteraient qu’une fois par jour, mes chances étaient à environ six mille contre 1. Bien, mais pas top.

Dans un souci de diversité et en cherchant si un autre fournisseur me permettait de créer un grand nombre d’alias j’ai découvert que Google fait abstraction des points dans la partie locale d’une adresse : ainsi, nom.prenom@gmail.com est routé comme nomprenom@gmail.com, tout comme n.ompren.om@gmail.com l’est également - la seule restriction étant de se conformer aux RFC et donc dans ce cas que le premier et le dernier caractère de la partie locale ne sont pas des points. Quelques minutes plus tard, un autre bout de code s’occupait de ça, et j’avais “instantanément” des milliards d’adresses mail disponibles en plus : techniquement et sémantiquement, ce sont toutes des adresses e-mail valides et distinctes, j’étais donc conforme au règlement du concours.

Obfuscation

User-agent

Pour maximiser mes chances d’être tiré au sort, je comptais envoyer autant de requêtes que possible quitte à me faire remarquer : il me fallait donc “cacher” mes votes afin d’éviter que ceux-ci ne puissent trop facilement être effacés par un webmaster peu scrupuleux du règlement.

Ne sachant pas si l’identifiant du navigateur était conservé, je trouvais en quelques minutes une liste des 100 user-agent strings les plus courants durant l’année en cours : je n’avais qu’à en choisir un au hasard à chaque fois que je votais. Je n’allais pas non plus passer à côté de l’occasion de cacher quelques messages utiles et sympathiques mais - obfuscation oblige - il me fallait à chaque fois les altérer de façon subtile de sorte à ce qu’un administrateur qui tombe dessus en comprenne le contenu, mais que cela reste très difficile de filtrer ces résultats. Ce fut une tâche très intéressante et à la fois amusante, où j’introduisais des erreurs volontaires tout en faisant attention à ce que la répartition statistique de ces erreurs soit homogène : je devais m’assurer que même un regex serait à peu près inutile et même contre-productif pour filtrer ces votes. Voyez plutôt:

Ol/\f Su|"[-r I<-uane
Ol(Lf Su|7EI2 ][gU/-\ne
Olaf Super Iguane
Olaf Super Igu^ne
|olaf zUP|=-r IgU^ne
plaf ehsup3|^ !6U^Ne
olaF Supe3  Iguane
O7af svper iguanE
O|_af 5u|>e7 I9uane
O1af SU9er 3y3 guan3
Olaf Super Iguane
OlaF SL|per IgU(1ne
O7aF super IguaNe

L’algorithme est extrêment sensible à la valeur de ses paramètres qui sont définis en “part d’erreur par million de caractères”, ajoutant, supprimant ou modifiant des symboles aléatoires ; pourtant, l’exemple ci-dessus (authentique) montre que la chaîne de caractère peut parfois ne pas être modifiée du tout : il a été ardu de trouver le juste milieu de la granularité afin d’introduire assez d’erreurs pour rendre le mécanisme efficace, mais suffisament peu pour eviter de rentre le texte complètement illisible.

Adresse IP

Restait le problème de l’adresse IP : celle-ci est généralement conservée dans les logs et permet entre autres de savoir d’où proviennent les requêtes au sens de l’architecture du réseau Internet (et donc géographiquement). Il est très facile de filtrer les résultats provenant d’une ou d’un groupe d’adresses, mais on trouve à cet effet très facilement des millions d’ordinateurs par lesquels on peut faire transiter son traffic afin de cacher sa propre adresse. Ce sont parfois des machines dédiées situées dans des datacenters, mais aussi des ordinateurs privés et autres smartphones qui ont été piratés à l’insu du plein gré de leur propriétaire et qui paient sans le savoir une bande passante qui sera revendue (parfois même donnée gratuitement) par les pirates qui en font leur fond de commerce afin d’aider de pauvres bougres accros aux basquets. Pour des questions de performance et pour éviter de devoir - en plus des efforts déjà entrepris - collecter des statistiques sur la disponibilité et la performance de tel ou tel proxy, je décidais de payer une poignée de dollars et ainsi m’assurer d’un service de qualité et à priori sans porter préjudice à de naïfs utilisateurs dont l’appareil aurait été piraté.

Proxy pour baskets

Un problème technique transient me força cependant, une fois avoir utilisé le premier GB de données test et pendant quelques jours le temps que ce soit résolu, à utiliser ma propre adresse IP.

La croisière s’amuse

À ce stade, le résultat était excellent : sur l’image ci-dessous, chaque M vert correspond à un vote ; 12 process tournent simultanément pour voter (terms 0-11) tandis qu’un dernier process est un hack un peu moche qui kill un process qui commencerait à être trop gourmand en mémoire (term 14) avant que celui-ci ne soit automatiquement relancé quelques secondes plus tard.

Comme une horloge

Divination des réponses

Je passais donc les jours suivants à ajouter les réponses journalières et peaufiner mon script, remarquant au passage que les nouvelles questions étaient ajoutées manuellement et non via un script (un autre indice que ceux responsables de maintenir le site web étaient plus proches du pousse-bouton que du vrai développeur), et ajoutant un bout de code pour augmenter mes chances de trouver statistiquement la bonne réponse aux questions auxquelles je n’avais pas répondu explicitement : une fois qu’on avait voté, la réponse était plus ou moins donnée et une simple analyse statistique du nombre d’occurrences des mots-clés les plus importants donna des résultats satisfaisants. Ci-dessous, le rectangle bleu met en évidence le scores de chacune des réponses possibles3.

Réponses automatiques

Un peu trop fort?

J’avais quand même besoin de mon ordinateur pour autre chose durant cette période, et il apparaissait sensiblement plus lent : j’ai donc mis un autre ordinateur sur la tâche pour soulager ma machine principale, et remarquais que le serveur qui répondait à https://vacances-scientifiques.com/ commençait à peiner : je réduisais un peu la fréquence de mes votes et vaquais à autre chose.

Trop de processus

Poof!

Le lendemain matin ce fut “le drame” (notez que je m’y attendais) : mes requêtes ne passaient plus! Je constatais qu’il fallait en plus répondre à la question “Total de 7+3”. Cette question était accompagnée de la mention “pour vérifier que vous n’êtes pas un robot” et il serait tout naturel de se dire maintenant “Haha!” mais que néni ; un robot est un dispositif mécatronique et l’utilisation de ce terme pour un script est galvaudée : aucun élément mécanique n’est impliqué dans le cas qui nous occupe, je ne suis donc pas concerné. Ce flou sémantique réglé et 22 minutes plus tard j’avais adapté mon script et relancé la machine infernale. Ma nouvelle salve était immédiatement repérée, et une autre question à peine plus difficile faisait son apparition “Total de 7+3 ajouté de 1”. Je prenais en compte ce nouveau dispositif de protection, et dans les 10 minutes le formulaire était à nouveau hors-ligne, désactivé pour quiconque voulait y participer.

Calendrier désactivé

Le lendemain matin (pour ne pas paraître trop suspect) j’informais la direction d’OSI qui me remerciait chaleureusement : les webmasters n’avaient visiblement pas jugé utile de les informer du dérangement.

Fix, Poof (bis), Fix (bis) et re-Poof (tris)

Calendrier normal

Cinq jours plus tard, le formulaire était de nouveau en ligne ; une autre question anti-spam avait remplacé les précédentes (il fallait déduire 2 au lieu d’ajouter 1) et je corrigeais le problème en ajoutant une couche qui me permettait de prendre en compte toute une série de variations de la question : je verrai par la suite que ce n’était même pas nécessaire.

ints = {
    'zéro': 0,
    'un': 1,
    'deux': 2,
    'trois': 3,
    'quatre': 4,
    'cinq': 5,
    'six': 6,
    'sept': 7,
    'huit': 8,
    'neuf': 9,
}

if name == 'input_1':
	parabot = form.find('label', {'for':'champ_'+name}).text
	match parabot:
		case "Total de 7+3 (pour vérifier que vous n'êtes pas un robot)  (obligatoire)":
			files.append((name, (None, '10', None)))
		#case "Total de 7+3 ajouté de 1 (pour vérifier que vous n'êtes pas un robot)  (obligatoire)":
		#	files.append((name, (None, '13', None)))
		#case "Total de 7+3 moins 2 (pour vérifier que vous n'êtes pas un robot)  (obligatoire)":
		#	files.append((name, (None, '13', None)))
		case other:
			pb = parabot.split(' ')
			init_sum = pb[2]
			if init_sum != '7+3':
				raise Exception("New parabot question :-)", parabot)
			if pb[3] == 'ajouté':
				add = ints[pb[5]]
			elif pb[3] == 'moins':
				add = -ints[pb[4]]
			else:
				print(f"parabot: {parabot}")
				raise Exception("New parabot question :-)", parabot)
			files.append((name, (None, str(10+add), None)))

Ce faisant, une nouvelle protection avait été ajoutée, qui empêchait l’accès au formulaire une fois qu’on avait répondu à une question - pour autant qu’on utilise la même session de navigateur. On pouvait même toujours reposter sa réponse avec une simple pression sur la touche F5 et mon script n’était pas du tout affecté par cette soi-disant sécurité qui - comble de l’absurde - augmentait donc statistiquement mes chances d’être tiré au sort. Quelques heures plus tard, le calendrier était désactivé pour la troisième fois.

Statistiques

Lees graphiques ci-dessous proviennent du fournisseur de proxy : on y voit clairement les premiers tests le 6 décembre, suivi d’une semaine sans proxy jusqu’au 13, journée durant laquelle j’améliorais brutalement l’efficacité de mon code avec une modification “mineure” ; le 14 je mettais en route la deuxième machine, première et seconde coupures le 15 au matin, remise en route le 19 et re-coupure le 20. On voit également qui si j’avais pu continuer jusqu’au 24, j’aurais utilisé à peu près toute la bande passante à disposition (et voter cinq fois plus). Nickel.

Utilisation proxy

Utilisation proxy

Problème de fond

Je retournais vérifier de temps à autre si le formulaire était remis en ligne : sans succès. Je recevais toutefois un mail de la direction informant que le calendrier était à nouveau en ligne : ils ignoraient de toute évidence que dans l’intervalle il avait été de nouveau désactivé, et cela me montrait des lacunes certaines dans le processus de communication interne à l’ONG.

J’espérais vivement que le formulaire soit encore une fois remis en ligne, cette fois avec une sécurité un peu plus sérieuse sous forme de captcha, ces petites questions irritantes où il faut par exemple cliquer sur les images qui contiennent des vélos et où l’humanité perd son temps à entraîner - gratuitement et généralement sans le savoir - l’IA de Google : cela m’aurait permis de faire la démonstration que le système économique mondial est mal foutu au point que l’on trouve - tout aussi facilement que l’on trouve des proxys - des pauvres qui passent des années à répondre à notre place à ces questions parfois trop difficiles pour des ordinateurs, et que le plaisir de contourner la protection compense largement l’investissement. Dommage.

Épilogue

Nous sommes maintenant le 26 décembre, et j’en profite pour écrire ce compte-rendu éducatif : au risque de voir mes prochains efforts à peine plus difficiles, j’aurai peut-être l’occasion de collaborer avec OSI pour partager quelques unes de mes connaissances, et aussi valoriser le travail de techniciens compétents que les administrations mettent trop souvent de côté au profit d’incompétents qui acceptent de travailler pour un salaire inférieur : au tarif horaire, ces derniers sont comparativement sur-payés, et l’inaptitude des dirigeants à rétribuer correctement un travail de qualité est la cause directe de la valorisation de l’incompétence et de la fuite des cerveaux vers d’autres horizons comme le crime et les services psychiatriques.

Le 29 décembre, OSI envoyait un mail général pour s’excuser d’une “avarie technique”, précisant qu’une nouvelle interface de jeu serait prochainement disponible (note: on attend toujours). Quoiqu’il en soit, si les vacances d’OSI sont autant convoitées que les dernières paires de Nike ou d’Adidas, on est sur le bon chemin.

Avarie technique

Post-épilogue

Je voulais faire une chouette vidéo pour expliquer tout ça, mais des problèmes m’ont empêché de le faire : tout n’est pas perdu car j’ai toujours la matériel source, et dès que possible je mettrai cette documentation à jour avec un lien ves la vidéo.

Par ailleurs, nous sommes fin mars, et je n’ai reçu toujours aucune information d’OSI pour le nouveau calendrier ; serait-ce prévu pour la fin de l’année 2023? Sans nouvelles mais toujours avec l’envie de partir en vacances, je me décide à publier cet article : on verra bien ce qui se passe.

Durant les quelques jours où le formulaire m’était accessible, j’ai pu soumettre plus de 2'963'536 votes4 au moyen de 242'037 adresses e-mail distinctes et toutes valides ; j’ai respecté les conditions de participation à la lettre et compte tenu des chiffres il est très improbable que je reparte complètement bredouille - sauf bien sûr si OSI met de côté son fair-play et décide d’annuler le concours au prétexte que tout ne s’est pas passé comme prévu : en admettant que dix-mille autres participants ont voté 15 fois chacun, j’ai seulement 1 chance sur 350 trillions de ne rien gagner du tout, 1 chances sur 20 de passer à côté des vacances et à peu près une chance sur deux de rafler la totalité des prix. Dans tous les cas, je me réjouis de ma future collaboration avec OSI pour contribuer à élever le niveau technique de l’humanité et - on l’espère - éviter à l’organisation d’autres déconvenues de ce type.

En fait et au final, les membres de la direction d’OSI ont été de très mauvais joueurs : non seulement ils ont demandé que j’occulte toute référence à l’organisation, mais ils ont refusé d’entrer en matière pour quoique ce soit quand bien même c’est eux qui ont décidé d’annuler le concours au prétexte d’un problème technique (le problème c’était soit moi, soit des dévs incompétents donc en aucun cas un problème technique). Donc non satisfaits de permettre à des citoyens ignares de base de déranger des espèces protégées et en voie voie de disparition dans leur environnement naturel, ils se permettent de snobber - au nom de l’ONU qui par ailleurs chercher à légaliser la pédophilie et le transgenrisme - des hackers comme moi qui cherchent à être tranquilles et clairement pacifiques. OSI: je vous emmerde, et famille ou pas (le fondateur est un cousin) je vais vous troller tant que je peux.


  1. il n’y a d’une part aucun acte de subtilisation et le règlement du concours a été strictement respecté : il est juste question d’habileté ↩︎

  2. j’aurais bien voulu ajouter ces quelques lignes ici, mais elles ont depuis été remplacées par d’autres ↩︎

  3. Dans cet exemple, c’est la réponse correcte qui sera choisie dans ce cas assez défavorable (des scores relatifs de [0,0,3] ou [0,1,5] étant plus proches de la norme) ; si le terme “OSI” avait été ignoré comme il aurait du (erreur de programmation), le score de l’exemple aurait été de [0,0,2] soit la quasi-certitude que la réponse correcte est bel est bien la troisième. ↩︎

  4. il faut ajouter y quelques dizaines d’autres soumis lors d’opérations manuelles ↩︎