\n

The Ocean Lotus group, also known as APT32, is a threat actor which has been known to target East Asian countries such as Vietnam, Laos and the Philippines. The group strongly focuses on Vietnam, especially private sector companies that are investing in a wide variety of industrial sectors in the country. While private sector companies are the group’s main targets, APT32 has also been known to target foreign governments, dissidents, activists, and journalists.

\n\n\n\n

APT32’s toolset is wide and varied. It contains both advanced and simple components; it is a mixture of handcrafted tools and commercial or open-source ones, such as Mimikatz and Cobalt Strike. It runs the gamut from droppers, shellcode snippets, through decoy documents and backdoors. Many of these tools are highly obfuscated and seasoned, augmented with different techniques to make them harder to reverse-engineer.

\n\n\n\n

In this article, we get up and close with one of these obfuscation techniques. This specific technique was used in a backdoor of Ocean Lotus’ tool collection. We’ll describe the technique and the difficulty it presents to analysts — and then show how bypassing this kind of technique is a matter of writing a simple script, as long as you know what you are doing.

\n\n\n\n

The deobfuscation plugin requires Cutter, the official GUI of the open-source reverse engineering framework – radare2. Cutter is a cross-platform GUI that aims to expose radare2’s functionality as a user-friendly and modern interface.  Last month, Cutter introduced a new Python plugin system, which figures into the tool we’ll be constructing below. The plugin itself isn’t complicated, and neither is the solution we demonstrate below. If simple works, then simple is best.

\n

 

\n\n\n\n

Downloading and installing Cutter

\n\n\n\n

Cutter is available for all platforms (Linux, OS X, Windows). You can download the latest release here. If you are using Linux, the fastest way to get a working copy of Cutter is to use the AppImage file.

\n\n\n\n

If you want to use the newest version available, with new features and bug fixes, you should build Cutter from source. If you are up for that detour, follow this tutorial.

\n

\"\"

\n\n\n\n

Fig 1: Cutter interface

\n\n\n\n

 

\n

The Backdoor

\n\n\n\n

First, let’s have a look at the backdoor itself. The relevant sample (486be6b1ec73d98fdd3999abe2fa04368933a2ec) is part of a multi-stage infection chain, which we have lately seen employed in the wild. All these stages are quite typical for Ocean Lotus, especially the chain origin being a malicious document (115f3cb5bdfb2ffe5168ecb36b9aed54). The document purports to originate from Chinese security vendor Qihoo 360, and contains a malicious VBA Macro code that injects a malicious shellcode to rundll32.exe. The shellcode contains decryption routines to decrypt and reflectively load a DLL file to the memory. The DLL contains the backdoor logic itself.

\n\n\n\n

First, the backdoor decrypts a configuration file which is pulled from the file resource. The configuration file stores information such as the Command and Control servers. The binary then tries to load an auxiliary DLL to the memory using a custom-made PE loader. This DLL is called HTTPProv.dll and is capable of communicating with the C2 servers. The backdoor can receive dozens of different commands from the Command and Control servers, including shellcode execution, creation of new processes, manipulation of files and directories, and more.

\n\n\n\n

Many obfuscation techniques are used by Ocean Lotus in order to make their tools harder to reverse engineer. Most noticeable, Ocean Lotus is using an enormous amount of junk code in their binaries. The junk code makes the samples much bigger and more complicated, which distracts researchers trying to pry into the binary. Trying to decompile some of these obfuscated functions is a lost cause; the assembly often plays around with the stack pointer, and decompilers are not well-equipped to handle this kind of pathological code.

\n

 

\n\n\n\n

The Obfuscation

\n\n\n\n

Upon analysis of the backdoor, one obfuscation technique can be immediately noticed. It is the heavy use of control flow obfuscation which is created by inserting junk blocks into the flow of the function. These junk blocks are just meaningless noise and make the flow of the function confusing.

\n\n\n\n

\"\"

\n

Fig 2: An example of a junk block

\n

 

\n\n\n\n

As you can see in the image above, the block is full of junk code which has nothing to do with what the function actually does. It’s best to ignore these blocks, but that’s easier said than done. A closer look at these blocks will reveal something interesting. These junk blocks are always being fail-jumped to by a conditional jump from a previous block. Furthermore, these junk blocks will almost always end with a conditional jump which is the opposite of the conditional jump of the previous block. For example, if the condition above the junk block was jo <some_addr>, the junk block will most likely end with jno <some_addr>. If the block above ended with jne <another_addr>, the junk block will then end with… you guessed right – je <another_addr>.

\n\n\n\n

\"\"

\n

Fig 3: Opposite conditional jumps

\n

 

\n\n\n\n

With this in mind, we can begin structuring the characteristics of these junk blocks. The first characteristic of the obfuscation is the occurrence of two successive blocks which end with opposite conditional jumps to the same target address. The other characteristic requires the second block to contain no meaningful instructions such as string references or calls.

\n\n\n\n

When these two characteristics are met, we can say with a high chance that the second block is a junk block. In such a case, we would want the first block to jump over the junk block so the junk block would be removed from the graph. This can be done by patching the conditional jump with an unconditional jump, aka a simple JMP instruction.

\n

\"\"

\n\n\n\n

Fig 4: Modifying the conditional jump to a JMP instruction will ignore the junk block

\n\n\n\n

 

\n

Writing the Plugin

\n\n\n\n

So here is a heads up for you – the plugin we present below is written for Cutter, but was designed to be compatible with radare2 scripts, for those of you who are CLI gurus. That means that we are going to use some nifty radare2 commands through r2pipe – a Python wrapper to interact with radare2. This is the most effective and flexible way for scripting radare2.

\n\n\n\n

It’s not trivial to get the plugin to support both Cutter and radare2, since one is a GUI program and the other is a CLI. That means that GUI objects would be meaningless inside radare2. Luckily, Cutter supports r2pipe and is able to execute radare2 commands from inside its Python plugins.

\n\n\n\n

 

\n

Writing the Core Class

\n\n\n\n

The first thing we are going to do is to create a Python class which will be our core class. This class will contain our logic for finding and removing the junk blocks. Let’s start by defining its __init__ function. The function will receive a pipe, which will be either an r2pipe (available from import r2pipe) object from radare2 or a cutter (available from import cutter) object from Cutter.

\n\n
class GraphDeobfuscator:\n   def __init__(self, pipe):\n       \"\"\"an initialization function for the class\n      \n       Arguments:\n           pipe {r2pipe} -- an instance of r2pipe or Cutter's wrapper\n       \"\"\"\n\n       self.pipe = pipe\n
\n\n

 

\n

Now we can execute radare2 commands using this pipe. The pipe object contains two major ways to execute r2 commands. The first is pipe.cmd(<command>) which will return the results of the command as a string, and the second is pipe.cmdj(<command>j) which will return a parsed JSON object from the output of radare2’s command.

\n\n\n\n
Note: Almost every command of radare2 can be appended with a j to get the output as JSON.
\n\n\n\n

 

\n

The next thing we would want to do is to get all the blocks of the current function and then iterate over each one of them. We can do this by using the afbj command which stands for Analyze Function Blocks and will return a Json object with all the blocks of the function.

\n\n
   def clean_junk_blocks(self):\n       \"\"\"Search a given function for junk blocks, remove them and fix the flow.\n       \"\"\"\n       # Get all the basic blocks of the function\n       blocks = self.pipe.cmdj(\"afbj @ $F\")\n       if not blocks:\n           print(\"[X] No blocks found. Is it a function?\")\n           return\n       modified = False\n\n       # Iterate over all the basic blocks of the function\n       for block in blocks:\n           # do something
