Black Hat Arsenal peepdf Challenge 2015 writeup

At the beginning of August I saw a link on twitter by Jose Miguel Esparza, the author of peepdf tool, about a challenge he created for Black Hat Arsenal conference in USA. So reading the blog post I decided to play with the challenge and now here’s my writeup solution. I hope that you like it.

PS: I suggest¬†you to spend a bit of your time to try to solve the challenge without reading the solution. It’s a very fun this challenge… Then come back here and read my solution. ūüėČ

For more information about the context, you can read the blog post by Jose in his blog here.

You can download the pdf challenge here or directly from the blog post linked above.

The pdf is not malicious so you can open with Adobe Reader on your PC without using a VM. The version of Adobe Reader we have to use is earlier than¬†XI version, e.g. X is fine,¬†otherwise you can’t understand and play the challenge. ūüėõ

You can download an old version of Adobe Reader from this site: Adobe Reader X

If all these things are clear, let’s go to solve the challenge!

Opening the pdf we can see only a page with the following image:

BHUSA

Another thing we can observe is that there is an attachment inside this pdf named peepdf.pdf. So let’s save and¬†open it. This new¬†pdf asks you to insert a password through a javascript form:

pdf_1

It’s a kind of a new generation crackme, instead of an executable we have to break a PDF, cool, isn’t it?

To extract the embedded pdf you can also use peepdf tool which we’ll see later. For example when you have identified the object that contains our pdf, into the interactive console simply use the command:

PPDF> object 13 > peepdf.pdf

It’s clear that we must investigate and analyze the pdf extracted to understand what is the password, that is the flag!

At this point from peepdf github repo¬†we do a¬†git clone to download peepdf and open the pdf in it. I recommend using the last version¬†for a reason that you’ll understand in a moment. ūüėČ We’ll see the following info:

info

First let’s check the four objects which contain Javascript code:
Object 5

PPDF> object 5

var version = app.viewerVersion.toString().split(".")[0];
if (version > 10){
	app.alert({cTitle:"Peepdf Challenge",cMsg:"You should try with an older version of Adobe Reader ;)"});
	this.closeDoc(true);
}
else{
	peepdf(r(a,x.d(this.info.author)));
}

Object 16

PPDF> object 16

app.doc.syncAnnotScan();
var z="";
var an = app.doc.getAnnots({nPage:0});
var s = an[this.numPages].subject;
var buf = s.split(/x1/);
for (var n = 0; n < buf.length; n++) {
	z += String.fromCharCode("0" + "x" + buf[n]);
}
peepdf(z);

Object 19

PPDF> object 19

function r(key, data){var kk = "";for(var i=0;i<data.length;i++){kk += String.fromCharCode(data.charCodeAt(i) ^ key.charCodeAt(i%key.length));}return kk}

Object 24

PPDF> object 24
<< /Length 4859 >>
stream
/*
 * Copyright (C) Paul Johnston 1999 - 2000.
 * Updated by Greg Holt 2000 - 2001.
 * See http://pajhome.org.uk/site/legal.html for details.
 */
var hex_chr = "0123456789abcdef";
function rhex(num)
{
  str = "";
  for(j = 0; j <= 3; j++)
    str += hex_chr.charAt((num >> (j * 8 + 4)) & 0x0F) +
           hex_chr.charAt((num >> (j * 8)) & 0x0F);
  return str;
}

function str2blks(str)
{
  nblk = ((str.length + 8) >> 6) + 1;
  blks = new Array(nblk * 16);
  for(i = 0; i < nblk * 16; i++) blks[i] = 0;
  for(i = 0; i < str.length; i++)
    blks[i >> 2] |= str.charCodeAt(i) << ((i % 4) * 8);
  blks[i >> 2] |= 0x80 << ((i % 4) * 8);
  blks[nblk * 16 - 2] = str.length * 8;
  return blks;
}

function add(x, y)
{
  var lsw = (x & 0xFFFF) + (y & 0xFFFF);
  var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
  return (msw << 16) | (lsw & 0xFFFF);
}

function rol(num, cnt)
{
  return (num << cnt) | (num >>> (32 - cnt));
}

function cmn(q, a, b, x, s, t)
{
  return add(rol(add(add(a, q), add(x, t)), s), b);
}
function ff(a, b, c, d, x, s, t)
{
  return cmn((b & c) | ((~b) & d), a, b, x, s, t);
}
function gg(a, b, c, d, x, s, t)
{
  return cmn((b & d) | (c & (~d)), a, b, x, s, t);
}
function hh(a, b, c, d, x, s, t)
{
  return cmn(b ^ c ^ d, a, b, x, s, t);
}
function ii(a, b, c, d, x, s, t)
{
  return cmn(c ^ (b | (~d)), a, b, x, s, t);
}

