Sam & Max » exercice http://sametmax.com Du code, du cul Sat, 07 Nov 2015 10:56:13 +0000 en-US hourly 1 http://wordpress.org/?v=4.1 Solution de l’exo la modif de flash 4 http://sametmax.com/solution-de-lexo-la-modif-de-flash/ http://sametmax.com/solution-de-lexo-la-modif-de-flash/#comments Fri, 21 Mar 2014 16:25:23 +0000 http://sametmax.com/?p=9833 L’exercice d’hier était plus un prétexte pour faire un peu de Python 3 avec manipulation de bytes qu’un vrai défi, mais c’est toujours marrant.

"""
    Kick flash bin ass so it stop giving us trouble will full screen
    vids on a dual screen.
"""
 
import sys
import argparse
 
# On déclare un argument positionnel obligatoire.
# Ce doit être un fichier que l'on veut
# ouvrir en lecture et mode binaire.
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('bin', type=argparse.FileType('rb'), help="Path to flash bin, man")
args = parser.parse_args()
 
# On charge le contenu du fichier en mémoire.
# Un peu bourrin, mais tellement plus simple
# que de chercher directement dans le fichier.
flash_oh_oooooooh = bytearray(args.bin.read())
# On chercher la chaîne binaire qui correspond à la constante
# qu'on veut écorcher.
if b"_NET_ACTIVE_WINDOW" not in flash_oh_oooooooh:
    print("Flash est déjà défoncé")
    sys.exit(0)
# On remplace juste un bit 
flash_oh_oooooooh[flash_oh_oooooooh.find(b"_NET_ACTIVE_WINDOW") + 1] = 42
 
# On écrit le résultat dans dans le fichier final.
try:
    with open(args.bin.name, 'wb') as news_flash:
        news_flash.write(flash_oh_oooooooh)
except PermissionError:
    sys.exit("Fozzy says you don't have right to write in '%s', man..." % args.bin.name)
 
print("Rosebud")

Voilà, rien d’incroyable, mais on trouve deux choses intéressantes : l’utilisation de FileType pour gérer un paramètre de type chemin de fichier sans se prendre la tête, et celle de bytesarray qui nous permet d’avoir un array mutable et donc de tout charger en mémoire comme un porc sans avoir à dupliquer tout le contenu lors de la modification.

J’ai vu certains ouvrir le fichier sans le flag binaire (‘b’), attention Python peut vous bousiller tout le contenu du fichier puisqu’il va interpréter les caractères spéciaux et faire du décodage sur un contenu sur lequel ça n’a pas de sens.

La vraie question, c’est : combien y a-t-il de références pop culturelles dans ce code ?


Télécharger le code de l’article.

]]>
http://sametmax.com/solution-de-lexo-la-modif-de-flash/feed/ 4
Exercice Python, round 3 15 http://sametmax.com/exercice-python-round-3/ http://sametmax.com/exercice-python-round-3/#comments Thu, 20 Mar 2014 10:43:45 +0000 http://sametmax.com/?p=9823 petite série fort sympathique, avec aujourd'hui un peu de manipulation de bytes, de gestion d'erreur et de parsing d'arguments de script.]]> Continuons cette petite série fort sympathique, avec aujourd’hui un peu de manipulation de bytes, de gestion d’erreur et de parsing d’arguments de script.

Souvenez vous de ce hack qui permet d’avoir sa video Flash en plein écran sur un écran, et travailler sur l’autre, quand on est en dual screen. Le problème c’est qu’il faut le faire à la main. De plus, il faut recommencer à chaque mise à jour de flash. On va automatiser tout ça.

Faites un script (en Python 3 \o/) qui va parser les arguments de la ligne de commande avec argparse. Il doit déclarer :

  • Une docstring.
  • Un message d’help à partir de cette docstring.
  • Un argument positionnel obligatoire devant être le chemin du binaire flash.

Ensuite on ouvre le fichier, on cherche le fameux _NET_ACTIVE_WINDOW, on le deface, et on sauvegarde le tout dans le fichier final. Ce sera l’occasion de jouer un peu avec la gestion plus propre du binaire en Python 3.

