🔒
There are new articles available, click to refresh the page.
Before yesterdayReverse Engineering 0x4 Fun

Comodo Antivirus - Sandbox Race Condition Use-After-Free (CVE-2019-14694)

13 August 2019 at 16:14
Hello,
In this blogpost I'm going to share an analysis of a recent finding in yet another Antivirus, this time in Comodo AV. After reading this awesome research by Tenable, I decided to give it a look myself and play a bit with the sandbox.

I ended up finding a vulnerability by accident in the kernel-mode part of the sandbox implemented in the minifilter driver cmdguard.sys. Although the impact is just a BSOD (Blue Screen of Death), I have found the vulnerability quite interesting and worthy of a write-up.

Comodo's sandbox filters file I/O allowing contained processes to read from the volume normally but redirects all writes to '\VTRoot\HarddiskVolume#\' located at the root of the volume on which Windows is installed.

For each file or directory opened (IRP_MJ_CREATE) by a contained process, the preoperation callback allocates an internal structure where multiple fields are initialized.

The callbacks for the minifilter's data queue, a cancel-safe IRP queue, are initialized at offset 0x140 of the structure as the disassembly below shows. In addition, the queue list head is initialized at offset 0x1C0, and the first QWORD of the same struct is set to 0xB5C0B5C0B5C0B5C.


(Figure 1)

Next, a stream handle context is set for the file object and a pointer to the previously discussed internal structure is stored at offset 0x28 of the context.
Keep in mind that a stream handle context is unique per file object (user-mode handle).

(Figure 2)

The only minifilter callback which queues IRPs to the data queue is present in the IRP_MJ_DIRECTORY_CONTROL preoperation callback for the minor function IRP_MN_NOTIFY_CHANGE_DIRECTORY.

Before the IRP_MJ_DIRECTORY_CONTROL checks the minor function, it first verifies whether a stream handle context is available and whether a data queue is already present within. It checks if the pointer at offset 0x28 is valid and whether the magic value 0xB5C0B5C0B5C0B5C is present.


(Figure 3) : Click to Zoom

Before the call to FltCbdqInsertIo, the stream handle context is retrieved and a non-paged pool allocation of size 0xE0 is made of which the pointer is stored in RDI as shown below.


(Figure 4)

Later on, this structure is stored inside the FilterContext array of the FLT_CALLBACK_DATA structure for this request and is passed as a context to the insert routine.

(Figure 5)

FltCbdqInsertIo will eventually call the InsertIoCallback (seen initialized on Figure 1). Examining this routine we see that it queues the callback data structure to the data queue and then invokes FltQueueDeferredIoWorkItem to insert a work item that will be dispatched in a system thread later on.

As you can see from the disassembly below, the work item's dispatch routine (DeferredWorkItemRoutine) receives the newly allocated non-paged memory (Figure 4) as a context.

(Figure 6) : Click To Zoom
Here is a quick recap of what we saw until now :
  • For every file/directory open, a data queue is initialized and stored at offset 0x140 of an internal structure.
  • A context is allocated in which a pointer to the previous structure is stored at offset 0x28. This context is set as a stream handle context.
  • IRP_MJ_DIRECTORY_CONTROL checks if the minor function is IRP_MN_NOTIFY_CHANGE_DIRECTORY.
  • If that's the case, a non-paged pool allocation of size 0xE0 is made and initialized.
  • The allocation is stored inside the FLT_CALLBACK_DATA and is passed to FltCbdqInsertIo as a context.
  • FltCbdqInsertIo ends up calling the insert callback (InsertIoCallback) with the non-paged pool allocation as a context.
  • The insert callback inserts the request into the queue, queues a deferred work item with the same allocation as a context. 
It is very simple for a sandboxed user-mode process to make the minifilter take this code path, it only needs to call the API FindFirstChangeNotificationA on an arbitrary directory.

Let's carry on.

So, the work item's context (non-paged pool allocation made by IRP_MJ_DIRECTORY_CONTROL for the directory change notification request) must be freed somewhere, right ? This is accomplished by IRP_MJ_CLEANUP 's preoperation routine.

As you might already know, IRP_MJ_CLEANUP is sent when the last handle of a file object is closed, so the callback must perform the janitor's work at this stage.

In this instance, The stream handle context is retrieved similarly to what we saw earlier. Next, the queue is disabled so no new requests are queued, and then the queue cleanup is done by "DoCleanup".

(Figure 8)

As shown below this sub-routine dequeues the pended requests from the data queue, retrieves the saved context structure in FLT_CALLBACK_DATA, completes the operation, and then goes on to free the context.

(Figure 9)
We can trigger what we've seen until now from a contained process by :
  • Calling FindFirstChangeNotificationA on an arbitrary directory e.g. "C:\" : Sends IRP_MJ_DIRECTORY_CONTROL and causes the delayed work item to be queued.
  • Closing the handle : Sends IRP_MJ_CLEANUP.
What can go wrong here ? The answer to that is freeing the context before the delayed work item is dispatched which would eventually receive a freed context and use it (use-after-free).

In other words, we have to make the minifilter receive an IRP_MJ_CLEANUP request before the delayed work item queued in IRP_MJ_DIRECTORY_CONTROL is dispatched for execution.

When trying to reproduce the vulnerability with a single thread, I noticed that the work item is always dispatched before IRP_MJ_CLEANUP is received. This makes sense in my opinion since the work item queue doesn't contain many items and dispatching a work item would take less time than all the work the subsequent call to CloseHandle does.

