Fun with self-decryption

[This post was written by ViRb3, if you want to post on this blog you can! Go here for more information…]

A month ago I was at the annual Hack Cambridge. Apart from all the programming and social fun I had, I also stumbled upon a daunting CTF challenge made by a team from Avast. In fact, it intrigued me so much that I took it home and finished it here. Among the puzzles there was a particularity interesting one - a binary that self-decrypted its code twice to reveal a secret message! We will solve that level today, with the help of x64dbg. More info about the challenge in the end.

We are left with the following text after completing the previous parts of the level:

============================================================================
| [ ADDR3S5  ] - [ H3X DUMP    ] ------ [ C0MM4ND                        ] |
============================================================================
| [ 00401000 ] > [ 31 C0       ] ------ [ XOR EAX,EAX                    ] |
| [ 00401002 ] . [ B0 40       ] ------ [ MOV AL,40                      ] |
| [ 00401004 ] . [ C1 E0 10    ] ------ [ SHL EAX,10                     ] |
| [ 00401007 ] . [ 80 F4 20    ] ------ [ XOR AH,20                      ] |
| [ 0040100A ] . [ FF D0       ] ------ [ CALL EAX                       ] |
| [ 0040100C ] . [ 50          ] ------ [ PUSH EAX                       ] |
| [ 0040100D ] . [ 8A 50 03    ] ------ [ MOV DL,BYTE PTR DS:[EAX+3]     ] |
| [ 00401010 ] . [ 30 54 01 04 ] ------ [ XOR BYTE PTR DS:[EAX+ECX+4],DL ] |
| [ 00401014 ] . [ FE C1       ] ------ [ INC CL                         ] |
| [ 00401016 ] . [ 80 F9 64    ] ------ [ CMP CL,64                      ] |
| [ 00401019 ] . [ 75 F5       ] ------ [ JNE SHORT 00401010             ] |
| [ 0040101B ] . [ 80 E9 5F    ] ------ [ SUB CL,5F                      ] |
| [ 0040101E ] . [ 00 C8       ] ------ [ ADD AL,CL                      ] |
| [ 00401020 ] . [ FF D0       ] ------ [ CALL EAX                       ] |
| [ 00401022 ] . [ F4          ] ------ [ HLT                            ] |
============================================================================
| [ ADDR3S5  ] - [ M3M0RY H3X DUMP                                       ] |
============================================================================
| [ 00402000 ] : [ 31 C9 C3 C0 | 1E 40 2C D0 | 3E 80 CA 41 | B0 CB C0 C0 ] |
| [ 00402010 ] : [ C9 DA 40 A8 | D3 DA 40 A8 | D8 F3 F0 00 | 90 03 2F D7 ] |
| [ 00402020 ] : [ 94 4A 1F 00 | 9E EC 3A 97 | DD 9E D9 EE | AD 3C 96 0D ] |
| [ 00402030 ] : [ E0 DF 9E E7 | 32 6B F7 D8 | 1D EA E1 CD | 6A 7E 6F 04 ] |
| [ 00402040 ] : [ 0A 0A 0A 0B | 0A 0A 0A 0B | 02 0B 7C 0A | 0A 0A 0B 0A ] |
| [ 00402050 ] : [ 0A 79 0C 0B | 0D 0C 09 03 | 03 79 0A 7F | 02 7C 7F 03 ] |
| [ 00402060 ] : [ 7F 0B 7C 08 | 03 79 0A 7F | 00 00 00 00 | 00 00 00 00 ] |
============================================================================

There are two main ways to approach the situation: we can use a x86 emulator (e.g. Unicorn), or use a debugger to hijack any program’s execution flow and replace its instructions with the ones given in our dump. I found the latter method a lot more fun, and even potentially faster, so in this solution we will stick to it.

First, we have to convert the dump to plain bytes. I did this by hand, and separated the hex dump and memory dump like this:

Hex dump (starts at 0x00401000):

31 C0 B0 40 C1 E0 10 80 F4 20 FF D0 50 8A 50 03 30 54 01 04 FE C1 80 F9 64 75 F5 80 E9 5F 00 C8 FF D0 F4