Contrairement aux exercices précédent, on met un minimum de check d’erreur car c’est un petit script (35 lignes copieusement commentées chez moi): donc au moins avertir proprement l’utilisateur si on ne peut pas lire ou écrire dans le fichier et pourquoi.

La solution demain.

]]>
http://sametmax.com/exercice-python-round-3/feed/ 15
Solution de l’exercice d’hier sur shadow 9 http://sametmax.com/solution-de-lexercice-dhier-sur-shadow/ http://sametmax.com/solution-de-lexercice-dhier-sur-shadow/#comments Wed, 29 Jan 2014 15:25:53 +0000 http://sametmax.com/?p=8917 Ça va de soit, mais ça va mieux en le disant, ceci n’est pas la solution unique de l’exercice d’hier, mais une solution possible parmi d’autres.

On note l’usage de crypt, qui évite de se faire chier à trouver le bon algo de hashing et gère le salt automatiquement. spwd, c’est vraiment pour le grosses larves comme moi qui veulent même pas faire un split.

Et c’est du Python 3, yo dog !

import io
import os
import crypt
import spwd
 
from urllib.request import FancyURLopener
from zipfile import ZipFile
 
PASSWORDS_SOURCE = "http://xato.net/files/10k%20most%20common.zip"
PASSWORDS_LIST = '10k most common.txt'
 
# Le fichier ZIP est derrière cloudflare, qui vous ferme la porte au nez si
# vous n'avez pas de User-Agent. On va donc créer un UrlOpener, un objet qui
# ouvre des ressources en utilisant leurs URLs, qui a un User-Agent 'TA MERE'.
# CloudFlare ne check pas que le UA est valide.
class FFOpener(FancyURLopener):
   version = 'TA MERE'
 
# Si le dictionnaire de passwords n'est pas là, on le télécharge
# via FFOpener().open(PASSWORDS_SOURCE).read(). C'est verbeux, c'est urllib.
# Normalement je ferais ça avec requests. Ensuite on lui donne une interface
# file-like object avec io.BytesIO pour que ZipFile puisse le traiter en mémoire
# sans avoir à le sauvegarder dans un vrai fichier sur le disque, et on
# extrait le ZIP.
if not os.path.isfile(PASSWORDS_LIST):
    ZipFile(io.BytesIO(FFOpener().open(PASSWORDS_SOURCE).read())).extractall()
 
# On extrait les mots de passe de la liste sous forme de tuple car c'est rapide
# à lire. Un petit rstrip vire les sauts de ligne.
passwords = tuple(l.rstrip() for l in open(PASSWORDS_LIST))
 
# spwd.getspall() nous évite de parser le fichier shadow à la main.
for entry in spwd.getspall():
    print('Processing password for user "%s": ' % entry.sp_nam, end='')
 
    # Pas de hash ? On gagne du temps avec 'continue'
    if not '$' in entry.sp_pwd:
        print('no password hash to process.')
        continue
 
    # On teste chaque password avec la fonction crypt, qui accepte en deuxième
    # paramètre le hash du mot de passe complet. Pas besoin de se faire chier
    # à le spliter, il va analyser les '$' et se démerder avec ça. On a juste
    # à comparer le résultat avec le hash d'origine.
    for pwd in passwords:
        if crypt.crypt(pwd, entry.sp_pwd) == entry.sp_pwd:
            print('password is "%s".' % pwd)
            # On break pour gagner quelques tours de boucles, et pouvoir
            # utiliser la condition 'else'.
            break
    else:
        print('fail to break password.')

Télécharger le code.

]]>
http://sametmax.com/solution-de-lexercice-dhier-sur-shadow/feed/ 9
C’est l’heure de faire de l’exercice 28 http://sametmax.com/cest-lheure-de-faire-de-lexercice/ http://sametmax.com/cest-lheure-de-faire-de-lexercice/#comments Tue, 28 Jan 2014 16:19:48 +0000 http://sametmax.com/?p=8910 Le dernier exercice avait bien été apprécié, alors je remet ça.

Consigne :


Créer un script de brute force de passwords Unix par dictionnaire.

Puisqu’on est pas non plus des Kevin Mitnick en puissance, on va supposer que vous êtes connectés sur la machine, que vous avez les droits root dessus et que vous avez localisé les mots de passe comme étant dans “/etc/shadow”.