So the idea here was to create multiple threads that infinitely call :
CloseHandle(FindFirstChangeNotificationA(..)) to saturate the work item queue as much as possible and delay the dispatching of work items until the contexts are freed. A crash occurs once a work item accesses a freed context's pool allocation that was corrupted by some new allocation.

Below is the proof of concept to reproduce the vulnerability :



And here is a small Windbg trace to see what happens in practice (inside parentheses is the address of the context) :
    1. [...]
       QueueWorkItem(fffffa8062dc6f20)
       DeferredWorkItem(fffffa8062dc6f20)
       ExFreePoolWithTag(fffffa8062dc6f20)
       [...]
    2. QueueWorkItem(fffffa80635d2ea0)
       ExFreePoolWithTag(fffffa80635d2ea0)
       QueueWorkItem(fffffa8062dd5c10)
       ExFreePoolWithTag(fffffa8062dd5c10)
       QueueWorkItem(fffffa8062dd6890)
       ExFreePoolWithTag(fffffa8062dd6890)
       QueueWorkItem(fffffa8062ddac80)
       ExFreePoolWithTag(fffffa8062ddac80)
       QueueWorkItem(fffffa80624cd5e0)
       [...]
    3. DeferredWorkItem(fffffa80635d2ea0)
In (1.) everything is normal, the work item is queued, dispatched and then the pool allocation it uses is freed.

In (2.) things start going wrong, the work item is queued but before it is dispatched the context is freed.

In (3.) the deferred work item is dispatched with freed and corrupted memory in its context causing an access violation and thus a BSOD.

We see in this case that the freed pool allocation was entirely repurposed and is now part of a file object :

(Figure 10) : Click to Zoom

Reproducing the bug, you will encounter an access violation at this part of the code:

(Figure 11)

And as we can see, it expects multiple pointers to be valid including a resource pointer which makes exploitation non-trivial.

That's all for this article, until next time :)

Follow me on Twitter : here



Panda Antivirus - Local Privilege Escalation (CVE-2019-12042)

Hello,

This blogpost is about a vulnerability that I found in Panda Antivirus that leads to privilege escalation from an unprivileged account to SYSTEM.

The affected products are : Versions < 18.07.03 of Panda Dome, Panda Internet Security, Panda Antivirus Pro, Panda Global Protection, Panda Gold Protection, and old versions of Panda Antivirus >= 15.0.4.

The vulnerability was fixed in the latest version : 18.07.03

The Vulnerability:


The vulnerable system service is AgentSvc.exe. This service creates a global section object and a corresponding global event that is signaled whenever a process that writes to the shared memory wants the data to be processed by the service. The vulnerability lies in the weak permissions that are affected to both these objects allowing "Everyone" including unprivileged users to manipulate the shared memory and the event.

(Click to zoom)





(Click to zoom)

Reverse Engineering and Exploitation :

The service creates a thread that waits indefinitely on the memory change event and parses the contents of the memory when the event is signaled. We'll briefly describe what the service expects the contents of the memory to be and how they're interpreted.


When the second word from the start of the shared memory isn't zero, a call is made to the function shown below with a pointer to the address of the head of a list.


(Click to zoom)


The structure of a list element looks like this, we'll see what that string should be representing shortly :

    typedef struct StdList_Event
    {
           struct StdList_Event* Next;
           struct StdList_Event* Previous;
           struct c_string
          {
               union
              {
                     char* pStr;
                     char str[16];
               };
               unsigned int Length;
               unsigned int InStructureStringMaxLen;
           } DipsatcherEventString;
           //..
    };


As shown below, the code expects a unicode string at offset 2 of the shared memory. It instantiates a "wstring" object with the string and converts the string to ANSI in a "string" object. Moreover, a string is initialized on line 50 with "3sa342ZvSfB68aEq" and passed to the function "DecodeAndDecryptData" along with the attacker's controlled ANSI string and a pointer to an output string object.


(Click to zoom)




The function simply decodes the string from base64 and decrypts the result using RC2 with the key "3sa342ZvSfB68aEq". So whatever we supply in the shared memory must be RC2 encrypted and then base64 encoded.


(Click to zoom)




When returning from the above function, the decoded data is converted to a "wstring" (indicating the nature of the decrypted data). The do-while loop extracts the sub-strings delimited by '|' and inserts each one of them in the list that was passed in the arguments.


(Click to zoom)

When returning from this function, we're back at the thread's main function (code below) where the list is traversed and the strings are passed to the method InsertEvent of the CDispatcher class present in Dispatcher.dll. We'll see in a second what an event stands for in this context.


(Click to zoom)

In Dispatcher.dll we examine the CDispatcher::InsertEvent method and see that it inserts the event string in a CQueue queue.



(Click to zoom)












The queue elements are processed in the CDispatcher::Run method running in a separate thread as shown in the disassembly below.




(Click to zoom)





The CRegisterPlugin::ProcessEvent method does parsing of the attacker controlled string; Looking at the debug error messages, we find that we're dealing with an open-source JSON parser : https://github.com/udp/json-parser 


(Click to zoom)




Now that we know what the service expects us to send it as data, we need to know the JSON properties that we should supply.

The method CDispatcher::Initialize calls an interesting method CRegisterPlugins::LoadAllPlugins that reads the path where Panda is installed from the registry then accesses the "Plugins" folder and loads all the DLLs there.

A DLL that caught my attention immediately was Plugin_Commands.dll and it appears that it executes command-line commands.



(Click to zoom)

Since these DLLs have debugging error messages, they make locating methods pretty easy. It only takes a few seconds to find the Run method shown below in Plugin_Commands.dll.


(Click to zoom)

In this function we find the queried JSON properties from the input :



(Click to zoom)