\n\n

 

\n

For each block, we want to know if there is a block which fails-to in a case where the conditional jump would not take place. If a block has a block to which it fails, the second block is an initial candidate to be a junk block.

\n\n
   def get_fail_block(self, block):\n       \"\"\"Return the block to which a block branches if the condition is fails\n      \n       Arguments:\n           block {block_context} -- A JSON representation of a block\n      \n       Returns:\n           block_context -- The block to which the branch fails. If not exists, returns None\n       \"\"\"\n       # Get the address of the \"fail\" branch\n       fail_addr = self.get_fail(block)\n       if not fail_addr:\n           return None\n       # Get a block context of the fail address\n       fail_block = self.get_block(fail_addr)\n       return fail_block if fail_block else None
\n\n
Note: Since our space is limited, we won’t explain every function that appears here. Functions as get_block (addr) or get_fail_addr (block) that are used in the snippet above are subroutines we wrote to make the code cleaner. The function implementations will be available in the final plugin that is shown and linked at the end of the article. Hopefully, you’ll find the function names self-explanatory.
\n\n\n\n

 

\n

Next, we would like to check whether our junk block candidate comes immediately after the block. If no, this is most likely not a junk block since from what we inspected, junk blocks are located in the code immediately after the blocks with the conditional jump.

\n\n
   def is_successive_fail(self, block_A, block_B):\n       \"\"\"Check if the end address of block_A is the start of block_B\n\n       Arguments:\n           block_A {block_context} -- A JSON object to represent the first block\n           block_B {block_context} -- A JSON object to represent the second block\n      \n       Returns:\n           bool -- True if block_B comes immediately after block_A, False otherwise\n       \"\"\"\n\n      return ((block_A[\"addr\"] + block_A[\"size\"]) == block_B[\"addr\"])
\n\n

 

\n

Then, we would want to check whether the block candidate contains no meaningful instructions. For example, it is unlikely that a junk block will contain CALL instructions or references for strings. To do this, we will use the command pdsb which stands for Print Disassembly Summary of a Block. This radare2 command prints the interesting instructions that appear in a certain block. We assume that a junk block would not contain interesting instructions.

\n\n
   def contains_meaningful_instructions (self, block):\n       '''Check if a block contains meaningful instructions (references, calls, strings,...)\n      \n       Arguments:\n           block {block_context} -- A JSON object which represents a block\n      \n       Returns:\n           bool -- True if the block contains meaningful instructions, False otherwise\n       '''\n\n       # Get summary of block - strings, calls, references\n       summary = self.pipe.cmd(\"pdsb @ {addr}\".format(addr=block[\"addr\"]))\n       return summary != \"\"\n
\n\n

 

\n

Last, we would like to check whether the conditional jumps of both blocks are opposite. This will be the last piece of the puzzle to determine whether we are dealing with a junk block. For this, we would need to create a list of opposite conditional jumps. The list we’ll show is partial since the x86 architecture contains many conditional jump instructions. That said, from our tests, it seems like the below list is enough to cover all the different pairs of opposite conditional jumps that are presented in APT32’s backdoor. If it doesn’t, adding additional instructions is easy.

\n\n
   jmp_pairs = [\n       ['jno', 'jo'],\n       ['jnp', 'jp'],\n       ['jb',  'jnb'],\n       ['jl',  'jnl'],\n       ['je',  'jne'],\n       ['jns', 'js'],\n       ['jnz', 'jz'],\n       ['jc',  'jnc'],\n       ['ja', 'jbe'],\n       ['jae', 'jb'],\n       ['je',  'jnz'],\n       ['jg',  'jle'],\n       ['jge', 'jl'],\n       ['jpe', 'jpo'],\n       ['jne', 'jz']]\n\n   def is_opposite_conditional(self, cond_A, cond_B):\n       \"\"\"Check if two operands are opposite conditional jump operands\n      \n       Arguments:\n           cond_A {string} -- the conditional jump operand of the first block\n           cond_B {string} -- the conditional jump operand of the second block\n      \n       Returns:\n           bool -- True if the operands are opposite, False otherwise\n       \"\"\"\n\n       sorted_pair = sorted([cond_A, cond_B])\n       for pair in self.jmp_pairs:\n           if sorted_pair == pair:\n               return True\n       return False\n
\n\n

 

\n

Now that we defined the validation functions, we can glue these parts together inside the clean_junk_blocks() function we created earlier.

\n\n
   def clean_junk_blocks(self):\n       \"\"\"Search a given function for junk blocks, remove them and fix the flow.\n       \"\"\"\n\n       # Get all the basic blocks of the function\n       blocks = self.pipe.cmdj(\"afbj @ $F\")\n       if not blocks:\n           print(\"[X] No blocks found. Is it a function?\")\n           return\n       modified = False\n\n       # Iterate over all the basic blocks of the function\n       for block in blocks:\n           fail_block = self.get_fail_block(block)\n           if not fail_block or \\\n           not self.is_successive_fail(block, fail_block) or \\\n           self.contains_meaningful_instructions(fail_block) or \\\n           not self.is_opposite_conditional(self.get_last_mnem_of_block(block), self.get_last_mnem_of_block(fail_block)):\n               continue\n
\n\n

 

\n

In case that all the checks are successfully passed, and we can say with a high chance that we found a junk block, we would want to patch the first conditional jump to JMP over the junk block, hence removing the junk block from the graph and thus, from the function itself.

\n\n\n\n

To do this, we use two radare2 commands. The first is aoj @ <addr> which stands for Analyze Opcode and will give us information on the instruction in a given address. This command can be used to get the target address of the conditional jump. The second command we use is wai <instruction> @ <addr> which stands for Write Assembly Inside. Unlike the wa <instruction> @ <addr> command to overwrite an instruction with another instruction, the wai command will fill the remaining bytes with NOP instructions. Thus, in a case where the JMP <addr> instruction we want to use is shorter than the current conditional-jump instruction, the remaining bytes will be replaced with NOPs.

\n\n
   def overwrite_instruction(self, addr):\n       \"\"\"Overwrite a conditional jump to an address, with a JMP to it\n      \n       Arguments:\n           addr {addr} -- address of an instruction to be overwritten\n       \"\"\"\n\n       jump_destination = self.get_jump(self.pipe.cmdj(\"aoj @ {addr}\".format(addr=addr))[0])\n       if (jump_destination):\n           self.pipe.cmd(\"wai jmp 0x{dest:x} @ {addr}\".format(dest=jump_destination, addr=addr))\n
\n\n

 

\n

After overwriting the conditional-jump instruction, we continue to loop over all the blocks of the function and repeat the steps described above. Last, if changes were made in the function, we re-analyze the function so that the changes we made appear in the function graph.

\n\n
   def reanalize_function(self):\n       \"\"\"Re-Analyze a function at a given address\n      \n       Arguments:\n           addr {addr} -- an address of a function to be re-analyze\n       \"\"\"\n       # Seek to the function's start\n       self.pipe.cmd(\"s $F\")\n       # Undefine the function in this address\n       self.pipe.cmd(\"af- $\")\n\n       # Define and analyze a function in this address\n       self.pipe.cmd(\"afr @ $\")\n
\n\n

 

\n

At last, the clean_junk_blocks() function is now ready to be used. We can now also create a function, clean_graph(), that cleans the obfuscated function of the backdoor.