Votre script va vérifier si il possède un dictionnaire de mots de passe. Si ce n’est pas le cas, il va télécharger celui-ci et le décompresser : http://xato.net/files/10k%20most%20common.zip.

Ensuite vous parcourez le fichier shadow, et vous essayez de trouver quel mot de passe se cache derrière chaque hash. Si il n’y a pas de hash, vous pouvez ignorer l’utilisateur.

Exemple de sortie:

Processing password for user "root": no password hash to process.
Processing password for user "daemon": no password hash to process.
Processing password for user "bin": no password hash to process.
Processing password for user "sys": no password hash to process.
Processing password for user "www-data": no password hash to process.
Processing password for user "sam": fail to break password.
Processing password for user "test": password is "cheese".
Processing password for user "messagebus": no password hash to process.
Processing password for user "avahi-autoipd": no password hash to process.
Processing password for user "avahi": no password hash to process.
...

Afin de simplifier l’exercice, il n’est pas demandé de gestion d’erreur ou de passage en a paramètre du script.

Comme d’habitude, il n’y a pas de solution ultimate de la mort qui tue, c’est juste pour le fun.

La soluce demain.

]]>
http://sametmax.com/cest-lheure-de-faire-de-lexercice/feed/ 28
Solution de l’exercice d’hier 10 http://sametmax.com/solution-de-lexercice-dhier/ http://sametmax.com/solution-de-lexercice-dhier/#comments Tue, 17 Dec 2013 09:17:44 +0000 http://sametmax.com/?p=8356 Il faut bien noter que ce n’est qu’une solution parmi d’autres :

import re
import sys
import string
import unicodedata
 
mots = {}
texte = open(sys.argv[1]).read().decode('utf8').replace(u'œ', 'oe')
texte = unicodedata.normalize('NFKD', texte).encode('ascii', 'ignore')
texte = re.sub('[^%s]' % string.ascii_lowercase, ' ', texte.lower())
 
for i, e in enumerate(texte.split()):
    mots.setdefault(e, []).append(i)
 
mots = sorted(mots.items(), key=lambda x: (len(x[1]), sorted(x[1])))
 
for mot, positions in mots:
    print('- %s: %s' % (mot, ', '.join(map(str, positions))))

On ignore cordialement toute gestion d’erreur, donc le code peut se permettre d’être court. Et .replace(u'œ', 'oe') n’est pas très générique :-)

Dans les propositions de code des commentaires, il faut noter :

  • Une utilisation fort maline du defaultdict par bob.
  • Le signalement d’unidecode par zanguu qui aurait géré 'œ' sans problème. Mais ça rajoute une dépendance.

Décorticage :

import re
import sys
import string
import unicodedata
 
# On va tocker les mots dans ce dico
mots = {}
 
# Je récupère en vrac le contenu du fichier. Comme on a pas de gestion des
# erreurs, je récupère cash pistache le chemin de la ligne de commande
# et je suppose un encoding en UTF8. Le résultat obtenu est un objet
# unicode de tout le texte du fichier, sans le caractère 'œ'.
texte = open(sys.argv[1]).read().decode('utf8').replace(u'œ', 'oe')
 
# Astuce pour normaliser les caractères spéciaux. Ne marche que pour 
# l'alphabet latin malheureusement. Donc le script est limité. Unidecode
# permettrait d'avoir un script plus générique.
texte = unicodedata.normalize('NFKD', texte).encode('ascii', 'ignore')
 
# string.ascii_lowercase contient toutes les lettres ASCII en minuscule,
# ce qui permet de faire un remplacement, via regex, de 
# [^abcdefghijklmnopqrstuvwxyz]', c'est à dire tout ce qui n'est pas
# une lettre ASCII minuscule.
texte = re.sub('[^%s]' % string.ascii_lowercase, ' ', texte.lower())
 