It also didn't hurt to intercept some of these JSON messages from the kernel debugger (it took me a few minutes to intercept a command-line execute event).


(Click to zoom)

The ExeName field is present as we saw in the disassembly, an URL, and two md5 hashes. By then, I was wondering if it was possible to execute something from disk and what properties were mandatory and which were optional.

Tracking the SourcePath property in the Run method's disassembly we find a function that parses the value of this property and determines whether it points to an URL or to a file on disk. So it seems that it is possible to execute a file from disk by using the file:// URI.


(Click to zoom)

Looking for the mandatory properties, we find that we must supply at minimum these two : ExeName and SourcePath (as shown below).




Fails (JZ fail) if the property ExeName is absent

Fails if the property SourcePath is absent


However when we queue a "CmdLineExecute" event with only these two fields set, our process isn't created. While debugging this, I found that the "ExeMD5" property is also mandatory and it should contain a valid MD5 hash of the executable to run.

The function CheckMD5Match dynamically calculates the file hash and compares it to the one we supply in the JSON property.


(Click to zoom)


And if successful the execution flow takes as to "CreateProcessW".



(Click to zoom)

Testing with the following JSON (RC2 + Base64 encoded) we see that we successfully executed cmd.exe as SYSTEM :


{
    "CmdLineExecute":       
    {
        "ExeName": "cmd.exe",                           
        "SourcePath": "file://C:\\Windows\\System32",               
        "ExeMD5": "fef8118edf7918d3c795d6ef03800519"
    }
}

(Click to zoom)

However when we try to supply an executable of our own, Panda will detect it as malware and delete it, even if the file is benign.

There is a simple bypass for this in which we tell cmd.exe to start our process for us instead. The final JSON would look like something like this :


 {
    "CmdLineExecute":                                  
    {
        "ExeName": "cmd.exe",                          
        "Parameters": "/c start C:\\Users\\VM\\Desktop\\run_me_as_system.exe",
        "SourcePath": "file://C:\\Windows\\System32",              
        "ExeMD5": "fef8118edf7918d3c795d6ef03800519" //MD5 hash of CMD.EXE
    }
}


The final exploit drops a file from the resource section to disk, calculates the MD5 hash of cmd.exe present on the machine, builds the JSON, encrypts then encodes it, and finally writes the result to the shared memory prior to signaling the event.

Also note that the exploit works without recompiling on all the products affected under all supported Windows versions.


(Click to zoom)


The exploit's source code is on my GitHub page, here is a link to the repository : https://github.com/SouhailHammou/Panda-Antivirus-LPE


Thanks for reading and until another time :)

Follow me on Twitter : here

Circumventing Windows Defender ATP's user-mode APC Injection sensor from Kernel-mode

6 April 2019 at 18:23
In this blogpost, I will share a simple technique to circumvent the check that was introduced in Windows 10 build 1809 to detect user-mode APC injection. This technique will only allow us to "bypass" the sensor when we're running code from kernel-mode, i.e., queuing a user-mode APC to a remote thread from user-mode will still be logged. For more information about this new feature, please check out my previous blogpost.

In short, the sensor will log any user-mode APCs queued to a remote thread, be it from user-mode or kernel-mode. The most important check is implemented in the kernel function : EtwTiLogQueueApcThread as shown below.

(Click to zoom)

So queuing a user-mode APC to a thread in a process other than ours is considered suspicious and will be logged. However, when having code execution in kernel-mode we can queue a kernel-mode APC that will run in the context of the target process and from there we can queue a user-mode APC. This way, the check when KeInsertQueueApc is called from the kernel-mode APC will always yield (UserApc->Thread->Process  == CurrentThread->Process).

I have written a simple driver to test this out : https://github.com/SouhailHammou/Drivers/tree/master/Apc-Injection-ATP-Bypass
  • The driver registers a CreateThreadNotifyRoutine in its DriverEntry.
  • CreateThreadNotifyRoutine queues a kernel-mode APC to a newly created thread.
  • The kernel-mode APC is delivered as soon as the IRQL drops below APC_LEVEL in the target thread in which we allocate executable memory in user-space, copy the shellcode, then queue the user-mode APC.
  • The user-mode APC is delivered in user-mode.
The only issue here is that Windows Defender's ATP will still log the allocation of executable memory thanks to another sensor.

Thanks for your time :)
Follow me on Twitter : here

Examining the user-mode APC injection sensor introduced in Windows 10 build 1809

27 March 2019 at 17:26
Yesterday I've read Microsoft's blog post about the new ATP kernel sensors added to log injection of user-mode APCs. That got me curious and I went to examine the changes in KeInsertQueueApc.

The instrumentation code invokes the function EtwTiLogQueueApcThread to log the event. The function's prototype looks like this :
VOID EtwTiLogQueueApcThread(
    BYTE PreviousMode,
    PKTHREAD ApcThread, //Apc->Thread
    PVOID NormalRoutine,
    PVOID NormalContext,
    PVOID SystemArgument1,
    PVOID SystemArgument2);

EtwTiLogQueueApcThread is only called when the queued APC is a user-mode APC and if KeInsertQueueApc successfully inserted the APC into the queue (Thread->ApcQueueable && !Apc->Inserted).

EtwTiLogQueueApcThread first checks whether the user-mode APC has been queued to a remote process or not and only logs the event in the former case.
It also distinguishes between remotely queued APCs from user-mode (NtQueueApcThread(Ex)) and those queued from kernel-mode; The former is used to detect user-mode injection techniques like this one.

As shown below, two event descriptors exist and the one to log is determined using the current thread's previous mode to know whether the APC was queued by a user process or by a kernel driver.

