Flare-On 5 CTF WriteUp (Part 6)

Flare-On 5 CTF WriteUp (Part 6)

. 10 min read

This is part 6 of the Flare-On 5 CTF writeup series.

9. leet editr

Its getting to the very late stage challenges now, so its probably a good point to just turn back, stop this insanity. What's that? You wanted more ASCII art? Ask and ye shall receive.

The intro warns, more ASCII art madness is about to befall. But we don't have a choice, do we? Running the binary leet_editr.exe we get a message box.
9-1

Followed by Internet Explorer launching and presenting us with an ASCII art editor.
9-2

This doesn't provide us with any useful information. Let's analyze the binary in IDA.

9-3-1

main calls CoInitialize to initialize the COM library. It then allocates a page calling VirtualAlloc, copies some data to it and sets the page permission bits to PAGE_NOACCESS. In total 6 pages are allocated in a loop. The size of the page to allocate is stored in the alloc_info structure which has a size of 36 bytes.

Immediately below, it allocates a single page following the same process.
9-4

We have seven pages in total. The first six of them are for storing code and the other for storing data as we will see later.

The code sets up a Vectored Exception Handler and jumps to one of the allocated code page (line 85 in the decompiled code) to start executing from there.
9-5

Debugging in x64dbg

At this point let's switch to Dynamic Analysis.
9-6

If we step into the call esi instruction we can see junk bytes perhaps encrypted.
9-7

Also the protection bits are set to PAGE_NOACCESS.
9-8

Executing code from such a page would trigger an exception and the vectored exception handler would be called. Set a breakpoint on it and resume execution (Shift + F9). We hit the breakpoint.

9-9

Single step all the way until the handler returns.
9-10

Going back to the page at 5300000 where the exception occurred we can see that just the first instruction got decrypted.
9-11

The page protection flags are now set to EXECUTE and READ_ONLY.
9-12

Looking at the EFLAGS register we can see that the Trap Flag (TF) is set.
9-13

With the Trap Flag set, the processor generates an exception after instruction an executed. This is a crude way in which one can implement single step debugging functionality. In our case, the vectored exception handler would be called to deal with the exception. Letting the handler to execute till return we will notice that the decrypted instruction is again re-encrypted back along with protection bits being set to PAGE_NOACCESS.

When the processor tries to execute the next instruction it triggers an exception. The handler decrypts the instruction, modifies the page protection bit, and sets the trap flag. Again, after the instruction is executed the trap flag generates a single step exception. This pattern continues.

Analyzing the decompiled code of the handler, this behaviour will be more clear.
9-14

Recall that among the seven pages one was allocated for data which was also set as PAGE_NOACCESS. Trying to read/write from that page will again trigger an exception which the vectored handler deals in a similar way.
9-15

The protection mechanism employed is highly effective. At no time is the complete code decrypted. Just the single instruction which is about to be executed is decrypted. Afterwards, single stepping using the trap flag it's re-encrypted back. Taking a memory dump is pointless as the complete decrypted code never exists in memory. We have to resort to better a technique to overcome the protection.

Understanding the protection mechanism

Let's try to have a clear view of how the entire protection mechanism works. Note that when an exception occurs the exception handler only decrypts the faulting instruction. This implies it must have a list of all such instructions stored somewhere with their offsets and length. Going through the decompiled code of the handler we can see the crypt_and_write function is responsible for decrypting the relevant instruction. It takes the corresponding alloc_info structure and the exception address as parameters.

9-16

From this, we conclude this structure must contain all of the information necessary to decrypt an instruction. The alloc_info structure has the following layout which can be deduced by analyzing the code of the handler.
9-17

The instruction_info member is a pointer to another structure array containing the offsets of the instruction along with their length in bytes.
9-19

Using an x64dbg script to force decrypt

The crypt_and_write function decrypts/encrypts the instruction but it needs the corresponding alloc _info structure to be passed as an argument along with the instruction address.

The six alloc_info structures are located starting from address 40C2B0.
9-18

We can write an x64dbg script leveraging its Python bindings, to call the crypt_and_write function for each of the encrypted instructions in all of the 6 pages.

#decrypt-code-pages.py
"""
Run this script at 0x004015F9

.text:004015F4 push    esi             ; a3
.text:004015F5 push    [ebp+exc_address2] ; a2
.text:004015F8 push    edi             ; a1
.text:004015F9 call    crypt_and_write
"""
import struct
from x64dbgpy.pluginsdk import *