# Je récupère tous les "mots", split() sans paramètre coupe en effet toute 
# combinaison de caractères non imprimables. enumerate() me permet d'avoir
# la position de chaque mot. setdefault() me permet d'ignorer les clés qui
# n'existent pas encore dans le dico. J'aurais pu utiliser un defaultdict, mais
# comme on a qu'une seule ligne ici, c'est plus court.
# J'obtiens donc un dico {mot1: [positon1, position2, ...], mot2: ...}
for i, e in enumerate(texte.split()):
    mots.setdefault(e, []).append(i)
 
# On récupère le contenu du dico sous forme de liste de tuples 
# [(mot, positions)...], et on l'ordonne selon le nombre d'apparitions
# (len(x[1])), ou a défaut par ordre naturel des apparitions sorted(x[1]).
# Pour rappel, key attend une fonction qui prend chaque élement, et retourne
# une clé. La clé est utilisée pour ordonner les éléments : chaque élément
# voit sa clé comparée à celle des autres, et ordonnée par ordre naturel.
# Y a un article sur ça : http://sametmax.com/ordonner-en-python/
# En gros, une entrée ('salut', 4, 18) aura pour clé (2, (4, 18)),
# ce que Python peut comparer facilement.
# Je réalise en rédigeant ces lignes que mon sorted est inutile, puisque 
# le processus est incrémental et déjà ordonné. Je le laisse comme référence.
mots = sorted(mots.items(), key=lambda x: (len(x[1]), sorted(x[1])))
 
# Et on affiche tout ça, non sans caster les positions du type int vers str
# pour éviter un crash
for mot, positions in mots:
    print('- %s: %s' % (mot, ', '.join(map(str, positions))))

Enoncé de l’exercice.

Télécharger le code de l’article.

]]>
http://sametmax.com/solution-de-lexercice-dhier/feed/ 10
Petit exercice en Python 33 http://sametmax.com/petit-exercice-en-python/ http://sametmax.com/petit-exercice-en-python/#comments Mon, 16 Dec 2013 07:27:43 +0000 http://sametmax.com/?p=8346 Nouveau concept, de temps en temps, je vais proposer un exercice à faire en Python, vous postez vos solutions, et j’en posterai une le lendemain.

Pas de pression, c’est pour le fun.

Les solutions dans un autre langage sont bienvenues, mais privilégiez Python si vous avez le choix, c’est quand même le but.

Postez votre code sur un pastebin ou ailleurs avec un lien en commentaire, pas le code directement dans les commentaires.

Exercice du jour :

Un script qui attend un fichier en paramètre, l’ouvre, et trouve toutes les positions de chaque mot.

Le script doit prendre en compte les apostrophes, supprimer la ponctuation, et normaliser la casse et les caractères spéciaux des mots.

Le résultat doit afficher une liste à de mots avec leurs positions ordonnée par le nombre d’apparition, ou en cas d’égalité, par ordre naturel des positions.

Aucune gestion d’erreur n’est demandée.

Par exemple si j’ai un fichier contenant :

« La marche des vertueux est semée d’obstacles qui sont les entreprises égoïstes que fait sans fin surgir l’œuvre du Malin. Béni soit-il l’homme de bonne volonté qui, au nom de la charité, se fait le berger des faibles qu’il guide dans la vallée d’ombre, de la mort et des larmes, car il est le gardien de son frère et la providence des enfants égarés. J’abattrai alors le bras d’une terrible colère, d’une vengeance furieuse et effrayante sur les hordes impies qui pourchassent et réduisent à néant les brebis de Dieu. Et tu connaîtras pourquoi mon nom est l’éternel quand sur toi s’abattra la vengeance du Tout-Puissant ! »

Ça fait des années que je répète ça. L’enfoiré qui l’entend, il meurt aussitôt. J’avais jamais cherché à comprendre, je trouvais seulement que ça en jetait de dire ça avant de flinguer un mec. Et puis ce matin, j’ai vu quelque chose qui m’a fait réfléchir. D’un seul coup, je me dis, ça pourrait bien vouloir dire que tu es l’œuvre du malin, et que l’homme vertueux c’est moi, et que mon joli 9 mm ce serait mon protecteur, mon berger dans la vallée de l’angoisse et des larmes. Ou encore mieux, c’est moi le berger et toi l’homme vertueux, et c’est le monde qui est l’œuvre de Lucifer. Qu’est-ce que tu dis de ça ? Mais rien de tout ça n’est juste. Ce qui est vrai, c’est que tu es le faible et que je suis la tyrannie des méchants. Et moi j’essaie, Ringo, au prix d’un effort harassant, de protéger les faibles.