(Click to zoom)

Looking at where the event provider registration handle EtwThreatIntProvRegHandle is referenced, we see that not only remote user-mode APC injection is logged but also a bunch of events that are commonly used by threats.

(Click to zoom)

Thanks for reading and until another time :)
Follow me on Twitter : here

VirtualProtectEx to bypass ASLR : A specific case study

5 February 2019 at 14:06
More than a year ago, I developed a local privilege escalation exploit for a product (that I cannot disclose unfortunately) in which I had to bypass ASLR.

For the record, these are the protections enabled in the targeted service's binary, it is a 32-bit executable running under Wow64 on 64-bit systems.


Basically, I was able to communicate through IPC with the system service and tell it to execute a function in its address space by pointer (it's a bit more tricky than this but you get the point). Actually, this would have been impossible if CFG was enabled.

Within the main module, I have located a function that invokes "system()" with an argument that I control. At this point, it was just a matter of bypassing ASLR in order to get that function's address and elevate privileges on the machine. However, I couldn't trigger any leaks through the IPC channel to get or deduce the main module's base.

But as luck would have it, the service exposed some other functionalities through IPC and one of them was the ability to call VirtualProtectEx and letting us supply a PID, the address, the size, and the new protection. The service was also kind enough to return the old protection of the region to our process via IPC.

Bypassing ASLR should be obvious by now knowing these two crucial points :
  • The function that we want to invoke resides in the main module.
  • On Windows the main executable's module of a process is the first module, with the lowest address in the user address space.
It is now only a matter of writing code to communicate with the service and to brute-force the location of the main module; we do that by looking for the first executable page in the address space and then calculating the main module's base : generally by subtracting 0x1000 from that page since the .text section is the first section.

The pseudo-code below shows an example of how this was done :




Launching a new process with SYSTEM privileges was easy at this point.

Thank you for reading and until another time :)

You can follow me on Twitter : here

Flare-On 5 CTF - Challenge 12 Writeup

6 October 2018 at 00:05
Flare-on was a blast this year ! All challenges were great but I enjoyed solving the last one the most, although it was somewhat frustrating.

Due to my tight schedule, I won't go over all the details involved in solving the challenge. But I'll do my best to paint a complete picture of what's going on and how I approached the problem.


We start we a floppy disk image that you can download from here (PW : infected) :

 

When we boot the floppy using bochs, we see that it automatically executes infohelp.exe that asks us for a password.
Entering an arbitrary password, the message "Welcome to FLARE..." prints slowly on the screen and the password checks are performed.

 

What I did after this, is I mounted the floppy on my Ubuntu virtual machine and extracted the files in bold.

[email protected]:/mnt/floppy$ ls
AUTOEXEC.BAT  EGA2.CPI      IO.SYS        KEYBRD3.SYS  MODE.COM
COMMAND.COM   EGA3.CPI      KEYB.COM      KEYBRD4.SYS  MSDOS.SYS
CONFIG.SYS    EGA.CPI       KEYBOARD.SYS  key.dat      TMP.DAT
DISPLAY.SYS   infohelp.exe  KEYBRD2.SYS   message.dat
[email protected]:/mnt/floppy$

Both key.dat and message.dat contain nothing interesting. However, TMP.DAT appeared to contain the welcome message we see after typing the password and some funny strings like : "NICK RULES" and "BE SURE TO DRINK YOUR OVALTINE".

What I did next is I threw infohelp.exe into IDA to examine its behavior. To my surprise, I found that it does nothing but writes the supplied password to key.dat and then reads the contents of message.dat and prints them to the screen.

Here I thought that there should be some hooks involved that redirect execution to somewhere else when message.dat is opened or read. To confirm my suspicions, I executed the "type" command on message.dat; Lo and behold, the password check is performed.

 

Next, I opened TMP.DAT in IDA and found that it contains some code that seems to be our hook. So I attached IDA to bochs and started debugging.

To locate the hook within memory, I took advantage of the fact that the message is printed in a slow fashion so what I did is pause execution while stuff was still printing. I found myself in a loop implementing the subleq VM.

The caller supplies a pointer to the bytecode, its size, and the offset to the instruction where execution should start.

 

Each instruction is 6 bytes and has the following format :

struct inst {
    WORD src_index;
    WORD dest_index;
    WORD branch_index;
}; 

The type of the subleq bytecode array is WORD*, so the VM views that its instruction size is 3 while it is 6 actually. This realization comes in handy when building an IDA processor module for the subleq.

As I did with last year's binary, I re-implemented the subleq VM with C to output each executed instruction to a file. However, I had an impossible-to-analyze file with over 1 GB. So what I did, is only print the subleq for the instructions that perform the password checks; That way I had a 30 MB-ish file that I could examine looking for patterns.

The way I had the emulated instructions printed was the following :

IP : sub [dest_index], [src_index] ; subtraction = result

The only thing that was visible on the fly is that the subleq starts by doing some initialization in the beginning and then enters a loop that keeps executing until the end. Here where suspicions of a second VM started to arise in my mind (OMG !).

I tried to locate the password characters inside the subleq and tried to figure out what operations were done on them but it was not clear at all.

I also did some text diffing between iterations and saw that the code was modifying itself. In these instances, the self-modification was done in order to dereference VM pointers and use their values as indexes in subsequent operations.

So, what I decided to do here is write a very minimal processor module that would allow me to view the subleq in a neat graph.

The module's source code is available here.

The file I extracted contains bytecode starting from IP : 0x5. So here's how you load it into IDA :