function calc(str)
{
  x = str2blks(str);
  a =  1732584193;
  b = -271733879;
  c = -1732584194;
  d =  271733878;

  for(i = 0; i < x.length; i += 16)
  {
    olda = a;
    oldb = b;
    oldc = c;
    oldd = d;

    a = ff(a, b, c, d, x[i+ 0], 7 , -680876936);
    d = ff(d, a, b, c, x[i+ 1], 12, -389564586);
    c = ff(c, d, a, b, x[i+ 2], 17,  606105819);
    b = ff(b, c, d, a, x[i+ 3], 22, -1044525330);
    a = ff(a, b, c, d, x[i+ 4], 7 , -176418897);
    d = ff(d, a, b, c, x[i+ 5], 12,  1200080426);
    c = ff(c, d, a, b, x[i+ 6], 17, -1473231341);
    b = ff(b, c, d, a, x[i+ 7], 22, -45705983);
    a = ff(a, b, c, d, x[i+ 8], 7 ,  1770035416);
    d = ff(d, a, b, c, x[i+ 9], 12, -1958414417);
    c = ff(c, d, a, b, x[i+10], 17, -42063);
    b = ff(b, c, d, a, x[i+11], 22, -1990404162);
    a = ff(a, b, c, d, x[i+12], 7 ,  1804603682);
    d = ff(d, a, b, c, x[i+13], 12, -40341101);
    c = ff(c, d, a, b, x[i+14], 17, -1502002290);
    b = ff(b, c, d, a, x[i+15], 22,  1236535329);    

    a = gg(a, b, c, d, x[i+ 1], 5 , -165796510);
    d = gg(d, a, b, c, x[i+ 6], 9 , -1069501632);
    c = gg(c, d, a, b, x[i+11], 14,  643717713);
    b = gg(b, c, d, a, x[i+ 0], 20, -373897302);
    a = gg(a, b, c, d, x[i+ 5], 5 , -701558691);
    d = gg(d, a, b, c, x[i+10], 9 ,  38016083);
    c = gg(c, d, a, b, x[i+15], 14, -660478335);
    b = gg(b, c, d, a, x[i+ 4], 20, -405537848);
    a = gg(a, b, c, d, x[i+ 9], 5 ,  568446438);
    d = gg(d, a, b, c, x[i+14], 9 , -1019803690);
    c = gg(c, d, a, b, x[i+ 3], 14, -187363961);
    b = gg(b, c, d, a, x[i+ 8], 20,  1163531501);
    a = gg(a, b, c, d, x[i+13], 5 , -1444681467);
    d = gg(d, a, b, c, x[i+ 2], 9 , -51403784);
    c = gg(c, d, a, b, x[i+ 7], 14,  1735328473);
    b = gg(b, c, d, a, x[i+12], 20, -1926607734);
    
    a = hh(a, b, c, d, x[i+ 5], 4 , -378558);
    d = hh(d, a, b, c, x[i+ 8], 11, -2022574463);
    c = hh(c, d, a, b, x[i+11], 16,  1839030562);
    b = hh(b, c, d, a, x[i+14], 23, -35309556);
    a = hh(a, b, c, d, x[i+ 1], 4 , -1530992060);
    d = hh(d, a, b, c, x[i+ 4], 11,  1272893353);
    c = hh(c, d, a, b, x[i+ 7], 16, -155497632);
    b = hh(b, c, d, a, x[i+10], 23, -1094730640);
    a = hh(a, b, c, d, x[i+13], 4 ,  681279174);
    d = hh(d, a, b, c, x[i+ 0], 11, -358537222);
    c = hh(c, d, a, b, x[i+ 3], 16, -722521979);
    b = hh(b, c, d, a, x[i+ 6], 23,  76029189);
    a = hh(a, b, c, d, x[i+ 9], 4 , -640364487);
    d = hh(d, a, b, c, x[i+12], 11, -421815835);
    c = hh(c, d, a, b, x[i+15], 16,  530742520);
    b = hh(b, c, d, a, x[i+ 2], 23, -995338651);

    a = ii(a, b, c, d, x[i+ 0], 6 , -198630844);
    d = ii(d, a, b, c, x[i+ 7], 10,  1126891415);
    c = ii(c, d, a, b, x[i+14], 15, -1416354905);
    b = ii(b, c, d, a, x[i+ 5], 21, -57434055);
    a = ii(a, b, c, d, x[i+12], 6 ,  1700485571);
    d = ii(d, a, b, c, x[i+ 3], 10, -1894986606);
    c = ii(c, d, a, b, x[i+10], 15, -1051523);
    b = ii(b, c, d, a, x[i+ 1], 21, -2054922799);
    a = ii(a, b, c, d, x[i+ 8], 6 ,  1873313359);
    d = ii(d, a, b, c, x[i+15], 10, -30611744);
    c = ii(c, d, a, b, x[i+ 6], 15, -1560198380);
    b = ii(b, c, d, a, x[i+13], 21,  1309151649);
    a = ii(a, b, c, d, x[i+ 4], 6 , -145523070);
    d = ii(d, a, b, c, x[i+11], 10, -1120210379);
    c = ii(c, d, a, b, x[i+ 2], 15,  718787259);
    b = ii(b, c, d, a, x[i+ 9], 21, -343485551);

    a = add(a, olda);
    b = add(b, oldb);
    c = add(c, oldc);
    d = add(d, oldd);
  }
  return rhex(a) + rhex(b) + rhex(c) + rhex(d);
}
endstream

