ayoubfaouzi / al-khaser

Public malware techniques used in the wild: Virtual Machine, Emulation, Debuggers, Sandbox detection.
GNU General Public License v2.0
5.95k stars 1.18k forks source link

Int2d detection always BAD and some strange workarounds #223

Closed SpriteOvO closed 3 years ago

SpriteOvO commented 3 years ago

image

I compiled this project myself and tested it on multiple machines, the Checking Interupt 0x2d result was always BAD.

I did the following checks:

Then I found any of the following workarounds that could make it work:

But I can't figure out why the exception handler doesn't work, and why these workarounds make it work. Looking at these workarounds, it seems like the optimizer screwed something up, but I didn't see any issues from IDA. If someone knows and is willing to explain, that would be great.

Im using Visual Studio 2019, compiled it with Release-x64 configuration. And here is the binary file I compiled: Release.zip

Thanks in advance!

gsuberland commented 3 years ago

I have a hunch here, so please try adding /d2FH4- to your compiler flags in the project settings (Configuration Properties -> C/C++ -> Command Line) to see if this fixes it.

SpriteOvO commented 3 years ago

@gsuberland Thanks for reply, but it still shows BAD.

image

gsuberland commented 3 years ago

Sorry, that should've been -d2FH4- in the C/C++ command line options, and -d2:-FH4- in the linker command line options.

SpriteOvO commented 3 years ago

Sorry, that should've been -d2FH4- in the C/C++ command line options, and -d2:-FH4- in the linker command line options.

@gsuberland I added them both, but it still shows BAD.

gsuberland commented 3 years ago

I can't reproduce the issue on my system. The only difference I can see between your binary and the one I compiled straight out of the repo is that yours imports __CxxFrameHandler4 from VCRUNTIME140_1.DLL, and mine doesn't. This could be related to a new compiler feature described here.

gsuberland commented 3 years ago

Can you post the full set of compiler flags for the x64 build target, from the command line page?

SpriteOvO commented 3 years ago

Can you post the full set of compiler flags for the x64 build target, from the command line page?

@gsuberland MSBuildDetailedOutput.txt It is these contents, right?

gsuberland commented 3 years ago

Not quite what I was looking for, but that works. Those logs show that you are passing /d2FH4 to the compiler, not /d2FH4-.

Line 1453:

1>    D:\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.28.29333\bin\HostX86\x64\CL.exe /c /Zi /nologo /W3 /WX- /diagnostics:column /sdl /O2 /Oi /GL /D NDEBUG /D _CONSOLE /D _UNICODE /D UNICODE /Gm- /EHsc /MD /GS /Gy /fp:precise /Qspectre /permissive- /Zc:wchar_t /Zc:forScope /Zc:inline /Yc"pch.h" /Fp"x64\Release\al-khaser.pch" /Fo"x64\Release\\" /Fd"x64\Release\vc142.pdb" /Gd /TP /FC /errorReport:prompt /d2FH4 pch.cpp
SpriteOvO commented 3 years ago

Not quite what I was looking for, but that works. Those logs show that you are passing /d2FH4 to the compiler, not /d2FH4-.