- Choose the subleq bytecode processor module and make sure to disable auto-analysis. It ruins everything when enabled.

 

-  Change the ROM start address to 0xA and the input file loading address to the same : 0x5 * sizeof(WORD) == 0xA.


The bytecode will be loaded without being analyzed.

 

- Press 'P' at 0xA to convert into code and automatically into a function. You should have a beautiful graph as a result.

 


 


Well, it is not quite as beautiful as you might think, since we still have to deal with self-modifying code (knowing what exactly is modified) and also understanding the code as a whole. 

It is quite hard to understand what subleq does by only reading "subleq" instructions, so the next thing that came to mind is to convert the subleq to MOV and ADD instructions without wasting too much time.

IDAPYTHON TO THE RESCUE ! 

I wrote a minimal script that looks for ADD and MOV patterns in the subleq and comments these instructions. First of all, the script intentionally skips the instructions that will be self-modified and doesn't comment the SUB since it's already there.

And the result was this :

 

More understandable ... still, not so much. 

So what I did next is decompile this manually into C and then simplify the code.

______________________________________________________________
    WORD* ptr = vm_code;

    int i = 0;

    while ( ptr[0] < 0x4B6E )
    {
        WORD op_addr = ptr[ ptr[0] ];
        WORD res;

        if ( op_addr == -2 )
        {
            break; //Exit
        }

        if ( op_addr == -1 )
        {
            ptr[0]++;
        }
        else
        {
            res = ptr[op_addr] - ptr[1]; //SUB op, ACCUM

            ptr[1] = res; //ACCUM

            if ( op_addr != 2 )
            {
                ptr[op_addr] = res;
            }

            if ( res < 0 )
            {
                ptr[0]++;
            }

            ptr[0]++;
        }

        if ( ptr[6] == 1 )
        {
            printf("%c", ptr[4]);
            ptr[6] = ptr[4] = 0;
        }
        if ( ptr[5] == 1 )
        {
            ptr[5] = 0;
            scanf("%c", &ptr[3]);
        }
    }
______________________________________________________________

So it is indeed another VM interpreted by the subleq. The nature of the VM was unknown to me until later when someone told me that it was RSSB. But I was able, however, to solve the challenge without needing that information.

Now, this RSSB VM executes bytecode that starts at offset 0xFEC from the start of the subleq or at offset 0x1250 of the TMP.DAT file.

If you dumped the bytecode from memory as I did, you'd find that the password you typed was written inside the RSSB VM at offset 0x21C (circled in red).

 
So far so good. I copied the whole RSSB bytecode and added it as an array and modified the C emulator code to print the "sub" instructions while executing the VM; the same way I did with the subleq.

The result looked like this :

 
IP : p[dest_index], ACCUM ; operation

Reading the code, I found out that a sum is calculated for the characters in the password. In addition to that, the number of characters in the password must be 64. I knew that by examining a variable that gets decremented from 64 at each iteration of the sum calculation.

 

For information, the sum is stored at : p[0b47].


So I patched the memory to store a 64 byte string and then I looked up where the first character of the input was read apart from where the sum was calculated. I performed a search for [010e] ( 0x21C / 2 == 0x010E).

 
65 in dec == 0x41 in hex

Long story short, the algorithm works in a loop, in each iteration two characters of the password are used to calculate a value. The sum of all characters is then added to that value as shown in the figure below (sum : in red, value : in green).


A few instructions later, a hardcoded value at [0a79] is subtracted from the resulting value of the previous operation.

 

We can see that the resulting value of the next two characters for example is compared against the next hardcoded value at [0a7a] and so on until the 30th character.

So, we have a password of 64 bytes but from which only the first 30 bytes are checked !
Let's leave that aside for now and ask : what makes a check correct ? Maybe the result of the subtraction must be zero ?

I quickly added a check onto my C emulator that did the following : 

            res = ptr[op_addr] - ptr[1];

            if ( ptr[0] == 0x203d ) //IP of the check, see figure above
            {
                res = 0;
            }

This will simply patch the result to zero in the check at 0x203d. I ran the program and it showed that the password was correct, so I knew I was on the right path.


I also observed (based on a single test case) that in each iteration the calculated value depends on the position of the two characters. So even if we have the same two characters at different places the calculated value will be different.

Solution : Here I am going to describe how I solved the challenge during the CTF. Surely this is not the best solution out there, but I'd like to show my line of thought and how I came to solve this level.

