Use YARA rules on Time Travel Debugging traces
A simple binary that launches calc.exe, packed with UPX.
yara-ttd finds the calc.exe
string in the module memory, during a thread creation.
The tested binary decrypts a shellcode, and runs it in a new thread.
yara-ttd manages to find the calc.exe
string on the heap when hooking on the ntdll!NtCreateThreadEx
function.
YARA is a powerful pattern matching tool for binaries.
YARA is a tool aimed at (but not limited to) helping malware researchers to identify and classify malware samples. With YARA you can create descriptions of malware families (or whatever you want to describe) based on textual or binary patterns.
Thanks to YARA, we can save a lot of time by automatically classifying malware samples in a pipeline before analyzing them.
Unfortunately, most malware samples are protected with different kinds of packers. A classical runtime-packer scheme like UPX or VMProtect consists of self-extracting and running obfuscated and sometimes encrypted code. Therefore, YARA cannot deal with packed binaries because it won't find any matches in the packed code.
Time Travel Debugging - or TTD - is a feature of the native Windows debugger WinDbg.
Time Travel Debugging is a tool that allows you to capture a trace of your process as it executes and then replay it later both forwards and backwards. Time Travel Debugging (TTD) can help you debug issues easier by letting you "rewind" your debugger session, instead of having to reproduce the issue until you find the bug.
For malware analysis, TTD is a powerful tool because it allows recording the trace of a malware in a sandbox. Hence, we can safely replay and share the recorded file outside of the sandbox to analyze the malware.
Also, TTD is not a debugger, so it won't be detected by classical anti-debuggind techniques such as PEB.BeingDebugged
.
For a deeper analysis of TTD from a security point of view, check this article: Deep dive into the TTD ecosystem
Of course, TTD is a proprietary software, and this work could not exist without the ttd-bindings developped by commial.
The idea behind yara-ttd
is to use the trace files recorded by TTD with yara
itself to defeat packers.
Because yara
cannot scan the packed binary itself, yara-ttd
provides a way to analyze the trace file that contains all the runtime information, including the unpacking process.
With yara-ttd
, you can select a set of positions in the trace file where you want to scan the memory with your yara rules.
Hence, you can hook the packed binary wherever you want with your yara rules!
yara-ttd
provides several memory scanning strategies, like when modules are loaded, or when virtual memory is allocated, that are often used to store the malware code once it has been unpacked.
For now, yara-ttd
only supports Windows, because it needs to interact with the TTD API.
First build the project:
mkdir build
cd build
cmake ..
cmake --build .
Then, add the TTDReplay.dll
and TTDReplayCPU.dll
files in the same directory as the executable.
You will find these DLL files in %LocalAppData%\Microsoft\WindowsApps\Microsoft.WinDbg_X\TTD
First you need to record a trace (.run
file) of your program.
For a detailed help about how to record a TTD trace, see this tutorial.
Then simply run:
.\yara-ttd path\to\rule path\to\trace\file
There are three modes to scan memory:
- Mode 0 (default): Loaded Module Memory
- Mode 1: Virtual Allocated memory + Loaded Module Memory
- Mode 2: Full memory (can be very long)
You can select the mode with -m
:
.\yara-ttd -m 1 path\to\rule path\to\trace\file
Like the official yara CLI, you can use the -s
flag to print the details of the matches, like the memory address and the exact subrule which matched.
Because TTD doesn't store the memory structure, yara-ttd
computes before the scan the structure of the heap addresses for each time position.
It will create a cache file with the extenstion .tmp
in the same path as the trace file to store the heap structure.
If you want to rerun yara-ttd on a trace and load the heap structure from the generated cache file you can simply do:
.\yara-ttd -m 1 --cache=path\to\cache\file path\to\rule path\to\trace\file
We can access the full memory range of a process with TTD, which corresponds to the min and max address returned by GetSystemInfo
. Nevertheless, most of the time, this range is too important to be scanned without any optimisation.
The default time positions that will be scanned are:
- Exception events
- Module loaded events
- Thread created events
With the flag -t
and -f
, you can select other positions or function calls to scan:
.\yara-ttd -m 1 -t 3C1:A -t 5FF:22 -f ntdll!FreeVirtualMemory -f ntdll!NtCreateThreadEx path\to\rule path\to\trace\file
The -t
flag expects TTD positions in the form major:minor
(both in hexadecimal, without 0x
).
The -f
flag expects a string in the form module_name!function_name
.
You can also add all the functions you want to hook in a file:
> cat .\functions.txt
ntdll!FreeVirtualMemory
ntdll!NtCreateThreadEx
> .\yara-ttd -m 1 -F functions.txt path\to\rule path\to\trace\file
If you have multiple files to scan with yara-ttd
, you can also scan a whole directory.
yara-ttd
will ignore all the following extensions in the folder:
.tmp
(cache files generated byyara-ttd
).idx
(index files generated byttd
).err
(error files generated byttd
).out
(log files generated byttd
)
In the following example, yara-ttd
will scan the files trace01.run
and trace02.run
only.
> dir .\traces
trace01.run
trace01.out
trace01.idx
trace02.run
trace01.run.err
trace01.tmp
> .\yara-ttd path\to\rule .\traces
Alternatively, you can specify all the traces you want to scan in a file and use the flag --scan-list
or -l
. It will scan the files listed, one per line:
> cat .\scan_me.txt
.\traces\trace01.run
.\traces\trace02.run
> .\yara-ttd -l path\to\rule .\scan_me.txt
When scanning multiple files,
yara-ttd
runs scans one after the other, and not simultaneously with threading like the originalyara
cli.
- libyarattd with Python bindings
- Link with ttddbg and IDA: feature that loads IDA at the end of the scan at the position of the match in time and memory
- TTD: Follow child when forking the process