Hello.
For everyone who interested :)
Turla container (in most of published papers named
messages queue, we don't think it proper name) represent itself a combined container of linked data entries describing various parts of rootkit configuration, including payload libraries. Container contains: header (fields include full size, used container size, number of chains etc) and set of chains (entries).
The following structure describes container entry. Contents of data encrypted with CAST5 symmetric block cipher with small modification. Maybe in different variants of Turla there are other changes too.
Code: Select alltypedef struct _CONTAINER_ENTRY {
ULONG EntryTypeOrGroupId;
ULONG EntryId;
ULONG EntriesInGroup;
ULONG DataType;
ULONG GroupId;
ULONG Reserved1[3];
ULONG TimeStamp1;
ULONG TimeStamp2;
ULONG TimeStamp3;
ULONG EntrySize;
ULONG DataSize;
ULONG Reserved2[3];
BYTE Data[0];
} CONTAINER_ENTRY, *PCONTAINER_ENTRY;
Container extracts by dropper from dropper resource section and writes at rootkit contolled VFS as system.sav
Malware driver fully responsible for container parsing and is able both use encoding and decoding using CAST5.
Our goal was: find and extract payload libraries from Turla 2013 sample provided by R136a1 and learn basic principles how this malware manages it data.
Fully parsed container from sample
Code: Select all
= new group: ID=1/5501 count of entries: 11
> entry id=5501-1-2.bin DataType:551 Size:16
> entry id=5501-2-2.bin DataType:552 Size:16
> entry id=5501-3-2.bin DataType:553 Size:16
> entry id=5501-4-2.bin DataType:223 Size:9
> entry id=5501-5-2.bin DataType:224 Size:9
> entry id=5501-6-2.bin DataType:400 Size:9
> entry id=5501-7-2.bin DataType:225 Size:9
> entry id=5501-8-2.bin DataType:226 Size:9
> entry id=5501-9-2.bin DataType:323 Size:9
> entry id=5501-10-2.bin DataType:324 Size:9
> entry id=5501-11-2.bin DataType:1 Size:9
= new group: ID=2/5502 count of entries: 11
> entry id=5502-1-2.bin DataType:551 Size:16
> entry id=5502-2-2.bin DataType:552 Size:16
> entry id=5502-3-2.bin DataType:553 Size:16
> entry id=5502-4-2.bin DataType:223 Size:9
> entry id=5502-5-2.bin DataType:224 Size:9
> entry id=5502-6-2.bin DataType:400 Size:9
> entry id=5502-7-2.bin DataType:225 Size:9
> entry id=5502-8-2.bin DataType:226 Size:9
> entry id=5502-9-2.bin DataType:323 Size:9
> entry id=5502-10-2.bin DataType:324 Size:9
> entry id=5502-11-2.bin DataType:1 Size:9
= new group: ID=3/5503 count of entries: 11
> entry id=5503-1-2.bin DataType:551 Size:16
> entry id=5503-2-2.bin DataType:552 Size:16
> entry id=5503-3-2.bin DataType:553 Size:16
> entry id=5503-4-2.bin DataType:223 Size:9
> entry id=5503-5-2.bin DataType:224 Size:9
> entry id=5503-6-2.bin DataType:400 Size:9
> entry id=5503-7-2.bin DataType:225 Size:9
> entry id=5503-8-2.bin DataType:226 Size:9
> entry id=5503-9-2.bin DataType:323 Size:9
> entry id=5503-10-2.bin DataType:324 Size:9
> entry id=5503-11-2.bin DataType:1 Size:9
= new group: ID=4/5504 count of entries: 11
> entry id=5504-1-2.bin DataType:551 Size:16
> entry id=5504-2-2.bin DataType:552 Size:16
> entry id=5504-3-2.bin DataType:553 Size:16
> entry id=5504-4-2.bin DataType:223 Size:9
> entry id=5504-5-2.bin DataType:224 Size:9
> entry id=5504-6-2.bin DataType:400 Size:9
> entry id=5504-7-2.bin DataType:225 Size:9
> entry id=5504-8-2.bin DataType:226 Size:9
> entry id=5504-9-2.bin DataType:323 Size:9
> entry id=5504-10-2.bin DataType:324 Size:9
> entry id=5504-11-2.bin DataType:1 Size:9
> entry id=4294967293-1-2.bin DataType:553 Size:16
> entry id=4294967293-1-3.bin DataType:1000004 Size:33793
> entry id=5501-12-2.bin DataType:500 Size:17
> entry id=5503-12-2.bin DataType:500 Size:17
> entry id=5504-12-2.bin DataType:500 Size:17
> entry id=4294967293-2-3.bin DataType:1000005 Size:140801
> entry id=5501-13-2.bin DataType:501 Size:17
> entry id=5503-13-2.bin DataType:501 Size:17
> entry id=5504-13-2.bin DataType:501 Size:17
> entry id=4294967293-3-3.bin DataType:1000000 Size:148993
> entry id=5502-12-2.bin DataType:651 Size:33
> entry id=5502-13-2.bin DataType:652 Size:33
> entry id=5502-14-2.bin DataType:653 Size:89
> entry id=4294967293-4-3.bin DataType:1000001 Size:583681
> entry id=5502-15-2.bin DataType:654 Size:33
> entry id=5502-16-2.bin DataType:655 Size:33
> entry id=5502-17-2.bin DataType:656 Size:89
> entry id=4294967293-5-3.bin DataType:1000002 Size:49665
> entry id=5501-14-2.bin DataType:502 Size:17
> entry id=4294967293-6-3.bin DataType:1000003 Size:58369
> entry id=5501-15-2.bin DataType:503 Size:17
> entry id=5502-18-2.bin DataType:8 Size:17
> entry id=5502-19-2.bin DataType:9 Size:9
> entry id=5501-16-2.bin DataType:100 Size:9
> entry id=5501-17-2.bin DataType:101 Size:33
> entry id=5501-18-2.bin DataType:102 Size:49
> entry id=5501-19-2.bin DataType:112 Size:97
> entry id=5501-20-2.bin DataType:113 Size:41
> entry id=5502-20-2.bin DataType:100 Size:9
> entry id=5502-21-2.bin DataType:101 Size:49
> entry id=5502-22-2.bin DataType:102 Size:57
> entry id=5502-23-2.bin DataType:112 Size:97
> entry id=5502-24-2.bin DataType:113 Size:41
> entry id=5502-25-2.bin DataType:767 Size:73
> entry id=5502-26-2.bin DataType:1034 Size:9
> entry id=5503-14-2.bin DataType:100 Size:9
> entry id=5503-15-2.bin DataType:101 Size:41
> entry id=5503-16-2.bin DataType:102 Size:49
> entry id=5503-17-2.bin DataType:112 Size:97
> entry id=5503-18-2.bin DataType:113 Size:41
> entry id=5504-14-2.bin DataType:100 Size:9
> entry id=5504-15-2.bin DataType:101 Size:41
> entry id=5504-16-2.bin DataType:102 Size:49
> entry id=5504-17-2.bin DataType:112 Size:97
> entry id=5504-18-2.bin DataType:113 Size:41
DataType (only we wanted):
553 is a key used to decrypt given group data. Always 16 bytes length.
500 is a DataType ID of payload dll X86 (stored as encrypted string)
501 is a DataType ID of payload dll X64 (stored as encrypted string)
Other types include different information: list of processes to inject payloads, xoring bytes, process+associated injected dll, varios configuration params.
You can notice 6 entries with big amount data inside.
Note odd sizes - this is because of special byte added to the data. CAST is block cipher and this implementation uses 8 bytes per block which means if you encode with it 1 byte as result you will have 8 bytes. This special byte added to the data and it determines significant number of bytes in the tail of encrypted data.
How we decrypt entries in container? We did it with help of rootkit itself -> decrypted X64 driver, patched it in several places and then used parts of it binary code. Code is simple and our goal wasn't creating cute looking Turla decoder as we don't see sense in it. So we are sorry for such code :)
Each group of entries encoded with the same key.
Another note: during decoding malware implementation does additional xor over supplied key, *(PDWORD)key ^= DataSize
CAST decoding procedure prototype.
Code: Select alltypedef unsigned long (__stdcall *RAW_CODE_PTR)(unsigned char *Key, unsigned long KeySize, unsigned char *OrigText, unsigned char *CipherText, unsigned long TextSize);
What we will do:
Enumerate all groups and entries, remember group key, apply group key for data of the group.
Code: Select alltypedef struct _RESIDKEY {
ULONG GroupId;
BYTE GroupKey[16];
} RESIDKEY, *PRESIDKEY;
Parsing and decoding:
Code: Select all HANDLE f = INVALID_HANDLE_VALUE;
DWORD iobytes;
LARGE_INTEGER sz = {0};
PBYTE membuf = NULL, origText = NULL, dcode, pmemset = (PBYTE)&memset, pmemcpy = (PBYTE)&memcpy;
WCHAR textbuf[64];
PCONTAINER_ENTRY ent;
unsigned long i=0, k;
RAW_CODE_PTR malware_ucast = NULL, malware_dcast;
RESIDKEY keys[16];
membuf = (PBYTE)VirtualAlloc(NULL, 2*1024*1024, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
origText = (PBYTE)VirtualAlloc(NULL, 2*1024*1024, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
dcode = (PBYTE)VirtualAlloc(GetModuleHandle(NULL) + 0x100000, 1*1024*1024, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
f = CreateFile(TEXT("C:\\Malware\\UROBOROS\\root.dmp"), GENERIC_READ | SYNCHRONIZE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
GetFileSizeEx(f, &sz);
ReadFile(f, dcode, sz.LowPart, &iobytes, NULL);
CloseHandle(f);
/* fix memcpy, memset references */
pmemset = (PBYTE)(pmemset-(dcode+0x1d030+0x2F+4));
*(PDWORD)(dcode+0x1d030+0x2F)=(DWORD)pmemset;
pmemcpy = (PBYTE)(pmemcpy-(dcode+0x10166));
*(PDWORD)(dcode+0x10162)=(DWORD)pmemcpy;
pmemcpy = (PBYTE)&memcpy;
pmemcpy = (PBYTE)(pmemcpy-(dcode+0x1024a));
*(PDWORD)(dcode+0x10246)=(DWORD)pmemcpy;
malware_ucast = (RAW_CODE_PTR)(dcode+0x10130); /* encoding procedure, not used here */
malware_dcast = (RAW_CODE_PTR)(dcode+0x10210); /* decoding procedure */
do {
f = CreateFile(TEXT("C:\\Malware\\UROBOROS\\root\\resources\\103.resource-unknown-container.bin"),
GENERIC_READ | SYNCHRONIZE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
if ( f == INVALID_HANDLE_VALUE )
break;
GetFileSizeEx(f, &sz);
if ( !ReadFile(f, membuf, sz.LowPart, &iobytes, NULL) )
OutputDebugString(TEXT("partial read\r\n"));
CloseHandle(f);
/* skip header */
ent = (PCONTAINER_ENTRY)(membuf+0x2c);
while ( ent->EntryTypeOrGroupId != 0 ) {
switch ( ent->EntryTypeOrGroupId ) {
case 0xfffffffe:
OutputDebugString(TEXT("= new group: ID="));
ultostrW(ent->EntryId, textbuf);
OutputDebugString(textbuf);
OutputDebugString(TEXT("/"));
ultostrW(ent->GroupId, textbuf);
OutputDebugString(textbuf);
OutputDebugString(TEXT(" count of entries: "));
ultostrW(ent->EntriesInGroup, textbuf);
OutputDebugString(textbuf);
OutputDebugString(TEXT("\r\n"));
break;
default:
/* extract group key */
if ( ent->DataType == 553 ) {
keys[i].GroupId = ent->EntryTypeOrGroupId;
memcpy(keys[i].GroupKey, ent->Data, 16);
i++;
}
OutputDebugString(TEXT("> entry id="));
ultostr(ent->EntryTypeOrGroupId, textbuf);
_strcat(textbuf, TEXT("-"));
ultostrW(ent->EntryId, _strend(textbuf));
_strcat(textbuf, TEXT("-"));
ultostrW(ent->EntriesInGroup, _strend(textbuf));
_strcat(textbuf, TEXT(".bin"));
OutputDebugString(textbuf);
for (k=0; k<i; k++) {
if ( keys[k].GroupId == ent->EntryTypeOrGroupId ) {
f = CreateFile(textbuf, GENERIC_WRITE | SYNCHRONIZE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, 0, NULL);
if ( f != INVALID_HANDLE_VALUE ) {
malware_dcast(keys[k].GroupKey, 16, origText, ent->Data, ent->DataSize);
WriteFile(f, origText, ent->DataSize-1, &iobytes, NULL);
CloseHandle(f);
}
break;
}
}
OutputDebugString(TEXT(" DataType:"));
ultostrW(ent->DataType, textbuf);
OutputDebugString(textbuf);
OutputDebugString(TEXT(" Size:"));
ultostrW(ent->DataSize, textbuf);
OutputDebugString(textbuf);
OutputDebugString(TEXT("\r\n"));
}
ent = (PCONTAINER_ENTRY)((PBYTE)ent+sizeof(CONTAINER_ENTRY)+ent->EntrySize);
}
} while (FALSE);
if ( membuf != NULL )
VirtualFree(membuf, 0, MEM_RELEASE);
if ( origText != NULL )
VirtualFree(origText, 0, MEM_RELEASE);
if ( dcode != NULL )
VirtualFree(dcode, 0, MEM_RELEASE);
Attached:
1) patched x64 rootkit driver we used for decryption
2) decrypted container data, including 6 MZ PE libraries (all mentioned in articles dll's)
Thanks to EP_X0FF & MP_ART.
Best Regards,
-rin