Log in

View Full Version : Beware of int 2c instruction


ZaiRoN
December 19th, 2007, 16:41
In the last days I played a little with Win32.Virtob virus, thanks to Kayaker for passing it out to me. It’s a nice virus with some interesting tricks. You can read a detailed analysis made by Kayaker from his recent blog entries.
In this post I’m not going to talk about the malware itself, I simply focus my attention on an single instruction: int 2c. The instruction is located in the beginning of the virus and I spent some time on it trying to answer Kayaker’s question: is “int 2c” used as an antidebug trick? I don’t have an answer for the question, but after some tries I came up with something that can be interesting.

Int2c
execution changes the content of some registers, but one of them (in particular edx) is always changed in the same way. edx contains the address of the instruction that follows the int 2c instruction.
Code:
100: mov edx, 80 <– edx = 0×80
105: int 2c <– it changes edx value
107: xor eax, eax <– here: edx = 0×107

Pretty weird. To know why you have to look at ntoskrnl code (starting from KiSetLowWaitHighThread), it’s pretty easy to find out why. Here’s the last part of the int2c code:
Code:
804d4f95 pop edx <-- edx is changed here
804d4f96 add esp,8
804d4f99 pop ecx
804d4f9a sti
804d4f9b sysexit

Sysexit is the key of this snippet. The function is used when there’s a transition from ring0 to ring3 code. ring3.eip is updated with ring0.edx value. That’s why edx contains the magic value, if you want to go back into ring3 world you have to put the return address into edx.
Is this fact important? Not really, per se. There’s a interesting use of this behaviour btw, infact it could be used inside an obfuscation engine or as an anti Ollydbg trick.

Look at this simple and stupid sample I wrote (attached at the end of the post):
Code:
00401000 LEA EDX,DWORD PTR DS:[403000] ; 403000 -> "easy sample"
00401006 PUSH EDX
00401007 INT 2C
00401009 PUSH 0 ; Style = MB_OK|MB_APPLMODAL
0040100B INT 2C
0040100D SUB BYTE PTR DS:[EDX+6],9 ; 403006 = ‘X’
00401011 LEA EAX,DWORD PTR DS:[403015] ; 403015 -> “I’m the caption”
00401017 PUSH EAX ; Title_1: “I’m the caption”
00401018 INT 2C
0040101A XOR BYTE PTR DS:[EDX+6],1D ; 403006 = ‘E’
0040101E LEA EAX,DWORD PTR DS:[403038] ; 403038 -> “I’m the text”
00401024 PUSH EAX ; Text_1: “I’m the text”
00401025 INT 2C
00401027 PUSH 0 ; hOwner = NULL
00401029 INT 2C
0040102B CALL <JMP.&user32.MessageBoxA> ; MessageBoxA, display the 1° message box
00401030 POP EDX ; edx -> “easy sEmple”
00401031 PUSH 0 ; Style = MB_OK|MB_APPLMODAL
00401033 PUSH msgbox.0040300C ; Title_2: “Int2c…”
00401038 PUSH EDX ; Text_2: “easy sEmple” (”sEmple” and not “sample”)
00401039 PUSH 0 ; hOwner = NULL
0040103B CALL <JMP.&user32.MessageBoxA> ; MessageBoxA, display the 2° message box
00401040 PUSH 0 ; ExitCode = 0
00401042 CALL <JMP.&kernel32.ExitProcess> ; ExitProcess

This is the static analysis of the snippet above, it’s not right! Try running the attached exe (I can assure you it’s not dangerous) and you’ll see the real behaviour of this piece of code. It displays two message boxes with the real messages, the first one with:
caption: “Int2c…”
text: “Obfusction sample”
and the second with:
caption: “Int 2c…”
text: “easy sample” (it’s not “sEmple”)

Why? Well, it’s pretty simple:
Code:
0040100B CD 2C INT 2C ; int2c changes edx value
0040100D 806A 06 09 SUB BYTE PTR DS:[EDX+6],9 ; edx = 40100D, it changes 403015 into 40300C
00401011 8D05 15304000 LEA EAX,DWORD PTR DS:[403015] ; eax = 40300C, 40300C -> “Int2c…”

Execution of int 2c changes edx value, and if you don’t know anything about it you’ll have a wrong static analysis. It’s a simple code obfuscation, but you have to take care of it for sure.