\n\n
   def clean_junk_blocks(self):\n       \"\"\"Search a given function for junk blocks, remove them and fix the flow.\n       \"\"\"\n\n       # Get all the basic blocks of the function\n       blocks = self.pipe.cmdj(\"afbj @ $F\")\n       if not blocks:\n           print(\"[X] No blocks found. Is it a function?\")\n           return\n       # Have we modified any instruction in the function?\n       # If so, a reanalyze of the function is required\n       modified = False\n\n       # Iterate over all the basic blocks of the function\n       for block in blocks:\n           fail_block = self.get_fail_block(block)\n           # Make validation checks\n           if not fail_block or \\\n           not self.is_successive_fail(block, fail_block) or \\\n           self.contains_meaningful_instructions(fail_block) or \\\n           not self.is_opposite_conditional(self.get_last_mnem_of_block(block), self.get_last_mnem_of_block(fail_block)):\n               continue\n           self.overwrite_instruction(self.get_block_end(block))\n           modified = True\n       if modified:\n           self.reanalize_function()\n   \n\n   def clean_graph(self):\n       \"\"\"the initial function of the class. Responsible to enable cache and start the cleaning\n       \"\"\"\n\n       # Enable cache writing mode. changes will only take place in the session and\n       # will not override the binary\n       self.pipe.cmd(\"e io.cache=true\")\n       self.clean_junk_blocks()\n
\n\n

This concludes the core class.

\n\n\n\n

 

\n

Cutter or Radare2?

\n\n\n\n

As mentioned earlier, our code will be executed either as a plugin for Cutter, or straight from the radare2 CLI as a Python script. That means that we need to have a way to understand whether our code is being executed from Cutter or from radare2. For this, we can use the following simple trick.

\n\n
# Check if we're running from cutter\ntry:\n   import cutter\n   from PySide2.QtWidgets import QAction\n   pipe = cutter\n   cutter_available = True\n# If no, assume running from radare2\nexcept:\n   import r2pipe\n   pipe = r2pipe.open()\n   cutter_available = False\n
\n\n

The code above checks whether the cutter library can be imported. If it can, we are running from inside Cutter and can feel safe to do some GUI magic. Otherwise, we’re running from inside radare2, and so we opt to import r2pipe. In both statements, we are assigning a variable named pipe which will be later passed to the GraphDeobfuscator class we created.

\n\n\n\n

 

\n

Running from Radare2

\n\n\n\n

This is the simplest way to use this plugin. Checking if __name__ equals “__main__” is a common Python idiom that checks if the script was run directly or imported. If this script was run directly, we simply execute the clean_graph() function.

\n\n
if __name__ == \"__main__\":\n   graph_deobfuscator = GraphDeobfuscator(pipe)\n   graph_deobfuscator.clean_graph()\n
\n\n

 

\n

Running from Cutter

\n\n\n\n

Cutter’s documentation describes how to go about building and executing a Plugin for Cutter, and we follow its lead. First, we need to make sure that we are running from inside Cutter. We already created a boolean variable named cutter_variable. We simply need to check whether this variable is set to True. If it does, we proceed to define our plugin class.

\n\n
if cutter_available:\n   # This part will be executed only if Cutter is available.\n   # This will create the cutter plugin and UI objects for the plugin\n   class GraphDeobfuscatorCutter(cutter.CutterPlugin):\n       name = \"APT32 Graph Deobfuscator\"\n       description = \"Graph Deobfuscator for APT32 Samples\"\n       version = \"1.0\"\n       author = \"Itay Cohen (@Megabeets_)\"\n\n       def setupPlugin(self):\n           pass\n\n       def setupInterface(self, main):\n           pass\n   \n   def create_cutter_plugin():\n       return GraphDeobfuscatorCutter()\n
\n\n

 

\n

This is a skeleton of a Cutter plugin — it contains no proper functionality at all. The function create_cutter_plugin() is called by Cutter upon loading. At this point, if we will place our script in Cutter’s plugins directory, Cutter will recognize our file as a plugin.

\n\n\n\n

To make the plugin execute our functionality, we need to add a menu entry that the user can press to trigger our deobfuscator. We chose to add a menu entry, or an Action, to the “Windows -> Plugins” menu.

\n\n
if cutter_available:\n   # This part will be executed only if Cutter is available. This will\n   # create the cutter plugin and UI objects for the plugin\n   class GraphDeobfuscatorCutter(cutter.CutterPlugin):\n       name = \"APT32 Graph Deobfuscator\"\n       description = \"Graph Deobfuscator for APT32 Samples\"\n       version = \"1.0\"\n       author = \"Megabeets\"\n\n       def setupPlugin(self):\n           pass\n\n       def setupInterface(self, main):\n           # Create a new action (menu item)\n           action = QAction(\"APT32 Graph Deobfuscator\", main)\n           action.setCheckable(False)\n           # Connect the action to a function - cleaner.\n           # A click on this action will trigger the function\n           action.triggered.connect(self.cleaner)\n\n           # Add the action to the \"Windows -> Plugins\" menu\n           pluginsMenu = main.getMenuByType(main.MenuType.Plugins)\n           pluginsMenu.addAction(action)\n\n       def cleaner(self):\n           graph_deobfuscator = GraphDeobfuscator(pipe)\n           graph_deobfuscator.clean_graph()\n           cutter.refresh()\n\n\n   def create_cutter_plugin():\n       return GraphDeobfuscatorCutter()\n
\n\n

 

\n

The script is now ready, and can be placed in the Python folder, under Cutter’s plugins directory. The path to the directory is shown in the Plugins Options, under “Edit -> Preferences -> Plugins“. For example, on our machine the path is: “~/.local/share/RadareOrg/Cutter/Plugins/Python“.

\n\n\n\n

Now, when opening Cutter, we can see in “Plugins -> Preferences” that the plugin was indeed loaded.

\n

\"\"

\n\n\n\n

Fig 5: The plugin was successfully loaded

\n

 

\n\n\n\n

We can also check the “Windows -> Plugins” menu to see if the menu item we created is there. And indeed, we can see that the “APT32 Graph Deobfuscator” item now appears in the menu.

\n\n\n\n

\"\"

\n

Fig 6: The menu item we created was successfully added

\n

 

\n\n\n\n

We can now choose some function which we suspect for having these junk blocks, and try to test our Plugin. In this example, We chose the function fcn.00acc7e0. Going to a function in Cutter can be done either by selecting it from the left menu, or simply pressing “g” and typing its name or address in the navigation bar.

\n\n\n\n

Make sure you are in the graph view. Feel free to wander around and try to spot the junk blocks. We highlighted them in the image below which shows the Graph Overview (mini-graph)  window.

\n

\"\"

\n\n\n\n

Fig 7: Junk block highlighted in fcn.00acc7e0

\n\n\n\n

Since we have a candidate suspicious function, we can trigger our plugin and see if it successfully removes them. To do this, click on “Windows -> Plugins -> APT32 Graph Deobfuscator“. After a second, we can see that our plugin successfully removed the junk blocks.

\n

\"\"

\n\n\n\n

Fig 8: The same function after removing the junk blocks

\n

 

\n\n\n\n

In the following images, you can see more pairs of function graphs before and after the removal of junk blocks.

\n

\"\"

\n\n\n\n

Fig 9: Before and After of fcn.00aa07b0

\n

\"\"

\n\n\n\n

Fig 10: Before and After of fcn.00a8a1a0

\n\n\n\n

 

\n

Final Words

\n\n\n\n

Ocean Lotus’ obfuscation techniques are in no way the most complicated or hard to beat. In this article we went through understanding the problem, drafting a solution and finally implementing it using the python scripting capabilities of Cutter and Radare2. The full script can be found in our Github repository, and also attached to the bottom of this article.

\n\n\n\n

