In this post, I will show how to unpack, dump, and analyze a TrickBot sample. The goal is to show the reader the techniques used by malware analysts and why they are used, including wrong assumptions, mistakes, and everything that happens in real-life malware analysis, in contrast to the usual tutorials, where everything goes right (and if something goes wrong, the reader is left alone).
The requirements are, as usual, basic reverse engineering skills, a VM, and IDA.
Sample hash: 01e771dc6cf9572eac3d87120d7a7d1ff95fdc1499b668c7fde2919e0f685256
After opening the sample in IDA, we can quickly see that the sample does a bunch of boring initialization (deobfuscating API call names/strings, finding functions, etc):
Then, it proceeds to call GetProcessMemoryInfo:
A quick Google search yields this anti-debug technique, so if
your debugger is caught, just invert CF on the corresponding ja
instruction and move on.1
Looking forward, you can see that the sample tries to create a mutex, and if it doesn’t exist, it relaunches itself
with -l
as a command line parameter, and deletes itself. In the next run, the mutex already exists, so the other branch
is taken, which is the path that ultimately leads to infection:
However, you don’t really need to let it launch another process, attach to it, etc. Just modify the control flow so that it directly tries to infect you, to spare yourself a minute or two:
As you can see, I selected the inverse branch, and used Ctrl + N to skip through the first step.
Now, the next function that is called is a bit scary:
You don’t need to read nor understand all of that - if you do, you’re accomplishing the author’s goal - to distract
you from actual analysis. As you can see at the end of that GIF, there’s an API call: CreateProcessA
, which suggests
that the sample is going to do a RunPE or something like that. You don’t care – all you want is the final, unpacked,
easy-to-analyze sample, and that’s what you’ll get.
To proceed, put a breakpoint on CreateProcessA
(you can put it in CreateProcessInternalW
if you’re unsure, as all CreateProcess
friends ultimately go through there), and run:
Oops. Something broke. Sometimes, malware uses SEH to obfuscate the control flow, but if you try to open Debugger
> Debugger windows
> SEH list
, you get an error:
This suggests that the SEH list is empty, and that the exception is legit (i.e. something broke). However, if you run the binary outside of a debugger, it infects you, so it must be us breaking something.
To find out, look at the faulting instruction:
.text:0040286D mov esi, [ebp+arg_0]
.text:00402870 mov dl, [esi+edx] ; esi = 0, edx = 0xA -> exception
esi
is 0
, but that’s obviously wrong - to find out what its original value should be, look at the instruction above:
esi
comes from arg_0
. Cross-reference the function to find out where the first parameter comes from, and you end up with:
As you can see, some global variable is zero. Cross-reference it to see where it gets set,
and select the w
item (i.e. xref that writes to the variable):
At first sight, it appears that the GlobalAlloc
call is failing, but if you put a breakpoint and run the binary,
it never hits, and you still get the exception, which means that the other branch is taken - which, in turn, means
that GetFileSize
is returning -1
. But how can that be possible? The referenced file is the sample itself, why can
it not get its own size?
To find the answer, put a breakpoint on GetFileSize
, re-launch the binary, and check the handle passed to the function, to
find out more about the file that’s causing problems:
Snap! It doesn’t hit. If you look above, you’ll see:
v151 = kernel32_CreateFileA(a1, 2147483648, 0, 0, 3, 0, 0);
if ( v151 == -1 )
return 0;
A-ha! That must be the part that’s returning. Putting a breakpoint on the call to CreateFileA
, you see that’s what effectively
happens, and that CreateFileA
returns FFFFFFFF
- but why? a1
is just the path to the sample itself, but CreateFileA
can’t
get a handle to it.
If you look at the CreateFile documentation,
you’ll see that the third parameter is dwShareMode
, and here it is 0
, which means that the call is asking for full control over the file, without
allowing sharing with other processes:
Prevents other processes from opening a file or device if they request delete, read, or write access.
However, our debugger has a handle to that file, and CreateFileA
won’t close it - it’ll just complain that it can’t
get that handle, and return -1
. Our assumption is further confirmed by checking TIB.LastErrorValue
:
TIB[0000071C]:7EFDD000 dd 20h ; LastErrorValue
As per the documentation, 0x20
means
ERROR_SHARING_VIOLATION
- which confirms our previous assumptions. To fix it, the simplest way (imo) is to put a breakpoint on
the CreateFileA
call and change dwShareMode
to FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE
(all permissions), which
is 1 | 2 | 4
-> 7
:
As you can see, eax
now contains a valid HANDLE
, and our CreateProcessA
breapoint finally hits:
It is creating a process using CREATE_SUSPENDED
, which smells a lot like RunPE / process hollowing / whatever you want to call it. Therefore, we
risk it and put a breakpoint on ntdll_NtWriteVirtualMemory
and ntdll_NtResumeThread
, and run; the former would supposedly be used to write the PE,
while the latter to execute the thread that’s going to run the written bytes. The breakpoint on ntdll_NtWriteVirtualMemory
might seem unnecessary, but it
is quite necessary, because if you manage to catch the non-memory-mapped PE file before it’s written, you can very easily dump it and further reversing
gets much easier.
Going forward, after running the binary, the NtWriteVirtualMemory
breakpoint will hit:
If you check the Buffer
parameter, you see:
debug015:00365C80 db 4Dh ; M
debug015:00365C81 db 5Ah ; Z
debug015:00365C82 db 90h
debug015:00365C83 db 0
Bingo! Now, dump that and kill the current debugging session.
To be continued…
If you want to leave a comment, check out the reddit discussion.
-
This “anti-debug” used to catch Cuckoo Sandbox as well (in my experience, at least). ↩
Article Link: https://qmemcpy.io/post/reverse-engineering-malware-trickbot-part-1-packer