Lieu :
- Insomni'Hack 2010
- Genève, CH
Fichier de départ :
- Nom : 1.pdf
- Type : PDF document, version 1.6
- MD5 : 66791d1068b963f04845cf6474b92f4e
- Taille : 8,6 Mo !
Outils :
- Didier Stevens' Tools
- Origami
- QPDF
Objectifs :
- s'amuser ;-)
- obtenir pour chaque niveau un token à remettre aux organisateurs
Indices :
- Level 1 : la taille, ca compte
- Level 2 : il faut savoir recoller les morceaux
- Level 3 : ???
Plus bas, les solutions ...............
Level 1
Le fichier ne s'ouvre pas avec evince / xpdf / ...
Une analyse manuelle ou via pdfid.py montre des données (au format Base64) à la fin du fichier :
$> python pdfid.py -e 1.pdf
[...]
After last %%EOF 2518
[...]
=> Offset : 0x894175 == 8995189
Récuperation des données Base64 :
$> dd bs=1 skip=8995189 if=1.pdf of=data.b64
OU $> tail -n 42 1.pdf > data.b64
$> base64 -d data.b64 > data.bin
$> file data.bin
=> PDF document, version 1.4
$> mv data.bin data.pdf
=> Le PDF obtenu (data.pdf) contient en texte non compressé le mot de passe du level 1 (spk!sal10)
Suppression du footer Base64 :
$> dd bs=1 count=8995189 if=1.pdf of=orig.pdf
=> Obtention d'une doc Adobe (format PDF 1.7)
Level 2
Le fichier data.pdf contient un autre fichier (3.pdf) :
[...]
/embeddedFiles
[...]
/F (3.pdf )
[...]
Le contenu de ce fichier est encodé :
[...]
/Filter /FlateDecode
[...]
Obtention du password (volkl+sf%) contenu dans le fichier :
- Normalisation QPDF :
$> qpdf --qdf data.pdf normalized.pdf
=> Décodage des streams, le passwd apparait alors en clair dans le fichier normalized.pdf
=> NB : Résultat n'apparait pas si normalized.pdf est ouvert dans un reader !
- Dump via PDF-Parser :
Le contenu du fichier est stocké dans le 4ieme objet : "-o 4"
-o OBJECT id of indirect object to select (version independent)
Le stream est codé avec /FlateDecode : "-f"
-f pass stream object through filters
On récupère le contenu (décodé) du stream
$> pdf-parser.py -f -o 4 data.pdf | grep '%PDF' > 3a.pdf
On remplace les \x.., \r, \n ...
$> echo `cat 3a.pdf` > 3b.pdf
=> Le fichier 3b.pdf peut alors etre ouvert avec n'importe quel reader
=> Ce fichier peut par la suite être utilisé pour le 3ième niveau
- Origami :
Avec la "unfinished GUI" walker.rb :
Lancer le logiciel : $> ./origami-1.0.0-beta1/sources/walker/walker.rb &
Ouvrir le fichier data.pdf et naviguer jusqu'à l'entrée "Stream"
Le panneau en bas à gauche affiche la version décodée, le passwd apparait en clair
En scriptant l'analyse :
$> ruby ./level2.rb > 3b.pdf
# Open and parse the file
pdf = PDF.read('data.pdf', :verbosity => Parser::VERBOSE_QUIET)
# Return an array of objects containing the string 'WD'
# This string is found only in the binary blob describing '3.pdf'
objs = pdf.grep('WD')
objs.each do |obj|
# Print the stream
print obj.data
end
Level 3
Le fichier 3b.pdf contient deux streams encodées :
/Filter /LZWDecode (objet 7)
/Filter /PlatDecode (objet 10)
Décompression de l'objet 7 (LZWDecode) :
$> pdf-parser.py -f -o 7 3b.pdf
=> "hervearaison$maiscenestpaslemotdepasse..."
Décompression de l'objet 10 (PlatDecode) :
$> pdf-parser.py -f -o 10 3b.pdf
=> "Unsupported filter: ['/PlatDecode']"
Décompression de l'objet 10 (FlateDecode) :
$> pdf-parser.py -f -o 10 3b.pdf
=> "FlateDecode decompress failed"
Décompression de l'objet 10 (LZWDecode) :
$> pdf-parser.py -f -o 10 3b.pdf
=> "LZWDecode decompress failed"
Vu que les deux streams commencent par "80 10 8a 80" et que la 1ère est bien décodée en LZW, on insiste.
Ouverture avec walker.rb, avec encodage de l'objet 10 positionné à "LZWDecode" :
=> lecture de la deuxième stream OK : password = "%opi598*kljp"