Memory dump (starts at 0x00402000):

31 C9 C3 C0 1E 40 2C D0 3E 80 CA 41 B0 CB C0 C0 C9 DA 40 A8 D3 DA 40 A8 D8 F3 F0 00 90 03 2F D7 94 4A 1F 00 9E EC 3A 97 DD 9E D9 EE AD 3C 96 0D E0 DF 9E E7 32 6B F7 D8 1D EA E1 CD 6A 7E 6F 04 0A 0A 0A 0B 0A 0A 0A 0B 02 0B 7C 0A 0A 0A 0B 0A 0A 79 0C 0B 0D 0C 09 03 03 79 0A 7F 02 7C 7F 03 7F 0B 7C 08 03 79 0A 7F 00 00 00 00 00 00 00 00

Next, we have to find ourselves some executable space. We start up x32dbg (not x64dbg, since we are working with x32 code), and open any 32-bit executable. Let’s use x32dbg.exe itself.

The process initializes, and we stop at the System breakpoint:

We now have to insert our first dump at the origin (current execution point) using Ctrl+Shift+V, or right-click > Binary > Paste (Ignore Size). We end up with the following block of instructions:

Now, it is very important to paste the hex dump and memory dump bytes exactly 0xFDE bytes apart (distance between 0x00402000 and 0x00401022), so the original structure is intact. The easiest way to do so is selecting the last instruction of the first block (HALT), pressing Ctrl+G or Go to > Expression, and appending +FDE. For me the field is: 7714DB02+FDE.

This will lead us to the exact location where we should paste the second dump. After pasting it looks like this: (end trimmed in screenshot)

We note the beginning address of this block - 7714EAE0 - for reference, and go back to the origin (Numpad *).

Now, we step through the first instructions, until we reach CALL EAX:

We look at the EAX register: it is 00402000. Does that ring a bell? This is the address of the second block (check the original dump). In our case, however, this address is invalid, and we have to replace it with the real address we wrote down a moment ago. We double-click on the register value and change it. For me that was 7714EAE0. We step into the call and continue stepping over, until we are back in our first block.

Now comes a tricky bit. Notice the following instruction:

It replaces the byte at a given address. This isn’t usually a problem, but in our case it will raise an exception. The reason is that we are currently in the .text section, which is executable code, and it cannot be overwritten! To fix this, we have to select the memory pages that correspond to this section and mark them all as FULL ACCESS, or at least give them WRITE ACCESS.

In x64dbg we do this by right-clicking the above instruction > Follow in Memory Map. We then right-click the highlighted page > Set Page Memory Rights > Select All > FULL ACCESS > Set Rights and close the window.

We can now return to the CPU tab.

Before we continue, we analyze the following instructions:

While not necessary, we can deduce that this is essentially a XOR decryption loop. The code enclosed between 7714DAF0 - 7714DAF9 will loop for 0x64 times, or 100. Surely we won’t want to step over that manually, so we select the instruction after the jump (sub cl,5F), and press F2 or right-click > Breakpoint > Toggle) to place a breakpoint. Now Run the program (F9). When we break, we step in the next few instructions, until we jump to the second block, which is now decrypted.

We see the following instructions:

Clearly even more decryption? If we check EAX+A we see that it leads to the code in the first block! Pay attention to the end of this routine: a combination of PUSH + RET becomes a JMP, since the RET returns to the value on top of the stack, and here that is the value PUSH just pushed (EAX).

Another tricky bit is the XOR instructon just before that. In terms of the original dump, this will set the last two bits of the address to 0, and so land at the beginning of the hex dump block (00401000). In our case, however, this XOR will mess things up, since our (real) address doesn’t end with 00. To fix this, we step in until we reach the PUSH instruction, and then change EAX to the address of the first instruction of the first block (7714DAE0 for me).

We step over the PUSH and RET instructions and we land back at the first block. Again, we look at the code:

We already know what these instructions do. Step over to CALL EAX, change EAX to the address of the second block (7714EAE0), step in once to land at the second block, then step over until you come back in the first block.

Now, we examine the code:

Same decryption, with a different XOR value. We breakpoint directly on the CALL EAX, Run (F9), and step in once. We land at the second block.

We now analyze the final routine in this binary challenge:

By carefully reading the instructions, we notice something unusual: the byte at EBX is overwritten every time in the loop, and in the end even overwritten with F4, which in turn will end the program execution. It is therefore safe to bet that the values of EBX (or DL) will be interesting for us.

To log these values, we set a breakpoint at our point of interest (mov byte ptr ds:[ebx],dl). We then head to the Breakpoints tab, find our breakpoint, and right-click > Edit. We can now specify a Log Text, which will be logged every time x64dbg executes this instruction. In our case, we want it to log the value of DL, so we set Log Text to: {DL}. String formatting occurs inside the curly brackets, where you can insert an expression. The expression here is the DL register. We also set the Break Condition to 0, so we only log, and not break.

For more information about string formatting, check the documentation.

We go back to the CPU tab and put an extra breakpoint on the instruction after the JNE (mov byte ptr ds:[ebx],F4).

We Run the program and land at the second breakpoint.

We now head to the Log tab and write down the logged bytes:

We get:

50 44 55 3E 30 30 30 31 30 30 30 31 38 31 46 30 30 30 31 30 30 43 36 31 37 36 33 39 39 43 30 45 38 46 45 39 45 31 46 32 39 43 30 45

Hurray, the binary ‘evil’ has been defeated! In case you are wondering, this byte array translates to an SMS message which gives us the password for this level.

To check the rest of the amazing challenges (and give them ago!), head to my write-up. Many thanks to the mysterious team from Avast for creating this CTF!

Comments

The big handle gamble

Yesterday I was debugging some programs and after restarting I saw that the status label stayed stuck on Initializing. At first it didn’t seem to impact anything, but pretty soon after that other things started breaking as well.

Reproduction steps:

  • Load some debuggee
  • Hold step for some time
  • Press restart
  • Repeat until the bug shows

Observed behaviours:

  1. The label stays stuck on Initializing
  2. The label stays stuck on Paused (appears to be more rare)

A shot in the dark

After getting more or less stable reproductions I started to look into why this could be happening. On the surface the TaskThread appeared to be correct, but since the WakeUp function was probably failing I put an assert on ReleaseSemaphore, which should trigger the TaskThread:

template <typename F, typename... Args>
void TaskThread_<F, Args...>::WakeUp(Args... _args)
{
    ++this->wakeups;
    EnterCriticalSection(&this->access);
    this->args = CompressArguments(std::forward<Args>(_args)...);
    LeaveCriticalSection(&this->access);
    // This will fail silently if it's redundant, which is what we want.
    if(!ReleaseSemaphore(this->wakeupSemaphore, 1, nullptr))
        __debugbreak();
}

I tried to reproduce the bug and unsurprisingly the assert triggered! At this point I suspected memory corruption, so I inserted a bunch of debug tricks in the TaskThread to store the original handle in a safe memory location:

struct DebugStruct
{
    HANDLE wakeupSemaphore = nullptr;
};

template <int N, typename F, typename... Args>
TaskThread_<N, F, Args...>::TaskThread_(F fn,
                                     size_t minSleepTimeMs, DebugStruct* debug) : fn(fn), minSleepTimeMs(minSleepTimeMs)
{
    //make the semaphore named to find it more easily in a handles viewer
    wchar_t name[256];
    swprintf_s(name, L"_TaskThread%d_%p", N, debug);
    this->wakeupSemaphore = CreateSemaphoreW(nullptr, 0, 1, name);
    if(debug)
    {
        if(!this->wakeupSemaphore)
            __debugbreak();
        debug->wakeupSemaphore = this->wakeupSemaphore;
    }
    InitializeCriticalSection(&this->access);

    this->thread = std::thread([this, debug]
    {
        this->Loop(debug);
    });
}

The TaskThread instance is now initialized and called like so:

void GuiSetDebugStateAsync(DBGSTATE state)
{
    GuiSetDebugStateFast(state);
    static TaskThread_<
               6,
               decltype(&GuiSetDebugState),
               DBGSTATE>
           GuiSetDebugStateTask(
               &GuiSetDebugState,
               300,
               new (VirtualAlloc(0,
                                 sizeof(DebugStruct),
                                 MEM_RESERVE | MEM_COMMIT,
                                 PAGE_EXECUTE_READWRITE)
                   ) DebugStruct()
           );
    GuiSetDebugStateTask.WakeUp(state, true);
}

Semaphore handle

Now I started x64dbg and used Process Hacker to find the _TaskThread6_XXXXXXXX semaphore to take note of the handle. I then reproduced and found to my surprise that the value of wakeupSemaphore was 0x640, the same value as on startup!

However when I checked the handle view again, 0x640 was no longer the handle to a semaphore, but rather to a mapped file!

Mapped file handle

Pushing our luck

This started to smell more and more like bad WinAPI usage. Tools like Application Verifier exist to find these kind of issues, but I could not get it to work so I had to roll my own.

The idea is rather simple:

  1. Use minhook to hook the CloseHandle API.
  2. Save the correct semaphore handle to a global variable
  3. Crash if this handle is ever closed
static DebugStruct* g_Debug = nullptr;
typedef BOOL(WINAPI* CLOSEHANDLE)(HANDLE hObject);
static CLOSEHANDLE fpCloseHandle = nullptr;

static BOOL WINAPI CloseHandleHook(HANDLE hObject)
{
    if(g_Debug && g_Debug->wakeupSemaphore == hObject)
        __debugbreak();
    return fpCloseHandle(hObject);
}

static void DoHook()
{
    if(MH_Initialize() != MH_OK)
        __debugbreak();
    if(MH_CreateHook(GetProcAddress(GetModuleHandleW(L"kernelbase.dll"), "CloseHandle"), &CloseHandleHook, (LPVOID*)&fpCloseHandle) != MH_OK)
        __debugbreak();
    if(MH_EnableHook(MH_ALL_HOOKS) != MH_OK)
        __debugbreak();
}

This time reproducing the issue gave some very useful results:

WinDbg callstack

Winner winner chicken dinner!

The actual bug turned out to be in TitanEngine. The ForceClose function is supposed to close all the DLL handles from the current debug session, but all of these handles were already closed at the end of the same LOAD_DLL_DEBUG_EVENT handler.

__declspec(dllexport) void TITCALL ForceClose()
{
    //manage process list
    int processcount = (int)hListProcess.size();
    for(int i = 0; i < processcount; i++)
    {
        EngineCloseHandle(hListProcess.at(i).hFile);
        EngineCloseHandle(hListProcess.at(i).hProcess);
    }
    ClearProcessList();
    //manage thread list
    int threadcount = (int)hListThread.size();
    for(int i = 0; i < threadcount; i++)
        EngineCloseHandle(hListThread.at(i).hThread);
    ClearThreadList();
    //manage library list
    int libcount = (int)hListLibrary.size();
    for(int i = 0; i < libcount; i++)
    {
        if(hListLibrary.at(i).hFile != (HANDLE) - 1)
        {
            if(hListLibrary.at(i).hFileMappingView != NULL)
            {
                UnmapViewOfFile(hListLibrary.at(i).hFileMappingView);
                EngineCloseHandle(hListLibrary.at(i).hFileMapping);
            }
            EngineCloseHandle(hListLibrary.at(i).hFile); // <-- this is there the bug happens
        }
    }
    ClearLibraryList();

    if(!engineProcessIsNowDetached)
    {
        StopDebug();
    }
    RtlZeroMemory(&dbgProcessInformation, sizeof PROCESS_INFORMATION);
    if(DebugDebuggingDLL)
        DeleteFileW(szDebuggerName);
    DebugDebuggingDLL = false;
    DebugExeFileEntryPointCallBack = NULL;
}