def decrypt_page(page_base, ins_offset_table, alloc_info_address):
    r_esp = GetESP()
    for offset in ins_offset_table:
        WriteDword(r_esp, alloc_info_address)
        WriteDword(r_esp+4, page_base+offset)
        Run()
        # Now we hit the breakpoint at 0x004015FE
        assert GetEAX() == 1
        # Set EIP to 0x004015F9
        SetEIP(0x004015F9)

def read_ins_offset_table(addr, count): 
    return [ReadDword(addr+(i*4)) for i in xrange(0, 2*count, 2)]

def main():
    assert GetEIP() == 0x004015F9
    alloc_info_base = 0x40C2B0
    alloc_info_fmt = '<LLLLLLLLL'
    sizeof_alloc_info = struct.calcsize(alloc_info_fmt)
    DeleteBreakpoint(0x004015F9)
    SetBreakpoint(0x004015FE)

    for i in xrange(6):
        alloc_info_address = alloc_info_base + (i*sizeof_alloc_info)
        buf = Read(alloc_info_address, sizeof_alloc_info)        
        unpacked = struct.unpack(alloc_info_fmt, buf)   
        alloc_size = unpacked[2]
        ins_count = unpacked[4]
        ins_info_addr = unpacked[5]
        alloc_address = unpacked[8]

        ins_offset_table = read_ins_offset_table(ins_info_addr, ins_count)
        x64dbg.DbgCmdExecDirect('setpagerights %x, ExecuteReadWrite' %alloc_address)
        decrypt_page(alloc_address, ins_offset_table, alloc_info_address)
        x64dbg.DbgCmdExecDirect('setpagerights %x, ExecuteRead' %alloc_address)        

    DeleteBreakpoint(0x004015FE)
    SetEIP(0x004015FE)
    SetBreakpoint(0x004015F9)

main()

Running the script when EIP is at 4015F9 we can decrypt all of the six code page. Similarly, by modifying the script a little we can decrypt the single data page.

#decrypt-data-page.py
"""
Run this script at 0x004016D1

.text:004016CB push    1               ; a3
.text:004016CD push    [ebp+ExceptionInfo] ; a2
.text:004016D0 push    edi             ; a1
.text:004016D1 call    crypt_and_write
.text:004016D6 add     esp, 10h
"""
import struct
from x64dbgpy.pluginsdk import *

def decrypt_page(page_base, ins_offset_table, alloc_info_address):
    r_esp = GetESP()
    for offset in ins_offset_table:
        WriteDword(r_esp, alloc_info_address)
        WriteDword(r_esp+4, page_base+offset)
        Run()
        # Now we hit the breakpoint at 0x004016D6
        assert GetEAX() == 1
        # Set EIP to 0x04016D1
        SetEIP(0x04016D1)

def read_ins_offset_table(addr, count): 
    return [ReadDword(addr+(i*4)) for i in xrange(0, 2*count, 2)]

def main():
    assert GetEIP() == 0x004016D1
    alloc_info_base = 0x0040CD00
    alloc_info_fmt = '<LLLLLLLLL'
    sizeof_alloc_info = struct.calcsize(alloc_info_fmt)
    DeleteBreakpoint(0x04016D1)
    SetBreakpoint(0x004016D6)
    alloc_info_address = alloc_info_base
    buf = Read(alloc_info_address, sizeof_alloc_info)        
    unpacked = struct.unpack(alloc_info_fmt, buf)   
    alloc_size = unpacked[2]
    ins_count = unpacked[4]
    ins_info_addr = unpacked[5]
    alloc_address = unpacked[8]

    ins_offset_table = read_ins_offset_table(ins_info_addr, ins_count)
    x64dbg.DbgCmdExecDirect('setpagerights %x, ReadWrite' %alloc_address)
    decrypt_page(alloc_address, ins_offset_table, alloc_info_address)
    x64dbg.DbgCmdExecDirect('setpagerights %x, ReadOnly' %alloc_address)        
        
    DeleteBreakpoint(0x004016D6)
    SetEIP(0x004016D6)
    SetBreakpoint(0x04016D1)

main()

Navigating to the decrypted data page in the memory dump window we find a piece of VB script code which we can dump to a file.
9-20

Analyzing the VBS code

The VB script code implements the root logic of the ASCII art editor and hence this challenge.

9-22
Main calls PetMeLikeATurtle.

9-23
PetMeLikeATurtle obtains a piece of RC4 encrypted text from the hosting application leet_editr.exe, the operation being obfuscated using the fso.CreateTextFile call.

Setting a breakpoint on the RC4 decryption function we can obtain the decrypted text which looks like Base64 encoded data.
9-21

