Flare-on 8 task 9 write-up

Bogdan Vennyk
8 min readOct 25, 2021

It has been couple days since annual Eighth Flare-On challenge has ended, shout out to organizers for this amazing CTF and all participants who successfully completed all challenges! Out of 10 tasks, I really enjoyed spending my time using IDA with last two, and today I would like to cover one of them in this blog.

Backstory for the task is following:

Mandiant’s unofficial motto is “find evil and solve crime”. Well here is evil but forget crime, solve challenge. Listen kid, RFCs are for fools, but for you we’ll make an exception :)

Static analysis

Quick review with pestudio didn’t show any useful strings or imports, so they are probably resolved dynamically. One thing to notice is the size of the binary which is almost 3Mb, so maybe there is some encrypted content or payload inside.

Let’s give it simple run with debugger. After some time of stepping into different functions, we eventually reached following instruction at address 0x62460 which generated access violation exception with writing data to address 0x0. Simple step over(pass the exception) leads to process termination . Task description hints us that this is part of the challenge.

According to Microsoft documentation an exception is an event that occurs during the execution of a program, and requires the execution of code outside the normal flow of control. To be able to handle these exceptions we use Structured exception handling (SEH) where we define block of code which will be executed as soon as we hit specific exception. To view currently registered Structured exception handlers - use SEH tab from x32dbg.

Besides SEH, there is also Vectored exception handling (VEH) which is an extension to structured exception handling. Main advantage of VEH is an application can register a function to watch or handle all exceptions for the application. To register handler use AddVectoredExceptionHandler function. Set breakpoint at this function and run the binary again to discover that handler is registered at address 0x66ad0. Set breakpoint at handler’s address, reach the instruction with access violation once again and make step-over to land inside handler function.

Let’s jump into analyzing handler function. Microsoft documentation says that VEH has only one argument with type _EXCEPTION_POINTERS. So we can straight away change type and name of the first argument to make analysis much easier.

Function sub_4054b0 is dynamic import resolution function where second argument is hash of the function name we want to resolve. At first is uses offset to IMAGE_EXPORT_DIRECTORY to get import names from DLLs and then uses quite simple hashing routine to calculate hash.

This hashing routine can be converted to the following python code snippet. Just take into the consideration that this is 32-bit executable. More on how to analyze dynamic imports is covered on OALabs channel , so definitely check it out!

Back to the VEH handler analysis. Values of EDX and ECX registers are used to resolve import and store its address to EAX register, then it utilizes VirtualProtect to make memory execute-read-write at address EIP where exception occurs and patches code at offset EIP+3 with value 0xD0FF and set EIP to this address. Value 0xD0FF is opcode for call eax instruction.

To summarize, this is exotic way to simply resolve and call an import and we can try to use IDAPython to resolve imports and patch functions in the way that exception handler does.

At first we need to collect all instructions which will lead to an exceptions — divide by zero or access violation

“33 C0 F7 F0” # xor eax,eax # div eax
“33 C0 8B 00” # xor eax,eax # mov eax, [eax]
“33 F6 F7 F6” # xor esi,esi # div esi
“33 FF F7 F7” # xor edi,edi # div edi

To find sequence of bytes we can use idc.find_binary function where you specify start address, search direction(UP, DOWN) and byte string to look for. It returns next occurrence of byte sequence so we need to add it to the while loop to find all occurrences.

Next comes probably most difficult part of our IDA programming — track what import hash was moved into ECX register right before exception occurs. In this case hash is stored as local variable and can be referenced using base pointer offset EBP-0x24. From python prospective we need to find what variable(EBP-offset) was moved into ECX and what value was moved into that variable before.

Once we got import hash and resolved its name we can add import name as a comment using function idc.set_cmt, and patch bytes at offset EIP+3 using call to ida_bytes.patch_word. For proper analysis we need to undefine code that was auto-analyzed before and re-analyze it once again. Patching function should look like following

