Skip to content

Commit

Permalink
Merge pull request #4 from chkp-aliaksandrt/mr-001
Browse files Browse the repository at this point in the history
DebugBreak, debug flags, etc (fixes by AlexC)
  • Loading branch information
chkp-alexanderc authored Sep 19, 2022
2 parents 929665b + 7cd7e92 commit f2f00a2
Show file tree
Hide file tree
Showing 5 changed files with 231 additions and 20 deletions.
4 changes: 2 additions & 2 deletions _includes/footer.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
<nav class="navbar navbar-default navbar-fixed-bottom">
<div class="container footer-content">

Made with <font class="a-pink"><i class ="fa fa-heart"></i></font> by Check Point Research | <a class="a-pink" href="https://research.checkpoint.com">Research blog</a> | <a class="a-pink" href="https://research.checkpoint.com/about-us/">About Us</a> | <a class="a-pink" href="https://github.com/CheckPointSW"><i class="fa fa-github fa-lg"></i></a> <a class="a-pink" href="https://twitter.com/_CPResearch_"><i class="fa fa-twitter fa-lg"></i></a>
Made with <font class="a-pink"><i class ="fa fa-heart"></i></font> to serve the community by Check Point Research | <a class="a-pink" href="https://research.checkpoint.com">Research blog</a> | <a class="a-pink" href="https://research.checkpoint.com/about-us/">About Us</a> | <a class="a-pink" href="https://github.com/CheckPointSW"><i class="fa fa-github fa-lg"></i></a> <a class="a-pink" href="https://twitter.com/_CPResearch_"><i class="fa fa-twitter fa-lg"></i></a>

<br>

© 1994-2020 Check Point Software Technologies LTD | All rights reserved | Property of CheckPoint.com
© 1994-2022 Check Point Software Technologies LTD | All rights reserved | Property of CheckPoint.com


</div>
Expand Down
52 changes: 42 additions & 10 deletions _techniques/assembly.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ tags: assembly