We know that the same sum is used in all the operations, and that there can be only one sum (or a handful as we'll get to see later) that will satisfy all the checks.

We can run a bruteforce attack where we let the VM calculate the value for two given characters (by patching the characters at run-time) then run the check on all possible sums (by patching the sum at run-time too). The first check will give us a lot of sums that we'll use to bruteforce the second check. In its turn, this check will filter out invalid sums that we'll eliminate from the third check and so on until we do all 15 of them. (30 characters / 2 characters per check == 15 checks).

At the end, we'll get the valid sums from which we can easily deduce the characters that generated them in each check.

The problem I had with this approach was performance. For each combination of two characters, and for each sum, I was running the VM all over again which, if I left like that, would take a huge amount of time : printing the welcome message, calculating the sum for junk characters ... over and over again each time !

What I ended up doing is "snapshotting" the VM in multiple states.

Snapshot 1 : Where the first character of the two is read (depending on the iteration we're in).
Snapshot 2 : For each two characters, take a snapshot after the value that depends on both of them is calculated and right at the address where the sum is read and added to the calculated value (IP == 0x1ff7).

The optimization here is that we execute what we only need to execute and nothing more. We patch the two characters by the ones we're currently bruteforcing at Snapshot 1 and then after the value is calculated for those two we take Snapshot 2 and we only get to patch the sum. When each iteration is over, we re-initialize the snapshots to their original states.

Here's the overall algorithm involved in pseudo-code (this is not C nor anything close) :

sums [xxx] = {...};
new_sums [yyy] = {0};

for ( i = 0; i < 15; i++)
{
       memcpy(initial_state_tmp, initial_state);
       snapshot_1 = take_snapshot_1(initial_state_tmp, i); //i is passed to go to the corresponding check
       
       for ( c1 = 0x20; c1 <= 0x7F; c1++ )
       {
              for ( c2 = 0x20; c2 <= 0x7F; c2++)
              {
                       memcpy(snapshot_1_tmp, snapshot_1);
                       write_characters_to_mem_at_offset(snapshot_1_tmp ,c1 , c2 , i);

                       snapshot_2 = take_snapshot_2(snapshot_1_tmp);
                       for ( sum in sums )
                       {
                                memcpy(snapshot_2_tmp, snapshot_2);
                                write_sum_to_mem(snapshot_2_tmp ,sum);
                                          //Execute the subtraction check and return a boolean 
                                          if ( execute_check(snapshot_2_tmp) )
                                {
                                         append(new_sums, sum); //valid sum, append it
                                }
                       }                       
              }
       }
       sums = new_sums;
       new_sums = [0];
}

print sums;

At the end we'll get the valid sums that resulted in the check being equal to 0.

Here's the full C script implementing this (a bit messy) :



After the 15 checks are done, the script gives us files containing the valid sums that passed each of the checks. We're only interested in the last file (4KB in size) highlighted below :

 

 
Contents of array_14

I actually forgot to include the characters that generated the sum for each check. And I had to do it separately.

This requires some modifications of the code above : we initialize the sums array with the contents of array_14 and for each sum bruteforce the two characters that pass each check. To optimize, I listed the first four characters (two first checks) for each one of these sums.

And one of them was particularly interesting. The sum 0xd15e resulted in these four characters "Av0c".

Running the same script for this single sum while bruteforcing all of the characters gives us the flag :



Flag : [email protected]


Well in the end, this one really deserved being the 12th, it was time consuming, frustrating and above all FUN !

Thanks for bearing with me and until another time guys - take care :)

Follow me on Twitter : here

CSAW 2018 Quals - "kvm" Reversing 500 Writeup

16 September 2018 at 20:10
Hello,

In this challenge we're given an x64 ELF binary. The program acts as a userspace host for KVM virtualization. Among other things, it sets up the VM's address space, initializes the necessary VM registers, copies the code from the ".payload" section to it, then finally runs it.


Additionally, the userspace host expects the VM to trap when executing the three illegal instructions : IN, OUT, and HLT as shown below. The host will do some processing and then fix the VM's state so it can graciously continue executing.


 

And here is an instance of a HLT instruction within the VM's code.

 

Let's now describe the behavior of the host for each illegal instruction.

IN (port 0xE9) : Reads a single character from STDIN and returns it to the VM (The first thing that the VM does is read user input from STDIN).
OUT (port 0xE9) : Outputs a single character to STDOUT.
HLT :  Before the VM executes a HLT instruction, it moves a special value into EAX. After it traps, our host reads this value and uses it as a key in an array. Each key corresponds to a handler routine within the VM's address space.

Here is a list of all present handlers :

Handler 0
Key : 0xc50b6060 => Handler : 0x454
===================
Handler 1
Key : 0x9d1fe433 => Handler : 0x3ed
===================
Handler 2
Key : 0x54a15b03 => Handler : 0x376
===================
Handler 3
Key : 0x8f6e2804 => Handler : 0x422
===================
Handler 4
Key : 0x8aeef509 => Handler : 0x389
===================
Handler 5
Key : 0x3493310d => Handler : 0x32c
===================
Handler 6
Key : 0x59c33d0f => Handler : 0x3e1
===================
Handler 7
Key : 0x968630d0 => Handler : 0x400
===================
Handler 8
Key : 0xef5bdd13 => Handler : 0x435
===================
Handler 9
Key : 0x64d8a529 => Handler : 0x3b8
===================
Handler 10
Key : 0x5f291a64 => Handler : 0x441
===================
Handler 11
Key : 0x5de72dd => Handler : 0x347
===================
Handler 12
Code : 0xfc2ff49f => Handler : 0x3ce
===================

What the host does then is set VM's RIP register to the corresponding handler.
And by examining the handlers, we see that they invoke each other using the HLT instruction.

 

Now, let's try to examine what the VM does and figure out what these handlers are used for.

Briefly, 0x2800 bytes are read from STDIN and for each of these bytes sub_1E0 is called. The first time it's called, this function takes the user-supplied character and the address 0x1300 which points to some data.


 

sub_1E0 initializes local variables and then branches to the handler at 0x32c.
This one examines the dereferenced value, if it is 0xFF it branches to the handler at 0x347, if not it branches to a handler that compares the dereferenced value with the user-supplied character.

 
Now, examining the handler at  0x347 and the handlers it invokes (see the screenshot below : renamed sub_1E0 to traverse_tree), we see that the address 0x1300 is a root node of a binary tree.

In the tree at 0x1300, parent nodes have 0xFF as a value and contain two pointers for the left & right children. A leaf node, contains an ASCII character as a value which we suspect constitutes our flag. Recall that when a leaf is encountered a comparison is made with the user-supplied character and a boolean value is returned (RET instruction).

In the screenshot below, we see that the tree is recursively traversed and when a leaf is encountered and the comparison is successful sub_172 is called as we return from the functions recursively called. 
  

 

When we traverse a left node, sub_172 is called with 0 whereas when we traverse a right node 1 is passed.
What this function does is build an array of bits starting at 0x1320 in the following manner :

BYTE* bits = 0x1320;
BYTE count = 0;

void sub_172( BYTE bit )
{
       *bits |= bit << count++;
       if ( count == 8 )
      {
            count = 0;
            bits++;
      }
}  

This way, the bit array will represent the path traversed from the leaves to the root for all characters.

When this is done for all input characters, the resulting bit array is compared against the bit array for the correct input at 0x580. So, what we have to do is this :

  1. Extract the correct bit array from 0x580 as bytes.
  2.  Reverse the order of the bytes and then convert them to binary representation. We reverse the order because we want to traverse the tree from root to leaf, doing the opposite would be impossible since all bits are concatenated. Also, when doing this, we'll start by extracting the last character and so on until we reach the first.
Below is the IDA Python script that you should run on the extracted ".payload" section to get the flag :


   
As a result, we get the flag and we see that the VM was expecting a tar file as input:

flag.txt0000664000175000017500000000007113346340766011602 0ustar toshitoshiflag{who would win? 100 ctf teams or 1 obfuscat3d boi?}


Thank you for reading :)

