# Write up DG Hack Involucrypt 2 **Consignes:** Pendant une investigation numérique sur un disque dur, vous trouvez un fichier chiffré en utilisant un algorithme propriétaire. Heureusement, le script utilisé pour chiffrer et déchiffrer se trouve dans le même dossier, mais vous ne disposez pas de la clé. Bon courage ! Fichier crypt.py (algo) ``` import itertools import operator import sys BLOCK = 150 class mersenne_rng(object): def __init__(self, seed=5489): self.state = [0] * 624 self.f = 1812433253 self.m = 397 self.u = 11 self.s = 7 self.b = 0x9D2C5680 self.t = 15 self.c = 0xEFC60000 self.l = 18 self.index = 624 self.lower_mask = (1 << 31) - 1 self.upper_mask = 1 << 31 # update state self.state[0] = seed for i in range(1, 624): self.state[i] = self.int_32( self.f * (self.state[i - 1] ^ (self.state[i - 1] >> 30)) + i ) def twist(self): for i in range(624): temp = self.int_32( (self.state[i] & self.upper_mask) + (self.state[(i + 1) % 624] & self.lower_mask) ) temp_shift = temp >> 1 if temp % 2 != 0: temp_shift = temp_shift ^ 0x9908B0DF self.state[i] = self.state[(i + self.m) % 624] ^ temp_shift self.index = 0 def get_random_number(self): if self.index >= 624: self.twist() y = self.state[self.index] y = y ^ (y >> self.u) y = y ^ ((y << self.s) & self.b) y = y ^ ((y << self.t) & self.c) y = y ^ (y >> self.l) self.index += 1 return self.int_32(y) def get_rand_int(self, min, max): return (self.get_random_number() % (max - min)) + min def int_32(self, number): return int(0xFFFFFFFF & number) def shuffle(self, my_list): for i in range(len(my_list) - 1, 0, -1): j = self.get_rand_int(0, i + 1) my_list[i], my_list[j] = my_list[j], my_list[i] def keystream(seeds, length, base=None): key = base if base else [] for seed in seeds: random = mersenne_rng(seed) for _ in range(BLOCK): if len(key) == length: break key.append(random.get_rand_int(0, 255)) random.shuffle(key) if len(key) == length: break return key def encrypt(string, key): key = keystream(map(ord, key), len(string)) stream = itertools.starmap(operator.xor, zip(key, map(ord, string))) return bytearray(stream) if __name__ == "__main__": if sys.version_info >= (3, 0): sys.stdout.buffer.write(encrypt(sys.stdin.read(), sys.argv[1])) else: sys.stdout.write(encrypt(sys.stdin.read(), sys.argv[1])) ``` Fichier involucrypt2 (clé)(exemple): ``` ‹+ÉguƒeÛïþ,«c.¾[\”1…Ä3–Š’ ¥‚à« ‹ÓS^֐¾•@QõÔKÇÓ¶Ë|O_'ç/¥{=0 ìÂôò—‡ WËzL‹ëïkaãBmToìÚÚgÓ¬ä ÉkŽGæ2þ%¶M:¡!³Nl‰$h"˜»#²l¨…›¾ØÚBRVüzõË–þ䂸ØÒî4+y«¼],3|צ[/7¤N|2 ¾Á Ùg(¤Á3»„¨BþcC¬ƒØwÝݶ² &{GO¼jÆa¯°ýíÄåøJèºç;L¶Œ>XI¤‚MTYñØè" uŸëîèOú  §Ü·ï"ó>õKƒ%eΉ´D‹Ú÷oSw-Ýà†mW ¤H“+9œsØ;¼o 0ˏšóuÈûNˆ=K ÏSÍV÷T]XÛé OM‰?(¸Ë^+¡ 4wÓ¤gÌÃ*‡’žÝe·R:ºžúÛsòžlí‡[lƒ¬›*åMÙFRQT-TÆô¾y¼ÛBð,7éGÍÀ{‚*ÛGJbªéÅI"ôÎòºbG‹ªõ,²9«‚×öñ1©#ÇbíYÃ¼åߨ»SÃ¥ìýÓ‡R¯_·qÕÕh'zœVÏ@JŒ¯Qü˜§˜9uìB¶¼›c!ÊÁ çúéãÞKé ¯CÛx´dð“§iRûÓP*㩘Jj4`@W¼ž!ö,H†›ín¤Ž¦0¸Ž¹(Õô¹Á HLªf!^ÅùMÞG¨'5‹5ð,«TîÛú-ÕžƒßÎmgàbƒ³ûa©™3 6Q¨Dtês£>FHy;éÔÄê Þ•^P²yqH!iȵ`:‘ ô=0©/¯ƒ÷¾'ÏpE`×±7VŽ;í$óÏ*¶à‚¶²"Òç' ¿+He3Çu·E"½b©k@Ë$Š«–m7­À`(„­ÇC¥„=ÖÑ ^O¯Ä.ÙÂF. K*–<üQÑb,)°Ñ€U›&Áf´aÁ 8ƒÈ¾Æ¼~öf8YÞÉÕ•^ FðÞ"Úž¾6rÁ ÐtLÀ{ËöW 7Ô±¯m)m;€FÇN„³1/jÅg„Á]C™cŠ#i“þ‚Û Ðü7çîý(¾ê¥Îm|©­ÖcÀÈþ5rÏÀû~«V< aJ—Ãî)×PYwàÕ¾¶Ã2rŽQ)IW(a«3ž‘ÐéSªj‚¹©ê:p·¶ÊI¸>̯g{ìé™nj/qÕÑyøJ IÛµNWONo¥á+<ùØÍ7¨Ó©ë˜èå`–µà<òI"$²³X¿€¤ s‚ ëÃ,Î" Hñ«Ï»KtºÙã¬gDÙ$“bš`X<ͳ‡Wövoìƒ~žá)ëH›`1>;|+W‚7ã°Iø¼v›<–b!¨©¡'&CaP·O¼×ÒêÕÃĝ>uâñLË—¥âöïT®—Þª,ôÙì"Ð[Á9÷è¤ê—Š|ƒŽ‹/„iÄ “2«L3ßÇR!^;[S ¨­îE›Ï˜}Л¶*ŽbI`½&’Å\aU‰X Çaí¤ÍOÑ«Á¼ û;ìoÐ}uLâœâx?úÙ*íŠmK( ká\,7Œ".g¼€HÔ’gV¥©=IH#ùr,ýc£\Ù÷ÚY.µ6²umM…IÓdl6ÎØöÛø23*MÏ߬yDZô06D`-“â(Ö@÷Ú^cÔ/&ë@aóAOì.Ë|ÑÐÖIm ŽDw(úì ÒÂÌÊ9bu‹¯ð>L–Çæs êÎ ŒÄ‰ÑÑÕ¬m²æï•&ô­—,¡æ…j«¸s&2à = ï‚™ŽG§–¡*r]ÜAï˜dŒ Ï´ˆ™`ê« ý²¥Å>(…ju7>+…(q ¡ÀjpÝè·ÚÞ0} ò ääCª½Kn²@¢]g4KíºçFX:Ó¥œg¡ ``` --- # Analyse Alors, me revoila assez vite avec le meme algo de chiffrement, mais un texte chiffré de 1497 caractères cette fois. *Si vous voulez bien comprendre ce write up, allez lire celui que j'ai fait pour [Involucrypt1](https://https://hackmd.io/@ctp/HkYgKLdcv).* On a toujours la meme taille de bloc: ``` BLOCK = 150 for _ in range(BLOCK): ``` On remarque que notre clé qui fait 1497 caractères, a été chiffrée à partir des 10 premiers caractères de la clé. (150+150+150+150+150+150+150+150+150+147). Le temps de chiffrement de 1497 caractères est plus long que pour les 370 d'Involucrypt1. Et puis il y a 10 caractères à brute force. Il suffit de trouver les 10 premiers caractères de la clé, allé hop dirction OVH pour louer un VPS à 1400€ et le tour est joué. Je plaisainte. J'ai un pc portable de 2012, il va falloir faire avec. --- # Résolution: Brute 'finesse' Cette fois, stratégie différente. Vu que le challenge est à 200 points, j'imagine que la clé n'est plus seulement composée de lettres et de chiffres, mais aussi de caractères spéciaux ({,#,~,/,& etc) Je pars encore une fois du code python fourni, mais cette fois ci, j'utilise comme alphabet tous les caractères compris entre 32 et 126 de la table ASCII. ![](https://i.imgur.com/ZzuUz0A.gif) Comme pour Involucrypt1, je vais détecter le texte en clair en comptant le nombre de caractères visibles (compris eux aussi entre 32 et 126). Mais attention, vu que le texte est long il doit y avoir des sauts de ligne. Donc je décide de chercher jusqu'à que j'obtienne un texte avec **1430**/1497 caractères visibles. (quitte à devoir chercher manuellement les derniers). Comme je l'avais remarqué dans Involucrypt1, lorsqu'on trouve l'un des caractère de la clé, il y a un plus grand nombre de caractères visibles dans le texte déchiffré. Je décide donc d'essayer tous les caractères à la première position de la clé, puis de conserver le meilleur (celui qui donne le plus grand nombre de caractères visibles dans le texte déchiffré). Puis pareil pour le 2eme caractère de la clé, etc, jusqu'au 10eme. Une fois arrivé au 10eme, je recommence en essayant tous les caractères à la place du premier, puis du 2eme, etc en faisaint chaque fois augmenter le "score" de caractère visibles. Voici ca que ca donne au niveau du code: ``` import itertools import operator import sys BLOCK = 150 with open('involucrypt2(1)', 'rb') as f: read_data = f.read() t=len(read_data) class mersenne_rng(object): def __init__(self, seed=5489): self.state = [0] * 624 self.f = 1812433253 self.m = 397 self.u = 11 self.s = 7 self.b = 0x9D2C5680 self.t = 15 self.c = 0xEFC60000 self.l = 18 self.index = 624 self.lower_mask = (1 << 31) - 1 self.upper_mask = 1 << 31 # update state self.state[0] = seed for i in range(1, 624): self.state[i] = self.int_32( self.f * (self.state[i - 1] ^ (self.state[i - 1] >> 30)) + i ) def twist(self): for i in range(624): temp = self.int_32( (self.state[i] & self.upper_mask) + (self.state[(i + 1) % 624] & self.lower_mask) ) temp_shift = temp >> 1 if temp % 2 != 0: temp_shift = temp_shift ^ 0x9908B0DF self.state[i] = self.state[(i + self.m) % 624] ^ temp_shift self.index = 0 def get_random_number(self): if self.index >= 624: self.twist() y = self.state[self.index] y = y ^ (y >> self.u) y = y ^ ((y << self.s) & self.b) y = y ^ ((y << self.t) & self.c) y = y ^ (y >> self.l) self.index += 1 return self.int_32(y) def get_rand_int(self, min, max): return (self.get_random_number() % (max - min)) + min def int_32(self, number): return int(0xFFFFFFFF & number) def shuffle(self, my_list): for i in range(len(my_list) - 1, 0, -1): j = self.get_rand_int(0, i + 1) my_list[i], my_list[j] = my_list[j], my_list[i] def keystream(seeds, length, base=None): key = base if base else [] o=49 for seed in seeds: random = mersenne_rng(seed) for _ in range(BLOCK): if len(key) == length: break key.append(random.get_rand_int(0, 255)) #key.append(o) random.shuffle(key) if len(key) == length: break o=o+1 return key def encrypt(string, key): key = keystream(map(ord, key), len(string)) stream = itertools.starmap(operator.xor, zip(key, string)) return bytearray(stream) #return bytearray(key) def keystream2(seeds, length, base=None): key = base if base else [] o=49 for seed in seeds: random = mersenne_rng(seed) for _ in range(BLOCK): if len(key) == length: break #key.append(random.get_rand_int(0, 255)) key.append(o) random.shuffle(key) if len(key) == length: break o=o+1 return key def encrypt2(string, key): key = keystream2(map(ord, key), len(string)) stream = itertools.starmap(operator.xor, zip(key, string)) #return bytearray(stream) return bytearray(key) if __name__ == "__main__": #ouverture + fermeture en w pour remettre le fichier à 0 f = open("demofile3.txt", "wb") f.close() if sys.version_info <= (3, 0): sys.stdout.buffer.write(encrypt(sys.stdin.read(), sys.argv[1])) else: cle="HJeB0cbqazaeadffssse<<se<sf<eszrzeffqtehethetheethehetha" txt="bite" bestCle="azerty" k=0 l=0 bestVis=0 while bestVis < 1430: #construire la clé à essayer qq=l%10 #couper la chaine en 2 au niveau du caractère à remplacer tmp1=cle[0:qq] tmp2=cle[qq+1:] #initialisation du meilleur caractère bestChar=cle[qq] #on essaye les 126-32=94 caractères for a in range(32,126): #contruction de la clé cleTmp=tmp1+chr(a)+tmp2 print(cleTmp+" best="+str(bestVis)+"/"+str(t)+"\r\n=>best clé="+bestCle+"\r\nl="+str(l)+"\r\n") fg=encrypt(read_data, cleTmp) #compte le nombre de caractères visibles count = 0 for i in range(0, len(fg)): if (fg[i]>=32) and (fg[i]<=126): count=count+1 #si on trouve une meillure clé, on remplace la notre if count > bestVis: bestVis=count print("plus grand vis: "+str(bestVis)+"/"+str(t)+" pour cle="+cleTmp) bestChar=chr(a) bestCle=tmp1+bestChar+tmp2 f = open("demofile3.txt", "ab") f.write(bytes("\r\n----------->CODE:",'UTF-8')) f.write(bytes(str(k), 'UTF-8')) f.write(bytes("\r\n", 'UTF-8')) f.write(fg) f.close() #reconstitution de la clé cle=tmp1+bestChar+tmp2 l += 1 k=k+1 f.close() ``` Plus qu'à attendre. Au bout de 8h, le matin on commence à avoir des truc intéressants: ``` ----------->CODE:0 It's someÅong abouthya giyl, ~l~~~~~~~~~~~~~~~~~~~~~~~º~~~ that j8st ™akes$my head wanna twirl, 8~~~~~~~~~~~G~~~~~~~~~~~~~~~ oh you got me want to tell allêthpm ÛtŒer girls, ~~~¼~~~~~~~~~~~~~~~~~~~~~~~~~ t€eóe's nothing 1lsi bettera@n this wUrld! ~~~~~Õ~Ô~~~~~~á~~~~¶~~~~~`~~~ the momenÚ i seen her¾i w‹s 1n shock ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ so shocked you would've Ûink I've ¢u,t been shot, ~~~~~~~ú~~@~~~~°~á~~~~™~~~~~~ sÎt down reg ƒ now i ·his spot. ~W~~~~~~~~~~~~~~~~~~~~~~~~~¯~ But ~oo bad thYs dîesn't happe& a lo». ~~~~~Z~~~Ù~J~}~~~~~~~~~~~~~~> Ow grl you gÎt Üe ôisualizing me on top, ~~~~~~~~¦~L~~~~ÿ~~~~÷~~~~~~~~ on& op of your hot body whilÈ were sweating a lot, ~~~~~~~~~~~~~~~~~~~~~~~~~y~~~ a lot of time on th cock before we have to stop. ~~~~~~~ø~4~~~~~~~$~~~~~~~~~ž~ too bad /hez not i&to that stuff a lot, ~~~~~~~~~~~~~~~~~~~~~~~~~=Ý~ oh ½xnOshes really super hot, ~~~~~7~~~~~+~~~~~~~~~~~€~~~~~ hotter than t¥e&sun thŸt's right on top, ~~c~~~~~~~~~~~~~Së~~~~~~~~~~~ Oh man there she goes iEhad to stop, ~~~1~~~~~~¿~~~~~~~~~K~~q~~~~ “nd ank her some ques ions thatii had án ±tock. ~~~~~~~~~~~Ö~~¾~~~~~~~~~~~~~~ SÄe said phe wanths to take 3t slowÌ ~~~~K~~~~~À~ ~~~~~~~~~~~~'~~~wð[m nt tat type on guy ill let c0a knowa ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ when i °ee ,hat red l¬gh* all]i know is go, ~~”~~C~~~~~~È~q~~~×Î~~~~~~~~ so baby girl lets do this on the floor. (~~~~~~~~~~~~~~~~~~~~~~~~~~~~ show me your moves¾ha makes you such a pro *the flag±is sup:hotfire* ----------->CODE:0 It's some8*Íng aboutÜya gi:l, ~Ò~~~~~~~~~~~~~~~~~~~~~~~Q~~~ that jwst akesZmy head wanna twirl,ÝÙ~~~~~~~~~~~|~~~~~~~~9~~~~~~~ oh you got me want to tell allIthám it²er girls, ~~~m~~~~~~~~~~~~~~~~~~~~~~~~~ tfee's nothing lsM betteråÂn this w rld! ~~~~~8~q~~~~~~´~~~~K~~~~~N~~~ the momenŽ i seen herði ws .n shock ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ so shocked you would've A­ink I've ®uõt been shot, ~~~~~~~%~~ú~~~~É~ã~~~~A~~~~~~ sUöt down r1g‚q now i7 °his spot. ~w~~~~~~~~~~~~~~~~~~~~~~~~~ ~ But (oo bad th/s dîesn't happeŠ a loÆ. ~~~~~~~~y~i~½~~~~~~~~~~~~~~4 OC grl you g_t >e isualizing me on top, ~~~~~~~~±~§~~~~ñ~~~~?~~~~~~~~ on2 op of your hot body whilT were sweating a lot, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ a lot of time on thX cock before we haveBto stop. ~~~~~~~Š~e~~~~~~~°~~~~~~~~~¹~ too bad Lheû not iSto that stuff a lot, ~b~~~~~~~~~~~~~~~~~~~~~~~~é§~ oh kan¦shes really super hot, ~~~~~~~~~~Ñ~~~~~~~~~~~¦~~~~~ hotter than t¯eƒsun thít's right on top, ~~ï~~~~~~~~~~~~~]C~~~~~~~~~~~ Oh man there she goes i§had to stop, ~~~†~~~~~~ì¦~~~~~~~~~'~~D~~~~ Ÿnd auk her some ques ions that@i had ën °tock. ~~~~~~~~~~~¥~~~~~~~~~~~~~~~~ Sèe said he wantrs to take Út slowÈ ~~~~p~~~~~:~å~~~~~~~~~~~~0~~~¶?m nt tíat type o½ guy ill let cña knowŽ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ when i çee shat red lBghv allÂi know is go, ~~Æ~~.~~~~~~õ~O~~~e7~~~~~~~~ so baby girl lets do this on the floor. #~~~~~~~~~~~~~~~~~~~~~~~~~~~~ show me your moves£,haõ makes you such a pro *the flagXis sup[hotfire* ``` Je peux presque déchiffrer à la main. Mais bon on verra à midi cette fois aussi. A midi, Jackpot: ``` ----------->CODE:0 It's something about ya girl, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ that just makes my head wanna twirl, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ oh you got me want to tell all them other girls, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ there's nothing else better in this world! ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ the moment i seen her i was in shock ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ so shocked you would've think I've just been shot, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ shot down right now in this spot. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ But too bad this doesn't happen a lot. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Oh girl you got me visualizing me on top, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ on top of your hot body while were sweating a lot, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ a lot of time on the clock before we have to stop. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ too bad shes not into that stuff a lot, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ oh man shes really super hot, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ hotter than the sun that's right on top, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Oh man there she goes i had to stop, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ and ask her some questions that i had in stock. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ She said she want's to take it slow, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ i'm not that type of guy ill let cha know, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ when i see that red light all i know is go, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ so baby girl lets do this on the floor. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ show me your moves that makes you such a pro *the flag is supahotfire* ``` I'm not a wrapper but I got the key ! Dans la console ca donne (extrait): ``` _jjfisptoiaeadffssse<<se<sf<eszrzeffqtehethetheethehetha best=1372/1497 =>best clé=2jjfisptoiaeadffssse<<se<sf<eszrzeffqtehethetheethehetha l=90 plus grand vis: 1373/1497 pour cle=_jjfisptoiaeadffssse<<se<sf<eszrzeffqtehethetheethehetha `jjfisptoiaeadffssse<<se<sf<eszrzeffqtehethetheethehetha best=1373/1497 =>best clé=_jjfisptoiaeadffssse<<se<sf<eszrzeffqtehethetheethehetha l=90 ajjfisptoiaeadffssse<<se<sf<eszrzeffqtehethetheethehetha best=1373/1497 =>best clé=_jjfisptoiaeadffssse<<se<sf<eszrzeffqtehethetheethehetha l=90 plus grand vis: 1453/1497 pour cle=ajjfisptoiaeadffssse<<se<sf<eszrzeffqtehethetheethehetha bjjfisptoiaeadffssse<<se<sf<eszrzeffqtehethetheethehetha best=1453/1497 =>best clé=ajjfisptoiaeadffssse<<se<sf<eszrzeffqtehethetheethehetha l=90 ``` Les 10 premiers caractères sont donc "ajjfisptoi". Le script a tourné pendant environ 10h sur mon pc, et l=90 signifie qu'on a essayé 90 fois toutes les possibilités d'un des 10 caractères de la clé. Donc que chaque caractère a été retesté 9 fois avant de trouver. --- # Analyse 1. La clé contient juste des minuscules et il y a le mot "flag" dans le résultat, au final j'aurais pu faire plus simple en réduisant l'alphabet. 2. Par contre en cherchant le mot "flag", le code se serait arreté trop tôt car le mot apparait avant la clé. 3. Si le flag avait été noyé au milieu de caractères "non visibles",(crypto+little stegano) mon code n'aurait pas fonctionné, il n'aurait pas détecté le texte en clair. 4. Mon code réduit à 90*94=8460 le nombre de combinaisons contre 94^10=53861511409489970176 en brute force pur. a+ Shurex / CTP