If you are interested in reading more about Ocean Lotus, we recommend this high-quality article published by ESET’s Romain Dumont. It contains a thorough analysis of Ocean Lotus’ tools, as well as some exposition of the obfuscation techniques involved.

\n\n\n\n

 

\n

Appendix

\n\n\n\n

Sample SHA-256 values

\n\n\n\n\n\n\n\n

 

\n

APT32 Graph Deobfuscator – Full Code

\n\n
\"\"\" A plugin for Cutter and Radare2 to deobfuscate APT32 flow graphs\nThis is a python plugin for Cutter that is compatible as an r2pipe script for\nradare2 as well. The plugin will help reverse engineers to deobfuscate and remove\njunk blocks from APT32 (Ocean Lotus) samples.\n\"\"\"\n\n__author__  = \"Itay Cohen, aka @megabeets_\"\n__company__ = \"Check Point Software Technologies Ltd\"\n\n# Check if we're running from cutter\ntry:\n    import cutter\n    from PySide2.QtWidgets import QAction\n    pipe = cutter\n    cutter_available = True\n# If no, assume running from radare2\nexcept:\n    import r2pipe\n    pipe = r2pipe.open()\n    cutter_available = False\n\n\nclass GraphDeobfuscator:\n    # A list of pairs of opposite conditional jumps\n    jmp_pairs = [\n        ['jno', 'jo'],\n        ['jnp', 'jp'],\n        ['jb',\t'jnb'],\n        ['jl',\t'jnl'],\n        ['je',\t'jne'],\n        ['jns', 'js'],\n        ['jnz', 'jz'],\n        ['jc',\t'jnc'],\n        ['ja', 'jbe'],\n        ['jae', 'jb'],\n        ['je',\t'jnz'],\n        ['jg',  'jle'],\n        ['jge', 'jl'],\n        ['jpe', 'jpo'],\n        ['jne', 'jz']]\n\n    def __init__(self, pipe, verbose=False):\n        \"\"\"an initialization function for the class\n        \n        Arguments:\n            pipe {r2pipe} -- an instance of r2pipe or Cutter's wrapper\n        \n        Keyword Arguments:\n            verbose {bool} -- if True will print logs to the screen (default: {False})\n        \"\"\"\n\n        self.pipe = pipe\n\n        self.verbose = verbose\n\n    def is_successive_fail(self, block_A, block_B):\n        \"\"\"Check if the end address of block_A is the start of block_B\n\n        Arguments:\n            block_A {block_context} -- A JSON object to represent the first block\n            block_B {block_context} -- A JSON object to represent the second block\n        \n        Returns:\n            bool -- True if block_B comes immediately after block_A, False otherwise\n        \"\"\"\n\n        return ((block_A[\"addr\"] + block_A[\"size\"]) == block_B[\"addr\"])\n\n    def is_opposite_conditional(self, cond_A, cond_B):\n        \"\"\"Check if two operands are opposite conditional jump operands\n        \n        Arguments:\n            cond_A {string} -- the conditional jump operand of the first block\n            cond_B {string} -- the conditional jump operand of the second block\n\n        Returns:\n            bool -- True if the operands are opposite, False otherwise\n        \"\"\"\n\n        sorted_pair = sorted([cond_A, cond_B])\n        for pair in self.jmp_pairs:\n            if sorted_pair == pair:\n                return True\n        return False\n\n    def contains_meaningful_instructions (self, block):\n        '''Check if a block contains meaningful instructions (references, calls, strings,...)\n        \n        Arguments:\n            block {block_context} -- A JSON object which represents a block\n        \n        Returns:\n            bool -- True if the block contains meaningful instructions, False otherwise\n        '''\n\n        # Get summary of block - strings, calls, references\n        summary = self.pipe.cmd(\"pdsb @ {addr}\".format(addr=block[\"addr\"]))\n        return summary != \"\"\n\n    def get_block_end(self, block):\n        \"\"\"Get the address of the last instruction in a given block\n        \n        Arguments:\n            block {block_context} -- A JSON object which represents a block\n        \n        Returns:\n            The address of the last instruction in the block\n        \"\"\"\n\n        # save current seek\n        self.pipe.cmd(\"s {addr}\".format(addr=block['addr']))\n        # This will return the address of a block's last instruction\n        block_end = self.pipe.cmd(\"?v $ @B:-1\")\n        return block_end\n\n    def get_last_mnem_of_block(self, block):\n        \"\"\"Get the mnemonic of the last instruction in a block\n        \n        Arguments:\n            block {block_context} -- A JSON object which represents a block\n        \n        Returns:\n            string -- the mnemonic of the last instruction in the given block\n        \"\"\"\n\n        inst_info = self.pipe.cmdj(\"aoj @ {addr}\".format(addr=self.get_block_end(block)))[0]\n        return inst_info[\"mnemonic\"]\n\n    def get_jump(self, block):\n        \"\"\"Get the address to which a block jumps\n        \n        Arguments:\n            block {block_context} -- A JSON object which represents a block\n        \n        Returns:\n            addr -- the address to which the block jumps to. If such address doesn't exist, returns False \n        \"\"\"\n\n        return block[\"jump\"] if \"jump\" in block else None\n\n    def get_fail_addr(self, block):\n        \"\"\"Get the address to which a block fails\n        \n        Arguments:\n            block {block_context} -- A JSON object which represents a block\n        \n        Returns:\n            addr -- the address to which the block fail-branches to. If such address doesn't exist, returns False \n        \"\"\"\n        return block[\"fail\"] if \"fail\" in block else None\n\n    def get_block(self, addr):\n        \"\"\"Get the block context in a given address\n        \n        Arguments:\n            addr {addr} -- An address in a block\n        \n        Returns:\n            block_context -- the block to which the address belongs\n        \"\"\"\n\n        block = self.pipe.cmdj(\"abj. @ {offset}\".format(offset=addr))\n        return block[0] if block else None\n\n    def get_fail_block(self, block):\n        \"\"\"Return the block to which a block branches if the condition is fails\n        \n        Arguments:\n            block {block_context} -- A JSON representation of a block\n        \n        Returns:\n            block_context -- The block to which the branch fails. If not exists, returns None\n        \"\"\"\n        # Get the address of the \"fail\" branch\n        fail_addr = self.get_fail_addr(block)\n        if not fail_addr:\n            return None\n        # Get a block context of the fail address\n        fail_block = self.get_block(fail_addr)\n        return fail_block if fail_block else None\n\n    def reanalize_function(self):\n        \"\"\"Re-Analyze a function at a given address\n        \n        Arguments:\n            addr {addr} -- an address of a function to be re-analyze\n        \"\"\"\n        # Seek to the function's start\n        self.pipe.cmd(\"s $F\")\n        # Undefine the function in this address\n        self.pipe.cmd(\"af- $\")\n\n        # Define and analyze a function in this address\n        self.pipe.cmd(\"afr @ $\")       \n\n    def overwrite_instruction(self, addr):\n        \"\"\"Overwrite a conditional jump to an address, with a JMP to it\n        \n        Arguments:\n            addr {addr} -- address of an instruction to be overwritten\n        \"\"\"\n\n        jump_destination = self.get_jump(self.pipe.cmdj(\"aoj @ {addr}\".format(addr=addr))[0])\n        if (jump_destination):\n            self.pipe.cmd(\"wai jmp 0x{dest:x} @ {addr}\".format(dest=jump_destination, addr=addr))\n\n    def get_current_function(self):\n        \"\"\"Return the start address of the current function\n\n        Return Value:\n            The address of the current function. None if no function found.\n        \"\"\"\n        function_start = int(self.pipe.cmd(\"?vi $FB\"))\n        return function_start if function_start != 0 else None\n\n    def clean_junk_blocks(self):\n        \"\"\"Search a given function for junk blocks, remove them and fix the flow.\n        \"\"\"\n\n        # Get all the basic blocks of the function\n        blocks = self.pipe.cmdj(\"afbj @ $F\")\n        if not blocks:\n            print(\"[X] No blocks found. Is it a function?\")\n            return\n        # Have we modified any instruction in the function?\n        # If so, a reanalyze of the function is required\n        modified = False\n\n        # Iterate over all the basic blocks of the function\n        for block in blocks:\n            fail_block = self.get_fail_block(block)\n            # Make validation checks\n            if not fail_block or \\\n            not self.is_successive_fail(block, fail_block) or \\\n            self.contains_meaningful_instructions(fail_block) or \\\n            not self.is_opposite_conditional(self.get_last_mnem_of_block(block), self.get_last_mnem_of_block(fail_block)):\n                continue\n            if self.verbose:\n                print (\"Potential junk: 0x{junk_block:x} (0x{fix_block:x})\".format(junk_block=fail_block[\"addr\"], fix_block=block[\"addr\"]))\n            self.overwrite_instruction(self.get_block_end(block))\n            modified = True\n        if modified:\n            self.reanalize_function()\n        \n    def clean_graph(self):\n        \"\"\"the initial function of the class. Responsible to enable cache and start the cleaning\n        \"\"\"\n\n        # Enable cache writing mode. changes will only take place in the session and\n        # will not override the binary\n        self.pipe.cmd(\"e io.cache=true\")\n        self.clean_junk_blocks()\n        \n\nif cutter_available:\n    # This part will be executed only if Cutter is available. This will\n    # create the cutter plugin and UI objects for the plugin\n    class GraphDeobfuscatorCutter(cutter.CutterPlugin):\n        name = \"APT32 Graph Deobfuscator\"\n        description = \"Graph Deobfuscator for APT32 Samples\"\n        version = \"1.0\"\n        author = \"Itay Cohen (@Megabeets_)\"\n\n        def setupPlugin(self):\n            pass\n\n        def setupInterface(self, main):\n            # Create a new action (menu item)\n            action = QAction(\"APT32 Graph Deobfuscator\", main)\n            action.setCheckable(False)\n            # Connect the action to a function - cleaner.\n            # A click on this action will trigger the function\n            action.triggered.connect(self.cleaner)\n\n            # Add the action to the \"Windows -> Plugins\" menu\n            pluginsMenu = main.getMenuByType(main.MenuType.Plugins)\n            pluginsMenu.addAction(action)\n\n        def cleaner(self):\n            graph_deobfuscator = GraphDeobfuscator(pipe)\n            graph_deobfuscator.clean_graph()\n            cutter.refresh()\n\n\n    def create_cutter_plugin():\n        return GraphDeobfuscatorCutter()\n\n\nif __name__ == \"__main__\":\n    graph_deobfuscator = GraphDeobfuscator(pipe)\n    graph_deobfuscator.clean_graph()\n\n
\n\n

 

