Journal: FlareOn7 (Part 3)
This is the last part of the series :)
(Linux, Signals, Crypto, Maths) 10_-_break
I love and hate this particular level. While I was solving it, there were too many hair-pulling moments. The few hours after I submitted the flag for it, I told myself and many others, “I’m not gonna solve that again!”. A few days after finishing the entire challenge, I revisited it after a chat with a friend about the ingenious mechanisms behind it and couldn’t help being in awe of the design of this binary. Now I’m grateful for this binary for teaching me so much :)
When I was solving this challenge, I was blind to the exact mechanisms behind it. Mostly I took the approach of “just run it and see what happens”. But for the sake of maximizing learning value of this journal, I’ve included some of my thoughts and learning points post-solving here. Without further ado, let’s get into the land of sunshine and rainbows.
First thing we see when we execute the binary is that my input got stolen with a smile. The audacity!
We are going to do quite a fair bit of static analysis before embarking on dynamic-assisted analysis. Let’s get started by looking around the functions with IDA Pro. Very quickly we can see two interesting functions with some switch cases in them. It sure looks like these two functions will be the most visited code during our analysis.
During the initialization of the binary, a fork() was called and the parent process is made to run the first big function (for ease of reference, let’s call it very_big_function_80490C4 as you would see in my screen captures). The child process is made to “refuse debugging” by restricting ptrace scope with pctl().
Lets take a cursory look at very_big_function_80490C4. One of the most common thing done in this function is calls to this function that loads and call ptrace().
This function dynamically loads the “ptrace” function from “libc.so.6” and then calls ptrace with the given arguments e.g. PTRACE_ATTACH. This is an important function and it directly affects how we code our solution. Because ptrace is explicitly loaded from “libc.so.6”, when we want to install a hook for ptrace, we would need to patch this within the binary so that our hook will succeed. We’ll get to that later.
In the first part of very_big_function_80490C4, it does some checking on who is debugging who and it is hard to “trick” the binary into letting us debug it with GDB.
It might help if you refer to sys/wait.h and signal.h for understanding the signal checking.
When the binary is convinced that nobody else is spying on it other than its own components, it will then execute the intended job it has.
After this challenge, we will all become very familiar with the man page of ptrace. Before we go further, I just want to briefly highlight a few types of requests that can be made to ptrace() which we would find useful later.
The ptrace function signature is as follows:
long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);
The requests that we would find useful:
· PTRACE_POKEDATA: Copy the word data to the address addr in the tracee’s memory.
· PTRACE_GETREGS: Copy the tracee’s general-purpose or floating-point registers, respectively, to the address data in the tracer.
· PTRACE_PEEKDATA: Read a word at the address addr in the tracee’s memory, returning the word as the result of the ptrace() call.
Ok, now we are ready to analyse.
The first “task” that binary performs is to make the function that compares our input with “email@example.com” raise a signal when executed. Such a neat way to start off the series of actions to steal our input.
So the first word of the string comparison function gets overwritten with the value “0xB0F”, which is an undefined instruction.
Then another fork() is done and the signal handler is put in place. First thing that this signal handler does is to ignore all attempts to stop the binary. If we try to Ctrl-C while the binary is running we’ll see the binary yelling at us :<
This function conch_and_interrupts_8049C9C is in fact the second big function with switch cases which I mentioned in the beginning of this post. When SIGSEGV is detected, then it does some stuff.
Back to very_big_function_80490C4. A while loop is started and a value is calculated based on the “orig_eax” register. This value is to be used as the switch case that decides what is being executed. A little research into this mechanism later on revealed that the value stored in the “orig_eax” register is in fact the syscall number. Hence, what is happening throughout the binary is interception of syscalls.
Ok let’s think about what we need to do from here on to reach the flag. We are not able to debug the binary, but we can hook onto ptrace() to see what the binary is doing with all these calls to ptrace. Since we are writing hooks, then we should also write for the other API calls that the binary uses. The difference between hooking ptrace() and all these other API calls is that only ptrace() needs to be handled separately due to it being dynamically loaded. Confusing? Maybe taking a look at my code will make things clearer — https://github.com/asuna-amawaka/Flareon7-2020
There are only so many imported functions. We can start small by hooking onto those API functions that does comparison first. Perhaps the flag is being derived through some comparison checks.
Compile it with:
gcc -shared -ldl -m32 myhooks.c -o libz.so.6
Then execute the binary with LD_PRELOAD.
Wow, what was that? w3lc0mE_t0_Th3_l…..?
Anyway, this shows my hook is working, and we got part 1 of the flag easily.
Patch the binary and we can try hooking ptrace next.
Great! Now with this set of logs, we can follow the execution flow and make sense out of the binary.
I’m going to move quickly to solving for the second part of the flag. Whatever that happens between this point and the next “crucial” part of the binary has only smokescreens and lies :< For example, in the first interesting switch case that we encounter when the value is 0x83411ce4.
The strings that can get decrypted look like this:
Here is another function that decrypts a set of strings that wasted our time.
Ok, let’s move on from the time wasting strings. The next “crucial” part of the binary happens when our input gets compared with “w3lc0mE_t0_Th3_l”.
Word of caution: Patch out the execve call here or avoid pressing enter without typing an input when testing the binary. I’ve lost two VM images because failed to notice this call until I lost one image.. and the second time was when I was writing this post and I forgot about this fact :<
Now, here comes the fun part. The first 16 bytes of our input is expected to be “w3lc0mE_t0_Th3_l”, and then the remaining is checked in the function get_2nd_part_flag_8048F05.
When we get into analyzing the function decrypt_data_804C369, this is where things start to look abnormal. There are calls to _pivot_root, _mlockall, _uname and _chmod, and none of these calls makes any sense in the context of this binary. Let’s hook onto the calls to try and figure out what is going on.
It looks like some looping is going on, but this is not apparent from the disassembled code. This is because of the signaling mechanism in place! As I’ve mentioned earlier, when I was solving this, I had no idea what this underlying mechanism is. I only knew that the binary was able to loop based on the output of my hook trace. I spent 2 days staring at these traces, with only a vague idea that some decryption is happening amongst these loops. I’m looking at an algorithm that is building a set of 16 round keys via pivot_root/mlockall/uname and then these round keys are used to decrypt 0x9C40 bytes of data. The process of decryption also involves some XOR and swapping. I resorted to writing down with pen and paper the process that went on within decrypt_data_804C369, trying to identify the algorithm and finally realised the algorithm is in fact the most fundamental concept in many block cryptography.
The Fiestal Cipher. What’s left to do now is to figure out the flag. The first 0x20 bytes of the encrypted buffer has some placeholder values. We can see that the next 0x20 bytes of our input (after “w3lc0mE_t0_Th3_l”) is overwrites the placeholder within the buffer to be decrypted.
We would need to know the resulting values in order to deduce our flag. Let’s see how we can print the output of the decryption function.
Notice from the trace logs printout that a syscall #5C was being intercepted. This is the call for truncate. Although when I was solving this, I was not aware that this is actually the syscall interception for truncate, I noticed that the execution flow was directed to the switch case for 0x4a51739a. Either way, we are led to analyze the code snippet for this switch case.
There it is: the expected value of the 0x20 bytes of the flag after “decryption”.
Also notice in the trace that there seems to be some ASCII text in the decrypted output:
That looks like… bee movie stuff again? This observation is critical to lead us to the flag. It seems that the first 0x20 bytes (which is our input) of the 0x9C40 buffer is to be “encrypted”, while the rest is to be “decrypted” into bee movie text. We can use this bee movie text and their corresponding ciphertext to help us with testing our attempt to reverse the fiestal cipher. I used the first 8 bytes of the bee movie in the following illustration.
Take a while to figure this whole “flip the round keys around” out, maybe pen and paper will help (like it helped me).
Anyway, after figuring out that we simply need to reverse the round keys used, just use our hooks to do some memory manipulation and we will get the flag.
Firstly we shall copy 0x28 bytes of ciphertext in 0x81A5100 into the buffer that the decryption algorithm acts on. Note that I created an extra 8 bytes of “padding” within the buffer (I duplicated the first 8 bytes, you can do anything you want), because I need to use one extra loop during the decryption phase to get the round keys’ address.
Then we do the roundkey reversal within chmod’s hook.
You might have noticed that this decryption takes very very very long because it works hard to decrypt 0x9C40 bytes. We only need to decrypt the first 0x20 bytes, hence we can patch the binary to change the size check to 0x40 (more than enough) so that we don’t need to wait too long.
Now, finally we can run it!
Yes! 2nd part of the flag in the bag.
We’ve got the hardest part of the flag out of the way. Finding the clue to the next part of the flag took me awhile, because I kept working with the “shortened” binary I made. Apparently, there’s shellcode among the decrypt bee movie text. Sneaky sneaky..
I took the static analysis approach for this shellcode because I can’t get the binary to execute it. Buffer overflow perhaps? Doesn’t matter, static analysis works fine.
The two important things that will lead us to the flag after we are able to identify that the shellcode:
- Works with big integers
- Is implementing the Elgamal public-key cryptosystem
The second fact took me awhile to click and realise what I’m supposed to be doing. Googling for it led me to this library on Github:
And it’s the perfect match with the stuff found in the shellcode!
After some analysis and reading, we can confirm that the algorithm is Elgamal. The binary also enforced 2 conditions that made our work easier — one gets rid of the “randomness” and unknowns (the value k) and the other gives us an equation to work with.
The notation I’m using follows the explanation given on this site: https://www.geeksforgeeks.org/elgamal-encryption-algorithm/
k = random % q (q is known)
S = h^k mod q (h is known)
p = g^k mod q
g = g^k mod q (check condition 1)
hence p = g, k = 1 (got rid of randomness)
At this point, we know S, q and c.
(M*S) mod q = c (check condition 2)
The idea is to solve for M, using modular inverse (I found this great material that helped me with the steps to solving this maths — https://courses.cs.washington.edu/courses/cse311/15au/documents/ModularEquivalences.pdf)
I wrote my solution with Java because I thought using Java’s BigInteger class is the most straightforward.
Anyway, here is my Java solution:
I also wrote a C version, just for fun. The modular inverse function did not work as expected, hence I took the S^-1 value from the Java output :X
That’s it, we have the whole flag: w3lc0mE_t0_Th3_l4nD_0f_De4th_4nd_d3strUct1oN_4nd_n0_puppi3s@flare-on.com
Phew! We are done with #10.
(Malware Analysis) 11_-_rabbithole
The last one is always the most painful to work through but I enjoyed this one a lot despite the pain. I don’t think I can do a better job detailing the malware analysis than those that are already out there (all of them helped me a lot during the solving process too). Let’s do a quick one for this writeup!
First thing to do is to find where is the fileless malware within the given registry hive. When told that we are looking for fileless stuff, some keywords (powershell, function, eval, wscript…) would come to mind. Use these keywords as a start to search.
That regkey look suspicious — especially when the variable name looks so random.
The powershell script that we found does an injection of binary into powershell.exe and this binary turns out to be the Gozi ISFB malware. In retrospect, “malware analysis” of this binary is considered to be the easy part, given that the source code is on Github and the encryption algorithms involved are more or less the same — the difficult part is figuring out where might the flag be hiding and then how to get to it..
I was able to execute the binary by executing the powershell script found from the registry. Simply modify the shellcode to start with EB FE (infinite loop), print the allocated memory address and attach to the powershell process with the debugger.
After cursory analysis of the binary, we would find out that the binary is reading registry keys and the names of the registry keys to be retrieved depended on the user SID value XORed with a hardcoded value 0xEDB88320. The result of this XOR operation is the variable g_machinerandseed. We will need this value later on.
To make the binary read the registry keys from our NTUSER.dat, we would have to change the SID being used in the binary. For me, I put a breakpoint here during runtime and fixed the memory directly. What we would need to do next is to get the binary help us decrypt all of the keys in Timerpro\Columncurrent and Timerpro\LanguageTheme. These are the plugin modules loaded by the malware to perform specific tasks. There is a nice article here to help understand these modules: https://vms.drweb.com/virus/?i=16014468&lng=en
My approach is rather crude and did not involve writing any code. To get the plugin modules decrypted:
- Rename the regkey that you want to decrypt to “WebsoftwareProcesstemplate”. I’m doing this manually in regedit, with a VM snapshot taken right before the key is read by the binary. Rinse and repeat these steps as many times as the number of regkeys that needed to be decrypted.
- Put a breakpoint after RSA and serpent decryption is done, dump the decrypted contents from memory
- Run the decrypted contents through aplib-decompression (https://github.com/snemes/aplib/blob/master/aplib.py)
- Use hasherezade’s tool to convert PX to PE (https://github.com/hasherezade/funky_malware_formats/tree/master/isfb_parser)
You know this is the right way to go when you get taunted by the challenge author…
Also, a C2 configuration-like data can be found in MonitornewWarningmap
Intuitively, that looks like a key or password that would definitely come useful later. Now we can pause and think about where to get our flag.
One of the suspected place where the flag could be hiding would be in the registry, as we can see that there are quite a few that have not been used so far under Timerpro. Guessing from the length of the registry keys, if the flag is in there somewhere, it should be in TimerPro\DiMap. The challenge hinted at us to work on the malware’s file exfiltration feature, which means we are probably looking for a compressed file (e.g. .zip or .rar).
A clue could be found in TimerPro\WordTimer\MAIN, which decrypts to this (via rsa and serpent followed by aplib decompression):
Here’s the hypothesis: the flag is within a text file that is the target of exfiltration (probably zipped). The contents of this file ready to be exfiltrated is most likely stored in TimerPro\DiMap. What we need to do is to find the function within the binary or its plugin modules that reads from/writes to the registry and within close proximity of such a function, there is code that performs encryption (because the regkey value is obviously encrypted).
Sounds easy, but I spent a good long time trying to find code that matches the above requirement. We can try to infer the roles of each plugin extracted by looking at their export functions. Also, each of these plugins seem to be dependent on some other plugins based on their imports.
Notice that all the imports are named with some number. These happen to be the calculated value used to “choose” the random words that happen at this code snippet (refer to screenshot below). We can make use of this code snippet to tell us which regkey name is which DLL, by manipulating the value of ECX. For example, if I want to know the regkey of “d6306e08.dll”:
Look at how the regkey name is selected to be “WordlibSystemser”.
Through analysis of export functions and API calls, we can identify that WordlibSystemser is the plugin that interacts with the registry.
From here, I inspected every function involving registry operations and found this within RowmapGuiprotocol that used export functions from WordlibSystemser:
From the source code, I found this set of crypto algorithms that seem to correspond to the above seen “XOREncryptBuffer” and “XORDecryptBuffer”. Upon closer look at the implementation, note that they are a little different. This has to be it!
We can then find the function at sub_B4F4 that is identical to “XORDecryptBuffer” in the main binary, that’s good. We can use the binary to help us decrypt dynamically later.
When looking up how this function is being used in the source code, it looks like it uses this variable g_machinerandseed as the key. Remember we figured this value out earlier as the result of 0xEDB88320 XORed with the victim’s SID. Take note of the value to be 0xFB307BFA.
This means that the key/password that we saw in MonitornewWarningmap is not used. That cannot be! I dropped into a few more rabbit holes at this point trying to find where that key could have been used. But a friend liberated me from my torture by telling me I was right about the XORDecryptBuffer and after that it’s just serpent decryption again. Hurray? I was about to go into all of these, thank goodness..
Let’s make the binary do the work for us. Put the contents in DiMap through XORDecryptBuffer by setting RCX, RDX, R8 and R9 with the respective values:
Followed by serpent decryption with the key “GSPyrv3C79ZbR0k1” gives us..
Andddd we are done :)
That’s it for FlareOn7, my friends. See you again next year!