The object 5 contain JS code to check if the Adobe Reader version is higher than 10. If this is true the string “You should try with an older version of Adobe Reader ūüėČ” appears and the pdf is closed, else run peepdf function (which we’ll understand what it does later).

The object 16 contain the getAnnots() function (CVE-2009-1492). This function returns an array of Annotation objects and through subject method extracts from this array annotations info. But where is the annotation array returned by getAnnots()? It’s simple, in the pdf file format there are many tags, one of these is named /Annots so let’s go to investigate the /Annots tag to discover what object contains this info. To quickly find /Annots tag we can use the ‘search‘ command like this:

PPDF> search /Annots
[3]

OK, jump to object 3:

PDF> object 3
<< /Contents 4 0 R
 /Parent 2 0 R
 /Resources << /Font << /F1 << /Type /Font
 /BaseFont /Arial
 /Subtype /Type1 >> >> >>
 /AA << /O << /S /JavaScript
 /JS 5 0 R >> >>
 /Annots [ 20 0 R 21 0 R ]
 /Type /Page >>

Here it¬†is the tag we were looking for, which refers to 2 specific objects: 20 and 21. What is the right object? It’s simple because if you check the js code you can see:

var an = app.doc.getAnnots({nPage:0});
var s = an[this.numPages].subject;

that translated to human language is: take the second object (because numPages = 1) of annots array and read the Subject field. So the correct object is 21!

PPDF> object 21
<< /Rect [ 100 180 300 210 ]
 /Type /Annot
 /Subtype /Text
 /Subj 22 0 R
 /Name /Comment >>

The content of subject field is into object 22:

PPDF> object 22
<< /Length 2914 >>
 stream
 76x161x172x120x178x120x13dx120x17bx10ax109x12fx12fx168x174x174x170x13ax12fx12fx177x177x177x12ex177x165x162x174x16fx16fx16cx16bx169x174x12ex169x16ex166x16fx12fx10ax109x16bx13ax120x122x152x153x154x155x156x157x158x159x15ax161x162x163x164x141x142x143x144x145x146x147x148x149x16ex16fx170x171x172x173x174x175x176x177x178x179x17ax130x131x132x133x134x14ax14bx14cx14dx14ex14fx150x151x165x166x167x168x169x16ax16bx16cx16dx135x136x137x138x139x12bx12fx13dx122x12cx10ax109x164x13ax120x166x175x16ex163x174x169x16fx16ex120x128x169x16ex170x175x174x129x120x17bx10ax109x109x176x161x172x120x16bx16bx120x13dx120x122x122x13bx10ax109x109x176x161x172x120x163x131x12cx120x163x132x12cx120x163x133x12cx120x163x134x13bx10ax109x109x176x161x172x120x165x131x12cx120x165x132x12cx120x165x133x12cx120x165x134x13bx10ax109x109x176x161x172x120x169x120x13dx120x130x13bx10ax109x109x169x16ex170x175x174x120x13dx120x169x16ex170x175x174x12ex172x165x170x16cx161x163x165x128x12fx15bx15ex141x12dx15ax161x12dx17ax130x12dx139x15cx12bx15cx12fx15cx13dx15dx12fx167x12cx120x122x122x129x13bx10ax109x109x177x168x169x16cx165x120x128x169x120x13cx120x169x16ex170x175x174x12ex16cx165x16ex167x174x168x129x120x17bx10ax109x109x109x165x131x120x13dx120x174x168x169x173x12ex16bx12ex169x16ex164x165x178x14fx166x128x169x16ex170x175x174x12ex163x168x161x172x141x174x128x169x12bx12bx129x129x13bx10ax109x109x109x165x132x120x13dx120x174x168x169x173x12ex16bx12ex169x16ex164x165x178x14fx166x128x169x16ex170x175x174x12ex163x168x161x172x141x174x128x169x12bx12bx129x129x13bx10ax109x109x109x165x133x120x13dx120x174x168x169x173x12ex16bx12ex169x16ex164x165x178x14fx166x128x169x16ex170x175x174x12ex163x168x161x172x141x174x128x169x12bx12bx129x129x13bx10ax109x109x109x165x134x120x13dx120x174x168x169x173x12ex16bx12ex169x16ex164x165x178x14fx166x128x169x16ex170x175x174x12ex163x168x161x172x141x174x128x169x12bx12bx129x129x13bx10ax109x109x109x163x131x120x13dx120x128x165x131x120x13cx13cx120x132x129x120x17cx120x128x165x132x120x13ex13ex120x134x129x13bx10ax109x109x109x163x132x120x13dx120x128x128x165x132x120x126x120x131x135x129x120x13cx13cx120x134x129x120x17cx120x128x165x133x120x13ex13ex120x132x129x13bx10ax109x109x109x163x133x120x13dx120x128x128x165x133x120x126x120x133x129x120x13cx13cx120x136x129x120x17cx120x165x134x13bx10ax109x109x109x16bx16bx120x13dx120x16bx16bx120x12bx120x153x174x172x169x16ex167x12ex166x172x16fx16dx143x168x161x172x143x16fx164x165x128x163x131x129x13bx10ax109x109x109x169x166x120x128x165x133x120x121x13dx120x136x134x129x120x17bx16bx16bx120x13dx120x16bx16bx120x12bx120x153x174x172x169x16ex167x12ex166x172x16fx16dx143x168x161x172x143x16fx164x165x128x163x132x129x13bx17dx10ax109x109x109x169x166x120x128x165x134x120x121x13dx120x136x134x129x120x17bx16bx16bx120x13dx120x16bx16bx120x12bx120x153x174x172x169x16ex167x12ex166x172x16fx16dx143x168x161x172x143x16fx164x165x128x163x133x129x13bx17dx10ax109x109x17dx10ax109x109x172x165x174x175x172x16ex120x16bx16bx13bx10ax109x17dx10ax17dx13b
 endstream