Base64 decoding the data we obtain another piece of RC4 encrypted data with the key "yummy". This is decrypted by the function oh.
9-24

Using an online RC4 decryption utility followed by Base64 decoding we get another VBS script.

    PetMeLikeATurtle = False
    Set processes = GetObject("winmgmts:").ExecQuery("SELECT * FROM Win32_Process WHERE " & _
        "Caption = 'idaq64.exe' " & _
        "OR Caption = 'idaq.exe' " & _
        "OR Caption = 'idag64.exe' " & _
        "OR Caption = 'idag.exe' " & _
        "OR Caption = 'idaw64.exe' " & _
        "OR Caption = 'idaw.exe' " & _
        "OR Caption = 'idau64.exe' " & _
        "OR Caption = 'idau.exe' " & _
        "OR Caption = 'win32_remote.exe' " & _
        "OR Caption = 'win64_remotex64.exe' " & _
        "OR Caption = 'windbg.exe' " & _
        "OR Caption = 'windbg(x64).exe' " & _
        "OR Caption = 'windbg(x86).exe' " & _
        "OR Caption = 'cdb.exe' " & _
        "OR Caption = 'kd.exe' " & _
        "OR Caption = 'ntsd.exe' " & _
        "OR Caption = 'livekd.exe' " & _
        "OR Caption = 'dbgsrv.exe' " & _
        "OR Caption = 'gflags.exe' " & _
        "OR Caption = 'DbgX.Shell.exe' " & _
        "OR Caption = 'OLLYDBG.exe' " & _
        "OR Caption = 'olly.exe' " & _
        "OR Caption = 'immunity.exe' " & _
        "OR Caption = 'ImmunityDebugger.exe' " & _
        "OR Caption = 'x32dbg.exe' " & _
        "OR Caption = 'x64dbg.exe' " & _
        "OR Caption = 'x96dbg.exe' " & _
        "OR Caption = 'gdb.exe' " & _
        "OR Caption = 'processmonitor.exe' " & _
        "OR Caption = 'sysanalyzer.exe' " & _
        "OR Caption = 'regmon.exe' " & _
        "OR Caption = 'regshot.exe' " & _
        "OR Caption = 'procmon.exe' " & _
        "OR Caption = 'procmon64.exe' " & _
        "OR Caption = 'procexp.exe' " & _
        "OR Caption = 'procexp64.exe' " & _
        "OR Caption = 'tcpview.exe' " & _
        "OR Caption = 'wireshark.exe' " & _
        "OR Caption = 'syelogd.exe' " & _
        "OR Caption = 'withdll.exe' " & _
        "OR Caption = 'apatedns.exe' " & _
        "OR Caption = 'fakenet.exe' " & _
        "OR Caption = 'fakenet64.exe' " & _
        "OR Caption = 'apimonitor.exe' " & _
        "OR Caption = 'winapioverride32.exe' " & _
        "OR Caption = 'CFF Explorer.exe' " & _
        "OR Caption = 'peid.exe' " & _
        "OR Caption = 'peview.exe' " & _
        "OR Caption = 'petools.exe' " & _
        "")
    For Each process In processes
        PetMeLikeATurtle = True
        Exit For
    Next

The script implements anti-debugging functionality. If any of the blacklisted processes are running it returns True terminating the entire application. Bypassing the anti-debug technique is simple. For example to run our debugger x32dbg which is blacklisted we can rename the file x32dbg.exe to a different name bypassing the check.

The function initMe injects a piece of Javascript code with the Internet Explorer web page.
9-25

The injected code checks that the string j##mmmmmmm6 is present in the textin element. This element is the textarea containing the ASCII art.
9-26

Also, the strhash of the contents must match 1164071950. The strhash function is implemented in Javascript and looks like.
9-30

Searching for the string j##mmmmmmm6 in the VBS code we get one hit, the FLARE ASCII art at the beginning of the script.

9-27

Pasting it into the ASCII art editor, a message shows up.
9-28

Apparently, it's asking us to provide a title for our art which at the moment is "untitled". Analyzing the VBS code which checks the title we can infer it must have the word "FLARE" in it and its strhash must equal -1497458761.
9-29

Searching for the word "FLARE" in memory we get the title If I were to title this piece, it would be 'A_FLARE_f0r_th3_Dr4m4t1(C)'
9-31

Using this as a title we are greeted by another window displaying the flag as an ASCII art.
9-32

FLAG: scr1pt1ng_sl4ck1ng_and_h4ck1ng@flare-on.com

Continue to the next part: Flare-on 5 CTF Write-up (Part 7)