dgse - richelieu
07/07/2019Arrivé sur la page d’accueil
On arrive sur une page avec un compteur indiquant le nombre de jours restants avant la fin du challenge. Cependant, aucune information explicite sur le challenge à résoudre.
<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<link rel="manifest" href="manifest.json">
<title>Challenge Richelieu</title>
<link href="static/css/main.ba9d9d7d.chunk.css" rel="stylesheet">
</head>
<body>
<noscript>Vous devez activer JavaScript pour utiliser l'application.</noscript> <script>let login = "rien";
let password = "nothing";
if (login === password) {
document.location="./Richelieu.pdf";
}
</script>
</body>
</html>
Après un petit tour par le code source du site, on peut vois l’existence d’un ./Richelieu.pdf
dans l’arborescence du site.
$ wget https://www.challengecybersec.fr/Richelieu.pdf
Ce fichier est un PDF assez volumineux avec plus de 9 Mo.
Composé d’un grand nombre de page (364). Cependant, il n’y a du texte (visible) que sur la première page. On se demande alors quelle est l’utilité de toutes les autres pages. On remarque avec notre souris que du texte écrit en très petite police occupe toutes les pages blanches. Il faudrait donc extraire tout le texte de ce PDF.
$ pdftotext Richelieu.pdf text_extracted.txt
Nous allons donc extraire tout le texte du fichier Richelieu.pdf
vers text_extracted.txt
en utilisant pdftotext
.
$ cat text_extracted.txt
Richelieu
L’histoire de la cryptologie serait incomplète sans citer Richelieu qui, dès
1624, sut recruter les meilleurs spécialistes en mathématiques. Il a ainsi pu
fonder, par la suite, ce qui est considéré aujourd’hui comme l'un des tout
premiers bureaux du chiffre en Europe.
Lors du siège de La Rochelle (1627-1628), la cryptanalyse des messages des
Huguenots lui permit d’anticiper l’arrivée des Anglais venus aider ces derniers
par la mer. En octobre 1628, la ville finira par capituler sans condition.
/9j/2wBDADIiJSwlHzIsKSw4NTI7S31RS0VFS5ltc1p9tZ++u7Kfr6zI4f/zyNT/16yv+v/9////
////wfD/////////////wgALCA20CD4BAREA/8QAGQABAQEBAQEAAAAAAAAAAAAAAAECAwQF/9oA
CAEBAAAAAeFzUWUsiyzUFlgFgChLKud5lms2KluaLBNZs1neNSaRclSxqWLJ0xYVNQFyusxQlA1M
#...
x1x1eAsAAQToAwAABOgDAABQSwECHgMUAAkACACrg51OHIIIsGZHXwCfQ18ADwAYAAAAAAAAAAAA
pIFlCgAAbHNiX1JHQi5wbmcuZW5jVVQFAANCCsdcdXgLAAEE6AMAAAToAwAAUEsFBgAAAAAGAAYA
RAIAACRSXwAAAA==
On peut retrouver le texte déjà visible sur le PDF, accompagné de tout le texte cachait, qui été en blanc sur le PDF. On a sûrement à faire à de la base64
.
$ cat text_extracted.txt | wc -l
117798
$ cat text_extracted.txt | sed -n 9,117797p > file.b64
$ cat file.b64 | base64 -di > file.bin
# -i, --ignore-garbage: when decoding, ignore non-alphabet characters
$ file file.bin
file.bin: JPEG image data, progressive, precision 8, 2110x3508, frames 1
$ cp file.bin file.jpg
On retire les 9 premières lignes et on crée le fichier file.b64
. On le décode avec base64 -id
. On remarque que l’on a à faire à un JPG, on le renomme alors. On voit alors une image de Richelieu. Il y a sûrement d’autres choses dans un fichier si grand.
ZIP
$ binwalk file.jpg
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
445628 0x6CCBC Zip archive data, encrypted, name: .bash_history
445979 0x6CE1B Zip archive data, encrypted, name: suite.zip
446405 0x6CFC5 Zip archive data, encrypted, name: prime.txt
446943 0x6D1DF Zip archive data, encrypted, name: public.key
447670 0x6D4B6 Zip archive data, encrypted, name: motDePasseGPG.txt.enc
448289 0x6D721 Zip archive data, encrypted, name: lsb_RGB.png.enc
6693156 0x662124 End of Zip archive, footer length: 22
Avec binwalk
on peut voir la présence d’une archive qui est composée de plusieurs fichiers intéressants.
$ binwalk --dd=".*" file.jpg
$ cd _file.jpg.extracted/
$ ll
-rw-r--r-- 1 adel adel 22 mai 24 19:58 662124
-rw-r--r-- 1 adel adel 6247550 mai 24 19:58 6CCBC
$ file 662124
662124: Zip archive data (empty)
$ file 6CCBC
6CCBC: Zip archive data, at least v2.0 to extract
$ cp 6CCBC file.zip
$ unzip file.zip
Archive: file.zip
[file.zip] .bash_history password:
Après extraction, on se retrouve avec 2 fichiers, un qui ne sert sûrement à rien (662124
) et un qui parait être utile: 6CCBC
. C’est une archive chiffrée bien sûr. Il faut trouver un mot de passe. Après quelques recherches sans succès, revenons aux basiques.
$ strings file.zip
# ...
a#W|+
~8!6
.bash_historyUT
Le mot de passePK
suite.zipUT
de cette archivePK
prime.txtUT
est : DGSE{t.D=@Bx^A%n9FQB~_VL7Zn8z=:K^4ikE=j0EGHqI}PK
public.keyUT
motDePasseGPG.txt.encUT
lsb_RGB.png.encUT
Le mot de passe est sûrement sous nos yeux…
$ mkdir zip_files
$ unzip -P "DGSE{t.D=@Bx^A%n9FQB~_VL7Zn8z=:K^4ikE=j0EGHqI}" file.zip -d zip_files
Archive: file.zip
inflating: zip_files/.bash_history
extracting: zip_files/suite.zip
inflating: zip_files/prime.txt
inflating: zip_files/public.key
extracting: zip_files/motDePasseGPG.txt.enc
inflating: zip_files/lsb_RGB.png.enc
Nous avons accès à tous les fichiers du zip.
$ cd zip_files/
$ ll
-rw-r--r-- 1 adel adel 578 avril 26 17:20 .bash_history
-rw-r--r-- 1 adel adel 6243231 avril 29 16:29 lsb_RGB.png.enc
-rw-r--r-- 1 adel adel 512 avril 29 16:29 motDePasseGPG.txt.enc
-rw-r--r-- 1 adel adel 868 avril 29 16:29 prime.txt
-rw-r--r-- 1 adel adel 800 avril 29 16:29 public.key
-rw-r--r-- 1 adel adel 331 avril 29 16:28 suite.zip
Essayons donc de les parcourir et de faire une analyse rapide.
.bash_history
$ file .bash_history
.bash_history: ASCII text
$ cat .bash_history
# gpg encryption
1337 gpg -o lsb_RGB.png.enc --symmetric lsb_RGB.png
# create file with futur GPG password ?
1338 vim motDePasseGPG.txt
# gen 4096 bits rsa private key
1339 openssl genrsa -out priv.key 4096
# gen public key
1340 openssl rsa -pubout -out public.key -in priv.key
# extract prime 1 from priv key ?
1341 openssl rsa -noout -text -in priv.key | grep prime1 -A 18 > prime.txt
# -A NUM, --after-context=NUM: Print NUM lines of trailing context after matching lines.
# change file content with sed !!
1342 sed -i 's/7f/fb/g' prime.txt
1343 sed -i 's/e1/66/g' prime.txt
1344 sed -i 's/f4/12/g' prime.txt
1345 sed -i 's/16/54/g' prime.txt
1346 sed -i 's/a4/57/g' prime.txt
1347 sed -i 's/b5/cd/g' prime.txt
# encryption of motDePasseGPG.txt with public.key
1348 openssl rsautl -encrypt -pubin -inkey public.key -in motDePasseGPG.txt -out motDePasseGPG.txt.enc
Le fichier .bash_history
sous Linux contient tout l’historique des commandes qui ont étaient exécutaient sur le terminal.
motDePasseGPG.txt.enc
$ file motDePasseGPG.txt.enc
motDePasseGPG.txt.enc: data
$ cat motDePasseGPG.txt.enc
# ciphered data
On comprend rapidement que notre but sera de retrouver la clé privée utilisée pour réaliser le déchiffrement du fichier motDePasseGPG.txt.enc
.
prime.txt
$ file prime.txt
prime.txt: ASCII text
$ cat prime.txt
prim66:
00:fb:40:dc:44:ba:03:d1:53:42:f7:59:08:e0:f9:
30:05:96:64:4a:de:94:68:5e:08:e2:8c:9a:b1:64:
0c:2f:62:c2:9a:b9:a2:39:82:4b:9e:be:eb:76:ae:
6d:87:21:a3:5e:9e:d9:8d:7e:57:38:3e:59:09:34:
a5:78:cd:f7:2e:89:5d:5c:37:52:ea:fd:f6:31:cc:
ba:d2:d9:60:e4:45:1d:67:76:d2:1f:12:9c:9d:c9:
b1:90:45:51:ed:d2:fb:dd:b6:74:b4:99:fb:b1:0a:
d9:b7:c2:be:8b:57:07:22:0a:8e:3a:36:ff:6d:c1:
1d:63:93:af:cb:4e:c0:47:9f:65:bf:df:e3:f0:5f:
1e:98:61:45:74:ec:36:a7:a5:b1:f1:8d:3d:97:6b:
5a:82:49:09:00:08:0d:9d:c2:74:57:4e:30:a1:39:
68:2f:22:34:71:13:aa:3b:f2:20:4f:8e:10:eb:d4:
d0:9b:cd:8c:c2:53:5f:9d:71:13:0c:0f:21:b6:6e:
13:39:40:d3:a6:b1:eb:74:ad:dd:0a:29:14:81:b1:
90:ad:e0:53:f0:89:c8:00:fe:dc:ad:56:59:fc:28:
1d:c0:cf:5e:08:c0:54:33:24:a3:52:bb:f3:25:10:
43:c3:73:b8:40:4f:fc:6b:6b:77:bd:5f:22:24:eb:
fb:15
Voici donc le fichier manipulé dans le .bash_history
, il ne faut pas oublier que ce dernier à été modifié après avoir été créé. Il faut donc penser à le modifier pour retrouver ses valeurs d’origines.
public.key
$ file public.key
public.key: ASCII text
$ cat public.key
-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAzV+KJMdgUAiJejySLA6B
Lnad4KRkQsNQy3jHhoU589OKrICz5qUGYFkQ6FmYBrTR0Ujy9rgdoEeWqKWu4Y8p
6D4Wd1oqCgCHBUH2V07RQ4Y2rgoMEW4HEE9I9yCUhjo4aeHI/CIGJyeJYvsihz4x
VvGOVd7JTpcAZOx/Tg6IRUAS4v1d/l+NGb8XD5zLP0bg/RAZvLAtkIOgcDxhf5lj
eeZHg1SnOubmrLzh9DM+z68kNmo+l30808v+jYo4e9h2v9q4SI9vR78fvjMBD9LX
4itNsuVneDzgtgbbhrk3WXFMT2OWp/ufdMQCEEOw89RtJjPr1DqHeGPffWgPUGWH
wRndZBAMqDHOKvM9lRtSTF8GtJ9b8ss4HnQYGTDQaoBQXAar1b9IcPDJ+1gb2A26
iJZgY5+Tbt6o/l0Mnq5YBi7WkyUlg8ccx4K6YT4BQ45ptD+eZOyoT56gToEa17Oe
/Xh20ba1AcT0iszm8kI59sBAKHiBNc2Iw9Fb4PLrt96enBmnqTA3AF7gqaZAutoz
LsDQXunwioMjVKBIepJ9XogGbiVp5sXUaI5CK/oLJ8YXHG178Cm/2RZXUq8ZqnGz
Oh6nC2w3H7IeR/Un2At9BPWCrZ+ZNa9yNoLcAcqYgGIYcN7LetFWSM307xUwFvPm
2HkzuOxUz6H9+HxGcCCj51MCAwEAAQ==
-----END PUBLIC KEY-----
Nous possédons également, la clé publique utilisée pour le chiffrement.
lsb_RGB.png.enc
$ file lsb_RGB.png.enc
lsb_RGB.png.enc: GPG symmetrically encrypted data (AES256 cipher)
D’après le .bash_history
, ce fichier a été chiffré, avec GPG.
suite.zip
$ file suite.zip
suite.zip: Zip archive data, at least v2.0 to extract
$ unzip suite.zip
Archive: suite.zip
[suite.zip] suite.txt password:
Prochaine étape du challenge.
Attaque sur RSA
Commençons par essayer d’attaquer la clé publique, même si je pense qu’il y a peu de chances d’y arriver.
$ rsactftool.py --publickey ./public.key --private
Killed
$ rsactftool.py --publickey ./public.key --uncipherfile ./motDePasseGPG.txt.enc
Killed
Pas possible d’attaquer la clé publique (module bien trop grand).
$ cat prime.txt
prim66:
00:fb:40:dc:44:ba:03:d1:53:42:f7:59:08:e0:f9:
30:05:96:64:4a:de:94:68:5e:08:e2:8c:9a:b1:64:
0c:2f:62:c2:9a:b9:a2:39:82:4b:9e:be:eb:76:ae:
6d:87:21:a3:5e:9e:d9:8d:7e:57:38:3e:59:09:34:
a5:78:cd:f7:2e:89:5d:5c:37:52:ea:fd:f6:31:cc:
ba:d2:d9:60:e4:45:1d:67:76:d2:1f:12:9c:9d:c9:
b1:90:45:51:ed:d2:fb:dd:b6:74:b4:99:fb:b1:0a:
d9:b7:c2:be:8b:57:07:22:0a:8e:3a:36:ff:6d:c1:
1d:63:93:af:cb:4e:c0:47:9f:65:bf:df:e3:f0:5f:
1e:98:61:45:74:ec:36:a7:a5:b1:f1:8d:3d:97:6b:
5a:82:49:09:00:08:0d:9d:c2:74:57:4e:30:a1:39:
68:2f:22:34:71:13:aa:3b:f2:20:4f:8e:10:eb:d4:
d0:9b:cd:8c:c2:53:5f:9d:71:13:0c:0f:21:b6:6e:
13:39:40:d3:a6:b1:eb:74:ad:dd:0a:29:14:81:b1:
90:ad:e0:53:f0:89:c8:00:fe:dc:ad:56:59:fc:28:
1d:c0:cf:5e:08:c0:54:33:24:a3:52:bb:f3:25:10:
43:c3:73:b8:40:4f:fc:6b:6b:77:bd:5f:22:24:eb:
fb:15
Nous devons donc travailler sur ce fichier.
Rappels sur RSA:
Tout d’abord, nous devons nettoyer prime.txt
afin de retrouver la valeur de $p$.
1341 openssl rsa -noout -text -in priv.key | grep prime1 -A 18 > prime.txt
1342 sed -i 's/7f/fb/g' prime.txt
1343 sed -i 's/e1/66/g' prime.txt
1344 sed -i 's/f4/12/g' prime.txt
1345 sed -i 's/16/54/g' prime.txt
1346 sed -i 's/a4/57/g' prime.txt
1347 sed -i 's/b5/cd/g' prime.txt
La première commande permet de récupérer les 18 lignes se trouvant après la chaîne prime1
, ce qui correspond donc à notre $p$.
$ cp prime.txt prime_good.txt
$ sed -i 's/cd/b5/g' prime_good.txt
$ sed -i 's/57/a4/g' prime_good.txt
$ sed -i 's/54/16/g' prime_good.txt
$ sed -i 's/12/f4/g' prime_good.txt
$ sed -i 's/66/e1/g' prime_good.txt
$ sed -i 's/fb/7f/g' prime_good.txt
Les lignes suivantes, réalisent des substitutions sur le fichier à l’aide de la commande sed
, il faut donc les inverser pour connaître le contenu originel de prime.txt
.
$ cat prime_good.txt
prime1:
00:7f:40:dc:44:ba:03:d1:53:42:f7:59:08:e0:f9:
30:05:96:64:4a:de:94:68:5e:08:e2:8c:9a:b1:64:
0c:2f:62:c2:9a:b9:a2:39:82:4b:9e:be:eb:76:ae:
6d:87:21:a3:5e:9e:d9:8d:7e:a4:38:3e:59:09:34:
a5:78:b5:f7:2e:89:5d:5c:37:52:ea:fd:f6:31:cc:
ba:d2:d9:60:e4:45:1d:67:76:d2:1f:f4:9c:9d:c9:
b1:90:45:51:ed:d2:7f:dd:b6:74:b4:99:7f:b1:0a:
d9:b7:c2:be:8b:a4:07:22:0a:8e:3a:36:ff:6d:c1:
1d:63:93:af:cb:4e:c0:47:9f:65:bf:df:e3:f0:5f:
1e:98:61:45:74:ec:36:a7:a5:b1:f1:8d:3d:97:6b:
5a:82:49:09:00:08:0d:9d:c2:74:a4:4e:30:a1:39:
68:2f:22:34:71:13:aa:3b:f2:20:4f:8e:10:eb:d4:
d0:9b:b5:8c:c2:53:5f:9d:71:13:0c:0f:21:b6:6e:
13:39:40:d3:a6:b1:eb:74:ad:dd:0a:29:14:81:b1:
90:ad:e0:53:f0:89:c8:00:fe:dc:ad:56:59:fc:28:
1d:c0:cf:5e:08:c0:16:33:24:a3:52:bb:f3:25:10:
43:c3:73:b8:40:4f:fc:6b:6b:77:bd:5f:22:24:eb:
7f:15
Voici donc la vrai valeur de notre nombre $prime1$ ou $p$. Nous devons maintenant trouver $q$, qui vaut:
Nous avons le clé publique public.key
, nous pouvons donc facilement extraire $n$.
$ openssl rsa -pubin -inform PEM -text -noout -in public.key > modulus.txt
RSA Public-Key: (4096 bit)
Modulus:
00:cd:5f:8a:24:c7:60:50:08:89:7a:3c:92:2c:0e:
81:2e:76:9d:e0:a4:64:42:c3:50:cb:78:c7:86:85:
39:f3:d3:8a:ac:80:b3:e6:a5:06:60:59:10:e8:59:
98:06:b4:d1:d1:48:f2:f6:b8:1d:a0:47:96:a8:a5:
ae:e1:8f:29:e8:3e:16:77:5a:2a:0a:00:87:05:41:
f6:57:4e:d1:43:86:36:ae:0a:0c:11:6e:07:10:4f:
48:f7:20:94:86:3a:38:69:e1:c8:fc:22:06:27:27:
89:62:fb:22:87:3e:31:56:f1:8e:55:de:c9:4e:97:
00:64:ec:7f:4e:0e:88:45:40:12:e2:fd:5d:fe:5f:
8d:19:bf:17:0f:9c:cb:3f:46:e0:fd:10:19:bc:b0:
2d:90:83:a0:70:3c:61:7f:99:63:79:e6:47:83:54:
a7:3a:e6:e6:ac:bc:e1:f4:33:3e:cf:af:24:36:6a:
3e:97:7d:3c:d3:cb:fe:8d:8a:38:7b:d8:76:bf:da:
b8:48:8f:6f:47:bf:1f:be:33:01:0f:d2:d7:e2:2b:
4d:b2:e5:67:78:3c:e0:b6:06:db:86:b9:37:59:71:
4c:4f:63:96:a7:fb:9f:74:c4:02:10:43:b0:f3:d4:
6d:26:33:eb:d4:3a:87:78:63:df:7d:68:0f:50:65:
87:c1:19:dd:64:10:0c:a8:31:ce:2a:f3:3d:95:1b:
52:4c:5f:06:b4:9f:5b:f2:cb:38:1e:74:18:19:30:
d0:6a:80:50:5c:06:ab:d5:bf:48:70:f0:c9:fb:58:
1b:d8:0d:ba:88:96:60:63:9f:93:6e:de:a8:fe:5d:
0c:9e:ae:58:06:2e:d6:93:25:25:83:c7:1c:c7:82:
ba:61:3e:01:43:8e:69:b4:3f:9e:64:ec:a8:4f:9e:
a0:4e:81:1a:d7:b3:9e:fd:78:76:d1:b6:b5:01:c4:
f4:8a:cc:e6:f2:42:39:f6:c0:40:28:78:81:35:cd:
88:c3:d1:5b:e0:f2:eb:b7:de:9e:9c:19:a7:a9:30:
37:00:5e:e0:a9:a6:40:ba:da:33:2e:c0:d0:5e:e9:
f0:8a:83:23:54:a0:48:7a:92:7d:5e:88:06:6e:25:
69:e6:c5:d4:68:8e:42:2b:fa:0b:27:c6:17:1c:6d:
7b:f0:29:bf:d9:16:57:52:af:19:aa:71:b3:3a:1e:
a7:0b:6c:37:1f:b2:1e:47:f5:27:d8:0b:7d:04:f5:
82:ad:9f:99:35:af:72:36:82:dc:01:ca:98:80:62:
18:70:de:cb:7a:d1:56:48:cd:f4:ef:15:30:16:f3:
e6:d8:79:33:b8:ec:54:cf:a1:fd:f8:7c:46:70:20:
a3:e7:53
Exponent: 65537 (0x10001)
# autre façon
$ rsactftool.py --dumpkey --key ./public.key
[*] n: 837849563862443268467145186974119695264713699736869090645354954749227901572347301978135797019317859500555501198030540582269024532041297110543579716921121054608494680063992435808708593796476251796064060074170458193997424535149535571009862661106986816844991748325991752241516736019840401840150280563780565210071876568736454876944081872530701199426927496904961840225828224638335830986649773182889291953429581550269688392460126500500241969200245489815778699333733762961281550873031692933566002822719129034336264975002130651771127313980758562909726233111335221426610990708111420561543408517386750898610535272480495075060087676747037430993946235792405851007090987857400336566798760095401096997696558611588264303087788673650321049503980655866936279251406742641888332665054505305697841899685165810087938256696223326430000379461379116517951965921710056451210314300437093481577578273495492184643002539393573651797054497188546381723478952017972346925020598375000908655964982541016719356586602781209943943317644547996232516630476025321795055805235006790200867328602560320883328523659710885314500874028671969578391146701739515500370268679301080577468316159102141953941314919039404470348112690214065442074200255579004452618002777227561755664967507
[*] e: 65537
Nous connaissons maintenant la valeur de $n$, not:re $modulus$. C’est la forme en base 10 qui va nous intéresser.
$ python3
>>> p = ''.join(open('prime_good.txt').readlines())
>>> import re
>>> p = int(re.sub('[^0-9a-f]','',p[6:]), 16)
>>> import gmpy
>>> gmpy.is_prime(p)
0
Sauf que, problème, on se rend compte qu’après avoir passé notre prime_good.txt
en base 10, celui-ci n’est pas premier… :(
Après réflexions, l’inversion des sed
que l’on a fait plus haut n’est peut-être pas suffisante. Par exemple, si nous avons la chaîne ; xxxxbbxx
et que l’on applique sed -i s/bb/xx/g
, ce qui nous donne xxxxxxxx
. On pense alors qu’il suffit d’inverser le sed
en sed -i s/xx/bb/g
pour retrouver le message originel. Sauf que non, on se retrouve avec bbbbbbbb
. C’est sûrement le même problème avec notre prime_good.txt
, l’inversion du sed
ne garantis pas de retrouver le vrai $prime$ d’origine.
$ sed -i 's/cd/b5/g' prime_good.txt # 2 occurrences de 'b5'
$ sed -i 's/57/a4/g' prime_good.txt # 3 occurrences de 'a4'
$ sed -i 's/54/16/g' prime_good.txt # 1 occurrence de '16'
$ sed -i 's/12/f4/g' prime_good.txt # 1 occurrence de 'f4'
$ sed -i 's/66/e1/g' prime_good.txt # on ignore car présent au début
$ sed -i 's/fb/7f/g' prime_good.txt # 4 occurrences de '7f' dans godd_prime.txt
Il faut donc tester toutes les combinaisons possibles, chaque occurrence peut prendre 2 valeurs, soit garder sa valeur actuelle, soit revenir à sa valeur d’origine. Nous allons tester toutes les combinaisons possibles appliquées au fichier prime_good.txt
puis tester à chaque fois si notre $q$ est premier.
Voici le script qui va effectuer cette tâche :
#/usr/bin/env python3
import re
import itertools
import subprocess as sp
import gmpy
# read modulus (n)
n = ''.join(re.findall('[0-9a-f]{2}:.*:[0-9a-f]{2}', ''.join(open('modulus.txt').readlines()), re.DOTALL)).replace('\n', '').replace(' ', '').replace(':', '')
n = int(n,16)
# read prime to ajust
f = ''.join(open('prime_good.txt').readlines())
# hex to ajust
tab_reg = ['7f', 'f4', '16', 'a4', 'b5']
# find position for each previous hex
positions_occ = []
for reg in tab_reg:
tmp = []
for m in re.compile(reg).finditer(f):
tmp.append(m.start())
positions_occ.append(tmp)
# we ill use binary to find all combinaisons
combinaisons_7f = list(itertools.product([0,1], repeat=len(positions_occ[0])))
combinaisons_f4 = list(itertools.product([0,1], repeat=len(positions_occ[1])))
combinaisons_16 = list(itertools.product([0,1], repeat=len(positions_occ[2])))
combinaisons_a4 = list(itertools.product([0,1], repeat=len(positions_occ[3])))
combinaisons_b5 = list(itertools.product([0,1], repeat=len(positions_occ[4])))
# adapt '7f'
for c_7f in combinaisons_7f:
pos_7f = positions_occ[0]
new_7f_0 = '7f' if c_7f[0] == 0 else 'fb'
new_7f_1 = '7f' if c_7f[1] == 0 else 'fb'
new_7f_2 = '7f' if c_7f[2] == 0 else 'fb'
new_7f_3 = '7f' if c_7f[3] == 0 else 'fb'
f = f[0:pos_7f[0]] + new_7f_0 + f[pos_7f[0]+2:pos_7f[1]] + new_7f_1 + f[pos_7f[1]+2:pos_7f[2]] + new_7f_2 + f[pos_7f[2]+2:pos_7f[3]] + new_7f_3 + f[pos_7f[3]+2:]
# adapt 'f4'
for c_f4 in combinaisons_f4:
pos_f4 = positions_occ[1]
new_f4_0 = 'f4' if c_f4[0] == 0 else '12'
f = f[0:pos_f4[0]] + new_f4_0 + f[pos_f4[0]+2:]
# adapt '16'
for c_16 in combinaisons_16:
pos_16 = positions_occ[2]
new_16_0 = '16' if c_16[0] == 0 else '54'
f = f[0:pos_16[0]] + new_16_0 + f[pos_16[0]+2:]
# adapt 'a4'
for c_a4 in combinaisons_a4:
pos_a4 = positions_occ[3]
new_a4_0 = 'a4' if c_a4[0] == 0 else '57'
new_a4_1 = 'a4' if c_a4[1] == 0 else '57'
new_a4_2 = 'a4' if c_a4[2] == 0 else '57'
f = f[0:pos_a4[0]] + new_a4_0 + f[pos_a4[0]+2:pos_a4[1]] + new_a4_1 + f[pos_a4[1]+2:pos_a4[2]] + new_a4_2 + f[pos_a4[2]+2:]
# adapt 'b5'
for c_b5 in combinaisons_b5:
pos_b5 = positions_occ[4]
new_b5_0 = 'b5' if c_b5[0] == 0 else 'cd'
new_b5_1 = 'b5' if c_b5[1] == 0 else 'cd'
f = f[0:pos_b5[0]] + new_b5_0 + f[pos_b5[0]+2:pos_b5[1]] + new_b5_1 + f[pos_b5[1]+2:]
# final test
p = f.replace('\n', '').replace(' ', '').replace('prime1', '').replace(':', '')
p = int(p,16)
if gmpy.is_prime(p) == 1 or gmpy.is_prime(p) == 2:
q = n//p
if p*q == n:
print('p\n', p)
print('q\n', q)
exit(0)
Code pas du tout optimal, mais fonctionnel :)
$ python3 ./find_good_prime.py
p
31717798413454838971739311391870214101486054474438584033384974797696836002786889741922328038249283935148167589396475764515984792248325276562635675483085593307632384945480084324354199386711259069590701728377654610059849152461565385315663116675949927841648944186909571007960100266136674008112969369512988507367569334838093403144731951113528040013763615354283491955226704334394856253005551393545293491547587439064269735583121706098798182168292621767902095641352443871167789104818694502605762517497996162527138366803593402358312011933528177646501311411008602369245268966975849244259437732574655956250792264392115994984213
q
26415754111957012456882978698568998595543408604540679602012008905307229528398419508696699258861541673208334031720118065722015191735056250991124159829757615035331104814626815428071461389740988358621575175288995137250131479702118666935818278843603742157096854979085966659730153703721295913600711482578441198179188796849780101926878867272222747848775311501107737838687624647749187764563156054858429897018261077616060929102880860789449995240869652963349913062375402021334173237153511868665597034141167420444312865576543827395764456254276820652449103574192677537410783040227047254038577626347718324544296319787177388746439
Nous avons maintenant notre $p$ et $q$ qui sont premiers.
$ rsatool.py -o priv.key -p 3171779841... -q 2641575411...
# ...
Saving PEM as priv.key
Avec rsatool.py
nous créons notre clé privée connaissant $p$ et $q$.
$ openssl rsautl -decrypt --inkey priv.key -in motDePasseGPG.txt.enc -out motDePasseGPG.txt
$ cat motDePasseGPG.txt
DGSE{Ti,%yei3=stlh_,5@pIrrMU.^mJC:luYbt1Qe_-Y}
On déchiffre ensuite avec openssl
.
1337 gpg -o lsb_RGB.png.enc --symmetric lsb_RGB.png
À ce stade nous n’avons toujours pas utilisé cette commande, étrange…
$ unzip -d suite_files/ -P "DGSE{Ti,%yei3=stlh_,5@pIrrMU.^mJC:luYbt1Qe_-Y}" suite.zip
Archive: suite.zip
skipping: suite.txt incorrect password
Et non, ce mot de passe ne permet pas de déchiffrer le zip suite.zip
.
GPG
1337 gpg -o lsb_RGB.png.enc --symmetric lsb_RGB.png
En regardant la commande, on se rend compte, que nous avons à faire à un chiffrement symetric
. Alors que naturellement, GPG est un système de chiffrement asymétrique avec une notion de clé privée et publique. Pour du symétrique, il ne faut que le mot de passe pour déchiffrer, pas besoin de clé privée.
$ gpg --batch --yes --passphrase "DGSE{Ti,%yei3=stlh_,5@pIrrMU.^mJC:luYbt1Qe_-Y}" -o lsb_RGB.png --decrypt lsb_RGB.png.enc
gpg: AES256 encrypted data
gpg: encrypted with 1 passphrase
En cherchant un peu, on trouve comment déchiffrer du symétrique issu de GPG. On peut alors utiliser notre mot de passe que nous avions trouvé. Le fichier déchiffré est une autre image de Richelieu encore une fois.
Le nom est assez explicite pour comprendre que la prochaine étape consiste à extraire des données en LSB.
LSB
In digital steganography, sensitive messages may be concealed by manipulating and storing information in the least significant bits of an image or a sound file. In the context of an image, if a user were to manipulate the last two bits of a color in a pixel, the value of the color would change at most +/- 3 value places, which is likely to be indistinguishable by the human eye. The user may later recover this information by extracting the least significant bits of the manipulated pixels to recover the original message. This allows for the storage or transfer of digital information to be kept concealed.
Pour cette étape, nous devons utiliser l’image lsb_RGB.png
afin d’extraire des données de celle-ci. Le LSB utilise la valeur des bits de poids faible des couleurs Rouge, Vert et Bleue. La difficulté du LSB réside dans la bonne sélection des bits de poids faibles. Nous utiliserons la librairie PIL(Python Imaging Library)
.
>>> from PIL import Image
>>> img = Image.open('lsb_RGB.png')
>>> img.size
(1562, 2424) # (y,x)
>>> img.mode
'RGB'
>>> img_data = img.load()
>>> img_data[0,0]
(60, 180, 75)
On peut tout d’abord voir que l’on a affaire à un PNG de type RGB (et non RGBA, car pas de valeur alpha). Chaque pixel correspond alors à un tuple de taille 3, par exemple (60, 180, 75)
correspond à la valeur du pixel en position [y=0,x=0]
. La valeur 60
correspond à la quantité de rouge (Red) du pixel (valeur maximale de 255), 180
est la quantité de vert (Green) et 75
la quantité de bleu (Blue).
>>> [bin(i)[2:].zfill(8) for i in img_data[0,0]]
['00111100', '10110100', '01001011']
# zfill(8): prefix with "0" to fixing size
Chacune de ces valeurs est codée sur 8 bits. Par exemple, la valeur 63
en base 10, est défini par 00111100
en base 2.
'00111100' -> '.......0'
'10110100' -> '.......0'
'01001011' -> '.......1'
Pour ce premier pixel, la valeur LSB du RED est 0
, celle du GREEN est 0
et celle du BLUE est 1
.
La difficulté réside maintenant dans le fait de savoir si nous devons utiliser les 3 bits précédents ou simplement le LSB du RED, ou du GREEN, … ou même seulement le LSB du GREEN et BLUE.
Également, nous devons savoir dans quelle direction parcourir les pixels de notre image, soit ligne par ligne (de gauche à droite) ou en colonnes (haut en bas).
Après pas mal de tests, on sait qu’il faut utiliser les 3 LSB, le RED, GREEN et BLUE, de plus on doit parcourir les pixels de notre image en colonne, de haut en bas.
Voici notre extracteur LSB:
#!/usr/bin/env python3
import sys
from PIL import Image
def extract(src):
img = Image.open(src)
dimX,dimY = img.size
data = img.load()
all_bits = ''
for y in range(dimX):
for x in range(dimY):
r,g,b = data[y,x]
r_bit = bin(r)[2:].zfill(8)[-1] # RED last bit
g_bit = bin(g)[2:].zfill(8)[-1] # GREEN last bit
b_bit = bin(b)[2:].zfill(8)[-1] # BLUE last bit
all_bits += r_bit + g_bit + b_bit
tab_all_bits = [all_bits[i:i+8] for i in range(0, len(all_bits), 8)]
return''.join([chr(int(c,2)) for c in tab_all_bits])
print('Start')
open('lsb_data', 'wb').write(extract(src='lsb_RGB.png').encode())
print('Done')
Ce script se charge de parcourir les pixels de l’image en colonne de haut en bas. Pour chaque pixel, on récupère le dernier bit des 3 composantes (RED, GREEN, BLUE). On construit ensuite un tableau d’octets (8 bits) sur lesquels on récupère leur caractère ASCII. Pour terminer, on enregistre tout ça dans le fichier lsb_data
.
# autre manière de trouver les infos LSB
$ zsteg -a ./lsb_RGB.png
...
b1,rgb,lsb,yx .. text: "00000000: 7f45 4c46 0201 0103 0000 0000 0000 0000 .ELF............\n00000010: 0200 3e00 0100 0000 107f 4400 0000 0000 ..>.......D.....\n00000020
...
$ python3 ./lsb.py
Start
Done
$ file lsb_data
lsb_data: ASCII text
$ head lsb_data
00000000: 7f45 4c46 0201 0103 0000 0000 0000 0000 .ELF............
00000010: 0200 3e00 0100 0000 107f 4400 0000 0000 ..>.......D.....
00000020: 4000 0000 0000 0000 0000 0000 0000 0000 @...............
00000030: 0000 0000 4000 3800 0200 4000 0000 0000 ....@.8...@.....
00000040: 0100 0000 0500 0000 0000 0000 0000 0000 ................
00000050: 0000 4000 0000 0000 0000 4000 0000 0000 ..@.......@.....
00000060: 2487 0400 0000 0000 2487 0400 0000 0000 $.......$.......
00000070: 0000 2000 0000 0000 0100 0000 0600 0000 .. .............
00000080: 2854 0b00 0000 0000 2854 6b00 0000 0000 (T......(Tk.....
00000090: 2854 6b00 0000 0000 0000 0000 0000 0000 (Tk.............
Notre lsb_data
correspond à la vue d’un éditeur hex d’un binaire, plus précisément un ELF
(Executable and Linkable Format). La prochaine étape est sûrement d’extraire ce binaire et de l’analyser.
Mais avant de continuer, on doit se rappeler que notre script python précédent va réaliser l’extraction des bits LSB sur TOUS les pixels de l’image. Mais il y a de grande chance que notre extraction va trop loin. Essayons de voir cela en ouvrant lsb_data
avec vim
.
$ sed -n 18592,18595p lsb_data
000489f0: 414c 4421 0d16 0807 f3b9 2205 c301 7d67 ALD!......"...}g # ligne 18592
00048a00: 0009 0000 9a02 0000 5044 0b00 4918 006b ........PD..I..k # ligne 18593
00048a10: bc00 0000 .... # ligne 18594
q?Ð qdG/A§Ge¿{`Üø)í÷Ö°Ú@¾ïÄt¤#&óP_WlòÛ<7_&f|á}ª # ligne 18595
oDî$õ¬ËÕzµ'ßØQ
On peut donc voir que nous devons couper en ligne 18594 pour ne pas polluer notre extraction.
$ sed -n 1,18594p lsb_data > lsb_data_clean
$ cat lsb_data_clean | less
00000000: 7f45 4c46 0201 0103 0000 0000 0000 0000 .ELF............
00000010: 0200 3e00 0100 0000 107f 4400 0000 0000 ..>.......D.....
00000020: 4000 0000 0000 0000 0000 0000 0000 0000 @...............
Nous avons maintenant une vision en hex propre. Pour continuer nous devons extraire toutes les valeurs hexadécimal des colonnes du milieu. La colonne de gauche représente les positions des valeurs hexa et la colonne de droite, leur traduction ASCII, ces deux informations ne nous sont pas utiles, nous devons les retirer.
$ cat lsb_data_clean | cut -d' ' -f2-9 > lsb_data_clean_center
$ python3
>>> content = open('lsb_data_clean_center').read()
>>> clean_content = content.replace(' ', '').replace('\n', '')
>>> import binascii
>>> final = binascii.unhexlify(clean_content)
>>> open('elf.bin', 'wb').write(final)
$ file elf.bin
elf.bin: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, no section header
$ chmod +x elf.bin
$ ./elf.bin
usage : ./elf.bin <mot de passe>
$ ./elf.bin test
Mauvais mot de passe
Nous avons maintenant le fichier caché dans le PNG, c’est un exécutable ELF qui prend en paramètre un mot de passe que nous devons sûrement trouver.
Reverse
$ file elf.bin
elf.bin: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, no section header
$ r2 -d ./elf.bin
Process with PID 3036 started...
= attach 3036 3036
bin.baddr 0x00400000
Using 0x400000
Warning: Cannot initialize section headers
Warning: Cannot initialize strings table
Warning: Cannot initialize dynamic strings
asm.bits 64
[0x00447f10]> afl
0x00447f10 3 50 entry0
0x00447f80 28 203 fcn.00447f80
0x00448125 15 107 -> 230 fcn.00448125
0x00448190 3 38 -> 40 fcn.00448190
On peut voir qu’il n’y a pas de main()
dans notre ELF, sûrement parce que ce dernier a été packé, il faut trouver le packer utilisé.
$ strings ./elf.bin | grep upx
$Info: This file is packed with the ALD executable packer http://upx.sf.net
On a donc à faire à un packer UPX, il faut réussir à le unpacker.
$ upx -d ./elf.bin -o elf_unpacked.bin
# -d: decompress
Ultimate Packer for eXecutables
Copyright (C) 1996 - 2017
UPX 3.94 Markus Oberhumer, Laszlo Molnar & John Reiser May 12th 2017
File size Ratio Format Name
-------------------- ------ ----------- -----------
upx: ./elf.bin: NotPackedException: not packed by UPX
Unpacked 0 files.
Sauf que, upx -d
n’y arrive pas. La décompression ne fonctionne pas.
$ strings -a ./elf.bin | grep -i upx
$Info: This file is packed with the ALD executable packer http://upx.sf.net $
Si nous revenons à cette étape, on peut remarquer que l’URL http://upx.sf.net
correspond bien à une preuve d’UPX, mais il y a un problème sur This file is packed with the ALD executable packer
. En effet, à la place de ALD
on est censé avoir UPX
. Également, notre grep -i upx
est censé trouvé la chaîne UPX!
plusieurs fois.
Après un peu de réflexion, peut-être que la chaîne UPX
a été remplacé par ALD
?
$ strings -a ./elf.bin | grep -i ald
ALD!
;AlD
$Info: This file is packed with the ALD executable packer http://upx.sf.net $
$Id: ALD 3.91 Copyright (C) 1996-2013 the ALD Team. All Rights Reserved. $
ALD!
ALD!
Donc oui, tous les UPX
ont été remplacé par des ALD
. Nous devons donc faire l’inverse pour continuer.
$ file elf.bin
elf.bin: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, no section header
$ cat elf.bin | sed s/ALD/UPX/g > elf_upx.bin
$ upx -d ./elf_upx.bin -o ./elf_upx_unpacked.bin
# -d: decompress
Ultimate Packer for eXecutables
Copyright (C) 1996 - 2017
UPX 3.94 Markus Oberhumer, Laszlo Molnar & John Reiser May 12th 2017
File size Ratio Format Name
-------------------- ------ ----------- -----------
738384 <- 297492 40.29% linux/amd64 elf_upx_unpacked.bin
Unpacked 1 file.
$ file ./elf_upx_unpacked.bin
./elf_upx_unpacked.bin: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.32, BuildID[sha1]=280771ce69bcde48b3a996331811768a49c7c120, stripped
$ chmod +x ./elf_upx_unpacked.bin
$ ./elf_upx_unpacked.bin
usage : ./elf_upx_unpacked.bin <mot de passe>
$ ./elf_upx_unpacked.bin test
Mauvais mot de passe
Nous avons donc réussi à décompresser (unpacker) notre elf.bin
en elf_upx_unpacked
. Nous pouvons maintenant commencer une analyse classique du binaire.
$ r2 -d ./elf_upx_unpacked.bin
> aaaa
> afl
0x00400990 1 43 entry0
0x00400aae 9 114 sub.w0c__:_aae
0x00400b20 6 83 main
0x00401080 66 828 fcn.00401080
0x00407840 3 158 fcn.00407840
0x00408010 30 445 -> 417 fcn.00408010
0x004440f0 296 11505 -> 7157 sub.n_in_writable_segment_detected_f0
> s main
> pdf
/ (fcn) main 83
| main (int arg2);
| ; arg int arg2 @ rsi
| ; DATA XREF from entry0 (0x4009ad)
| 0x00400b20 53 push rbx
| 0x00400b21 83ff01 cmp edi, 1 ; 1
| ,=< 0x00400b24 7e1f jle 0x400b45 # on s'assure d'un argv[1]
| | 0x00400b26 488b7e08 mov rdi, qword [rsi + 8] ; arg2
| | 0x00400b2a e87fffffff call sub.w0c__:_aae # appel de fonction
| | 0x00400b2f 89c3 mov ebx, eax # on récupére une valeur de
| | 0x00400b31 85c0 test eax, eax # retour qui va nous dire si mdp
| ,==< 0x00400b33 752b jne 0x400b60 # OK ou NOT OK
| || 0x00400b35 488d3d238d08. lea rdi, qword str.Mauvais_mot_de_passe
| || 0x00400b3c e8cf740000 call fcn.00408010 # NOT OK
| || ; CODE XREFS from main (0x400b5e, 0x400b71)
| ..---> 0x00400b41 89d8 mov eax, ebx
| ::|| 0x00400b43 5b pop rbx
| ::|| 0x00400b44 c3 ret
| ::|| ; CODE XREF from main (0x400b24) # si pas d'argument
| ::|`-> 0x00400b45 488b36 mov rsi, qword [rsi] ; arg2
| ::| 0x00400b48 488d3df58c08. lea rdi, qword str.usage_:__s__mot_de_passe
| ::| 0x00400b4f b800000000 mov eax, 0
| ::| 0x00400b54 e8e76c0000 call fcn.00407840 # OK
| ::| 0x00400b59 bb02000000 mov ebx, 2
| `====< 0x00400b5e ebe1 jmp 0x400b41
| :| ; CODE XREF from main (0x400b33)
| :`--> 0x00400b60 488d3d118d08. lea rdi, qword str.Bravo___Vous_pouvez_utiliser_ce_mot_passe_pour_la_suite
| : 0x00400b67 e8a4740000 call fcn.00408010
| : 0x00400b6c bb00000000 mov ebx, 0
\ `===< 0x00400b71 ebce jmp 0x400b41
0x00400b20 53 push rbx
0x00400b21 83ff01 cmp edi, 1
0x00400b24 7e1f jle 0x400b45 # on s'assure d'un argv[1]
Rapidement, on peut dire que notre programme s’assure de la présence d’un argv[1]
, si rdi <= 1
alors on envoi le message d’erreur usage: ./elf_upx_unpacked.bin <mot de passe>
avec un jle 0x400b45
. Le registre rdi
contient le nombre d’arguments du programme. Lorsque rdi = 0x00000001
alors message d’erreur, lorsque rdi = 0x00000002
on continu l’exécution.
rdi
etedi
sont la même entité.
0x00400b26 488b7e08 mov rdi, qword [rsi + 8] ; arg2
0x00400b2a e87fffffff call sub.w0c__:_aae
Ensuite, on se charge de récupérer le argv[1]
et d’appeler la fonction sub.w0c__:_aae
juste derrière.
0x00400b2a e87fffffff call sub.w0c__:_aae
0x00400b2f 89c3 mov ebx, eax
0x00400b31 85c0 test eax, eax # test de la valeur de retour de la fonction
0x00400b33 752b jne 0x400b60 # prise de décision succès ? ou échec ?
0x00400b35 488d3d238d08. lea rdi, qword str.Mauvais_mot_de_passe # échec
0x00400b3c e8cf740000 call fcn.00408010
0x00400b41 89d8 mov eax, ebx
0x00400b43 5b pop rbx
0x00400b44 c3 ret
...
0x00400b60 488d3d118d08. lea rdi, qword str.Bravo___Vous_pouvez_utiliser_ce_mot_passe_pour_la_suite # succès
0x00400b67 e8a4740000 call fcn.00408010
0x00400b6c bb00000000 mov ebx, 0
0x00400b71 ebce jmp 0x400b41
On peut voir que cette fonction est très importante, en effet, c’est elle qui décide quel message afficher. Lors d’un succès, on affiche Vous pouvez utiliser ce mot de passe pour la suite
et lors d’un échec, on affiche Mauvais mot de passe
.
Nous allons donc passer à l’analyse de la fonction sub.w0c__:_aae
.
> s sub.w0c__:_aae
> pdf
/ (fcn) sub.w0c__:_aae 114
| sub.w0c__:_aae (int arg1);
| ; arg int arg1 @ rdi
| ; CALL XREF from main (0x400b2a)
| 0x00400aae 4889fe mov rsi, rdi ; arg1
| 0x00400ab1 b800000000 mov eax, 0
| 0x00400ab6 48c7c1ffffff. mov rcx, -1
| 0x00400abd f2ae repne scasb al, byte [rdi]
| 0x00400abf b800000000 mov eax, 0
| 0x00400ac4 4883f9e0 cmp rcx, -0x20
| ,=< 0x00400ac8 7402 je 0x400acc
| | ; CODE XREF from sub.w0c__:_aae (0x400b1e)
| .--> 0x00400aca f3c3 ret
| :| ; CODE XREF from sub.w0c__:_aae (0x400ac8)
| :`-> 0x00400acc 0fb60e movzx ecx, byte [rsi]
| : 0x00400acf 84c9 test cl, cl
| :,=< 0x00400ad1 7446 je 0x400b19
| :| 0x00400ad3 488d15e68d08. lea rdx, qword str.w0c__: ; 0x4898c0 ; "w0c&]:\x0e;\rM*\x1f.\x1f-O(Q7z\x14v x\x0f!M!l\x11"
| :| 0x00400ada 488d7e01 lea rdi, qword [rsi + 1] ; r11
| :| 0x00400ade b801000000 mov eax, 1
| :| 0x00400ae3 be33000000 mov esi, 0x33 ; '3' ; 51
| :| 0x00400ae8 41b900000000 mov r9d, 0
| ,===< 0x00400aee eb16 jmp 0x400b06
| |:| ; CODE XREFS from sub.w0c__:_aae (0x400b0b, 0x400b15)
| ..----> 0x00400af0 410fb6c0 movzx eax, r8b
| ::|:| 0x00400af4 0fb632 movzx esi, byte [rdx]
| ::|:| 0x00400af7 0fb60f movzx ecx, byte [rdi]
| ::|:| 0x00400afa 4883c201 add rdx, 1
| ::|:| 0x00400afe 4883c701 add rdi, 1
| ::|:| 0x00400b02 84c9 test cl, cl
| ,======< 0x00400b04 7411 je 0x400b17
| |::|:| ; CODE XREF from sub.w0c__:_aae (0x400aee)
| |::`---> 0x00400b06 4589c8 mov r8d, r9d
| |:: :| 0x00400b09 85c0 test eax, eax
| |`=====< 0x00400b0b 74e3 je 0x400af0
| | : :| 0x00400b0d 31f1 xor ecx, esi
| | : :| 0x00400b0f 3a0a cmp cl, byte [rdx]
| | : :| 0x00400b11 410f94c0 sete r8b
| | `====< 0x00400b15 ebd9 jmp 0x400af0
| `------> 0x00400b17 f3c3 ret
| :| ; CODE XREF from sub.w0c__:_aae (0x400ad1)
| :`-> 0x00400b19 b801000000 mov eax, 1
\ `==< 0x00400b1e ebaa jmp 0x400aca
0x00400aae 4889fe mov rsi, rdi
0x00400ab1 b800000000 mov eax, 0
0x00400ab6 48c7c1ffffff. mov rcx, -1
0x00400abd f2ae repne scasb al, byte [rdi]
0x00400abf b800000000 mov eax, 0
0x00400ac4 4883f9e0 cmp rcx, -0x20
0x00400ac8 7402 je 0x400acc
0x00400aca f3c3 ret
Les premières instructions servent sûrement à gérer l’argument de la fonction (qui est notre mot de passe). Après cela, on compare rcx
à 0x20
. Si cette comparaison et vraie, alors on continue le l’exécution (on évite le ret
). Sinon on va sur le ret
qui va nous faire quitter la fonction avec un eax
à 0, ce qui va nous afficher Mauvais mot de passe
.
Pour faire simple, le cmp rcx, -0x20
va s’assurer que le argv[1]
fourni au programme va avoir une taille de 32 caractères.
argv[1] |
rcx |
-0x20 |
cmp rcx, -0x20 |
---|---|---|---|
AAAA |
0xfffffffffffffffa |
0xffffffffffffffdf |
NOT EQUAL |
AAAAAA |
0xfffffffffffffff8 |
0xffffffffffffffdf |
NOT EQUAL |
A *32 |
0xffffffffffffffdf |
0xffffffffffffffdf |
EQUAL |
Nous devons donc avoir un argv[1]
de taille 0x20, soit 32 caractères pour respecter cette condition.
> ood AAAA
> db 0x00400ac8
> dc
hit breakpoint at: 400ac8
> dr rip
0x00400ac8
> dr rip=0x00400acc
0x00400ac8 ->0x00400acc
Normalement, nous devons fournir un argument de taille 32 octets, mais nous allons juste donner ABCD
au programme, placer un breakpoint sur je 0x400acc
afin de modifier la valeur de rip
pour passer outre la condition des 32 caractères.
0x00400acc 0fb60e movzx ecx, byte [rsi]
0x00400acf 84c9 test cl, cl
0x00400ad1 7446 je 0x400b19
0x00400ad3 488d15e68d08. lea rdx, qword str.w0c__: ; 0x4898c0 ; "w0c&]:\x0e;\rM*\x1f.\x1f-O(Q7z\x14v x\x0f!M!l\x11"
0x00400ada 488d7e01 lea rdi, qword [rsi + 1]
0x00400ade b801000000 mov eax, 1
0x00400ae3 be33000000 mov esi, 0x33 ; '3' ; 51
0x00400ae8 41b900000000 mov r9d, 0
0x00400aee eb16 jmp 0x400b06
Dans ce bloc on peut remarquer que l’on va manipuler la chaîne w0c&]:\x0e;\rM*\x1f.\x1f-O(Q7z\x14v x\x0f!M!l\x11
.
0x00400af0 410fb6c0 movzx eax, r8b
0x00400af4 0fb632 movzx esi, byte [rdx]
0x00400af7 0fb60f movzx ecx, byte [rdi]
0x00400afa 4883c201 add rdx, 1
0x00400afe 4883c701 add rdi, 1
0x00400b02 84c9 test cl, cl
0x00400b04 7411 je 0x400b17
0x00400b06 4589c8 mov r8d, r9d
0x00400b09 85c0 test eax, eax
0x00400b0b 74e3 je 0x400af0
0x00400b0d 31f1 xor ecx, esi
0x00400b0f 3a0a cmp cl, byte [rdx]
0x00400b11 410f94c0 sete r8b
0x00400b15 ebd9 jmp 0x400af0
0x00400b17 f3c3 ret
0x00400b19 b801000000 mov eax, 1
0x00400b1e ebaa jmp 0x400aca
Dans la suite on observe une ligne intéressante xor ecx, esi
.
0x00400b0d 31f1 xor ecx, esi
0x00400b0f 3a0a cmp cl, byte [rdx]
On réalise un XOR puis un comparaison, c’est sûrement une partie importante du prgramme. Pour ce xor on utilise ecx
et esi
.
0x00400af4 0fb632 movzx esi, byte [rdx]
0x00400af7 0fb60f movzx ecx, byte [rdi]
0x00400afa 4883c201 add rdx, 1
0x00400afe 4883c701 add rdi, 1
Après pas mal de debug sur le programme sur les instructions importantes, on comprend comment fonctionne la vérification du mot de passe.
On peut retrouver ce dernier avec le script suivant :
#!/usr/bin/env python3
string = '3w0c&]:\x0e;\rM*\x1f.\x1f-O(Q7z\x14v x\x0f!M!l\x11'
for i in range(1, len(string)):
print(chr(ord(string[i-1]) ^ ord(string[i])), end='')
print()
# DGSE{g456@g5112bgyfMnbVXw.llM}
ZIP
$ unzip -d suite_files/ -P "DGSE{g456@g5112bgyfMnbVXw.llM}" suite.zip
Archive: suite.zip
inflating: suite_files/suite.txt
$ cat suite_files/suite.txt
Suite du challenge Richelieu :
ssh defi1.challengecybersec.fr -l defi1 -p 2222
mot de passe : DGSE{2f77c517b06f1cd1ce864f79f41f25ca8874413c8c1204f7ec9c6728c87f270a}
C’est ici que le challenge se termine pour moi.