\n

","status":"PUBLISHED","fileName":null,"link":"https://research.checkpoint.com/2019/deobfuscating-apt32-flow-graphs-with-cutter-and-radare2/","tags":[],"score":0.010067053139209747,"topStoryDate":null},{"id":"581","type":"Blog_Publication","name":"Viking Horde: a New Type of Android Malware on Google Play","author":"Jeff Zacuto","date":1462845630000,"description":"The Check Point research team uncovered a new Android malware campaign on Google Play it calls Viking Horde. Viking Horde conducts ad fraud, but can also be used for other attack purposes such as DDoS attacks, spam messages, and more. At least five instances of Viking Horde managed to bypass Google Play","content":"

The Check Point research team uncovered a new Android malware campaign on Google Play it calls Viking Horde. Viking Horde conducts ad fraud, but can also be used for other attack purposes such as DDoS attacks, spam messages, and more. At least five instances of Viking Horde managed to bypass Google Play malware scans so far.

\n

Check Point notified Google about the malware on May 5, 2016.

\n

On all devices — rooted or not — Viking Horde creates a botnet that uses proxied IP addresses to disguise ad clicks, generating revenue for the attacker. A botnet is a group of devices controlled by hackers without the knowledge of their owners. The bots are used for various reasons based on the distributed computing capabilities of all the devices. The larger the botnet, the greater its capabilities.

\n

On rooted devices, Viking Horde delivers additional malware payloads that can execute any code remotely, potentially compromising the security of data on the device. It also takes advantage of root access privileges to make itself difficult or even impossible to remove manually.

\n

Meet the Horde

\n

\"Viking

\n

The most widely-downloaded instance of Viking Horde is the app Viking Jump, which was uploaded to Google Play on April 15 and has between 50,000-100,000 downloads. In some local markets, Viking Jump is a Google Play top free app.

\n

\"Viking

\n

 

\n

The oldest instance is Wi-Fi Plus, which was uploaded to Google Play on March 29. Other instances include the apps Memory Booster, Parrot Copter, and Simple 2048. All Viking Horde-infected apps have a relatively low reputation which the research team speculates may be because users have noticed the odd behavior, such as asking for root permissions.

\n

\"Viking

\n

The botnet created by the attackers spread worldwide to users from various targeted countries. The Check Point research team collected data on the distribution of victims from one of the many Command & Control servers (C&C’s) used by attackers, which is illustrated below:

\n

\"Viking

\n
Source: Check Point Mobile Threat Research Team, May 6, 2016
\n

 

\n

How Viking Horde Works

\n

From its research of Viking Horde’s code and the C&C servers used in the attack, the research team can illustrate the malware process flow.

\n

\"Viking

\n

1. The malware is first installed from Google Play. While the app initiates the game, it installs several components, outside of the application’s directory. The components are randomly named with pseudo-system words from a preset list, such as core.bin, clib.so, android.bin and update.bin. They are installed on the SD card if the device is not rooted, and to root/data if it is. One of these files is used to exchange information between the malware’s components. A second file contains the list of the generated names of the components, to make them available to all components.

\n

2. The malware then checks whether the device is rooted:

\n\n

In both scenarios, once app_exec application is installed, it establishes a TCP connection with the C&C server and starts the communication. The communication consists of the following commands.

\n\n

3. The next step is to accomplish the main malicious functionality by creating an anonymous proxy connection. The C&C sends a “create_proxy” command with two IP addresses and ports as parameters. These IP addresses are used to open two sockets one for a remote server (which is a client of the botnet exploiting the anonymous proxy) and the other for the remote target. Then it reads the data received from the first socket and channels it to the target host. Using this technique, the malware developer (or someone using this botnet as “malware as a service”) can hide his IP behind the infected device’s IP.

\n

Botnet Activity

\n

It is important to understand that even if the device is not rooted, Viking Horde turns the into a proxy capable of sending and receiving information per the attacker’s commands. Below is an example of an infected device as seen from an attacker’s C&C.

\n

The remoteIP is the proxy’s IP, and the socksIP is the C&C server’s IP. The C&C contains some information about the device including its OS version, battery status, and GPS coordinates. In this case, the device is located in the US on T-Mobile.

\n

\"Viking

\n

The botnet is controlled by many C&C servers, each managing a few hundred devices. The malware’s primary objective is to hijack a device and then use it to simulate clicks on advertisements in websites to accumulate profit. The malware needs this proxy to bypass ad-nets’ anti-fraud mechanisms by using distributed IPs.

\n

