Hack.lu CTF 2011 Write-up : Romulan Business Network

Thu 22 September 2011 by guillaume

Once again, we participated in the Capture-The-Flag event organized by the FluxFingers team at Hack.lu. Just like last year's CTF, the challenges were fun and original, and we finished up first after 48 hours of rude competition. The challenge here consisted in a little PDF crackme to solve. I will explain how I got through it using Origami and a standalone JavaScript VM like V8.

When opening the PDF in Adobe Reader, we are prompted to enter a password:

hacklu_ctf_romulan.png

The document is clearly making use of JavaScript to interact with the user, so let's start by dumping the scripts with pdfextract:

$ pdfextract --js Gwl4U5fqQZlJxEpPlgFL0hRNQrG4mmhg.pdf
$ ls Gwl4U5fqQZlJxEpPlgFL0hRNQrG4mmhg.dump/
script_69980994953860.js  script_69980994958360.js

This is the first script we get:

function ___________________a(arg1){

        ozjcYQp = false;
        ozjcYQp = "TS779Q81xBa2g7kP2qABK00";

        if(ozjcYQp)
        {
                HeGJR4 = YcQpjo+cYQpjo+zjocYQp;

        }


}

var ozFjcYQp=this;
var okzFfjcYQp=info;
var YcQpjo=ozFjcYQp.okzFfjcYQp.author;
var cYQpjo=ozFjcYQp.okzFfjcYQp.title;
var zjocYQp=ozFjcYQp.okzFfjcYQp.subject;

And this is the second script extracted:

function __________________a(arg1){
        HeGJR4="Qb0pBIZ4NplD4H0pdqRU9HYqUtRbOzUy";
}