You can follow me on Twitter : here



MalwareFox AntiMalware (zam64.sys) - Privilege Escalation through Incorrect Access Control

6 February 2018 at 19:14

In this blog post, Ill be describing two bugs I found inside the MalwareFox AntiMalware drivers (zam32.sys/zam64.sys) that allow a non-privileged process to authenticate itself with the driver and issue special IOCTLs leading to privilege escalation.
This process of registration or authentication is used by the driver to know which processes to trust when receiving a device control request.  Normally, these processes should be the antimalwares own processes.
A process that is authenticated by the driver can send special IOCTLs that cannot be sent by other non-authenticated processes.  These special IOCTLs can be used to:
-          Enable/Disable real-time protection
-          Read/Write to raw disk
-          Create full access user-mode process handles
-          etc
Registered processes are stored in an array located in the data section of the driver. In zam64.sys, each element of the array has 0x980 bytes and the maximum number of elements is 100. An element contains information on the process such as its PID, its session id and the name of the image file name from the EPROCESS structure.
During the run-time of the anti-virus only a single process is registered with the driver, and that is MalwareFoxs own process ZAM.exe, which runs within session 1. Theres also a ZAM.exe process running as a Windows service but it doesnt seem to be registered.

Figure 1 MalwareFoxs entry in the registration array

So, by registering our process with the driver, we enable the god-mode capability to send special IOCTLs and basically make use of them to escalate privileges on the system.
I have found two ways to do so.

CVE-2018-6593: Register the process by connecting to the mini-filter communication port:

As shown in the figure below, a default security descriptor is built for the mini-filter communication port allowing access only to SYSTEM and the administrators. But right after that, RtlSetDaclSecurityDescriptoris called with a NULL DACL pointer. This leads to the DACL pointer, that was setup by FltBuildDefaultSecurityDescriptor, being overwritten with NULL. As a result, everyone has access to the object.
In addition, the maximum number of connections allowed to the port are 100 even though only a single connection appears to be needed by the Antimalware.
 
Figure 2

And heres what it looks like under Windbg.

Figure 3
The interesting thing here is that when a process connects to the port, the driver automatically registers it as a trusted process in the array we saw above. The figure below displays the first and second entries in the array when our process (exploit.exe) is connected to the port.

 
Figure 4

Our process is now registered and can send special IOCTLs as it pleases.

It turns out, the developers zeroed to DACL pointer of the ports security descriptor because their own process (ZAM.exe) doesnt run with administrator privileges and turns at medium IL. This of course isnt an excuse to disable all access checks and from everything we saw until now this is probably an anti-virus you dont want on your machine; and this is not all!

CVE-2018-6606: Registering the process by sending IOCTL 0x80002010

It turns out theres a straightforward way to register a process as trusted. Send IOCTL 0x80002010 with a process id of your choice and voilà the process with the PID you supplied is now registered and fully trusted by the driver!!!

What the driver fails to do here is check if the requestor process itself is a registered process. It does check for this when a process sends a special IOCTL, but it fails to do so if the process wants to register another process as trusted; rendering all other checks useless.

Thus, all we need to do to register our process is send IOCTL 0x80002010 with our processs PID.

Figure 5
We can now send any special IOCTLs we want, and well be using IOCTL 0x8000204C to elevate privileges.

Getting SYSTEM

MalwareFox seems to need user-mode full access handles to processes. And since we saw how its usermode process lacks the necessary privileges, it delegates this task of opening handles to its driver.

IOCTL 0x8000204C is a special IOCTL that must be sent by a registered process to the driver. The requestor simply provides a PID as an input and gets a full access handle as an output from kernel-mode; how cool is that for us ?

We use this opportunity to open a full access handle to winlogon.exe, inject a cmd.exe shellcode and then create a remote thread.

Figure 6


CVE-2018-6593:


CVE-2018-6606:



A video demonstrating the first bug (CVE-2018-6593):

Source code for CVE-2018-6593: https://goo.gl/yrxLfW
Source code for CVE-2018-6606: https://goo.gl/YtsYEo

Follow me on Twitter.

HXP CTF 2017 - "revenge_of_the_zwiebel" Reversing 100 Writeup

19 November 2017 at 13:22
revenge_of_the_zwiebel - 100 pts + 4 bonus pts ( 31 solves ):