Now that we¬†know the content of buf var it’s straightforward what the js does. It takes the content of the object 22, removes “x1” char and with the fromCharCode function converts the hex value in the corresponding ASCII character. To accomplish to this task you have different options: online tool, Notepad++ and Converter tool by Kahu Security, or, like I did, directly in peepdf.

PPDF> stream 22 $> to_replace
PPDF> replace variable to_replace x1 ''
The string has been replaced correctly

and at last with the following command we obtain the final value of the z var:

PPDF> decode variable to_replace ahx
var x = {
 //http://www.webtoolkit.info/
 k: "RSTUVWXYZabcdABCDEFGHInopqrstuvwxyz01234JKLMNOPQefghijklm56789+/=",
 d: function (input) {
 var kk = "";
 var c1, c2, c3, c4;
 var e1, e2, e3, e4;
 var i = 0;
 input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
 while (i < input.length) {
 e1 = this.k.indexOf(input.charAt(i++));
 e2 = this.k.indexOf(input.charAt(i++));
 e3 = this.k.indexOf(input.charAt(i++));
 e4 = this.k.indexOf(input.charAt(i++));
 c1 = (e1 << 2) | (e2 >> 4);
 c2 = ((e2 & 15) << 4) | (e3 >> 2);
 c3 = ((e3 & 3) << 6) | e4;
 kk = kk + String.fromCharCode(c1);
 if (e3 != 64) {kk = kk + String.fromCharCode(c2);}
 if (e4 != 64) {kk = kk + String.fromCharCode(c3);}
 }
 return kk;
 }
 };

We get a js code! ūüėČ

The object 19 contains the r function definition used by js code contained in object 5.

The object 24 contains js code which executes the code that checks the password typed in.

That said¬†let’s¬†come back to object 5 to understand the function inside the¬†else statement.

The function r takes 2 argument: a (the key) and x.d(this.info.author) (the data). First we must search the data block to be decrypted by r function. Where to find the data block is very simple because we must find the object which contain the pdf info, that is /Author tag. To do that just read the info at the beginning of the post and we can see a row like this: Info: 12. So jump to object 12:

PPDF> object 12
<< /CreationDate D:19820925000000
 /Author 11 0 R
 /Producer Peepdf Library X
 /ModDate D:20150805153000
 /Creator Scribus 1.3.3.14 >>

then