def patch_bytes(address):
ptr_addr = address + 3
ida_bytes.patch_byte(address + 2, 0x90)
ida_bytes.patch_word(ptr_addr, 0xd0ff)
ida_bytes.del_items(ptr_addr, 0, 0x16)
idc.create_insn(ptr_addr)

Let’s put all pieces together and run IDA python script (full code is provided in the end of the article). There is additional for loop for cases when the new code will be discovered after patching call eax instructions. Sometimes there are still places that are needed to be converted to the code manually.

Once you fix all missed spots, you should be able to convert that location to the function and generate pseudocode. To add import name comments to the pseudocode run script one more time. Now static analysis should go much easier and quicker.

Anti-debugging and anti-analysis techniques

This binary contains a lot of different anti-debugging techniques located at offset 0x4023d0. At first we have very similar approach that was used in VEH handler to patch DbgUiRemoteBreakin function. When a debugger calls the DebugActiveProcess, it also make a call to DbgUiRemoteBreakin correspondingly. To prevent the debugger from attaching to the process, DbgUiRemoteBreakin code is patched to invoke the TerminateProcess.

After that we have calls to function 0x402f20 which makes some calculations and simply terminates process, and function 0x4033f0 which is another anti-analysis function that uses WMI interface to enumerate Win32_PnPEntity, grab DeviceId and looks for specific hash value located at offset 0x587e1c otherwise terminates process. Both this anti-analysis function calls can be simply NOPed(replaced with NOP instructions). Then we have 6 different anti-debugging detections.

To bypass those anti-debugging techniques we can find corresponding check for debugger instructions and patch their results. For example, in function anti_BeingDebugged we have function sub_406ac0 which is used to get PEB offset and then value of PEB+0x2(beingdebugged flag) is compared to 0, so we just need to patch next instruction from jz(0x84 opcode) to jnz (0x85 opcode).

Network sniffer

Now we should be ready to start analyzing main functionality. After anti-debug calls there is check for second program argument and its value is passed to function 0x403a70 which is responsible for configuration of network listener. It uses function WSAIoctl and sets SIO_RCVALL control code which enables a socket to receive all IPv4 or IPv6 packets passing through a network interface, basically it configures network sniffer.

Then packet parsing function located at offset 0x404310 is called using CreateThread function. It looks for specific traffic — UDP traffic (iph_protocol=0x11) on port 4356.

There is additional check for specific fragment offset to be equal to 0x80 at address 0x4044bc so we need to patch it as well.

If all conditions are met then new structure is created (named data in pseudocode). In this structure we have information about source IP, destination IP, source port and UDP payload. This struct is passed to another thread responsible for payload analysis located at offset 0x404680

Payload analysis

First DWORD of our payload is a command. Binary accepts 3 different commands.

If command is equal to 1 then function 0x403fc0 will be called which decrypts bmp image of Rick Roll and returns false flag N3ver_G0nNa_g1ve_y0u_Up@flare-on.com.

If command is equal to 2 we jump to routine where 4 strings are decrypted in the memory — L0ve, s3cret, 5Ex and g0d. Then payload with offset 4 is used as input string length and payload with offset 8 is used as string. Payload string is compared to decrypted strings.

If both strings are equal then input string is used with decryption routine at offset 0x4069f0.

With each decrypted string there is associated DWORD where result of decryption will be written.

If command is equal to 3 and payload string is equal to “MZ” then we will attempt to decrypt byte array at offset 0x6cfb68 using decryption routine at location 0x4067a0. Key for decryption is located at offset 0x6d1680 which is set with previously mentioned decryption routine associated with those 4 strings. Basically we need to send L0ve, s3cret, 5Ex and g0d to set key for decryption (don’t forget to include \x00 as the string terminator).

Getting final flag

Let’s put all together — send strings and send command to decrypt byte array and you should get final flag.

IDAPython script used to patch binary

--

--