Now, try loading the attached exe file into Ollydbg. These are the results I got from my tests based on an XP sp1 and sp2 machines.

Ollydbg and XP sp1
The file runs fine when you launch it with F9 key, it shows the real messages.
The problems arise when you step through the code with “step into”(F7) or “step over”(F8) modes. No matter if you are stepping with F7 or F8, executing an int 2c instruction the debugger won’t break unless you have inserted a bpx onto one of the next instructions. A perfect trick for those who don’t know how to deal with it, you step the instruction and the malware performs all his nasty operations…

Ollydbg and XP sp2
The file runs fine when you launch it with F9 key, it shows the real messages.
F7 and F8 modes have the same behaviour, Ollydbg stops at the second instruction after “int 2c“; the problem is that edx value is not updated with the correct address value. Here’s what happen:
Code:
00401006 PUSH EDX ; edx = 403000
00401007 INT 2C ; F7 or F8 here and Ollydbg
00401009 PUSH 0 ; will break at 40100B
0040100B INT 2C ; F7 or F8 here and Ollydbg will
0040100D SUB BYTE PTR DS:[EDX+6],9 ; break here due to the exception:
; “Access violation when writing to [00000005]”


The exception occours because (at 40100D) edx value is 0xFFFFFFFF. edx value was changed by “int 2c” at 40100B.
The only way to obtain a correct edx value is to use F9 setting a bpx at 40100D.

I tried the same exe with Ida debugger on a XP sp2 machine. You can step using F8 without problem, but not with F7 because you’ll get the same access violation.

I think it’s definitely something to take care of, what do you think?

(File available here: http://www.box.net/shared/static/zebzmt8srt.zip)

JMI
December 19th, 2007, 23:06
Interesting, as always Zai. Thanks for your contribution.

Regards,

omega_red
December 24th, 2007, 08:24
Take a look at wrk-v1.2\base\ntos\ke\i386\trap.asm from Windows Research Kernel for full src of exception dispatcher/handler

Code:
IDTEntry _KiGetTickCount, D_INT332 ;2A: KiGetTickCount service
IDTEntry _KiCallbackReturn, D_INT332 ;2B: KiCallbackReturn
IDTEntry _KiRaiseAssertion, D_INT332 ;2C: KiRaiseAssertion service
IDTEntry _KiDebugService, D_INT332 ;2D: debugger calls
IDTEntry _KiSystemService, D_INT332 ;2E: system service calls
IDTEntry _KiTrap0F, D_INT032 ;2F: Reserved for APIC


Code:
page ,132
subttl "Raise Assertion"
;++
;
; Routine Description:
;
; This routine is entered as the result of the execution of an int 2c
; instruction. Its function is to raise an assertion.
;
; Arguments:
;
; None.
;
;--

ASSUME DS:NOTHING, SS:NOTHING, ES:NOTHING

ENTER_DR_ASSIST kira_a, kira_t, NoAbiosAssist

align dword
public _KiRaiseAssertion
_KiRaiseAssertion proc
push 0 ; push dummy error code

ENTER_TRAP kira_a, kira_t ;

sub dword ptr [ebp]+TsEip, 2 ; convert trap to a fault
mov ebx, [ebp]+TsEip ; set exception address
mov eax, STATUS_ASSERTION_FAILURE ; set exception code
jmp CommonDispatchException0Args ; finish in common code

_KiRaiseAssertion endp


...and more.

edit:
int 2b is interesting.
Code:
; NTSTATUS
; NtCallbackReturn (
; IN PVOID OutputBuffer OPTIONAL,
; IN ULONG OutputLength,
; IN NTSTATUS Status
; )
;
; Routine Description:
;
; This function returns from a user mode callout to the kernel
; mode caller of the user mode callback function.


"Backdoor" for r3->r0 jump. Return address is on the kernel stack, but maybe it's possible to do something fun with this proc.

Kayaker
December 24th, 2007, 09:21
That Int2C definition is only valid for Server2003 and Vista.
For XP the interrupt is defined as ntoskrnl!KiSetLowWaitHighThread. See the end of my post here:
http://www.woodmann.com/forum/showthread.php?t=11075

Checking with Livekd on XP, the Int2B KiCallbackReturn is the same though.