PPDF> object 11
aeJrtFmIbya6v42tZEOXZgxaCyxiAUeIbxx5aTxaBymjbndwWRHtAU9rBy8/qhEtAxZctFmIbyamrl2vSDZtCFyRsTt/Zz2qAiNMBFenZyZiZUewcVaGBTOrqyDjZheusWqZdzencI87Ag1GADDcwgJwB0pibGqaZ1dGCfisbEaxugDHT2NjwhmCcSi/aTinaDdSZ3dGZSVjrF2CCx8udzxZqjmyaz2AweJVAU8Bqxe5VhSaCD5Ftfiwbet+Zo2+BDJVCFxHbEamrhKeZxfFtfDIBjt9bTiuBS9atXi0ZDa6ZhfBAS1vAXissxt/Zz2qAiNtwUmFaeHPq4xur1abcXEScetLrGyEAS1tvGpqXymPbheYthNAAUivbWtqchyECDmXAzyppyDoAUmYtg1uaniUZDa6bGfpAHNtC3iabf1+qhxuZxpaCFWrBDHhdhfZZHNtC3ibbfZLZh8udS9ZAU1wCS17blEaCx8YtF1IB2t5bUDuaDEZAzxBsyxiZ4tuXfmsanitZDI6dhWptE8RZgxwsyH/ATiuZempC08BCIq6RUpuAxEZAzass1fhqFDHrxJpZndsZyZJdlWXd08SaFVwuWHbZ0fAADjZVzHsZyN/bG5ptitcYUmuuWHGZo2VCy5ZdU8wsypPdhfGADZXA3imZyp5cY2jdS9ZBhxaB2t5bUEptE8YtFRUsxtJZzKpCf8aaFDIZWt7bGiuASdpCFeGZWtgbTmubRDutGmIZxV/Zl2HaHIZWTeaafZJq4xwcVaGdUibpSpkZzOavERcSFDBZyaxqD2pASNdAki5aypkbhfGAx5bwFmlCESxqDjIdRHUZ3itZDI6AhItbRZXA3fss1jhqFDHrypACGmwAEpLAlutCDmranHScFdhdhIpri1mATxbbyW6SUWtCDtACgJwsWN5TzKrri18ZhErcfR7c0tttV1IvYpY

Ok, this is the data block we were looking for. Now the only thing left to do is find the key (first parameter of r function) to decrypt this data block.

Always from¬†the information provided by the info command we can see that there are 2 decoding errors for objects 6 and 8. So let’s inspect the errors:

JFIF

These objects have two specified filters: /ASCIIHexDecode /DCTDecode. The first decodes data encoded in an ASCII hexadecimal representation, reproducing the original binary data, the second instead decompresses data encoded using a DCT (discrete cosine transform) technique based on the JPEG standard, reproducing image sample data that approximates the original data. Moreover we can notice the marker of jpeg file format. So the presence of DCTDecode filter plus the marker JFIF lead us to say that object 6 and object 8 are two jpeg images. Let’s extract these images with:

PPDF> stream 6 > stream6.jpg
PPDF> stream 8 > stream8.jpg

At this point, to check if there is something hidden into the jpg we can use a jpg steganography toolset like stegdetect, in particular djpeg tool, which stands for decompress jpeg.

djpeg_stream6

djpeg_stream8

Uhhh, we have found 2 interesting strings:

  • var a=”QkhQMzNwZGY=”;
  • peepdf=eval;

But how is this possible? About that you can read this interesting article in VirusBulletin which explains the possibility to hide javascript code into compressed stream with DCT JPEG standard compression.

So now we know also the key to decrypt the data block and understand that peepdf() function is nothing more than eval function.

We create a this point a js file with the code below:

data = "aeJrtFmIbya6v42tZEOXZgxaCyxiAUeIbxx5aTxaBymjbndwWRHtAU9rBy8/qhEtAxZctFmIbyamrl2vSDZtCFyRsTt/Zz2qAiNMBFenZyZiZUewcVaGBTOrqyDjZheusWqZdzencI87Ag1GADDcwgJwB0pibGqaZ1dGCfisbEaxugDHT2NjwhmCcSi/aTinaDdSZ3dGZSVjrF2CCx8udzxZqjmyaz2AweJVAU8Bqxe5VhSaCD5Ftfiwbet+Zo2+BDJVCFxHbEamrhKeZxfFtfDIBjt9bTiuBS9atXi0ZDa6ZhfBAS1vAXissxt/Zz2qAiNtwUmFaeHPq4xur1abcXEScetLrGyEAS1tvGpqXymPbheYthNAAUivbWtqchyECDmXAzyppyDoAUmYtg1uaniUZDa6bGfpAHNtC3iabf1+qhxuZxpaCFWrBDHhdhfZZHNtC3ibbfZLZh8udS9ZAU1wCS17blEaCx8YtF1IB2t5bUDuaDEZAzxBsyxiZ4tuXfmsanitZDI6dhWptE8RZgxwsyH/ATiuZempC08BCIq6RUpuAxEZAzass1fhqFDHrxJpZndsZyZJdlWXd08SaFVwuWHbZ0fAADjZVzHsZyN/bG5ptitcYUmuuWHGZo2VCy5ZdU8wsypPdhfGADZXA3imZyp5cY2jdS9ZBhxaB2t5bUEptE8YtFRUsxtJZzKpCf8aaFDIZWt7bGiuASdpCFeGZWtgbTmubRDutGmIZxV/Zl2HaHIZWTeaafZJq4xwcVaGdUibpSpkZzOavERcSFDBZyaxqD2pASNdAki5aypkbhfGAx5bwFmlCESxqDjIdRHUZ3itZDI6AhItbRZXA3fss1jhqFDHrypACGmwAEpLAlutCDmranHScFdhdhIpri1mATxbbyW6SUWtCDtACgJwsWN5TzKrri18ZhErcfR7c0tttV1IvYpY";