Some user reviews of the app also claim it sends premium SMS messages, as seen in the screen capture below. This botnet could be used for various malicious purposes, such as DDoS attacks, spamming and delivering malware.

\n

\"Viking

\n

Vikings are a Persistent Horde

\n

The malware uses several techniques to remain on the device. First, Viking Horde installs several components with system-related names, so that they are hard to locate and uninstall.
\nIf the device is rooted, two more mechanisms are set in place:

\n

The app_exec component monitors the main application’s existence. If the user uninstalled the main application, app_exec decrypts a component called com.android.security and silently installs it. This component will be hidden, and run after boot. This component is a copy of itself and has the same capabilities.
\nThe watchdog component installs the app_exec component updates. If app_exec is removed, the watchdog will reinstall it from the update folder.

\n

Apparently, some users even noticed this activity:

\n

\"Viking

\n

Bonus component for rooted devices

\n

Perhaps the most dangerous functionality is the update mechanism. The update mechanism is split between app_exec and watchdog components. app_exec downloads the new binary from the server and stores it to /data directory with the app_exec_update name.

\n

Watchdog periodically checks if an update file exists and replaces app_exec with this file. This means that upon the server’s command, Viking Horde downloads a new binary. The watchdog component will replace the application with it. This allows downloading and executing any remote code on the device.

\n

Appendix 1: app package names

\n\n

Appendix 2: list of C&C servers

\n\n

Appendix 3: Infected binaries SHA256

\n

85e6d5b3569e5b22a16245215a2f31df1ea3a1eb4d53b4c286a6ad2a46517b0c
\n254c1f16c8aa4c4c033e925b629d9a74ccb76ebf76204df7807b84a593f38dc0
\nebfef80c85264250b0e413f04d2fbf9e66f0e6fd6b955e281dba70d536139619
\n10d9fdbe9ae31a290575263db76a56a601301f2c2089ac9d2581c9289a24998a
\na13abb024863dc770f7e3e5710435899d221400a1b405a8dd9fd12f62c4971de
\n1dd08afbf8a9e5f101f7ea4550602c40d1050517abfff11aaeb9a90e1b2caea1
\ne284a7329066e171c88c98be9118b2dce4e121b98aa418ae6232eaf5fd3ad521

\n

The post Viking Horde: a New Type of Android Malware on Google Play appeared first on Check Point Blog.

\n","status":"PUBLISHED","fileName":"380621455","link":"http://blog.checkpoint.com/2016/05/09/viking-horde-a-new-type-of-android-malware-on-google-play/","tags":["Botnet","Android"],"score":0.009913349524140358,"topStoryDate":null},{"id":"RS-23074","type":"Research_Publications","name":"Predator the Thief","author":null,"date":1580330206000,"description":"Overview Predator the Thief is a sophisticated malicious stealer which has been on the scene for around one and a half years. What started as coding experiments in malware development later evolved into a full-fledged menace to be reckoned with. Current versions of Predator use various anti-debugging and anti-analysis techniques to complicate analysis on the… Click to Read More","content":"

Overview

\n

Predator the Thief is a sophisticated malicious stealer which has been on the scene for around one and a half years. What started as coding experiments in malware development later evolved into a full-fledged menace to be reckoned with. Current versions of Predator use various anti-debugging and anti-analysis techniques to complicate analysis on the part of researchers while still smoothly performing data stealing.

\n

In this article we provide a retrospective analysis of the evolution process for Predator the Thief, journey into the Darknet and its malware sales, take peeks at the data left behind by the malware authors, and describe the malware itself.

\n

Background

\n

Darknet

\n

Predator the Thief was initially offered for sale on a Russian Darknet forum by Alexuiop1337 more than 1.5 years ago, on June 17, 2018.

\n

It is no longer available there, as this forum states it doesn’t deal with malicious tools anymore:

\n

\"\"

\n

Screenshot from forum with question about Predator the Thief thread and response stating that selling malware is now forbidden

\n

This was the initial sale offer for Predator:

\n

\"\"

\n

And some details on the offer:

\n

\"\"

\n

We have counted at least 5 different versions since then. While it may seem a low number, each version added something significant to the malware and the author is still continuing to enhance its capabilities.

\n

Where it is sold now

\n

Currently Predator is offered on another Russian forum as well as on the Telegram channel. Its price is $150, plus an additional $100 may be paid for the Clipper module which allows the buyer to customize stealing options for crypto-wallets.

\n

At the end of September 2019, this channel had only 773 subscribers. In a single month, the number of subscribers increased by one hundred, with a total of 875 subscribers to Telegram channel as of October 30.

\n

\"\"

\n

Development of the malware is ongoing and updates to Predator are posted regularly.

\n

This is the transcript of the Telegram message which describes changes in Predator v.3.3.0, the sample we obtained:

\n

Update v.3.3.0 as of 19.08.2019:

\n

Module Clipper

\n

* new module clipper, price is $100

\n

Module Loader

\n

* added option “random filename” for methods ShellExecute and CreateProcess

\n

Panel

\n

* “Domain Detect” for cookies was re-written with AJAX and now works realtime as well as passwords

\n

* changed log view

\n

* separated filters by general parameters, passwords and cookies

\n

* fixed bug with transferring logs to other users

\n

* fixed bug with wrong loader statistics

\n

Stealer:

\n

* temprorary removed support of *.onion domains

\n

* completely changed Outlook harvesting

\n

* implemented harvesting of new cold wallets and fixed paths for harvesting of old ones

\n

* mail client ThunderBird is now displayed correctly in passwords file

\n

* possibility to add one reserve domain to the build

\n

* other minor fixes and tweaks

\n

\"\"

\n

Peculiar thing is that author uses this channel not only for pure advertisement, but for publications on general topics as well. For example, there is a post on anti-disassembly tricks and their implementation is C/C++ code. Some methods which are implemented in Predator to harden the analysis are described in this article. Sources used in this publication reside in author’s GitHub
\nhttps://github.com/Alexuiop1337/AntiDisassembly

\n

It appears that the author puts some love in his creation as well as in what he’s doing overall.

\n

Panel

\n

We were able to obtain Predator the Thief panel source code which helped us to gather more information.

\n

Panel interface looks like this:

\n

\"\"

\n

It is in Russian which once again proves its origin.

\n

The author of certain panel scripts identifies himself as “melloy”:

\n

\"\"

\n

* author of this script Melloy

\n

Panel is written in PHP while malware is written in C++. There is nothing controversy in the ability to write code in both languages, however we can’t say for sure that the author of Predator and the author of its panel is the same person.

\n

Links with the actors

\n