* [1. INT 3](#int3)
* [2. INT 2D](#int2d)
* [3. ICE](#ice)
* [4. Stack Segment Register](#ss_register)
* [5. Instruction Counting](#instruction-counting)
* [6. POPF and Trap Flag](#popf_and_trap_flag)
* [7. Instruction Prefixes](#instruction_prefixes)
* [3. DebugBreak](#debugbreak)
* [4. ICE](#ice)
* [5. Stack Segment Register](#ss_register)
* [6. Instruction Counting](#instruction-counting)
* [7. POPF and Trap Flag](#popf_and_trap_flag)
* [8. Instruction Prefixes](#instruction_prefixes)
* [Mitigations](#mitigations)
<br />

Expand Down Expand Up @@ -123,7 +124,38 @@ bool IsDebugged()
<hr class="space">

<br />
<h3><a class="a-dummy" name="ice">3. ICE</a></h3>
<h3><a class="a-dummy" name="debugbreak">3. DebugBreak</a></h3>
As written in <a href="https://docs.microsoft.com/en-us/windows/win32/api/debugapi/nf-debugapi-debugbreak">DebugBreak documentation</a>, "<tt>DebugBreak</tt> causes a breakpoint exception to occur in the current process. This allows the calling thread to signal the debugger to handle the exception".

If the program is executed without a debugger, the control will be passed to the exception handler. Otherwise, the execution will be intercepted by the debugger.

<hr class="space">

<b>C/C++ Code</b>
<p></p>

{% highlight c %}

bool IsDebugged()
{
__try
{
DebugBreak();
}
__except(EXCEPTION_BREAKPOINT)
{
return false;
}

return true;
}

{% endhighlight %}

<hr class="space">

<br />
<h3><a class="a-dummy" name="ice">4. ICE</a></h3>
"<tt>ICE</tt>" is one of Intel's undocumented instructions. Its opcode is <tt>0xF1</tt>. It can be used to detect if the program is traced.

If <tt>ICE</tt> instruction is executed, the <tt>EXCEPTION_SINGLE_STEP</tt> (<tt>0x80000004</tt>) exception will be raised.
Expand Down Expand Up @@ -155,7 +187,7 @@ bool IsDebugged()
<hr class="space">

<br />
<h3><a class="a-dummy" name="ss_register">4. Stack Segment Register</a></h3>
<h3><a class="a-dummy" name="ss_register">5. Stack Segment Register</a></h3>
This is a trick that can be used to detect if the program is being traced.
The trick consists of tracing over the following sequence of assembly instructions:
{% highlight asm %}
Expand Down Expand Up @@ -200,7 +232,7 @@ movss_not_being_debugged:
<hr class="space">

<br />
<h3><a class="a-dummy" name="instruction-counting">5. Instruction Counting</a></h3>
<h3><a class="a-dummy" name="instruction-counting">6. Instruction Counting</a></h3>
This technique abuses how some debuggers handle <tt>EXCEPTION_SINGLE_STEP</tt> exceptions.

The idea of this trick is to set hardware breakpoints to each instruction in some predefined sequence (e.g. sequence of <tt>NOP</tt>s). Execution of the instruction with a hardware breakpoint on it raises the <tt>EXCEPTION_SINGLE_STEP</tt> exception which can be caught by a vectored exception handler. In the exception handler, we increment a register which plays the role of instruction counter (<tt>EAX</tt> in our case) and the instruction pointer <tt>EIP</tt> to pass the control to the next instruction in the sequence. Therefore, each time the control is passed to the next instruction in our sequence, the exception is raised and the counter is incremented. After the sequence is finished, we check the counter and if it is not equal to the length of our sequence, we consider it as if the program is being debugged.
Expand Down Expand Up @@ -299,7 +331,7 @@ bool IsDebugged()
<hr class="space">

<br />
<h3><a class="a-dummy" name="popf_and_trap_flag">6. POPF and Trap Flag</a></h3>
<h3><a class="a-dummy" name="popf_and_trap_flag">7. POPF and Trap Flag</a></h3>
This is another trick that can indicate whether a program is being traced.

There is a Trap Flag in the Flags register. When the Trap Flag is set, the exception <tt>SINGLE_STEP</tt> is raised. However, if we traced the code, the Trap Flag will be cleared by a debugger so we won't see the exception.
Expand Down Expand Up @@ -337,7 +369,7 @@ bool IsDebugged()
<hr class="space">

<br />
<h3><a class="a-dummy" name="instruction_prefixes">7. Instruction Prefixes</a></h3>
<h3><a class="a-dummy" name="instruction_prefixes">8. Instruction Prefixes</a></h3>
This trick works only in some debuggers. It abuses the way how these debuggers handle instruction prefixes.

If we execute the following code in OllyDbg, after stepping to the first byte <tt>F3</tt>, we'll immediately get to the end of <tt>try</tt> block. The debugger just skips the prefix and gives the control to the <tt>INT1</tt> instruction.
Expand Down
37 changes: 36 additions & 1 deletion _techniques/debug-flags.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ tags: debug-flags
* [2.2. NtGlobalFlag](#manual-checks-ntglobalflag)
* [2.3. Heap Flags](#manual-checks-heap-flags)
* [2.4. Heap Protection](#manual-checks-heap-protection)
* [2.5. Check KUSER_SHARED_DATA structure](#kuser_shared_data)
* [Mitigations](#mitigations-manual-checks)
<br />

Expand Down Expand Up @@ -711,7 +712,7 @@ bool Check()
{% endhighlight %}

<br />
<h4><a class="a-dummy" name="manual-checks-heap-protection">2.3. Heap Protection</a></h4>
<h4><a class="a-dummy" name="manual-checks-heap-protection">2.4. Heap Protection</a></h4>
If the <tt>HEAP_TAIL_CHECKING_ENABLED</tt> flag is set in <tt>NtGlobalFlag</tt>, the sequence <tt>0xABABABAB</tt> will be appended (twice in 32-Bit and 4 times in 64-Bit Windows) at the end of the allocated heap block.

If the <tt>HEAP_FREE_CHECKING_ENABLED</tt> flag is set in <tt>NtGlobalFlag</tt>, the sequence <tt>0xFEEEFEEE</tt> will be appended if additional bytes are required to fill in the empty space until the next memory block.
Expand All @@ -738,6 +739,35 @@ bool Check()

{% endhighlight %}

<br />
<h4><a class="a-dummy" name="kuser_shared_data">2.5. Check KUSER_SHARED_DATA structure</a></h4>
This technique was originally described as an <a href="https://github.com/mrexodia/TitanHide/issues/18">issue for TitanHide</a>, a kernel driver to hide debuggers from detection. The detailed documentation for the structure <tt>KUSER_SHARED_DATA</tt> and its fields is available <a href="https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/ntexapi_x/kuser_shared_data/index.htm">here</a>.

Here is what the author of the issue wrote in the post regarding the features of the structure and its appropriate field:
<i>"0x7ffe02d4 is actually 0x7ffe0000 + 0x2d4. 0x7ffe0000 is the fixed user mode address of the KUSER_SHARED_DATA structure that contains data that is shared between user mode and the kernel (though user mode doesn't have write access to it). The struct has some interesting properties:</i>
<li><i>its address is fixed and has been in all Windows versions since it was introduced</i></li>
<li><i>its user mode address is the same in 32 bit and 64 bit mode</i></li>
<li><i>all offsets and sizes are strictly fixed, and new fields are only ever appended or added in place of unused padding space</i></li>

<br>
<i>Hence this program will work in 32 bit Windows 2000 and 64 bit Windows 10 without recompiling".
</i>

<hr class="space">

<b>C/C++ Code</b>
<p></p>

{% highlight c %}

bool check_kuser_shared_data_structure()
{
unsigned char b = *(unsigned char*)0x7ffe02d4;
return ((b & 0x01) || (b & 0x02));
}

{% endhighlight %}

<br />
<h4><a class="a-dummy" name="mitigations-manual-checks">Mitigations</a></h4>

Expand Down Expand Up @@ -835,3 +865,8 @@ for (SIZE_T offset = 0; offset < nDwordsToPatch; offset++)
*((PDWORD)pHeapEnd + offset) = 0;

{% endhighlight %}

<br />
<b>For KUSER_SHARED_DATA:</b>
<p></p>
For a possible mitigation, please check the link when the technique is described (with the issue for TitanHide) and also a draft code for patching <tt>kdcom.dll</tt> <a href="https://gist.github.com/anonymous/b5024c25634fc36e699cd9d041224531">here</a>.
27 changes: 23 additions & 4 deletions _techniques/interactive.md
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,7 @@ The idea is simple. If a debugger is not present and <tt>kernel32!OutputDebugStr

<hr class="space">

<b>C/C++ Code</b>
<b>C/C++ Code (variant1)</b>
<p></p>

{% highlight c %}
Expand All @@ -401,14 +401,34 @@ bool IsDebugged()
return false;

DWORD dwLastError = GetLastError();
OutputDebugString(L"AntiDebug_OutputDebugString");
OutputDebugString(L"AntiDebug_OutputDebugString_v1");
return GetLastError() != dwLastError;
}

{% endhighlight %}

<hr class="space">

<b>C/C++ Code (variant2)</b>
<p></p>

{% highlight c %}

bool IsDebugged()
{
if (IsWindowsVistaOrGreater())
return false;

DWORD dwErrVal = 0x666;
SetLastError(dwErrVal);
OutputDebugString(L"AntiDebug_OutputDebugString_v2");
return GetLastError() != dwErrVal;
}

{% endhighlight %}

<hr class="space">

<br />
<h3><a class="a-dummy" name="mitigations">Mitigations</a></h3>
During debugging, it is better to skip suspicious function calls (e.g. fill them with <tt>NOP</tt>s).
Expand All @@ -426,5 +446,4 @@ If you write an anti-anti-debug solution, all the following functions can be hoo
* <tt>user32!SwitchDesktop</tt>
* <tt>kernel32!OutputDebugStringW</tt>

Hooked functions can check input arguments and modify the original function behavior.

Hooked functions can check input arguments and modify the original function behavior.
131 changes: 128 additions & 3 deletions _techniques/misc.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ tags: misc
* [4. DbgPrint()](#dbgprint)
* [5. DbgSetDebugFilterState()](#dbgsetdebugfilterstate)
* [6. NtYieldExecution() / SwitchToThread()](#switchtothread)
* [7. VirtualAlloc() / GetWriteWatch()](#getwritewatch)
* [Mitigations](#mitigations)
<br />

Expand All @@ -44,12 +45,16 @@ The following functions can be used:
{% highlight c %}

const std::vector<std::string> vWindowClasses = {
"antidbg",
"ID", // Immunity Debugger
"ntdll.dll", // peculiar name for a window class
"ObsidianGUI",
"OLLYDBG",
"Rock Debugger",
"SunAwtFrame",
"Qt5QWindowIcon"
"WinDbgFrameClass", // WinDbg
"ID", // Immunity Debugger
"Zeta Debugger",
"Rock Debugger",
"ObsidianGUI",
};

bool IsDebugged()
Expand Down Expand Up @@ -363,6 +368,124 @@ bool IsDebugged()

<hr class="space">

<br />
<h3><a class="a-dummy" name="getwritewatch">7. VirtualAlloc() / GetWriteWatch()</a></h3>
This technique was described as a <a href="https://codeinsecurity.wordpress.com/2018/01/24/anti-debug-with-virtualallocs-write-watch/">suggestion</a> for a famous al-khaser solution, a tool for testing VMs, debuggers, sandboxes, AV, etc. against many malware-like defences.

The idea is drawn from the <a href="https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-getwritewatch">documentation</a> for <tt>GetWriteWatch</tt> function where the following is stated in a "Remarks" section:

<i>"When you call the <b>VirtualAlloc</b> function to reserve or commit memory, you can specify <b>MEM_WRITE_WATCH</b>. This value causes the system to keep track of the pages that are written to in the committed memory region. You can call the <b>GetWriteWatch</b> function to retrieve the addresses of the pages that have been written to since the region has been allocated or the write-tracking state has been reset".</i>

This feature can be used to track debuggers that may modify memory pages outside the expected pattern.

<hr class="space">

<b>C/C++ Code (variant 1)</b>
<p></p>

{% highlight c %}

bool Generic::CheckWrittenPages1() const {
const int SIZE_TO_CHECK = 4096;

PVOID* addresses = static_cast<PVOID*>(VirtualAlloc(NULL, SIZE_TO_CHECK * sizeof(PVOID), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));
if (addresses == NULL)
{
return true;
}

int* buffer = static_cast<int*>(VirtualAlloc(NULL, SIZE_TO_CHECK * SIZE_TO_CHECK, MEM_RESERVE | MEM_COMMIT | MEM_WRITE_WATCH, PAGE_READWRITE));
if (buffer == NULL)
{
VirtualFree(addresses, 0, MEM_RELEASE);
return true;
}

// Read the buffer once
buffer[0] = 1234;

ULONG_PTR hits = SIZE_TO_CHECK;
DWORD granularity;
if (GetWriteWatch(0, buffer, SIZE_TO_CHECK, addresses, &hits, &granularity) != 0)
{
return true;
}
else
{
VirtualFree(addresses, 0, MEM_RELEASE);
VirtualFree(buffer, 0, MEM_RELEASE);

return (hits == 1) ? false : true;
}
}

{% endhighlight %}

<hr class="space">

<b>C/C++ Code (variant 2)</b>
<p></p>

{% highlight c %}

bool Generic::CheckWrittenPages2() const {
BOOL result = FALSE, error = FALSE;

const int SIZE_TO_CHECK = 4096;

PVOID* addresses = static_cast<PVOID*>(VirtualAlloc(NULL, SIZE_TO_CHECK * sizeof(PVOID), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));
if (addresses == NULL)
{
return true;
}

int* buffer = static_cast<int*>(VirtualAlloc(NULL, SIZE_TO_CHECK * SIZE_TO_CHECK, MEM_RESERVE | MEM_COMMIT | MEM_WRITE_WATCH, PAGE_READWRITE));
if (buffer == NULL)
{
VirtualFree(addresses, 0, MEM_RELEASE);
return true;
}

// Make some calls where a buffer *can* be written to, but isn't actually edited because we pass invalid parameters
if (GlobalGetAtomName(INVALID_ATOM, (LPTSTR)buffer, 1) != FALSE
|| GetEnvironmentVariable("This variable does not exist", (LPSTR)buffer, 4096 * 4096) != FALSE
|| GetBinaryType("This name does not exist", (LPDWORD)buffer) != FALSE
|| HeapQueryInformation(0, (HEAP_INFORMATION_CLASS)69, buffer, 4096, NULL) != FALSE
|| ReadProcessMemory(INVALID_HANDLE_VALUE, (LPCVOID)0x69696969, buffer, 4096, NULL) != FALSE
|| GetThreadContext(INVALID_HANDLE_VALUE, (LPCONTEXT)buffer) != FALSE
|| GetWriteWatch(0, &result, 0, NULL, NULL, (PULONG)buffer) == 0)
{
result = false;
error = true;
}

if (error == FALSE)
{
// A this point all calls failed as they're supposed to
ULONG_PTR hits = SIZE_TO_CHECK;
DWORD granularity;
if (GetWriteWatch(0, buffer, SIZE_TO_CHECK, addresses, &hits, &granularity) != 0)
{
result = FALSE;
}
else
{
// Should have zero reads here because GlobalGetAtomName doesn't probe the buffer until other checks have succeeded
// If there's an API hook or debugger in here it'll probably try to probe the buffer, which will be caught here
result = hits != 0;
}
}

VirtualFree(addresses, 0, MEM_RELEASE);
VirtualFree(buffer, 0, MEM_RELEASE);

return result;
}

{% endhighlight %}

<hr class="space">

<br />
<h3><a class="a-dummy" name="mitigations">Mitigations</a></h3>
During debugging: Fill anti-debug pr anti-traced checks with <tt>NOP</tt>s.
Expand All @@ -385,3 +508,5 @@ During debugging: Fill anti-debug pr anti-traced checks with <tt>NOP</tt>s.
5. For <tt>DbgSetDebugFilterState()</tt>: Hook <tt>ntdll!NtSetDebugFilterState()</tt>. If the process is running with debug privileges, return unsuccessfully from the hook.

6. For <tt>SwitchToThread</tt>: Hook <tt>ntdll!NtYieldExecution()</tt> and return an unsuccessful status from the hook.

7. For <tt>GetWriteWatch</tt>: Hook <tt>VirtualAlloc()</tt> and <tt>GetWriteWatch()</tt> to track if <tt>VirtualAlloc()</tt> is called with <tt>MEM_WRITE_WATCH</tt> flag. If it is the case, check what is the region to track and return the expected value in <tt>GetWriteWatch()</tt>.

0 comments on commit f2f00a2

Please sign in to comment.