var x = {
	//http://www.webtoolkit.info/
	k: "RSTUVWXYZabcdABCDEFGHInopqrstuvwxyz01234JKLMNOPQefghijklm56789+/=",
	d: function (input) {
		var kk = "";
		var c1, c2, c3, c4;
		var e1, e2, e3, e4;
		var i = 0;
		input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
		while (i < input.length) {
			e1 = this.k.indexOf(input.charAt(i++));
			e2 = this.k.indexOf(input.charAt(i++));
			e3 = this.k.indexOf(input.charAt(i++));
			e4 = this.k.indexOf(input.charAt(i++));
			c1 = (e1 << 2) | (e2 >> 4);
			c2 = ((e2 & 15) << 4) | (e3 >> 2);
			c3 = ((e3 & 3) << 6) | e4;
			kk = kk + String.fromCharCode(c1);
			if (e3 != 64) {kk = kk + String.fromCharCode(c2);}
			if (e4 != 64) {kk = kk + String.fromCharCode(c3);}
		}
		return kk;
	}
};

var a="QkhQMzNwZGY="; //BHP33pdf base64 encoded

function r(key, data) {
    var kk = "";
    for (var i = 0; i < data.length; i++) {
        kk += String.fromCharCode(data.charCodeAt(i) ^ key.charCodeAt(i % key.length));
    }
    return kk;
}

decrypted = r(a,x.d(data));

Save it like decrypted.js for example, then:

PPDF> js_eval file /home/honeydrive/Desktop/decrypted.js
The Javascript code has been evaluated successfully!!
PPDF> js_vars
['x', 'a', 'r', 'data', 'decrypted', 'evalCode']
PPDF> js_vars decrypted
var code = app.response({cQuestion:"Enter the magic code", cTitle:"Peepdf Challenge"});if (code == calc(app.doc.getAnnots({nPage:0})[0].subject+this.info.producer)){app.alert({cTitle:"Peepdf Challenge",cMsg:"You got it!! You deserve a peepdf t-shirt!! ;)"});app.alert({cTitle:"Peepdf Challenge",cMsg:"But you need to send a small writeup to peepdf at eternal-todo dot com to get one. Just for the three best reports! Go go go! ;)"});app.alert({cTitle:"Peepdf Challenge",cMsg:"If you are attending Black Hat just come to my presentation and explain how you solved it. Easier!!"});app.alert({cTitle:"Peepdf Challenge",cMsg:"Thanks for playing!! :)"});}else{app.alert({cTitle:"Peepdf Challenge",cMsg:"Try again!!"});}

We obtained another javascript code!!! Store this code in a variable named mmm… decrypted :P, then beautify the code to be more readable:

PPDF> js_vars decrypted $> decrypted
PPDF> js_beautify variable decrypted
var code = app.response({
 cQuestion: "Enter the magic code",
 cTitle: "Peepdf Challenge"
 });
 if (code == calc(app.doc.getAnnots({
 nPage: 0
 })[0].subject + this.info.producer)) {
 app.alert({
 cTitle: "Peepdf Challenge",
 cMsg: "You got it!! You deserve a peepdf t-shirt!! ;)"
 });
 app.alert({
 cTitle: "Peepdf Challenge",
 cMsg: "But you need to send a small writeup to peepdf at eternal-todo dot com to get one. Just for the three best reports! Go go go! ;)"
 });
 app.alert({
 cTitle: "Peepdf Challenge",
 cMsg: "If you are attending Black Hat just come to my presentation and explain how you solved it. Easier!!"
 });
 app.alert({
 cTitle: "Peepdf Challenge",
 cMsg: "Thanks for playing!! :)"
 });
 } else {
 app.alert({
 cTitle: "Peepdf Challenge",
 cMsg: "Try again!!"
 });
 }

Finally this is the code that controls the password inserted. The password is computed¬†with calc() function which we have seen in the js into object 24. If code == calc(app.doc.getAnnots({nPage: 0})[0].subject + this.info.producer) then the password is correct and we’ll see “You got it!! You deserve a peepdf t-shirt!! ;)” messagebox, else we’ll see “Try again!!” that is the password is incorrect. Like we have done¬†before¬†with getAnnots() function, let’s do:

PPDF> object 3
<< /Contents 4 0 R
 /Parent 2 0 R
 /Resources << /Font << /F1 << /Type /Font
 /BaseFont /Arial
 /Subtype /Type1 >> >> >>
 /AA << /O << /S /JavaScript
 /JS 5 0 R >> >>
 /Annots [ 20 0 R 21 0 R ]
 /Type /Page >>
PPDF> object 20
<< /Rect [ 100 180 300 210 ]
 /Type /Annot
 /Subtype /Text
 /Subj Black Hat US Arsenal 2015 - peepdf
 /Name /Comment >>

The subject of first annots is object 20 and is “Black Hat US Arsenal 2015 – peepdf” string.

PPDF> search producer
[12]
PPDF> object 12
<< /CreationDate D:19820925000000
 /Author 11 0 R
 /Producer Peepdf Library X
 /ModDate D:20150805153000
 /Creator Scribus 1.3.3.14 >>

Here instead we obtained “Peepdf Library X” string. So let’s go to join these two strings and we get “Black Hat US Arsenal 2015 – peepdfPeepdf Library X“. This string is the right argument of the calc() function, to get the password we must insert in the form when asked. This time we can create a js file named password.js, for example, with the following code and repeat the operation shown on the previous steps.

var hex_chr = "0123456789abcdef";

function rhex(num) {
    str = "";
    for (j = 0; j <= 3; j++)
        str += hex_chr.charAt((num >> (j * 8 + 4)) & 0x0F) +
        hex_chr.charAt((num >> (j * 8)) & 0x0F);
    return str;
}

function str2blks(str) {
    nblk = ((str.length + 8) >> 6) + 1;
    blks = new Array(nblk * 16);
    for (i = 0; i < nblk * 16; i++) blks[i] = 0;
    for (i = 0; i < str.length; i++)
        blks[i >> 2] |= str.charCodeAt(i) << ((i % 4) * 8);
    blks[i >> 2] |= 0x80 << ((i % 4) * 8);
    blks[nblk * 16 - 2] = str.length * 8;
    return blks;
}

function add(x, y) {
    var lsw = (x & 0xFFFF) + (y & 0xFFFF);
    var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
    return (msw << 16) | (lsw & 0xFFFF);
}

function rol(num, cnt) {
    return (num << cnt) | (num >>> (32 - cnt));
}

function cmn(q, a, b, x, s, t) {
    return add(rol(add(add(a, q), add(x, t)), s), b);
}

function ff(a, b, c, d, x, s, t) {
    return cmn((b & c) | ((~b) & d), a, b, x, s, t);
}

function gg(a, b, c, d, x, s, t) {
    return cmn((b & d) | (c & (~d)), a, b, x, s, t);
}

function hh(a, b, c, d, x, s, t) {
    return cmn(b ^ c ^ d, a, b, x, s, t);
}

function ii(a, b, c, d, x, s, t) {
    return cmn(c ^ (b | (~d)), a, b, x, s, t);
}