The table below contains resources which are linked with the authors of Predator the Thief.

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
DescriptionLinkWhere this lead was got from
GitHubhttps://github.com/Alexuiop1337Darknet and Telegram channel
Predator Beta (C#)https://github.com/Alexuiop1337/PredatorTT-Beta-Old
Twitterhttps://twitter.com/alexupi1
VKhttps://vk.com/alexindePanel source code
\n

 

\n

Judging by avatars in Twitter and VK, we can say that people (or a single person) behind the accounts like anime girls:

\n

\"\"

\n

Avatar of the malware author in Twitter

\n

 

\n

\"\"

\n

Page of the malware author in VK

\n

We can’t say for sure that both malware and panel were written by the same person though certain patterns from these leads match:

\n\n

Technical details

\n

Malware flow

\n

The entire malicious job is conducted in the address space of original executable. Several layers of unpacking and XOR-decryption are performed until main payload is reached.

\n

Functionality

\n

Malware steals data of various browsers, email clients, crypto-wallets and other software. It also takes desktop screenshot. This information is stored in ZIP archive which resides in memory and then sent to C2C.

\n

\"\"

\n

 

\n

What it is targeting

\n

It steals credentials from various applications:

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Browser and email clientsChrome based browsers
Firefox based browsers
Opera
Outlook
Thunderbird
Crypto-wallets

\n

(any crypto-system which stores `wallet.dat` is affected)

Armory
Atomic
Bytecoin
Electrum
Ethereum
Jaxx
MultiBit
Other software and accountsAuthy
BattleNet
Discord
Jabber
NordVPN
Osu
Pidgin
Skype
Steam
Telegram
WinFTP
WinSCP
\n

 

\n

It also gets system information (clipboard included) in the following manner:

\n

\"\"

\n

As a final touch, Predator the Thief grabs the list of installed software and takes desktop screenshot.

\n

The data is archived and is organized like shown in the following screenshot:

\n

\"\"

\n

Mutex creation

\n

Some recent versions of Predator the Thief create mutex with name transformed from disk volume serial number.

\n

 

\n

Anti-debug techniques

\n

Predator the Thief implements various techniques to harden the analysis.

\n

1.      Hiding thread from debugger

\n

Current thread is hidden from debugger with the call of NtSetThreadInformation and HideFromDebugger enum argument (11h):

\n

\"\"

\n

In order to bypass this detection method, we have to skip the call and return 0 (STATUS_SUCCESS) as a result.

\n

Here lies another trap. There is another call right after this call. This time with slightly different arguments:

\n

\"\"

\n

\"\"

\n

Note the difference in 3rd and 4th arguments which should be set to NULLs in order for the function to be executed correctly. If wrong arguments are passed, then 0xC0000004 error code is returned. And this is precisely what malware expects from the 2nd call: it is waiting for the error code to be returned. Thread will not be hidden from debugger in this case.

\n

If researcher gives up to temptation of skipping this 2nd call to NtSetThreadInformation function with wrong arguments and will set result to 0, it will lead malware through another branch – which will cause termination instead of correct execution.

\n

To sum it up: 1st call must be skipped. 2nd call must be executed as is, or result must be set to non-zero value.

\n

2.      CheckRemoteDebuggerPresent

\n

This check may be faked in many ways, via plugins (ScyllaHide, OllyAdvanced) or manually. Either way, in order to continue malware debugging, result of this function must be set to 0.

\n

3.      CloseHandle

\n

Consider this code sample:

\n

\"\"

\n

This code is executed differently depending on whether debugger is present or not. Malware uses this method to check if debugger was detected: it checks if exception handler was executed and that 1 was returned from it. If all the conditions fulfill, malware terminates its execution.

\n

When exception handler returns 1 –  which corresponds to EXCEPTION_EXECUTE_HANDLER – it is a sign that exception has been handled, as described here:
\n
https://docs.microsoft.com/en-us/cpp/cpp/try-except-statement?view=vs-2019

\n

To bypass this check method we have to set return value from exception handler to 0:

\n

\"\"

\n

Anti-analysis tricks (dynamic)

\n

1.      API bombing

\n

Malware pollutes the API call analysis with a lot of non-meaningful calls:

\n

\"\"

\n

This is done 28878 times:

\n

\"\"

\n

This is also true for meaningful operations where the same actions are performed several times to make the log larger:

\n

\"\"

\n

2.      Checking for installed anti-virus solutions

\n

Malware terminates its execution if successfully loads one of the following DLLs:

\n\n

`ActaRes.dll` and `SavUIRes.dll` are both libraries from Symantec Endpoint Protection.

\n

Our guess about `taleOfTheWorstOne.dll` is that this library was used as a kill-switch during malware development.

\n

\n

Anti-analysis tricks (static)

\n

1.      String obfuscation

\n

All the key strings are obfuscated with the help of one of the following instructions:

\n\n

They are put on stack before decryption. It looks like this:

\n

\"\"

\n

2.      Dynamic function calling

\n

When calling a function, malware first decrypts names of function and corresponding DLL. Then it refers to GetProcAddress wrapper and makes the call afterwards. It looks like this (decryption is not shown here):

\n

\"\"

\n

 

\n

3.      Jumps in the middle of instructions

\n

This method was described by the author of Predator in Telegram channel with source code residing on GitHub:
\nhttps://github.com/Alexuiop1337/AntiDisassembly

\n

Below we will show how to bypass it during research.

\n

Consider this code in debugger:

\n

\"\"

\n

Jump is taken in any case and leads to the middle of instruction. The following code is the right one:

\n

\"\"

\n

Here is the code representation in IDA:

\n

\"\"

\n

Outlined in red are fake instructions. When attempting to create a function in IDA for this case, an error is shown stating that wrong instructions are encountered.

\n

\"\"

\n

\"\"

\n

Replacing the fake function call with NOP is the way to go in such a case so that the function will be created indeed:

\n

\"\"

\n

Evasion techniques

\n

These checks are performed only if corresponding command is received from the server.

\n

1. Global Table Descriptor check

\n

This trick involves looking at the pointers to critical operating system tables that are typically relocated on a virtual machine. It’s what called “Red Pill” and was first introduced by Joanna Rutkowska:

\n

https://web.archive.org/web/20070325211649/https://www.invisiblethings.org/papers/redpill.html

\n

There is one Local Descriptor Table Register (LDTR), one Global Descriptor Table Register (GDTR), and one Interrupt Descriptor Table Register (IDTR) per CPU. They have to be moved to a different location when a guest operating system is running to avoid conflicts with the host.

\n

On real machines, for example, the IDT is located lower in memory than it is on guest (i.e., virtual) machines.

\n

Note: does not work on newer versions of VMWare Workstation (tested in v10 and v12).

\n

Three instructions are used to check locations of these tables:

\n\n

Predator the Thief uses only two of them: sidt and sgdt.

\n

Code sample:

\n

idt_vm_detect = ((get_idt_base() >> 24) == 0xff);
\n
ldt_vm_detect = (get_ldt_base() == 0xdead0000);
\n
gdt_vm_detect = ((get_gdt_base >> 24) == 0xff);

\n


\n
// sidt instruction stores the contents of the IDT Register (the IDTR which points to the IDT) in a processor register.
\n
ULONG get_idt_base() {
\n
    UCHAR idtr[6];
\n
#if defined (ENV32BIT)
\n
    _asm sidt idtr;
\n
#endif

\n

    return *((unsigned long *)&idtr[2]);
\n
}

\n

// sldt instruction stores the contents of the LDT Register (the LDTR which points to the LDT) in a processor register.
\n
ULONG get_ldt_base() {
\n
    UCHAR ldtr[5] = \"\\xef\\xbe\\xad\\xde\";
\n
#if defined (ENV32BIT)
\n
    _asm sldt ldtr;
\n
#endif
\n
    return *((unsigned long *)&ldtr[0]);
\n
}

\n

// sgdt instruction stores the contents of the GDT Register (the GDTR which points to the GDT) in a processor register.
\n
ULONG get_gdt_base() {
\n
    UCHAR gdtr[6];
\n
#if defined (ENV32BIT)
\n
    _asm sgdt gdtr;
\n
#endif
\n
    return gdt = *((unsigned long *)&gdtr[2]);
\n
}

\n

2. CPU specific

\n

This technique uses `cpuid` instruction to check if malware is run in VM. EAX is set to 1, ECX is set to 0, then `cpuid` instruction is called. If run in VM, 31st bit in ECX is set to 1. Malware checks exactly this bit.

\n

3. Reading status register

\n

This technique uses the smsw instruction which is described in the Intel Software developer’s manual:

\n

Store machine status word in low-order 16 bits of r32/m16; high-order 16 bits of r32 are undefined.

\n

The key here is the undefined high order bits. It has previously been observed that on Intel processors the return value of top 16 bits is consistently 0x8001, while on a virtualized CPU in VMware the target register contains the value preserved before the instruction was executed.

\n

When using this technique, first the target register is initialized with a “magic” value, then smsw is executed. If after execution the target register still contains the “magic” value, the application is treated as if it’s running inside a virtual machine.

\n

This technique doesn’t work in VMware workstation 12.

\n

Network

\n

Predator the Thief initiates two connections to the hardcoded C2C address. First it sends a warm welcome gets configuration file, then it sends stolen data in ZIP archive.

\n

1st step

\n

Config arrives in a classic Base64+RC4 encryption layer:

\n

\"\"

\n

Please find the decryption script in the `Attachments ` section.

\n

The following five configuration entries are stored in encrypted form:

\n

1 – settings for stealing in this order (for Boolean value: 1 – feature is on; 0 – feature is off):

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
1Take webcam snapshot (-3f4)
2Check VM (-3f3)
3Steal Skype
4Steal Steam (-3f1)
5Take screenshot (-3f0)
6No log if CIS (-3e8)
7Self-delete (-3e7)
8Steal Telegram (-3e6)
9Steal Windows cookies
10Max quantity of stolen items
\n

2 – directories where to steal files from and appropriate file masks

\n

3 – host location info

\n

4 – empty field. According to what we have seen on current site, it’s a place for loader module.

\n

5 – contact server for an additional module (in this case: HTTP POST /api/clipper.get). Clipper is a module for grabbing additional crypto-wallets with custom settings.

\n

Note: self-deletion is performed with the help of batch command “/c ping 127.0.0.1 && del [filename]”.

\n

Example of these values is listed below:

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Logical partValue
1[0;0;0;0;1;0;1;0;0;1000]
2[[%userprofile%\\Desktop|%userprofile%\\Downloads|%userprofile%\\Documents;*.txt,*.png,*.jpg,*.jpeg,;1000;;1]]
3[Montreal;Canada;45.5029;-73.5723;54.39.186.180;America/Toronto;H4]
4[]
5[Clipper]
\n

 

\n

2nd step

\n

At this step stolen data is sent to C2C server in ZIP archive:

\n

\"\"

\n

“p” parameters in the request indicate how many items of certain have been stolen, in this example:

\n

p1=0 & p2=243 & p3=0 & p4=0 & p5=0 & p6=0 & p7=0 & p8=0 & p9=0

\n

And the same information in `Information.txt` file:
\n\"\"

\n

Check `What it is targeting` section for what is stored in the archive.

\n

After sending the archive malware terminates its execution.

\n

Updates

\n

Predator is actively evolving; here are the descriptions of changes in versions 3.3.1 and 3.3.2 taken from its official Telegram channel:

\n

\"\"

\n

* screenshot from changes in version 3.3.1

\n

Predator the Thief update v.3.3.1

\n

Comeback of onion domains

\n

Now build can send data to onion domains so that chance of your server ban is really low.

\n

* build sends info directly without using proxies and the like

\n

** there is no guarantee for the same receive rate as for usual domain. Rate may be lower for onion domain

\n

*** admin panel in TOR doesn’t eliminate chance of server ban but minimizes it to zero

\n

Panel update

\n\n

Module update: loader

\n\n

Module update: stealer

\n\n

Changes in project terms

\n\n

Screenshot with changes in version 3.3.2 follows:

\n

\"\"

\n

Predator the Thief update v.3.3.2

\n

Stealer update

\n\n

Previous researches

\n

First publicly known research was conducted by fumik0_:
\nhttps://fumik0.com/2018/10/15/predator-the-thief-in-depth-analysis-v2-3-5/

\n

It was v.2.3.5, year 2018. fumik0_ has also covered v.2.3.7.

\n

Since then Predator has evolved and different versions were covered by more researches:
\nhttps://securelist.com/a-predatory-tale/89779/
\nhttps://www.fortinet.com/blog/threat-research/predator-the-thief-new-routes-delivery.html

\n

Differences in modern samples

\n

There are quite a few differences between current sample (v.3.3.0) and the ones researched previously.  We will list them as bullet points:

\n\n

Mutex name generation algorithm has completely changed

\n

Conclusion

\n

Though not among the most prevalent malware’s nowadays, Predator the Thief has all the possibilities to become one of them.
\nThe level of dedication that the author puts into his creation promises even more danger from this stealer in the future. Regular updates, evasions, anti-debug techniques, its capabilities, the fact that it can be easily used by thread actors without a lot of technical knowledge – all of these points make Predator a rising star player on malware stealer market, the one that should be definitely reckoned with.

\n

 

\n

IOCs

\n

Hashes

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
MD5SHA1
3cb386716d7b90b4dca1610afbd5b1466e7c5d252c3836eff17a3ad9bf69b8d4be4b81a1
cbcc48fe0fa0fd30cb4c088fae582118b1114fe6add1b570d16822a80678a0c7bef91795
c44920c419a21e07d753ed607fb6d7ca28dd84fd59868bf2bacfa49d7c5aa29cd1558e61
cf2273b943edd0752a09e90f45958c857df2f80abd86898c9befe482ce558541fa5d4efb
b2cbb3d80c8d830a3b3c2bd568ba1826c8f3171868b065dcb3af82c9813a35cefa6928e6
dff67a78bb4866f9da5a0c1781ed53487d9aa5ca823cd77430063a1f92b737722ee0f05a
25f9ec882eac441d4852f92e0eab85953753d1c51cf9612f50817165bbbdca5951e736fd
052ef78b897f555cd79805544e59746e7c69ca83d9f5a206326562fdf190e444269d2485
b380e0abd3c9515a23cc0ed5a25bd4b9e7bfd515ac0a0df4e80b43485b0b91ed62e63349
\n

 

\n

Domains

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Domain
kent-adam.myjino[.]ru
denbaliberdin.myjino[.]ru
15charliescene15.myjino[.]ru
axixaxaxu1337[.]us
madoko.jhfree[.]net
kristihack46.myjino[.]ru
j946104.myjino[.]ru
sayhello[.]host
u96191l2.beget[.]tech
www.haijiangfriut[.]com
btcinvest[.]company
\n

 

\n

Attachments

\n

Python mutex name generation algorithm

\n

import struct
\nimport win32api

\n

disk_num = 0
\nres = win32api.GetVolumeInformation(‘c:\\\\’)
\nif len(res) != 5:
\n    print ‘Something went wrong with API function’
\n    raise Exception
\ndisk_num = res[1]
\nprint ‘Volume serial number (hex): %#08x’ % struct.unpack(‘<I’, struct.pack(‘<i’, disk_num))[0]
\ninit_value = (disk_num * 5) & 0xFFFFFFFF
\nprint ‘Initial value (dec): %d’ % init_value
\ninit_string = str(init_value)
\ntransformed_string = init_string + init_string[2:]
\nprint ‘String value: %s’ % transformed_string
\nmutex_name = ”
\nfor i in range(0, len(transformed_string)):
\n    mutex_name += transformed_string[i] if i % 2 == 1 else chr(ord(transformed_string[i]) + 0x40)
\nprint ‘Mutex name: %s’ % mutex_name

\n

 

\n

 

\n","status":"PUBLISHED","fileName":null,"link":"https://research.checkpoint.com/2020/predator-the-thief/","tags":[],"score":0.007111676502972841,"topStoryDate":null}],"mapData":null,"topMalwareFamilies":null};