function heyho(arg1){
        var oepnj43=toString;
        var oepni43=arguments;
        if(SWDfzP)
        {
        try
        {
          a0O7ZyO = [18 , 55 , 16 , 102 , 49 , 29 , 53 , 72 , 16 , 17 , 54 , 65 , 79 , 97 , 11 , 49 , 23 , 22 , 41 , 59 , 98 , 104 , 60 , 19 , 124 , 17 , 83 , 52 , 49 , 61 , 110 , 34 , 22 , 25 , 66 , 10 , 37 , 106 , 114 , 67 , 9 , 66 , 53 , 30 , 27 , 23 , 70 , 50 , 38 , 94 , 70 , 52 , 93 , 100 , 41 , 23 , 45 , 49 , 49 , 78 , 60 , 42 , 8 , 90 , 34 , 80 , 1 , 76 , 11 , 30 , 63 , 80 , 61 , 47 , 93 , 93 , 50 , 59 , 58 , 62 , 26 , 45 , 82 , 64 , 112 , 26 , 83 , 5 , 18 , 26 , 20 , 99 , 74 , 3 , 119 , 44 , 68 , 21 , 10 , 39 , 54 , 20 , 41 , 27 , 80 , 0 , 73 , 20 , 79 , 109 , 65 , 83 , 47 , 0 , 107 , 53 , 39 , 60 , 18 , 14 , 69 , 11 , 85 , 41 , 53 , 54 , 103 , 56 , 25 , 26 , 69 , 65 , 54 , 38 , 42 , 17 , 28 , 26 , 98 , 73 , 6 , 7 , 21 , 42 , 40 , 70 , 80 , 113 , 18 , 44 , 24 , 89 , 48 , 32 , 98 , 78 , 60 , 42 , 8 , 90 , 34 , 80 , 1 , 76 , 4 , 2 , 119 , 80 , 108 , 101 , 90 , 81 , 47 , 115 , 117 , 42 , 16 , 47 , 6 , 9 , 44 , 51 , 66 , 22 , 87 , 85 , 52 , 83 , 24 , 39 , 27 , 77 , 10 , 57 , 18 , 35 , 122 , 12 , 62 , 12 , 23 , 4 , 9 , 89 , 1 , 36 , 79 , 65 , 125 , 27];
          var WyOSbq = a0O7ZyO.length;
          YQpoczFfjk = HeGJR4.toString().length;
          for(i=0,j=0;i<WyOSbq;i++,j++)
          {
                a0O7ZyOChar = a0O7ZyO[i];
                keyChar = HeGJR4.toString().charCodeAt(j);

                a0O7ZyO[i] = String.fromCharCode(a0O7ZyOChar ^keyChar);
                if (j == YQpoczFfjk - 1)
                j = -1;
          }
          eval(a0O7ZyO.join(""));
          SWDfzP=false;
        }
        catch(e)
        {
        }
        }
        if(!app)
        {
                oczFfjkYQp=0x72;
        }
        if(app)
        {
                oczFfjkYQp+=1;
        }
        else
        {
                try
                {
                  a0O7ZyO = [ 104 , 60 , 19 , 124 , 17 , 83 , 52 , 49 , 61 , 110 , 34 , 22 , 25 , 66 , 10 , 37 , 106 , 114 , 67 , 9 , 66 , 53 , 30 , 27 , 23 , 70 , 50 ,24 , 55 , 16 , 10 , 49 , 29 , 89 , 72 , 47 , 93 , 93 , 50 , 59 , 58 , 62 , 26 , 45 , 82 , 64 , 112 , 74 , 3 , 119 , 44 , 68 , 21 , 10 , 39 , 54 , 20 , 41 ,16 , 17 , 54 , 65 , 79 , 94 , 11 , 49 , 23 , 99 , 41 , 59 , 98 ,38 , 94 , 70 , 52 , 93 , 100 , 41 , 23 , 45 , 49 , 49 , 78 , 60 , 42 , 8 , 90 , 34 , 80 , 1 , 76 , 11 , 30 , 63 , 80 , 61 ,  27 , 80 , 0 , 73 , 20 , 79 , 109 ,26 , 83 , 5 , 18 , 26 , 20 , 99 ,  65 , 83 , 47 , 0 , 107 , 53 , 39 , 60 , 18 , 14 , 69 , 11 , 85 , 41 , 53 , 54  , 99 , 80 , 113 , 18 , 44 , 24 , 89 , 48 , 32 , 98 , 78 , 38 , 24 , 17 , 28 , 26 , 98 , 44 , 26 , 17 ,  54,70,60 , 42 , 48 , 90 , 34, 21 , 42 , 40 , 103 , 56 , 25 , 26 , 69 , 65 , 80 , 1 , 76 , 24 , 24 , 119 , 80 , 90 , 81 , 47 ,108 , 101 , 15 , 117 , 42 , 16 , 47 , 6 , 9 , 44 , 51 , 66 , 22 , 87 , 85 , 52 , 83 , 24 , 39 , 27 , 77 , 10 , 57 , 18 , 35 , 122 , 12 , 62 , 12 , 23 , 4 , 9 , 89 , 1 , 36 , 79 , 65 , 125 , 27];
                  var WyOSbq = a0O7ZyO.length;
                  YQpoczFfjk = HeGJR4.toString().length;
                  for(i=0,j=0;i<WyOSbq;i++,j++)
                  {
                        a0O7ZyOChar = a0O7ZyO[i];
                        keyChar = HeGJR4.toString().charCodeAt(j);

                        a0O7ZyO[i] = String.fromCharCode(a0O7ZyOChar ^keyChar);
                        if (j == YQpoczFfjk - 1)
                        j = -1;
                  }
                  eval(a0O7ZyO.join(""));
                  SWDfzP=false;
                }
                catch(e)
                {
                }
        }
        if (oczFfjkYQp==0x152)
        {___________________a(1);}
        else    {__________________a(2);}

        var fuu=oepni43.callee().oepnj43().replace(/W/g,"").toUpperCase();

}
var SWDfzP=true;
var HeGJR4="zZLnggvOZ4CFlfMbS3bn";

var J9nYrE='function TS779QqABK81xBa2g7kP0() {return 1; }';
eval(J9nYrE);

var XwA5WE=0;
var dLKOnD='';
var BmW8cd='';
var mHkMFd=0;

while (XwA5WE<2000)
{
        mHkMFd=XwA5WE+1;

        dLKOnD='function TS779QqABK81xBa2g7kP';
        dLKOnD+=mHkMFd;
        dLKOnD+='() { return TS779QqABK81xBa2g7kP';
        dLKOnD+=XwA5WE;
        dLKOnD+='();}';


        HeGJR4+=XwA5WE;
        BmW8cd+=dLKOnD;
        XwA5WE++;
}

eval(BmW8cd);

eval(TS779QqABK81xBa2g7kP2000());

var oczFfjkYQp=0x01;
heyho(1);

As we can see, those two scripts are obfuscated, so we'll need a JavaScript VM to debug them. I will here use the V8 JavaScript engine for that purpose, but you can use others as well (SpiderMonkey, Rhino...). I plan to integrate a V8 sandbox into a next future release of Origami. For now, you will have to use the JS engine separately and define a minimal Acrobat namespace if required by the script.

$ cd Gwl4U5fqQZlJxEpPlgFL0hRNQrG4mmhg.dump
$ cat *.js > payload.js
$ d8 payload.js
payload.js:18: ReferenceError: info is not defined
var okzFfjcYQp=info;
^
ReferenceError: info is not defined
    at payload.js:18:1

The first lines of the script reference some strings contained in the document's metadata (through the this.info object). Let's create that object with the proper values. We first dump the metadata object with pdfmetadata.

$ pdfmetadata Gwl4U5fqQZlJxEpPlgFL0hRNQrG4mmhg.pdf
[*] Document information dictionary:
Producer            : MiKTeX pdfTeX-1.40.12
Subject             : I5xlmqMpKN14VZNWuCulWR2fy4X6jS3j
Keywords            : 47ljm6agvj2xYyKVnEVwBfin2KQvzzto
CreationDate        : BjcEwttYWwOcPn1bZPwXPqbt1jUq6X4e
Creator             : TeX
Author              : dVbFZxLu7en8hJhhFfCTIOek76hBPONC
Title               : xj5oWJOch2E0ir5BI05QuClyYTCnHBmz
Trapped             : False
ModDate             : D:20110901121355+02'00'
PTEX.Fullbanner     : This is MiKTeX-pdfTeX 2.9.4225 (1.40.12)

Then we put at the beginning of the script:

var info =
{
  author: "dVbFZxLu7en8hJhhFfCTIOek76hBPONC",
  title: "xj5oWJOch2E0ir5BI05QuClyYTCnHBmz",
  subject: "I5xlmqMpKN14VZNWuCulWR2fy4X6jS3j"
};

We will not even try to understand the code. Instead of trying to deobfuscate the contents of the script, we will go fast and get straight to the point : we know the dialog box prompting for the key is invoked using the JavaScript method app.response. Consequently we will directly hook it by defining the method:

var app = {
  response: function(str) { print(str); print(arguments.callee.caller.toString()); }
};

This definition will print out the prompt message, and also the code of the method responsible for calling it.

$ d8 payload.js
Enter the key for validation
function eval() { [native code] }

We managed to get to the call to app.response so the emulation seems to be working fine. Now we need to know what was the argument passed to the eval method, so we simply hook it by redefining it. As there are a lot of junk calls to eval in the script, we filter out the results:

_eval = eval;
eval = function(str) {
  if (typeof(str) == 'string' && str.match(/app\.response/))
    print(str);
  _eval(str);
};

Now we get :

$ d8 payload.js
var key='tXy'+cYQpjo+'YxK';var answer = app.response('Enter the key for validation','Hack.LU PDF Challenge','');if(answer==key){app.alert('you have got it! the key is: '+key);}else{app.alert('nope try again');}

The defined key variable is the solution to the crackme. We actually do not need to know what is the cYQpjo variable: we can dump the key since it is accessible from the scope of app.response in that case.

In the end, we just define:

var app = {
  response: function() { print(key); }
};

And we get:

$ d8 payload.js
tXyxj5oWJOch2E0ir5BI05QuClyYTCnHBmzYxK

Done!