function calc(str) {
    x = str2blks(str);
    a = 1732584193;
    b = -271733879;
    c = -1732584194;
    d = 271733878;

    for (i = 0; i < x.length; i += 16) {
        olda = a;
        oldb = b;
        oldc = c;
        oldd = d;

        a = ff(a, b, c, d, x[i + 0], 7, -680876936);
        d = ff(d, a, b, c, x[i + 1], 12, -389564586);
        c = ff(c, d, a, b, x[i + 2], 17, 606105819);
        b = ff(b, c, d, a, x[i + 3], 22, -1044525330);
        a = ff(a, b, c, d, x[i + 4], 7, -176418897);
        d = ff(d, a, b, c, x[i + 5], 12, 1200080426);
        c = ff(c, d, a, b, x[i + 6], 17, -1473231341);
        b = ff(b, c, d, a, x[i + 7], 22, -45705983);
        a = ff(a, b, c, d, x[i + 8], 7, 1770035416);
        d = ff(d, a, b, c, x[i + 9], 12, -1958414417);
        c = ff(c, d, a, b, x[i + 10], 17, -42063);
        b = ff(b, c, d, a, x[i + 11], 22, -1990404162);
        a = ff(a, b, c, d, x[i + 12], 7, 1804603682);
        d = ff(d, a, b, c, x[i + 13], 12, -40341101);
        c = ff(c, d, a, b, x[i + 14], 17, -1502002290);
        b = ff(b, c, d, a, x[i + 15], 22, 1236535329);

        a = gg(a, b, c, d, x[i + 1], 5, -165796510);
        d = gg(d, a, b, c, x[i + 6], 9, -1069501632);
        c = gg(c, d, a, b, x[i + 11], 14, 643717713);
        b = gg(b, c, d, a, x[i + 0], 20, -373897302);
        a = gg(a, b, c, d, x[i + 5], 5, -701558691);
        d = gg(d, a, b, c, x[i + 10], 9, 38016083);
        c = gg(c, d, a, b, x[i + 15], 14, -660478335);
        b = gg(b, c, d, a, x[i + 4], 20, -405537848);
        a = gg(a, b, c, d, x[i + 9], 5, 568446438);
        d = gg(d, a, b, c, x[i + 14], 9, -1019803690);
        c = gg(c, d, a, b, x[i + 3], 14, -187363961);
        b = gg(b, c, d, a, x[i + 8], 20, 1163531501);
        a = gg(a, b, c, d, x[i + 13], 5, -1444681467);
        d = gg(d, a, b, c, x[i + 2], 9, -51403784);
        c = gg(c, d, a, b, x[i + 7], 14, 1735328473);
        b = gg(b, c, d, a, x[i + 12], 20, -1926607734);

        a = hh(a, b, c, d, x[i + 5], 4, -378558);
        d = hh(d, a, b, c, x[i + 8], 11, -2022574463);
        c = hh(c, d, a, b, x[i + 11], 16, 1839030562);
        b = hh(b, c, d, a, x[i + 14], 23, -35309556);
        a = hh(a, b, c, d, x[i + 1], 4, -1530992060);
        d = hh(d, a, b, c, x[i + 4], 11, 1272893353);
        c = hh(c, d, a, b, x[i + 7], 16, -155497632);
        b = hh(b, c, d, a, x[i + 10], 23, -1094730640);
        a = hh(a, b, c, d, x[i + 13], 4, 681279174);
        d = hh(d, a, b, c, x[i + 0], 11, -358537222);
        c = hh(c, d, a, b, x[i + 3], 16, -722521979);
        b = hh(b, c, d, a, x[i + 6], 23, 76029189);
        a = hh(a, b, c, d, x[i + 9], 4, -640364487);
        d = hh(d, a, b, c, x[i + 12], 11, -421815835);
        c = hh(c, d, a, b, x[i + 15], 16, 530742520);
        b = hh(b, c, d, a, x[i + 2], 23, -995338651);

        a = ii(a, b, c, d, x[i + 0], 6, -198630844);
        d = ii(d, a, b, c, x[i + 7], 10, 1126891415);
        c = ii(c, d, a, b, x[i + 14], 15, -1416354905);
        b = ii(b, c, d, a, x[i + 5], 21, -57434055);
        a = ii(a, b, c, d, x[i + 12], 6, 1700485571);
        d = ii(d, a, b, c, x[i + 3], 10, -1894986606);
        c = ii(c, d, a, b, x[i + 10], 15, -1051523);
        b = ii(b, c, d, a, x[i + 1], 21, -2054922799);
        a = ii(a, b, c, d, x[i + 8], 6, 1873313359);
        d = ii(d, a, b, c, x[i + 15], 10, -30611744);
        c = ii(c, d, a, b, x[i + 6], 15, -1560198380);
        b = ii(b, c, d, a, x[i + 13], 21, 1309151649);
        a = ii(a, b, c, d, x[i + 4], 6, -145523070);
        d = ii(d, a, b, c, x[i + 11], 10, -1120210379);
        c = ii(c, d, a, b, x[i + 2], 15, 718787259);
        b = ii(b, c, d, a, x[i + 9], 21, -343485551);

        a = add(a, olda);
        b = add(b, oldb);
        c = add(c, oldc);
        d = add(d, oldd);
    }
    return rhex(a) + rhex(b) + rhex(c) + rhex(d);
}

password = calc("Black Hat US Arsenal 2015 - peepdfPeepdf Library X");

Then:

PPDF> js_eval file /home/honeydrive/Desktop/password.js
The Javascript code has been evaluated successfully!!
PPDF> js_vars password
5af109e5f2e7770bf7f88bfde448d2fe

Bingooo!!! This is the password (flag): 5af109e5f2e7770bf7f88bfde448d2fe

Let’s go to verify with Adobe Reader:

verification

Yes, it is! =)

A big thanks to Jose Miguel Esparza for this wonderful challenge and for developing a great tool such as peepdf.

Antelox

 

Comments

  1. calc() function is actually MD5 hash