Before we jump into analysis, let’s do a quick recap of the previous actions performed by Tinba and described in the STAGE 1 post:
- Prepares a second layer payload
- Rewrites its own header with such payload
- JMP to the new EntryPoint
- Decrypt and copy another layer to a new section
- JMPs to the new section and continues execution
Just a reminder, all the behavior described in these series of posts is based on the following samples of Tinba:
Let’s take a look at the overall picture of the new section decompressed and copied by the malware. The last thing we saw in the post:
As you may notice, there is a list of different API functions from the Windows Libraries (kernel32 in this case). But before this list, there are some binary opcodes at the beginning of the section. These are the instructions:
Section Init Code
The first instruction is a relative CALL, it’s used to put current EIP to stack and then pop it into EBX registry. After subtracting 0x401005, EBX will be used as offset to access different elements into local memory addresses (for example the last CALL instruction of this snippet).
There are three consecutives CALLs after the EBX is set, let’s see what those functions do. First one is used to resolve an array of function’s names, all of them contained in the Kernel32 library by applying the following snippet of code (function named unk_1870CE8 has been renamed to ‘resolves_all_kernel32_functions’):
Kernel32 Functions Resolution
In the above image you can see how EBX is used as offset in some instructions as expected, this technique is widely used in Tinba.
First function of the above picture is exactly the same as the one described in the previous blog post. By looking at substrings in the current loaded modules’ names, it’s able to get the base address of the kernel32 library. In the same way, the second function just returns the address of the GetProcAddress function in the kernel32 library also using a substring comparison.
Finally, it gets the address where the array starts in order to use it in the following CALL to “functions_resolver”. Such function has the following content:
Functions Resolution Loop
The function iterates over the array of function’s names until 0xFFFFFFFF is found – end of array. The content of such an array after executing this function is as follows:
Looking at the above picture, we can see that the current content of the array of functions is:
- Addr of the function
- Null DWORD
- Function Name in Str (ascii)
Except GetProcAddress which is stored at the very beginning of the array, and not following the structure. Its name is not included in the array.
And, if you look at the Hex-View, we can see how the next set of functions like: “FindWindowA, GetWindowthreadProcessId…” are not resolved yet. First of all, it happens because the iteration stops after finding 0xFFFFFFFF. The function’s names array is grouped by library so that only Kernel32 functions have been resolved during this iteration. Moreover, only Kernel32 functions are needed in the current stage of the malware.
Continuing with the initial code of this layer, second function gets some information related to the system and uses the returning values to generate a string identifying the following actions that is whether the system is already infected by Tinba. It accomplishes this by opening a Mutex with the name generated during this process.
Finally, the third function is used to create a new Process in suspended mode and override its entry point with a payload to continue the infection. This technique is called RunPE. If we review the initial code snippet used in this layer and change the functions’ name, it has the following aspect:
In addition, the Process ends after doing the RunPE by calling ExitProcess, as stated before, it’s using the EBX register plus a hardcoded offset to access such functions (just resolved) within the array.
But let’s go a bit deeper in the RunPE to see how exactly it works, because it has some tricky actions. The do_RunPE function does the following:
Tries to open a Mutex
As you may notice, after trying to Open the Mutex, if the returned value is not Null, which means that Mutex can be opened, the execution flow jumps to 0x1870b15 at the end of the function.
On the other side, if OpenMutexA returns Null, it means that there is no Mutex already opened, which indicates that the current system is not already infected, then RunPE starts.
Creates a new Process in suspended mode
The above snippet shows a call to a relative 0x18709FD, making ‘winver’ string address to be added into the stack. It will be used as first parameter for: xor_ror function, which has the following aspect:
This function is used to ‘xor’ the first argument (in this case, ‘winver’), but for some reason the key value (first red mark in the above picture) is 0x00.Therefore, there is no change in the original string. In other versions of Tinba, this key had other values, for example: 0xcc. Also, because of this there is a ‘ror’ in the key, which is totally useless in this case.
Once the value has been ‘xored’, It will be used as processName in the input parameters of CreateProcess call. Winver is a system process and it is used, to keep stealth.
After the child creation in suspended mode, the process handler of the parent is duplicated:
Now, the handler referenced to the current working process can be used by the child, ‘winver’ in this case.
Finally, in order to terminate the RunPE, the WriteProcesMemory and ResumeThread are called:
The above code snippet is Writing 0xEB bytes from the current process to the child. Once it’s written, the Process with the new payload is resumed and starts its execution.
In the meanwhile, the parent process remains asleep for 20 seconds trying to open the Mutex every second (such mutex should be opened by the child executed process).
If any action of the RunPE function fails, or Mutex cannot be opened within 20 seconds, the binary tries to still infect the Explorer process in order to remain active in the system. Once this function is finished, ExitProcess is called.
Let’s see what the winver process does once ResumeThread is called.
The entry point of the process is as follows:
The above code creates a new memory allocation in the current process, winver.exe, and ReadProcessMemory from the parent process. As you may notice, it moves the ‘alloc_size’ address to EAX and then put into the stack the following values: old_process_addr (eax+4) and old_process_hndlr (eax+8). Those values were created in the parent process and moved to the current one by using the last WriteProcessMemory call in the parent.
The following picture shows the environment once everything has been executed and just before the RET instruction is called.
If we look at the Hex-View, we can see how all the malicious payload has been copied, it is the same payload as before, but this time there are no opcodes at the beginning. In addition, all the Kernel32.dll functions already resolved are still resolved, and the resolution addresses are directly copied into the new process.
Finally, before doing ret, there is a ‘push edi’, which is the offset in the new section where the execution must start. The initial function called after the returning opcode is executed is the following:
It turns out that this function is exactly the same as the one called as fallback if the RunPE fails. The objective of this function is to inject the malicious code into the Explorer process. We will see the next and final post!
To summarize what we saw in this post:
- Resolves Kernel32 functions from the array
- Creates new process (winver.exe – a system legit process) in suspended mode
- Overrides its memory
- Resumes process
- Child process copies Tinba core payload from parent
- Starts Explorer.exe infection