But how does the semaphore handle value come to be the same as a previous file handle? The answer to that puzzling question is given when you look at the flow of events:

  • LOAD_DLL_DEBUG_EVENT gets a file handle that is stored in the library list.
  • LOAD_DLL_DEBUG_EVENT immediately closes said file handle during the debug session.
  • The static initializer for the TaskThread is called when the debugger pauses for the first time and the semaphore is created with the same handle value as the (now closed) file handle from the LOAD_DLL_DEBUG_EVENT.
  • All goes well, until the ForceClose function is called and the file handle from LOAD_DLL_DEBUG_EVENT is closed once again.
  • Hell breaks loose because the TaskThread breaks.

Now for why this doesn’t happen every single time (sometimes I had to restart the debuggee 20 or more times), the handle value is ‘randomly’ reused from the closed handle pool and it’s kind of a coin toss as to when this happens. I found that you can greatly increase the likelyhood of this happening when your PC has been on for a few days and you have 70k handles open. Probably the kernel will use a more aggressive recycling strategy when low on handles, but that’s just my guess.

If you are interested in trying to reproduce this at home, you can use the handle_gamble branch. You can also take a look at the relevant issue.

Duncan

Comments

Goodbye Capstone, hello Zydis!

Full disclosure: I’m a co-author of Zydis. Opinions certainly biased.

So… this all began about a month ago, when mrexodia came into our Gitter, explaining that he’d like to replace Capstone in x64dbg. He asked whether we had considered writing a Capstone emulation interface on top of Zydis, allowing for drop-in replacement. We weren’t opposed to the idea, but after checking out the Capstone interface, decided that full emulation and mapping of all structures, flags and constants would be far from trivial and extremely error prone. This is especially true since nobody in our team had previous experience with Capstone and how it behaves in all the edge cases that might come. So instead, we decided to go on the journey of just contributing the port to x64dbg ourselves!

I checked out the repo and wiki for a guide on how to build the project, located one, followed the instructions and a few minutes later, found myself standing in front of a freshly built x64dbg binary. The port itself was pretty straight-forward. I began by reworking the Capstone wrapper class to no longer use Capstone, but Zydis instead. The rest of the work mainly consisted of replacing Capstone constants and structure accesses with their Zydis equivalents in places where the debugger and GUI code didn’t just use the abstraction, but accessed disassembler structures directly. I really won’t bore you with the details here, it was mostly search and replace work.

After completing the basic port, I threw my ass into the x64dbg IRC and had a little chit-chat with mrexodia. He suggested that we should copy & paste the old instruction formatter (CsTokenizer, the part of x64dbg that translates the binary instruction structure to the textual representation you see in the GUI) to a second file, using both Capstone and Zydis simultaneously, comparing their outputs. I quickly implemented that idea and started diffing.

Every time I found a collision between Capstone and Zydis, I added a whitelist entry, recompiled and continued diffing, throwing various different binaries and random data at it. This process not only showed up various issues in my ported CsTokenizer, it also found us 3 bugs in Zydis and >20 in Capstone, some of which have open issues created in 2015 connected to them.

So, what did x64dbg gain from the switch?

  • Most importantly: significantly more precise disassembly
    • As such, less room for misleading reversers
  • Support for more X86 ISA extensions
  • Support for many lesser known and undocumented instructions
    • We collected and diffed our data-tables from and against various different sources, such as Intel XED, LLVM and even did a full sweep through the Intel SDM for the sake of checking side-effects of all instructions
  • A (minor) boost in performance
    • Zydis is about 50% faster than Capstone
      • 500% faster when decoding to binary structure only, without formatting to human readable text assembly (CS doesn’t leave the user a choice right now)
    • However, in a project like x64dbg, that probably only affects the speed of whole module analysis (CTRL+A)
  • A decrease in binary size
    • Zydis is about ⅓ the size of Capstone (CS X86 only, all features compiled in)
    • Not that anyone would practically have to care these days
      • Nevertheless, low-level people tend to have a thing for small binaries

Finally, aside from all the negativity, I would like to make it clear that we very much appreciate all the work done in Capstone. The project simply has a different focus: it’s a great library if you’re looking into supporting many different target architectures. Zydis, on the other hand, is focused on supporting X86 — and supporting it well.

If you’re interested in checking out our work outside of x64dbg, you can take a look at the repo.

Comments