After executing the binary it prints : "Input Key:" and waits for us to enter the flag. The routine printing the "Input Key:" message is executed at initialization alongside a sub-routine implementing the ptrace anti-debugging trick. Since we're going to debug the binary, I patched the anti-debugging sub-routine's address with nullsub_1.


After setting up remote debugging under IDA and supplying some random input to the binary we see a call to some code that was stored in executable memory.
 

IDA sometimes has trouble displaying memory under its debugger, so let's setup a manual memory region and set it as an executable 64-bit segment.

Now we should be able to view the entirety of the bytes copied to memory.
In the figure below, the code that is executed starts by checking if the 4th bit of input[0xC] is set. If it's not set, the message ":(" is printed and the process exits.


However, if the bit is set the code proceeds to decrypt the next block and also XOR the subsequent blocks that are still encrypted. (see figure below)


There's also a second test, implemented in some blocks, which involves the NOT instruction (figure below). This means that the 3rd bit of input[0x11] must not be set.

The amount of code executed is huge and doing this manually is impossible. So, we have two options : 

1.  Either dump the encrypted data, decrypt it statically and then build the flag by automatically reading the disassembly.
2.  Automate the IDA debugger to save the index plus the bits that must be set and guarantee that everything will be executed by continually patching ECX in order not to take the JECXZ jump.

Even if the 2nd attempt would take longer to complete, I chose to implement it. If I ever do the first one, I'll be sharing it here too :)

So, what the script below does is create a dictionary where the key is the character's position and the value is an array of bits that must be set. I simply ignore the NOT case, since we only care about the bits that must be set.

For example if the character at index 2 needs to have bits : 0, 4 and 7 set, the dictionary entry would look like this : {2: [1, 16, 80]}
After the process terminates, we proceed to OR the numbers of each array which gives us the flag in the end.

Here's the script that must be executed under IDA to get the flag.
Script runtime : approx. 15 minutes
Full script : here

Follow me on Twitter : here

HXP CTF 2017 - "dont_panic" Reversing 100 Writeup

19 November 2017 at 12:36
dont_panic - 100 pts + 15 bonus pts ( 19 solves ):
The file is a GO binary. After locating the main function, by stepping in the debugger, I found that the binary expects a command line argument of 42 characters.
For each character it calls a sub-routine sub_47B760 that returns a calculated byte corresponding to the one supplied. This byte is then compared to the one it corresponds to in a hardcoded array of bytes, which clearly represents the encrypted flag.

I didn't really look into how the values are calculated since GO binaries tend to be messy and I didn't really have to do it in order to solve the challenge. The thing is, the program branches to the block that displays the fail message ("Nope") as soon as it finds that one character is wrong. This opens room for brute-forcing since we can brute-force the flag character by character dynamically.

I used python with GDB to do so. Here's the solution script :
full script : here


After a minute or so, we get the flag.

HXP CTF 2017 - "Fibonacci" Reversing 100 Writeup

19 November 2017 at 12:19
Fibonacci - 100 pts + 6 bonus pts ( 45 solves ):
This binary is supposed to print the flag directly into the screen. However, it will take a very very long time to print the whole flag since the output is based on the calculation of fibonacci numbers recursively.
For each bit of the encoded flag (length = 33 stored at 00000000004007E0), the fibonacci number of that bit's position is calculated : this means that it will calculate fibonacci values for numbers from 0 to 263.
This is not all. Since the flag needs to be decoded, each call to the fibonacci sub-routine expects a pointer to a bit value which is XORed with a calculated bit from the resulting fibonacci number. Keep in mind that the fibonacci implementation is recursive, and thus we expects this boolean value to be XORed multiple times for greater numbers.
When the fibonacci sub-routine returns to the main function, the corresponding bit of the encrypted flag is XORed with the calculated bit value.

The solution that came in mind is to modify the fibonacci implementation so as to save both the calculated bit value and the resulting fibonacci number for a given number. So instead of recursing and re-calculating the fibonacci number of a previously calculated one (in a previous call for a previous bit of the flag), we simply load the result of the calculation and XOR the current output bit with the one we already saved.

The solution is implemented in the script below. All modifications done to the original function are commented.
Full script : here


We immediately get the flag when we run the program.

 

Exploring Virtual Address Descriptors under Windows 10

This blog post is about my personal attempt to superficially list VAD types under Windows 10. It all started when I was wondering, out of sheer curiosity, if there's any way to determine the VAD type (MMVAD_SHORT or MMVAD) other than by looking at the pool tag preceding the structure. In order to do that, I had to list all VAD types, do some reverse engineering, and then draw a table describing what I've been able to find.
You can view the full document by clicking here 



From the table above it is possible to deduce the VAD structure type from both the VadType and PrivateMemory flags.

VadType flag
PrivateMemory flag
Type
0
0
MMVAD
0
1
MMVAD_SHORT
1
1
MMVAD
2
0
MMVAD
3
1
MMVAD_ENCLAVE

To test it out, I wrote a kernel driver that prints the deduced VAD type for each node of calc.exe. It also prints the pool tag so we can check the result.


And that's all for this article.
You can follow me on Twitter : here

RCTF 2017 - Crackme 714 pts Writeup


Crackme 714 pts (9 solves) :


Please submit the flag like RCTF{flag}
Binary download : here

The crackme is an MFC application :

 

We can locate the routine of interest by setting a breakpoint on GetWindowTextW. Keep in mind that the input is in Unicode.
Later on, we find that the program generates two distinct blocks of values. These are generated from hard-coded values independently from the user input, so they're always the same. We call the first one : static_block1 and the second static_block2.
Then, there's the call to the encrypt function which takes static_block1 as an argument.