Normal view

There are new articles available, click to refresh the page.
Before yesterdayBlog - Signal Labs

Rediscovering Epic Games 0-Days (Forever Unpatched?)

6 July 2022 at 22:30

How It Started

So one day I was browsing ZDI (usually its the same sort of targets, lots of Foxit bugs, Adobe, Ivanti, etc) and noticed a couple entries by @izobashi (ZDI-22-537, ZDI-22-538) for Epic Games Launcher, there were two things that stood out:

  1. It wasn’t patched at time of advisory release (which means no patch in 120 days since reporting it, maybe unpatched forever?)

  2. It was file overwrite and file deletion bugs which can be leveraged for LPE, and affected the installer (these bugs are common and very familiar to me)

Now as a gamer (albeit not one with Epic’s launcher installed) I’ve had the displeasure of noting multiple vulnerabilities in gaming related software (alongside a strong dislike for anti-cheats in my kernel or acting as a hypervisor, though I understand why they do), I figured I’d check now if I can find these same bugs in the latest version of Epic’s launcher.

Although we don’t have a PoC or really any detailed information from the ZDI listings, the bugs are familiar enough that we can jump right in with our trusty ProcMon and see what we find.

Finding the bugs

After installing Epic’s launcher I immediately find the installer in C:\Windows\Installer (shh! its a secret directory), I know this is Epic’s MSI due to the signature matching Epic as expected:

The reason bugs in installers are common can be noted to a few factors:

  1. They typically auto-elevate to SYSTEM (even if you’re just a lowly non-administrative user)

  2. They can be executed in install / remove / repair modes that perform various operations, including file operations (copy, rename, delete) and can run arbitrary bundled scripts

  3. People don’t spend security $$$ on hardening their installers? (I don’t know, but it sure seems like it)

Before we go any further, lets configure our ProcMon, but first — why ProcMon?:

  1. Tells us what processes are doing (to an extent)

    1. What files they’re accessing

    2. What permissions they’re operating at

    3. What files / registry entries they’re reading / writing / deleting

  2. Is filterable

    1. Write rules to only show / capture what you’re interested in

Now to configure ProcMon, what are we looking for exactly?:

  1. File creation/opening events that satisfy the following:

    1. Operating on folders or files we can control

      1. Why? So we can redirect them via symlinks of course

        1. If it overwrites a file in C:\windows\system32, how would our lowly non-admin user control it?

        2. If it overwrites a file in C:\users\lowly_user\Desktop that we can control, its a different story!

        3. (Or any other location we have write or similar access to)

    2. ?? (There are more potentially interesting events, like paths or files that don’t exist that we may create, etc… but for this exercise we don’t care)

Now you may think if we exclude the following folders, that’d be good enough to meet our requirements above:

The point of the above is:

  • Capture CreateFile and Load Image operations

  • Ensure username contains NT (e.g. NT SYSTEM)

  • Exclude folders we can’t modify / control:

    • C:\ProgramData\Microsoft

    • C:\Program Files*

    • C:\Windows\

Can you think what the problem with the above excluded directories is?

..

….

Well actually there are multiple (for example, C:\windows\temp is typically user-writable! Meaning we actually can have some control over the contents of this directory, yet in the above filters we exclude it, although this isn’t an issue for this particular example).

The actual issue is excluding all of C:\Program Files, because Epic actually applies a permissive DACL on c:\Program Files (x86)\Epic Games\Launcher and its subfolders! (Not a great thing to do in general…)

This can be verified with icacls:

(Tip: Enumerate ACLs on everything -> install software -> enumerate again -> diff!)

Ok so lets ensure the path C:\Program Files (x86)\Epic Games\Launcher is included in our procmon filter and start capturing (In this case I’m going to remove the exclude for C:\Program Files and specifically include the launcher path above — once we have the trace we can play with the filters to see other interesting events too, like searching for operations that begin with Set to see renames, deletions, etc).

Lets right click the .msi file and press Repair, wait for it to complete and see if anything interesting happens.

Well that’s a lot.

To be honest its not surprising, the .msi in repair mode is there to, well, “repair” its files (which is typically achieved by replacing them with a pre-packaged good version).

Since the ACLs on this folder are weak, what would happen if we were to redirect one of these files elsewhere?

To test this, I’m going to show the two 0-days, first is the file overwrite.