Yes, I found this command without dash from the link you just gave me. /d2FH4 and /d2FH4- I tried both, did not work. :(

1> D:\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.28.29333\bin\HostX86\x64\CL.exe /c /Zi /nologo /W3 /WX- /diagnostics:column /sdl /O2 /Oi /GL /D NDEBUG /D _CONSOLE /D _UNICODE /D UNICODE /Gm- /EHsc /MD /GS /Gy /fp:precise /Qspectre /permissive- /Zc:wchar_t /Zc:forScope /Zc:inline /Yc"pch.h" /Fp"x64\Release\al-khaser.pch" /Fo"x64\Release\\" /Fd"x64\Release\vc142.pdb" /Gd /TP /FC /errorReport:prompt /d2FH4- pch.cpp

SpriteOvO commented 3 years ago

Oh, I took a look at the article comments, without the dash is to enable it, I just noticed that I misunderstood the article content.

gsuberland commented 3 years ago

I have replicated this issue in the latest version of VS2019 and confirmed that this is unrelated to FH4 exception handling. Turning the feature off completely does not change the behaviour. I will continue to investigate the new features to see what might have caused this issue.

gsuberland commented 3 years ago

I've identified the issue.

The behaviour of the 0x2D interrupt is dependent on the value of the rax register at the time of the interrupt. In the unaffected builds, the code around the interrupt call looks something like this:

call AddVectoredExceptionHandler
mov rbx, rax
mov dword ptr [SwallowedException], 0x1
call __int2d
mov rcx, rbx
call RemoveVectoredExceptionHandler

In the affected builds, it instead looks like this:

call AddVectoredExceptionHandler
mov rbx, rax
mov eax, 1
mov [SwallowedException], eax
call __int2d
mov rcx, rbx
call RemoveVectoredExceptionHandler

Notice that in the unaffected builds, rax contains the return value of AddVectoredExceptionHandler. This is a pointer to a descriptor. By contrast, in the affected builds, the SwallowedException value of 1 is loaded through eax, which means that rax is 1 in these builds.

The value of rax affects the behaviour of the int 0x2d interrupt.

I noticed that this might be the culprit when I put a printf in the code and it started working, but only with certain text and only when I put it in a certain place. The return value of printf was overwriting the value in rax, and when I changed the text it changed the return value (based on the length of the printed string). When I moved the call, it changed how the compiler ordered the operations and how it assigned registers.

The behaviour of VEH doesn't appear to have changed since the VS2019 update. The only difference is that the updated compiler made different decisions about how to order the operations and how to allocate registers. Our original code only worked by sheer luck because the return value of the previous operation happened to not be a small number, so it didn't do anything special when put into rax. Your fixes happened to work for the same reason - they moved the calls around so that the value in rax just happened to not cause problems.

The actual code path taken that causes this issue is quite interesting. For reference, here's the assembly for __int2d, shown with opcode bytes:

0:  cd 2d                   int    0x2d
2:  90                      nop
3:  c3                      ret

When the int 0x2d instruction is hit, it causes the associated dispatch function registered in the IDT on to be executed. On Windows this is in KiDebugServiceTrap, which looks like this:

PUBLIC KiDebugServiceTrap
FUNC KiDebugServiceTrap
   /* No error code */
    EnterTrap TF_SAVE_ALL

    /* Increase Rip to skip the int3 */
    inc qword ptr [rbp + KTRAP_FRAME_Rip]

    /* Dispatch the exception (Params = service, buffer, legth) */
    DispatchException STATUS_BREAKPOINT, 3, [rbp+KTRAP_FRAME_Rax], [rbp+KTRAP_FRAME_Rcx], [rbp+KTRAP_FRAME_Rdx]

    /* Return */
    ExitTrap TF_SAVE_ALL
ENDFUNC

We can see here that it increments the rip register value in the trap frame, by one. Because we're triggering this from an int 0x2d instruction, this actually moves the instruction pointer to half way into the instruction instead. It then calls DispatchException, passing the values of rax, rcx, and rdx as three parameters. The code then constructs an EXCEPTION_RECORD structure that looks like this:

EXCEPTION_RECORD.ExceptionCode = STATUS_BREAKPOINT;
EXCEPTION_RECORD.ExceptionFlags = 0;
EXCEPTION_RECORD.ExceptionRecord = 0;
EXCEPTION_RECORD.ExceptionAddress = KTRAP_FRAME.rip; // address of int 0x2d plus 1
EXCEPTION_RECORD.NumberParameters = 3;
EXCEPTION_RECORD.ExceptionInformation[0] = KTRAP_FRAME.rax;
EXCEPTION_RECORD.ExceptionInformation[1] = KTRAP_FRAME.rcx;
EXCEPTION_RECORD.ExceptionInformation[2] = KTRAP_FRAME.rdx;

The KEXCEPTION_FRAME structure is then populated with all of the remaining registers and these structures are dispatched via KiDispatchException. The KiDispatchException implementation checks if the ExceptionCode is STATUS_BREAKPOINT and, if so, it undoes the increment that the KiDebugServiceTrap handler performed, by decrementing rip in the captured context structure. At this point rip is back where it started.

At this point the control flow ends up in KdpTrap, which implements debug services. When the exception record is STATUS_BREAKPOINT, as it is here, it uses the value in ExceptionInformation[0] as the debug service identifier. This is for things like debug printing and prompting. The defined values are:

 #define BREAKPOINT_BREAK                    0
 #define BREAKPOINT_PRINT                    1
 #define BREAKPOINT_PROMPT                   2
 #define BREAKPOINT_LOAD_SYMBOLS             3
 #define BREAKPOINT_UNLOAD_SYMBOLS           4
 #define BREAKPOINT_COMMAND_STRING           5

This is what the different rax values do when we hit an int 0x2d. The code stores the program counter from the context record and allows each of these service calls to modify it.

This is why many blogs mention setting rax to 1, 3, 4, or 5 in relation to this trick. In our case we don't care about the instruction pointer incrementing behaviour (in fact it gets incremented twice - once in KdpStub and once in KdpTrap), but rather the fact that it raises an exception when the debug trap handler is called. That's why we want rax to be 0, or some other value that isn't associated with a kernel debug service (e.g. rax=0x69 works just fine too).

If we set rax to 0 explicitly, at the start of int2d_x64.asm, the code suddenly starts working! This is because we are specifying the breakpoint command.

This is such a tiny fix that I'll apply it directly in the main branch.

gsuberland commented 3 years ago

Fix applied and I've verified that it's working as intended now. Thanks for submitting this one, I don't think I'd have ever found a fix without a sample executable to look at!

SpriteOvO commented 3 years ago

@gsuberland Thanks for your detailed explanation and fix too!