On appelle le script ainsi :

python ton_script.py ton_fichier.txt

Et il affiche ceci :

- marche: 1
- semee: 5
- obstacles: 7
- sont: 9
- entreprises: 11
- egoistes: 12
- sans: 15
- fin: 16
- surgir: 17
- beni: 22
- soit: 23
- bonne: 28
- volonte: 29
- charite: 35
- se: 36
- guide: 44
- ombre: 49
- mort: 52
- car: 56
- gardien: 60
- son: 62
- frere: 63
- providence: 66
- enfants: 68
- egares: 69
- abattrai: 71
- alors: 72
- bras: 74
- terrible: 77
- colere: 78
- furieuse: 82
- effrayante: 84
- hordes: 87
- impies: 88
- pourchassent: 90
- reduisent: 92
- neant: 94
- brebis: 96
- dieu: 98
- connaitras: 101
- pourquoi: 102
- eternel: 107
- quand: 108
- s: 111
- abattra: 112
- puissant: 117
- annees: 121
- repete: 124
- enfoire: 127
- entend: 130
- meurt: 132
- aussitot: 133
- avais: 135
- jamais: 136
- cherche: 137
- comprendre: 139
- trouvais: 141
- seulement: 142
- en: 145
- jetait: 146
- avant: 150
- flinguer: 152
- mec: 154
- puis: 156
- matin: 158
- ai: 160
- vu: 161
- quelque: 162
- chose: 163
- m: 165
- reflechir: 168
- seul: 171
- coup: 172
- me: 174
- pourrait: 177
- bien: 178
- vouloir: 179
- joli: 199
- mm: 200
- serait: 202
- protecteur: 204
- angoisse: 212
- ou: 216
- encore: 217
- mieux: 218
- monde: 233
- lucifer: 239
- mais: 248
- rien: 249
- n: 253
- juste: 255
- vrai: 259
- faible: 266
- suis: 270
- tyrannie: 272
- mechants: 274
- essaie: 278
- ringo: 279
- prix: 281
- effort: 284
- harassant: 285
- proteger: 287
- malin: 21, 187
- au: 31, 280
- nom: 32, 104
- faibles: 41, 289
- qu: 42, 240
- dans: 45, 207
- vallee: 47, 209
- larmes: 55, 215
- une: 76, 80
- vengeance: 81, 114
- sur: 85, 109
- toi: 110, 225
- tout: 116, 251
- dire: 148, 180
- dis: 175, 245
- es: 183, 264
- vertueux: 3, 192, 228
- oeuvre: 19, 185, 237
- du: 20, 115, 186
- homme: 26, 191, 227
- berger: 39, 206, 223
- a: 93, 138, 166
- un: 153, 170, 283
- moi: 195, 221, 276
- les: 10, 86, 95, 288
- fait: 14, 37, 119, 167
- il: 24, 43, 57, 131
- j: 70, 134, 159, 277
- tu: 100, 182, 244, 263
- mon: 103, 198, 203, 205
- je: 123, 140, 173, 269
- ce: 157, 201, 242, 256
- c: 193, 219, 230, 260
- d: 6, 48, 75, 79, 169, 282
- le: 38, 59, 73, 222, 232, 265
- des: 2, 40, 54, 67, 120, 214, 273
- qui: 8, 30, 89, 128, 164, 234, 257
- ca: 118, 125, 144, 149, 176, 247, 252
- la: 0, 34, 46, 51, 65, 113, 208, 271
- que: 13, 122, 143, 181, 189, 197, 243, 262, 268
- l: 18, 25, 106, 126, 129, 184, 190, 211, 226, 236
- est: 4, 58, 105, 194, 220, 231, 235, 241, 254, 258, 261
- de: 27, 33, 50, 61, 97, 147, 151, 210, 238, 246, 250, 286
- et: 53, 64, 83, 91, 99, 155, 188, 196, 213, 224, 229, 267, 275

Solution

]]>
http://sametmax.com/petit-exercice-en-python/feed/ 33