Lets start by grabbing the symbolic testing tools from GPZ and compile them.

Now lets turn a folder (in this case, C:\Program Files (x86)\Epic Games\Launcher\Engine\Binaries\Win32 into a symlink pointing to \RPC Control:

(If you can’t delete Win32, try stopping Epic’s running processes first)

Ok now lets try the repair operation again and see what happens.

Ok so its looking for a DLL in our Win32 folder, however Win32 now points to \RPC Control and there doesn’t exist any \RPC Control\msvcp140_2.dll for the target to obtain a handle to.

Lets try creating this, and redirecting it to C:\Windows\System32\License.rtf as an example, now lets first note the size of our License.rtf file:

Ok so yours is likely not 7 bytes like mine, but note that mine only allows modification by Administrators or higher, users just have RX.

Now lets create the link to it:

Now press Retry on the msi error, and you’ll notice it continues and pops up another error (for a different DLL!)

However, note that License.rtf has been overwritten!

This is the first 0-day, arbitrary file overwrite!
To ensure this sticks, we can now delete Win32, recreate it as a regular folder (mkdir Win32) and press Retry, this should cause the installer to continue without any more errors and leave the file overwritten.
However, we can turn this into an arbitrary deletion vulnerability by causing the target to now delete License.rtf!

We can do this by simply pressing Cancel instead of retry! The target MSI will rollback its operations, and this will cause it to delete the overwritten file entirely!

With these two bugs (file overwrite + file deletion) we can actually leverage them for LPE, there’s other posts on achieving this (e.g. https://www.zerodayinitiative.com/blog/2022/3/16/abusing-arbitrary-file-deletes-to-escalate-privilege-and-other-great-tricks)

Whos taking bets how long these bugs will remain as 0-days in Epic’s launcher?

Fuzzing WeChat’s Wxam Parser

7 August 2022 at 19:30

Background

WeChat (if you haven’t heard of it) is a super popular chat app similar to the likes of WhatsApp, and runs on iOS, Android, Windows and MacOS.
Being a chat app, it handles various file formats like images and videos, and also propriety formats like “Wxam” (which honestly I haven’t researched before so you’ll see how I approached that).

You’ll also see below some of the challenges I had in my harnessing of the target and how my initial fuzzer framework I chose had to be replaced due to lack of support for certain functionality that WeChat used (and how I debugged this).

Researching the Target

Now that we know what WeChat is we can look at how I decided to write a fuzzer (in 1 day!) for this target!
It started by deciding I wanted to blog about fuzzing something, previously I’ve had blogs on Logic bugs and I wanted to balance that with some cool fuzzing target I haven’t looked at before, so I started by browsing ZDI to see if any displayed targets were interesting.

I noticed a few entries for WeChat like the below:

ZDI WeChat bug disclosures

Now at this point I know what WeChat is, but I have no idea what WXAM is (but its safe to guess its some format that gets parsed).

So my next step was to simply install WeChat in a VM! Note that here I’m targeting the Windows build of WeChat, for the following reasons:

  1. I want this to be quick, its primarily for this blog post and I know I can fuzz Windows targets faster than iOS/Android

  2. If this parser also exists on other platforms, it probably isn’t much different (potentially if I find the bug on Windows, it’ll exist on the other platforms)

Now its installed and I have a bunch of executables and DLL files in C:\Program Files (x86)\Tencent\WeChat, so how do I find the WXAM parsing functionality?

Finding the Target

A good starting point may be to dump all the imported & exported functions from all the executables and DLLs and search for anything with the name “wxam” in it, but I went a different route — I simply guessed and opened the DLL that sounded interesting in IDA!

For me, looking at the list of DLLs I spotted “WeChatWin.dll”, this sounds like a main DLL for WeChat that handles certain Windows specific APIs or something? Who knows, but it stood out more than some of the other DLLs, so I opened this in IDA.

This DLL took a while to load, its pretty large (~40mb), once done the first thing I did was search in functions, imports & exports for the name “wxam”, there I found:

wxam2pic imported function shown in WeChatWin.dll

We spot an imported function named “wxam2pic” that lives in “VoipEngine.dll” — nice! This is a great starting point, it even sounds like a parser.

Before I look at wxam2pic in VoipEngine, I first examine cross-references to this import within WeChatWin.dll and see how WeChatWin uses this, I spot two functions that call this, including this one:

Usage of wxam2pic in WeChatWin.dll

Scrolling to the top of this function we spot:

Don’t you love debug prints?
This string alone implies the function we’re looking at is a “WxAMDecoderHelper”, specifically this function handles the “DecodeWxam” functionality — Awesome! This is exactly the type of function that corresponds with the ZDI entries we saw.

There’s something else notable about this function, look at how IDA shows the prototype:

Its a custom calling convention!

This means if we were to target this function for fuzzing directly, we’d have to match this custom parameter passing convention instead of Visual Studio’s provided options (fastcall, cdecl, etc).

Instead, I took a look at the function that calls this function, and I got:

(Note: ignore the function name itself, I named it this from what I saw!)

Nice, this function uses a standard calling convention (fastcall), takes only two arguments and calls the DecodeWxam function (handling the custom calling convention for us!)

We also see from the debug print that this function appears to decode the Wxam and then re-encode it as a jpeg, this would be a great function to fuzz!

(Note: There’s another decoder that transforms the Wxam to a GIF! We’re not going to look at that one in this blog, but its essentially the same).

Reversing the Target Function

Alright so I want to fuzz this function as it appears to take a Wxam file and parse it, lets analyze the parameters.

Lets view cross-references to this function to see how its called:

(Note: I named the read_file function myself, if you open this function you see a simple CreateFile + ReadFile operation on the provided fName variable!)

From this, I see the following:

  • A filename is provided to the function I myself named “read_file” and a buffer is returned in v11

  • The buffer and a value is passed to “isWxGF”, this function reads a header and the flag to determine if we should parse it further or not

    • Actually, turns out the input structure is a format of a 32bit input buffer pointer followed by a 32bit size of input. So isWxGF takes (pBuffer, buf_sz)

  • If we pass the “isWxGF” check, we call the decoder function passing through:

    • The address of an input structure that contains (pBuffer, buf_sz), the pseudocode looks similar to

      • InputStruct inputStruct = (pBuffer, buf_sz)

      • Where the first input to the decoder function is a pointer to our inputStruct

    • A pointer to a int containing the value 0

      • This pointer seems to be some output from the decoder, if its non-zero its assumed to be another valid pointer

This seems super easy to fuzz:

  • We can fuzz using shared-memory mode in a fuzzer like WinAFL

  • Our fuzz function will:

    • Call isWxGF; and if successful:

    • Calls the decoder

So I wrote a harness to do this in WinAFL, however:

This usually means our program is crashing before reaching the our fuzz function.

So I run WinAFL under WinDBG and see an invalid address dereference when trying to load the “WeChatWin.dll” file!

I analyze the DLL entry point and spot:

I see, this DLL uses CRT (also thread-local storage) — this causes issues with DynamoRIO (which I was using with WinAFL).

This can be confirmed by compiling my executable with CRT support and noting that WinAFL crashes before our process main executes at all!

So this means we can’t use DynamoRIO, our options include:

  • Using WinAFL in IntelPT mode (I’m using an AMD CPU, so no go here)

  • Use a different fuzzer

Well I chose a different fuzzer.

I could have gone the snapshot route with Nyx or what-the-fuzz, instead I decided to try Jackalope

This has a very similar command line to WinAFL, and uses TinyInst for instrumentation (no DynamoRIO!)

Upon trying this, it worked:

Its fuzzing, and we are getting new coverage!

At this point I stopped, I got the fuzzer working well enough I was happy for the day, next steps would include:

  • Analyzing coverage, ensuring we’re not hitting any roadblocks

  • Check stability / determinism, ensure there’s no globals we need to reset

    • Or just throw this into a snapshot fuzzer

  • Reverse the WXAM format and create better corpus, and a format-aware mutator

Also note that in the isWxGF function, I noted the header bytes it checks for and ensured my initial corpus had that header (so we start with an input that successfully passes that check).

There are other things I did in the harness, which are general fuzzing things like obtaining the non-exported function pointers to our target functions we wanted to fuzz.

I’ve included the harness I used below, along with the Jackalope command line I used to kick off fuzzing, feel free to take this and expand on it or view coverage to see how far it gets!

Overall this was a fun half a day exercise at quickly writing a basic fuzzing harness based on some ZDI entry.

Update — Android Bugs!

So, turns out some of the bugs I found from this fuzzer were reproducible on Android:

@h0mbre_ @domenuk ezpz? pic.twitter.com/CiLoUxQtb5

— Christopher (@Kharosx0) August 8, 2022

Files

I put all the files on my Github: https://github.com/Kharos102/BasicWXAMFuzzer

Want to Learn Fuzzing?

We offer Vulnerability Research & Fuzzing trainings live or self-paced (For our self-paced trainings, see: https://signal-labs.thinkific.com/collections)

For any questions, feel free to contact us!

Memory Corruption in vmware-vmx.exe

30 September 2022 at 18:11

Preface: Hypervisor Bugs?

Firstly — while the below is a memory corruption bug in VMware’s vmware-vmx.exe process, it is benign (not quite exploitable, partly why I’m comfortable dropping it here) but it is fun to talk about and came from personal VMware fuzzing adventures.

The Bug: A Tale of Two

To first reach the memory corruption bug, we have to take vmware-vmx.exe down the path of panicking!

This is because the actual memory corruption lives in VMware’s zlib.dll, this module is used when vmware-vmx.exe encounters an error and proceeds to create a crash-dump of itself and compress that to disk.

Snippet of the coredmp logic in vmware-vmx’s panic handler

Looking at xref’s to vmware’s panic handler, there’s quite a few ways to make the vmware-vmx process panic

Over 4k xrefs!

We just need to hit one! How can we do this? We need our first bug to hit the panic handler!
Turns out my VMware fuzzer found such a bug, and its in the LSI Logic handler in the process, in particular this line:

Yes, I am coverage-guided fuzzing btw

So this initial bug isn’t anything crazy, its essentially an ASSERT due to unexpected/malformed input, this bug alone will just crash our own VM and as such doesn’t really constitute a bug itself (unless you can do something else with it, I had ideas of continually restarting/crashing my own VM to take up crash-dump / log space on the host for instance)

However my fuzzer didn’t report this as an ASSERT, it found an actual memory corruption bug! Turns out during the panicing process in this instance data is sent to VMware’s zlib’s deflate function (for compression) and this code actually has an overflow read!

What happens here is that a buffer is looped over a set of iterations and for each iteration we read 8 bytes from the buffer, however it goes one iteration too far and attempts to read past the bounds of the buffer on the final iteration:

Oopsie!

Repro for Yourself

Want to test this yourself? Grab the bootx64.efi (uefi bootloader) I’ve made below, you can create a VMware vmdk and plant this in the vmdk’s “\EFI\Boot” folder, such that when you mount the vmdk the structure is “\EFI\Boot\bootx64.efi”.

When you launch the VM my bootloader will run and repro the crash for you (its not a 100% success rate, probably about 80%).

bootx64.efi: https://github.com/Kharos102/VmwareBlogBug

Windows Hotpatching & Process Injection

6 March 2023 at 07:01

Summary

I spent a day reversing syscalls and Kernel entry points to find new ways I could share memory or inject code between threads & processes, during which I decided to dive into Hotpatching on Windows and write a PoC which turned out to be a great exercise in working with the PE format.

Below I talk through Hotpatching, resulting in a PoC & PE32/32+ helper code.

What is hotpatching?

Hotpatching is a method supported throughout Windows for modifying running PE32/32+ objects in memory, including Kernel drivers & processes (even those in the Secure Kernel), usually to permit patching or updating code without requiring restarts. This is documented in a few places, including the PE32/32+ format (see an overview from Microsoft here: https://techcommunity.microsoft.com/t5/windows-os-platform-blog/hotpatching-on-windows/ba-p/2959541).

Enabling Hotpatch Support

Hotpatching is not enabled by default in all versions of the OS, its currently supported in Insider builds and in Azure edition of server builds (Azure editions are downloadable as ISOs or deployable in Azure directly, more information here: https://learn.microsoft.com/en-us/azure/automanage/automanage-hotpatch and here: https://learn.microsoft.com/en-us/windows-server/get-started/enable-hotpatch-azure-edition.

If you’re working with the raw ISO or an Insider build, you may need to set registry keys to turn on Hotpatching (as per the links above). Insider builds only require the registry keys to be set, which are included in the PoC at the end.

Hotpatching also relies on the presence of the Secure Kernel, though you can leverage Hotpatching without the Secure Kernel if you set an additional registry key.

All the information about Hotpatching can be gleamed from public documentation, including PE information here: https://microsoft.github.io/windows-docs-rs/doc/windows/Win32/System/SystemServices/struct.IMAGE_HOT_PATCH_INFO.html, here: https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_load_config_directory64 & through reversing of the NtManageHotPatch syscall in ntdll.dll and ntoskrnl.exe.

Pseudo Overview of the Hotpatching Process

In addition to the documentation above, below is my own general walkthrough of the Hotpatching process based on my PoC development.

Patches are created via the NtManageHotPatch syscall, this syscall takes multiple parameters which determine the operation to call. When we call it to create a patch it will expect to load a PE32/32+ file describing the patch.

These Hotpatch PE32/32+ files are like regular PE32/32+ executable images however they include Hotpatch entries (Including the IMAGE_HOT_PATCH_INFO struct here: https://microsoft.github.io/windows-docs-rs/doc/windows/Win32/System/SystemServices/struct.IMAGE_HOT_PATCH_INFO.html, the IMAGE_HOT_PATCH_BASE here: https://microsoft.github.io/windows-docs-rs/doc/windows/Win32/System/SystemServices/struct.IMAGE_HOT_PATCH_BASE.html & other IMAGE_HOT_PATCH_* structures.

The Hotpatch entries start with the IMAGE_HOT_PATCH_INFO struct which is stored in a section in the PE32/32+ file, pointed to by the HotPatchTableOffset field in the IMAGE_LOAD_CONFIG_DIRECTORY64 DataDirectory entry in the OptionalHeader of the PE32/32+ file.

The PE file is mapped into a system section by the Kernel, which parses the file to determine the offsets of the Hotpatch structures in the file, it then creates a Hotpatch entry in a list.

The Hotpatch will be valid for images of a certain checksum and timedatestamp, which will apply to any image with the corresponding checksum in its OptionalHeader, and its timedatestamp from its IMAGE_FILE_HEADER. This is how a Hotpatch file tells the system which image the Hotpatch is valid for, e.g. if we wanted to patch kernelbase.dll, we’d read the checksum and timedatestamp from kernelbase.dll and set the OriginalCheckSum and OriginalTimeDateStamp fields of our IMAGE_HOT_PATCH_BASE struct to those values.

Additionally, if the patch PE contains the exported function __PatchMainCallout__, it will be automatically invoked after the patch is loaded in a process.



Once the patch is loaded into the Kernel, depending on the type of patch it may automatically be applied to all running processes as the Kernel enumerates processes and calls a notification callback in ntdll.dll to handle checking for patches.

Limitations & Notes

While Hotpatching is a powerful feature, permitting code changes to multiple parts of the system, there are two main limitations (for non-Microsoft users)

  1. Administrator privileges is generally required to enable Hotpatching

  2. To globally apply a Hotpatched PE, the PE is required to be at least Microsoft signed or higher (preventing common injection of unsigned DLLs)

For 2. above, the PE does not need to be signed to be loaded into the Kernel list of Hotpatches, and you can still map the Hotpatch into your process by utilizing NtManageHotPatch, which provides a way to map the section handle of your Hotpatch regardless of its signature.

There are other behaviors of Hotpatching not mentioned here, such as targeting process by user SID, or the fact that processes may continually attempt to load your Hotpatch regardless of validity (which can cause processes to stop launching, or even act as a targeted DoS against certain processes).

Additionally if you target kernelbase the Hotpatched PE can be loaded outside of the typical notification callback, such as in LdrpInitializeKernel32Functions instead, which has its own interesting properties not discussed here.

I could also foresee uses of this by EDRs to supply ntdll & kernelbase patches, instead of their current approach of injecting + hooking.

Code Samples

Code samples (not complete) for Hotpatching (+ partial helper code for working with PE32 files) are included here: https://github.com/Signal-Labs/Hotpatching_PoC, the Hotpatch loader will take the compiled hotpatch_replace_vs file (which is expected to already have a LoadConfig table, which is possible if you compile with /GS for example) and create a new file that’s a clone with a Hotpatch entry (a partially valid entry, just enough to get it loaded as a Hotpatch record in the Kernel). It also includes support for enabling Hotpatching if you run as Admin.

In-Memory Disassembly for EDR/AV Unhooking

2 April 2023 at 21:33

Recap on EDR Hooks

EDR hooking isn’t a new thing, and is likely not new to you (otherwise see here), there’s plenty of samples online of unhooking NTDLL, though most tend to leverage direct syscalls or mapping of ntdll from disk or knowndlls.

Below we’ll walk through the hooks of a particular AV (Sophos AV) and determine why many of the public methods fail, and how we created our Rust PoC to work against the self-protection techniques of similar hooking engines.

Why Most NTDLL Unhooking Methods Fail (In This Case)

All public samples I’ve found that don’t use direct syscalls rely on using VirtualProtect (or NtProtectVirtualMemory) without avoiding any hooks placed on NtProtectVirtualMemory.

This is reminiscent of a chicken and egg problem, they require NtProtectVirtualMemory to unhook, yet NtProtectVirtualMemory itself is hooked, so they’re forced to go through a hooked function to unhook, and this gives the EDR/AV a chance to detect and prevent the operation (which Sophos AV does)

NtProtectVirtualMemory hook

One way to avoid this (the only way that I’ve found public samples for) rely on direct syscalls, meaning instead of modifying memory to unhook, you simply have your own syscall stubs and issue syscalls directly from your own modules instead of ntdll, meaning you no longer use any APIs from NTDLL, resulting in avoiding NTDLL hooks.

There’s a few concerns with this approach, one being not all NTDLL functions are syscalls (see PssNtCaptureSnapshot below)

PssNtCaptureSnapshot in NTDLL — notably more than a syscall

Secondly, inline syscalls themselves can be flagged as suspicious, though again this is the only public method I’ve found that would work against this target.

A (Publicly) New Method: In-Memory Disassembly

Its no secret we love Rust, so when we developed a Rust sample for unhooking against this target, we found a nice reason to publish unhooking without direct syscalls that also avoids NtProtectVirtualMemory hooks, something not found in other public samples.

This solution is based on the fact that the original code blocks that were replaced by hooks still live somewhere in memory, they have to as the AV/EDR may permit calls to go through if deemed legit.

So we utilize in-memory disassembly to identify patterns that lead to the original (unhooked) code blocks and find the unhooked original NtProtectVirtualMemory function, with that we can use it to apply the rest of our unhooking logic to remove all EDR hooks.

Lets identify the patterns in Sophos that lead to the original unhooked blocks:

NtProtectVirtualMemory hook, contains a direct jump followed by an indirect jump

The start of each hooked function in ntdll is a direct JMP, followed by an indirect JMP to somewhere outside of NTDLL (in this case, into hmpalert.dll).

This means detection of hooked functions is as simple as finding the JMP at the start of the function (we can leverage this to easily enumerate all hooked functions).

To enumerate the exported functions in NTDLL (and search for hooks), we get a handle to NTDLL via the PEB, then walk NTDLL’s export table.

The next problem: How do we identify the original unhooked code blocks for a particular function? Lets look at further disassembly in hmpalert’s hook:

Further disassembly of NtProtectVirtualMemory, identifying the original syscall stub

Further disassembly shows a pattern of an indirect pointer load into RAX, following by an indirect call that will JMP to the pointer stored in RAX, which in this case is the original syscall stub for NtProtectVirtualMemory.

This pattern is similar for non-syscall functions that are hooked, lets look at PssNtCaptureSnapshot:

PssNtCaptureSnapshot hook

Note how the hook starts the same way, a jump followed by an indirect jump into hmpalert.dll (outside of NTDLL).

Continuing disassembly we see:

PssNtCaptureSnapshot original code block

We see a similar pattern here, RAX is loaded with a pointer followed by an indirect call that JMPs to the address stored in RAX, which is the original code block of PssNtCaptureSnapshot without hooks!

As we can identify these patterns to locate the unhooked original functions using a disassembler, we simply translated that logic into Rust code that uses in-memory disassembly to identify the original code blocks at runtime.

Once we locate the unhooked/original functions at runtime, we replace the hooks from the EDR/AV with our own hook that JMPs into the unpatched originals, for example:

Unhooked PssNtCaptureSnapshot

Unhooked PssNtCaptureSnapshot

After our unhooking, the JMPs at the beginning of the previously hooked functions redirect to the original code blocks we found in memory! We avoiding direct syscalls, and we didn’t need to use any hooked APIs prior to unhooking (avoiding the previously mentioned chicken-egg problem).

Sample Code

Our sample code can be found here: https://github.com/Signal-Labs/iat_unhook_sample, note the unhook_exports function.

❌
❌