B-Sides London Challenge was supposed to be a one time thing. However when the peepdf’s author creates a challenge it’s hard to say no to that!
Don’t try to be like me and learn things the hard way. Trust me on this one and take my advice beforehand. Before you even consider reading this walkthrough update your tool! Otherwise you will spend a long time trying to solve it.
Lesson learned from the #BHUSA Arsenal @peepdf challenge. Before you start, first update your tools! Thanks @EternalTodo it was great fun!
Output of peepdf provides information about various suspicious elements which include EmbeddedFiles. Interestingly enough it also contains JS and AA elements which are often starting point of any analysis. Reviewing EmbeddedFiles reveals additional metadata about the file:
We see a very handy feature of peepdf which is information about known CVEs. Instead of jumping to the first JS object, let’s review the relationship between the objects to better understand structure of the file:
It seems that object 5 uses annotations and as we know this pdf likely contains a known getAnnots vulnerability it’s definitely worth looking at:
123456789
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)));
The above code checks the version of the software (the reason being is getAnnots vulnerability). If version is greater than 10 it closes the document. If not the following function peepdf(r(a,x.d(this.info.author))); is executed.
Let’s check the object containing getAnnots vulnerability:
1234567891011
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);
This function takes the annotation’s subject, splits it and performs decoding.
If you take a closer look at tree output once again one of the /Annot objects contains additional stream:
remnux@remnux:~/Desktop/PeePdf$ more decode.js
//app.doc.syncAnnotScan();
var shellcode='76x161x172x120x178x120x13dx120x17bx10ax109x12fx12fx168x174x174x170x13ax12fx12fx177x177x177x12ex177x165x162x174x16fx16fx16cx16bx169x174x12ex169x16ex166x16fx12fx10ax109
x16bx13ax120x122x152x153x154x155x156x157x158x159x15ax161x162x163x164x141x142x143x144x145x146x147x148x149x16ex16fx170x171x172x173x174x175x176x177x178x179x17ax130x131x132x133x134x14ax
14bx14cx14dx14ex14fx150x151x165x166x167x168x169x16ax16bx16cx16dx135x136x137x138x139x12bx12fx13dx122x12cx10ax109x164x13ax120x166x175x16ex163x174x169x16fx16ex120x128x169x16ex170x175x1
74x129x120x17bx10ax109x109x176x161x172x120x16bx16bx120x13dx120x122x122x13bx10ax109x109x176x161x172x120x163x131x12cx120x163x132x12cx120x163x133x12cx120x163x134x13bx10ax109x109x176x16
1x172x120x165x131x12cx120x165x132x12cx120x165x133x12cx120x165x134x13bx10ax109x109x176x161x172x120x169x120x13dx120x130x13bx10ax109x109x169x16ex170x175x174x120x13dx120x169x16ex170x175
x174x12ex172x165x170x16cx161x163x165x128x12fx15bx15ex141x12dx15ax161x12dx17ax130x12dx139x15cx12bx15cx12fx15cx13dx15dx12fx167x12cx120x122x122x129x13bx10ax109x109x177x168x169x16cx165x
120x128x169x120x13cx120x169x16ex170x175x174x12ex16cx165x16ex167x174x168x129x120x17bx10ax109x109x109x165x131x120x13dx120x174x168x169x173x12ex16bx12ex169x16ex164x165x178x14fx166x128x1
69x16ex170x175x174x12ex163x168x161x172x141x174x128x169x12bx12bx129x129x13bx10ax109x109x109x165x132x120x13dx120x174x168x169x173x12ex16bx12ex169x16ex164x165x178x14fx166x128x169x16ex17
0x175x174x12ex163x168x161x172x141x174x128x169x12bx12bx129x129x13bx10ax109x109x109x165x133x120x13dx120x174x168x169x173x12ex16bx12ex169x16ex164x165x178x14fx166x128x169x16ex170x175x174
x12ex163x168x161x172x141x174x128x169x12bx12bx129x129x13bx10ax109x109x109x165x134x120x13dx120x174x168x169x173x12ex16bx12ex169x16ex164x165x178x14fx166x128x169x16ex170x175x174x12ex163x
168x161x172x141x174x128x169x12bx12bx129x129x13bx10ax109x109x109x163x131x120x13dx120x128x165x131x120x13cx13cx120x132x129x120x17cx120x128x165x132x120x13ex13ex120x134x129x13bx10ax109x1
09x109x163x132x120x13dx120x128x128x165x132x120x126x120x131x135x129x120x13cx13cx120x134x129x120x17cx120x128x165x133x120x13ex13ex120x132x129x13bx10ax109x109x109x163x133x120x13dx120x12
8x128x165x133x120x126x120x133x129x120x13cx13cx120x136x129x120x17cx120x165x134x13bx10ax109x109x109x16bx16bx120x13dx120x16bx16bx120x12bx120x153x174x172x169x16ex167x12ex166x172x16fx16d
x143x168x161x172x143x16fx164x165x128x163x131x129x13bx10ax109x109x109x169x166x120x128x165x133x120x121x13dx120x136x134x129x120x17bx16bx16bx120x13dx120x16bx16bx120x12bx120x153x174x172x
169x16ex167x12ex166x172x16fx16dx143x168x161x172x143x16fx164x165x128x163x132x129x13bx17dx10ax109x109x109x169x166x120x128x165x134x120x121x13dx120x136x134x129x120x17bx16bx16bx120x13dx1
20x16bx16bx120x12bx120x153x174x172x169x16ex167x12ex166x172x16fx16dx143x168x161x172x143x16fx164x165x128x163x133x129x13bx17dx10ax109x109x17dx10ax109x109x172x165x174x175x172x16ex120x16
bx16bx13bx10ax109x17dx10ax17dx13b'
var z = "";
//var an = app.doc.getAnnots({
// nPage: 0
//});
//var s = an[this.numPages].subject;
var buf = shellcode.split(/x1/);
for (var n = 0; n < buf.length; n++) {
z += String.fromCharCode("0" + "x" + buf[n]);
}
//peepdf(z);
print(z)
After executing the code following output appears:
123456789101112131415161718192021222324
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;
}
};
Keep in mind that after opening the original file the following code peepdf(r(a,x.d(this.info.author))); will be executed. We now have found the definition of associative array x which contains function d and takes this.info.author as a parameter.
this.info.author can be found by reviewing the Info object:
1234567
PPDF> object 12
<< /CreationDate D:19820925000000
/Author 11 0 R
/Producer Peepdf Library X
/ModDate D:20150805153000
/Creator Scribus 1.3.3.14 >>
Third /JavaScript element contains definition of the function r() which seems to be a decoding function that accepts key and message as parameters:
123456789
PPDF> js_beautify 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
}
It looks like we finally have all the the pieces of the puzzle to execute the code in SpiderMonkey:
function d(input) {
var k = "RSTUVWXYZabcdABCDEFGHInopqrstuvwxyz01234JKLMNOPQefghijklm56789+/="
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 = k.indexOf(input.charAt(i++));
e2 = k.indexOf(input.charAt(i++));
e3 = k.indexOf(input.charAt(i++));
e4 = 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 author="aeJrtFmIbya6v42tZEOXZgxaCyxiAUeIbxx5aTxaBymjbndwWRHtAU9rBy8/qhEtAxZctFmIbyamrl2vSDZtCFyRsTt/Zz2qAiNMBFenZyZiZUewcVaGBTOrqyDjZheusWqZdzencI87Ag1GADDcwgJwB0pibGqaZ1dGCfisbEaxugDHT2NjwhmCcSi/aTinaDdSZ3dGZSVjrF2CCx8udzxZqjmyaz2AweJVAU8Bqxe5VhSaCD5Ftfiwbet+Zo2+BDJVCFxHbEamrhKeZxfFtfDIBjt9bTiuBS9atXi0ZDa6ZhfBAS1vAXissxt/Zz2qAiNtwUmFaeHPq4xur1abcXEScetLrGyEAS1tvGpqXymPbheYthNAAUivbWtqchyECDmXAzyppyDoAUmYtg1uaniUZDa6bGfpAHNtC3iabf1+qhxuZxpaCFWrBDHhdhfZZHNtC3ibbfZLZh8udS9ZAU1wCS17blEaCx8YtF1IB2t5bUDuaDEZAzxBsyxiZ4tuXfmsanitZDI6dhWptE8RZgxwsyH/ATiuZempC08BCIq6RUpuAxEZAzass1fhqFDHrxJpZndsZyZJdlWXd08SaFVwuWHbZ0fAADjZVzHsZyN/bG5ptitcYUmuuWHGZo2VCy5ZdU8wsypPdhfGADZXA3imZyp5cY2jdS9ZBhxaB2t5bUEptE8YtFRUsxtJZzKpCf8aaFDIZWt7bGiuASdpCFeGZWtgbTmubRDutGmIZxV/Zl2HaHIZWTeaafZJq4xwcVaGdUibpSpkZzOavERcSFDBZyaxqD2pASNdAki5aypkbhfGAx5bwFmlCESxqDjIdRHUZ3itZDI6AhItbRZXA3fss1jhqFDHrypACGmwAEpLAlutCDmranHScFdhdhIpri1mATxbbyW6SUWtCDtACgJwsWN5TzKrri18ZhErcfR7c0tttV1IvYpY";
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
}
//peepdf(r(a,x.d(this.info.author)));
print(r('QkhQMzNwZGY=',d(author)));
This results in the following output:
1234567891011121314151617181920212223242526272829
js-beautify final.js
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!!"
});
Solution to the challenge consist of the annotation /Subj and /Producer which are passed to calc() function.
Annotation subject can be found in the other /Annot object:
12345678910111213
PPDF> object 20
<< /Rect [ 100 180 300 210 ]
/Type /Annot
/Subtype /Text
/Subj Black Hat US Arsenal 2015 - peepdf
/Name /Comment >>
<< /CreationDate D:19820925000000
/Author 11 0 R
/Producer Peepdf Library X
/ModDate D:20150805153000
/Creator Scribus 1.3.3.14 >>
Adding /Subj and /Producer gives the following string “Black Hat US Arsenal 2015 - peepdfPeepdf Library X” which is passed to function calc(). Function calc() is a JavaScriptimplementation of MD5 and can be found in object 24.
MD5 of the string is the solution to the challenge: 5af109e5f2e7770bf7f88bfde448d2fe
That was a pretty cool challenge! Be sure to check out Jose’s walkthrough: