dfir it!

responding to incidents with candied bacon

Toxic PDF Walkthrough - BSides London Challenge

Unfortunately I wasn’t able to attend BSides London this year - otherwise there would probably be a DFIR.IT on Tour entry somewhere on the blog. Recently I haven’t got a lot of time to play with any DFIR challenges but when one of the guys at work mentioned about BSides Toxic PDF I decided to give it a try.

The door

Toxic PDF instruction states: “Don’t be afraid to walk through the door (if you can)…”

After opening the crackme.pdf the following screen appears:

Door

Door would open only when correct key is entered. But where can we find the key?

Whenever I need to analyze maldocs, malicious scripts, phishing emails my go-to platform is REMnux. I can’t emphasize how cool it is to have all the tools for reversing and malware analysis at one place!

Let’s crack on with the PDF analysis. For this we’ll use great tool peepdf created by Jose Miguel Esparza.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
remnux@remnux:~/Desktop/PDF-B$ peepdf -i crackme.pdf
File: crackme.pdf
MD5: 9a8e90fb547d8fd3c865ed74782af600
SHA1: b47f676fb46138273aa25609b1f8a9537605e3b6
Size: 127257 bytes
Version: 1.7
Binary: True
Linearized: False
Encrypted: False
Updates: 0
Objects: 46
Streams: 23
Comments: 0
Errors: 0

Version 0:
  Catalog: 1
  Info: 3
  Objects (46): [1, 2, 3, 4, 5, 6, 7, 8, 9, 14, 15, 16, 190, 191, 192, 264, 265, 266, 273, 274, 282, 283, 299, 300, 325, 326, 327, 334, 335, 373, 374, 375, 376, 518, 530, 531, 532, 533, 577, 578, 579, 879, 880, 904, 913, 914]
  Compressed objects (21): [192, 577, 2, 3, 5, 6, 7, 264, 9, 266, 299, 300, 14, 879, 16, 579, 518, 373, 327, 190, 325]
      Errors (3): [374, 880, 904]
  Streams (23): [8, 15, 191, 265, 273, 274, 282, 283, 326, 334, 335, 374, 375, 376, 530, 531, 532, 533, 578, 880, 904, 913, 914]
      Xref streams (1): [914]
      Object streams (1): [913]
      Encoded (23): [8, 15, 191, 265, 273, 274, 282, 283, 326, 334, 335, 374, 375, 376, 530, 531, 532, 533, 578, 880, 904, 913, 914]
      Decoding errors (1): [374]
  Objects with JS code (2): [880, 904]
  Suspicious elements:
      /AcroForm: [1]
      /AA: [913, 264, 14]
      /JS: [913, 299, 879]
      /JavaScript: [913, 299, 879]

Usually when analyzing malicious PDF documents objects like AcroForm, AA(Additional Actions) or JavaScript are the the most interesting to look at. As object 913 is on both AA and JS lists it seems to be a good starting point.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
PPDF> object 913

<< /Length 1065
/N 21
/Type /ObjStm
/Filter /FlateDecode
/First 163 >>
stream
2 0 3 37 5 182 6 419 7 530 9 602 14 635 16 882 190 915 192 1164 264 1197 266 1534 299 1555 300 1596 325 1611 327 1922 373 1943 518 2214 577 2229 579 2411 879 2432
<</Type/Pages/Count 1/Kids[ 4 0 R ]>>
<</Producer(Foxit PhantomPDF - Foxit Software Inc.)/Author(liv)/Title(Untitled)/CreationDate(D:20150308153143)/ModDate(D:20150315133057+00'00')>>

<</FT/Tx/Ff 1/Type/Annot/Subtype/Widget/F 4/M(D:20150308153155+00'00')/Rect[ 161.487 717.47 450.513 760.867]/BS
<</W 1/S/S>>/DA(/Helv 0 Tf 0 0 0 rg)/AP<</N 8 0 R >>/V(Knock! Knock! Anybody there?)/DV(Knock! Knock! Anybody there?)/T(msg)>>
<</DR<</Font<</Helv 7 0 R >>>>/DA(/Helv 0 Tf 0 g)/Fields[ 14 0 R  5 0 R  190 0 R  264 0 R  325 0 R  577 0 R ]>>
<</Type/Font/Subtype/Type1/BaseFont/Helvetica/Encoding/WinAnsiEncoding>>
<</Helvetica 7 0 R /FXF0 7 0 R >>

<</FT/Tx/Ff 8192/Type/Annot/Subtype/Widget/F 4/M(D:20150308153501+00'00')/Rect[ 188.362 683.035 423.638 708.979]/BS
<</W 1/S/S>>/DA(/Helv 0 Tf 0 0 0 rg)/AP<</N 15 0 R >>/T(pass)/MK
<</BC[ 0.25098 0 0.12549]>>/Q 1/MaxLen 50/V(123)/DV(1)/AA 518 0 R >>
<</Helvetica 7 0 R /FXF0 7 0 R >>

<</FT/Tx/Ff 0/Type/Annot/Subtype/Widget/F 6/M(D:20150308184228+00'00')/Rect[ 256.965 600.958 256.965 602.373]/BS
<</W 1/S/S>>/DA(/Helv 0 Tf 0 0 0 rg)/AP
<</N 191 0 R >>/V(9053d91a70acfd6614f0243caac70ce2)/DV(9053d91a70acfd6614f0243caac70ce2)/T(  %e)>>
<</Helvetica 7 0 R /FXF0 7 0 R >>

<</FT/Btn/Ff 65536/Type/Annot/Subtype/Widget/F 4/M(D:20150314181444+00'00')/Rect[ 236.219 401.896 372.009 655.204]/MK
<</BG[ 0.752941 0.752941 0.752941]/AC(test)/TP 1/IF<</S/P/FB true/SW/A/A[ 0.5 0.5]>>/IX 283 0 R /I 274 0 R >>/BS
<</W 1/S/S>>/H/N/DA(/Helv 0 Tf 0 0 0 rg)/AP<</N 265 0 R >>/T(door)/AA 300 0 R /TU(Click to open the door!)>>
<</Helvetica 7 0 R >><</Type/Action/S/JavaScript/JS 904 0 R >><</D 299 0 R >>

<</FT/Btn/Ff 65536/Type/Annot/Subtype/Widget/F 6/M(D:20150314183524+00'00')/Rect[ 236.804 400.953 371.652 654.733]/MK
<</BG[ 0.752941 0.752941 0.752941]/TP 1/IF<</S/P/FB true/SW/A/A[ 0.5 0.5]>>/I 335 0 R /IX 533 0 R >>/BS
<</W 1/S/S>>/H/P/DA(/Helv 0 Tf 0 0 0 rg)/AP<</N 326 0 R /R 530 0 R /D 531 0 R >>/T(door2)>>
<</Helvetica 7 0 R >>

<</Rect[ 496.955 602.865 577.955 685.865]/Subtype/Screen/P 4 0 R /M(D:20150314185841+00'00')/F 4/NM(31cede6f-f679-47c0-a8a9-d8eefad3b763)/IT/Img/BS<</S/S/W 0>>/MK
<</BC[ 0 0 1]/R 0/I 375 0 R /IF<</S/P/FB false/SW/A/A[ 0.5 0.5]>>>>/CA 1/C[ 0 0 0.003922]/AP
<</N 376 0 R >>>>
<</V 879 0 R >>

<</FT/Tx/Ff 4198400/Type/Annot/Subtype/Widget/F 36/M(D:20150314200638+00'00')/Rect[ 145.692 263.686 466.308 358.971]/BS
<</W 1/S/S>>/DA(/Helv 0 Tf 0 0 0 rg)/AP<</N 578 0 R >>/T(366)>>
<</Helvetica 7 0 R >><</Type/Action/S/JavaScript/JS 880 0 R >>
Endstream

Based on the above output we can see that Click to open the door action is handled by JavaScript object 904. This may include some sort of password validation code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61

PPDF> js_beautify object 904

function vv56(h3m, n7) {
    return h3m + n7;
}

function mz821a(hu4, v9) {
    return v9[hu4]
}

function mmu7d(kjj, y7, y8) {
    var kmn6 = y7 % kjj.length;
    var s = "";
    while (s.length < kjj.length) {
        s += mz821a(kmn6, kjj);
        kmn6 = (kmn6 + y8) % kjj.length;
    }
    return s;
}

var hnam4 = mmu7d("49%68%DC%79%C9%29%D8%DD%E9%19%4D%D9%18%9D%29%BD%19%B9%AC%3D%F9%C8%F8%09%8C%E8%E9%99%C8%FA%E9%78%9A%99%89%48%79%99%38%19%DD%C8%E8%89%D8%C9%7D%49%29%18%88%38%78%F8%18%9D%6D%C9%DD%99%E9%9D%E9%49%89%9D%9D%89%88%08%9D%38%E8%89%F9%FB%99%8C%D9%C8%48%89%39%DD%A9%8D%E8%6B%99%5D%18%88%D8%E8%1D%F8%39%39%2D%D8%39%3D%0D%DD%DD%EB%F9%59%AD%09%88%69%E8%E9%FC%38%8D%DD%C8%DD%EA%C9%19%19%F9%78%09%A9%FD%B8%C9%A8%DD%89%49%DD%DD%DD%DB%49%49%CB%D9%E9%FC%E8%B9%1C%6D%FC%D9%D9%9D%DD%A9%C9%1D%CD%A9%0A%59%28%DD%E8%29%E9%5D%49%3D%F8%D8%CC%48%29%99%48%28%4C%78%DA%", 199154, 198821);

var as6z = mmu7d("%ED%B9%C9%08%38%E8%29%89%89%68%A9%28%DD%2C%8D%9C%AB%8B%89%9D%FD%F9%E9%ED%58%F8%ED%8D%D9%59%19%49%C9%BD%39%19%1D%9B%9u%B8%4D%B8%C9%E9%8D%DD%D9%09%F9%F8%B9%B8%BC%49%D9%C9%A9%4D%6D%99%D9%DD%88%C9%28%A9%A8%DB%39%48%6C%ED%28%F8%38%E8%19%4D%59%5D%49%4D%3D%99%3C%89%79%98%C9%5D%BD%3D%49%88%8D%5D%6D%C9%98%C9%B9%09%49%D9%09%D95E9%F8%98%99%49%F9%3D%F8%3D%FD%5D%89%69%8D%9D%D8%99%38%3D%49%4B%99%DC%F9%F8%89%E9%19%C8%59%99%9D%59%59%88%E9%89%18%A9%1D%3D%98%D8%4D%F8%49%49%DD%DD%F9%39%F9%38", 334776, 334478);

function mns51() {
    if (this.secret === undefined) {
        return mmu7d("mfoCr", 3436, 3438);
    }
    return mmu7d("omfrB", 527, 528);
}

function alopre7(no, uv) {
    var yg1;
    yg1 = mns51();

    return mz821a(yg1 + no, uv);
}

function enx(u7b, i8uy) {
    var coded = "";
    var f = alopre7(mmu7d("ohCerda", 5748, 5752), String);

    for (var i = 0x39 + 3 + ~ - ~ - ~57; i < mz821a(mmu7d("elhtgn", 5071, 5075), u7b); i++) {
        coded += f(u7b[mmu7d("dtaoAhCecr", 8828, 8827)](i) ^ i8uy);
    }
    return coded;
}

function ty32() {
    return mz821a(mmu7d(vv56("a", "v") + "el", 2470, 2471), mz821a(mmu7d("ratteg", 4898, 4901), event));
}

var my6 = ty32();

function dsm33() {
    return my6(mmu7d("nuepacse", 6169, 6175));
}

var h7 = dsm33();
my6(enx(h7(as6z + hnam4), 0xFD));

Obfuscated code! That looks suspicious. Let’s dump it and look if we can deobfuscate the code.

1
PPDF> js_beautify object 904 > 904

The key

One of my favourite tools to analyze JavaScript is SpiderMonkey which is a JavaScript engine. It’s an easy way to run blocks of code to see what will be the result.

For instance in the above code, functions mmu7d() and mz821a() are used for string manipulation. You can put those functions into a file decode.js, then load the file into SpiderMonkey.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
js> load(['decode.js'])
js> mmu7d("mfoCr", 3436, 3438);
fromC
js> mmu7d("omfrB", 527, 528);
formB
js> mmu7d("ohCerda", 5748, 5752)
harCode
js> 0x39 + 3 + ~ - ~ - ~57
0
js> mmu7d("elhtgn", 5071, 5075)
length
js> mmu7d("dtaoAhCecr", 8828, 8827)
charCodeAt
js> mmu7d(addObjects("a", "v") + "el", 2470, 2471)
eval
js> mmu7d("ratteg", 4898, 4901)
target
js> mmu7d("nuepacse", 6169, 6175)
unescape

This makes our code more readable:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
function vv56(h3m, n7) {
    return h3m + n7;
}

function mz821a(hu4, v9) {
    return v9[hu4]
}

function mmu7d(kjj, y7, y8) {
    var kmn6 = y7 % kjj.length;
    var s = "";
    while (s.length < kjj.length) {
        s += mz821a(kmn6, kjj);
        kmn6 = (kmn6 + y8) % kjj.length;
    }
    return s;
}

var hnam4 = mmu7d("49%68%DC%79%C9%29%D8%DD%E9%19%4D%D9%18%9D%29%BD%19%B9%AC%3D%F9%C8%F8%09%8C%E8%E9%99%C8%FA%E9%78%9A%99%89%48%79%99%38%19%DD%C8%E8%89%D8%C9%7D%49%29%18%88%38%78%F8%18%9D%6D%C9%DD%99%E9%9D%E9%49%89%9D%9D%89%88%08%9D%38%E8%89%F9%FB%99%8C%D9%C8%48%89%39%DD%A9%8D%E8%6B%99%5D%18%88%D8%E8%1D%F8%39%39%2D%D8%39%3D%0D%DD%DD%EB%F9%59%AD%09%88%69%E8%E9%FC%38%8D%DD%C8%DD%EA%C9%19%19%F9%78%09%A9%FD%B8%C9%A8%DD%89%49%DD%DD%DD%DB%49%49%CB%D9%E9%FC%E8%B9%1C%6D%FC%D9%D9%9D%DD%A9%C9%1D%CD%A9%0A%59%28%DD%E8%29%E9%5D%49%3D%F8%D8%CC%48%29%99%48%28%4C%78%DA%", 199154, 198821);

var as6z = mmu7d("%ED%B9%C9%08%38%E8%29%89%89%68%A9%28%DD%2C%8D%9C%AB%8B%89%9D%FD%F9%E9%ED%58%F8%ED%8D%D9%59%19%49%C9%BD%39%19%1D%9B%9u%B8%4D%B8%C9%E9%8D%DD%D9%09%F9%F8%B9%B8%BC%49%D9%C9%A9%4D%6D%99%D9%DD%88%C9%28%A9%A8%DB%39%48%6C%ED%28%F8%38%E8%19%4D%59%5D%49%4D%3D%99%3C%89%79%98%C9%5D%BD%3D%49%88%8D%5D%6D%C9%98%C9%B9%09%49%D9%09%D95E9%F8%98%99%49%F9%3D%F8%3D%FD%5D%89%69%8D%9D%D8%99%38%3D%49%4B%99%DC%F9%F8%89%E9%19%C8%59%99%9D%59%59%88%E9%89%18%A9%1D%3D%98%D8%4D%F8%49%49%DD%DD%F9%39%F9%38", 334776, 334478);

function mns51() {
    if (this.secret === undefined) {
        return "fromC";
    }
    return "fromB";
}

function alopre7(no, uv) {
    var yg1;
    yg1 = mns51();

    return mz821a(yg1 + no, uv);
}

function enx(u7b, i8uy) {
    var coded = "";
    var f = alopre7("harCode", String);

    for (var i = 0; i < mz821a("length", u7b); i++) {
        coded += f(u7b["charCodeAt"](i) ^ i8uy);
    }
    return coded;
}

function ty32() {
    return mz821a("eval", mz821a("target", event));
}

var my6 = ty32();

function dsm33() {
    return my6("unescape");
}

var h7 = dsm33();

my6(enx(h7(as6z + hnam4), 0xFD));

It seems that last line of the code invokes all the functions. Let’s break it down:

  • as6z + hanm4 concatenates string variables
  • h7() invokes unescape()
  • enx(dstring,key) decodes string with a key
  • my6() invokes eval()

To see the results just comment out the following code and print() the results instead of eval():

1
2
3
4
5
6
7
8
9
10
11
12
//function ty32() {
//    return mz821a("eval", mz821a("target", event));
//}
//var my6 = ty32();
//function dsm33() {
//   return my6("unescape");
//}  

//var h7 = dsm33();
//my6(enx(h7(as6z+hnam4), 0xFD));

print(enx(unescape(as6z+hnam4, 0xFD)))

Now run it in SpiderMonkey:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
remnux@remnux:~/Desktop/PDF-B/Post$ js object-904 > results
remnux@remnux:~/Desktop/PDF-B/Post$ js-beautify results
function raven() {
    if (this.getField("pass").value == this.getField("e").value + "antistring") {
        this.getField("door2").hidden = false;
        app.alert({
            cMsg: "Congratulations! You have solved the puzzle",
            cTitle: "Access granted",
            nIcon: 3
        });
    } else {
        app.alert({
            cMsg: "Invalid password! Try again",
            cTitle: "Wrong password",
            nIcon: 0
        });
    }
}
raven();

Condition included in raven() function reveals that the key is comprised of the value of the e field and the word antistring. What is the e field value and where we can find it?

Let’s go back to the object 913. Fields name seem to be defined with /T, for instance:

  • /T(door)
  • /T(msg)
  • /T(door2)

If we look closer /T(e) is present in the output of the object 913. Next to it, there is a value /V of 9053d91a70acfd6614f0243caac70ce2.

1
2
3
4
remnux@remnux:~/Desktop/PDF-B/Post$ egrep /T object913
<</FT/Tx/Ff 0/Type/Annot/Subtype/Widget/F 6/M(D:20150308184228+00'00')/Rect[ 256.965 600.958 256.965 602.373]/BS
<</W 1/S/S>>/DA(/Helv 0 Tf 0 0 0 rg)/AP
<</N 191 0 R >>/V(9053d91a70acfd6614f0243caac70ce2)/DV(9053d91a70acfd6614f0243caac70ce2)/T(��%e)>>

Well, it turns out that 9053d91a70acfd6614f0243caac70ce2antistring is our key.

Door2

Bonus

During the initial analysis peepdf reported two JS objects (880, 904). We already know everything about object 904. What about the other one?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
PPDF> object 880

<< /Length 674
/Filter /FlateDecode >>
stream
var pluto = "its%13%05WUA%7DQ%17%025%07D_A@FQ@Q%13%0C%07%15%06Y%10%02%07G%12%0B%10I%11%0DR%08%1A%13XS%5EV%16J%11%15X%10%09%0A%02%01%16SID%0A_%5B@%13%00%0E%05%06%19%100%0A_%04N%1B%0FS%00%1A%0C%07%15%19DGZ%07RBA%5E%5E%02%0F%13%00S%16%5CA%0AD%5BD_%06A%0D%02NU%11%16%12%0E%08T%0C%1D%17%00%10%1E%13P_%5B%1FDZ%5D%04AU%13C%15%10D__S%15%10SZWC%08%0F%17RW%06%17%12%0C%0F%1A%00%03%01%1E%08%1A%0EV%5E%19%13%05LE%0EZQ%15%0A%05DPYCYFQQ@Z%0C%0F%12O%17X%0A%01V%04%00T%08%1D%10%16%0C%0D%08@%10ZQ%0E%5CR%15D%1Eki%25%0BXQCU%12E%5EUG%0A%0E%0F%10%1B%10%01%1C%12%0F%01%03I%0A%1B%07N%1C%02%19QYA%01XU%18%17V%00%0E%0F%08_WC%14%11YF%5C%13%17%09%04CUQ%10%0CQA%1A%06%00%10%1F%01I%0F%09%5D%10%5E%5D%0BN%11%09XGA%17%09DRSEQ%05D%12@%5B%06%0C@i%3Dd%0B%04%5C%0AN%0D%06%06T%14%06%1CGI%5CTJ%0DWVAV%5E%05C%15%01S%16H%5B%13%10S@%13%212%08%07RCB";

function whisper(lenore, tap) {
    var nap = "";
    for (i=0; i<lenore.length;i++) {
        var a = lenore.charCodeAt(i);
        var b = a ^ tap.charCodeAt(i % tap.length);

        nap = nap+String.fromCharCode(b);
    }
    return nap;
}


this.getField("366").value = whisper(unescape(pluto), event.value);

endstream

PPDF> object 880 > object880.js

This object also includes an obfuscated string. It took me a while to realize what it is. whisper() seems to be another decrypting routine that accepts string to be decrypted and key as parameters. We have the message (pluto) and now we know the key right? What if…

1
2
3
4
5
6
7
8
9
10
remnux@remnux:~/Desktop/PDF-B/Post$ js
js> load(["object880.js"])
js> pluto
its%13%05WUA%7DQ%17%025%07D_A@FQ@Q%13%0C%07%15%06Y%10%02%07G%12%0B%10I%11%0DR%08%1A%13XS%5EV%16J%11%15X%10%09%0A%02%01%16SID%0A_%5B@%13%00%0E%05%06%19%100%0A_%04N%1B%0FS%00%1A%0C%07%15%19DGZ%07RBA%5E%5E%02%0F%13%00S%16%5CA%0AD%5BD_%06A%0D%02NU%11%16%12%0E%08T%0C%1D%17%00%10%1E%13P_%5B%1FDZ%5D%04AU%13C%15%10D__S%15%10SZWC%08%0F%17RW%06%17%12%0C%0F%1A%00%03%01%1E%08%1A%0EV%5E%19%13%05LE%0EZQ%15%0A%05DPYCYFQQ@Z%0C%0F%12O%17X%0A%01V%04%00T%08%1D%10%16%0C%0D%08@%10ZQ%0E%5CR%15D%1Eki%25%0BXQCU%12E%5EUG%0A%0E%0F%10%1B%10%01%1C%12%0F%01%03I%0A%1B%07N%1C%02%19QYA%01XU%18%17V%00%0E%0F%08_WC%14%11YF%5C%13%17%09%04CUQ%10%0CQA%1A%06%00%10%1F%01I%0F%09%5D%10%5E%5D%0BN%11%09XGA%17%09DRSEQ%05D%12@%5B%06%0C@i%3Dd%0B%04%5C%0AN%0D%06%06T%14%06%1CGI%5CTJ%0DWVAV%5E%05C%15%01S%16H%5B%13%10S@%13%212%08%07RCB
js> whisper(unescape(pluto),"9053d91a70acfd6614f0243caac70ce2antistring")
PDF and JavaScript are often abused by attackers to hide exploit code. Some of their tricks include multiple layers of encryption, clever strings and integer manipulation, automatic form actions, hidden anddecoy objects.

Congratulations, by now you're already familiar with the basic tricks and know how to detect them!

Thank you for playing and see you at BSides!

Thank you BSides London and thank you Liviu Itoafă for creating this challenge. I am still not sure if this is the right way to solve the challenge but it was fun!

Comments