When analyzing malware, it is preferred that we identify the code of standard or third-party libraries to focus our analysis on code an attacker created. This study verified the effectiveness of approaches that identify functions of standard and third-party libraries by using FLIRT of IDA Pro.
-
We can use rustbinsign and RIFT to create signatures for standard and third-party libraries.
-
Our verification shows that signature files we generated using the same compilation options as those used for the malware to be analyzed are excellent in both the number of functions identified and the accuracy of identification.
Before creating a signature, we need to identify which crate and Rust version the executable file we analyze uses.
For this purpose, we can use the info command in rustbinsign.
This command enables us to identify the rust compiler version and the crate being used.
Because the rust compiler, the name of crate being used and their versions are extracted from the file field of the Location struct,
we cannot retrieve the crate being used if the location-detail=none option is used.
> rustbinsign info s4killer.exe
TargetRustInfo(
rustc_version='1.87.0',
rustc_commit_hash='4d30011f6c616be074ba655a75e5d55441232bbb',
dependencies=[
Crate(name='crossbeam-deque', version='0.8.5', features=[], repository=None),
Crate(name='crossbeam-epoch', version='0.9.18', features=[], repository=None),
Crate(name='crossbeam-utils', version='0.8.19', features=[], repository=None),
Crate(name='hashbrown', version='0.15.2', features=[], repository=None),
Crate(name='once_cell', version='1.19.0', features=[], repository=None),
Crate(name='rayon', version='1.8.1', features=[], repository=None),
Crate(name='rayon-core', version='1.12.1', features=[], repository=None),
Crate(name='sysinfo', version='0.30.5', features=[], repository=None),
Crate(name='windows', version='0.52.0', features=[], repository=None),
Crate(name='windows-core', version='0.52.0', features=[], repository=None)
],
rust_dependencies_imphash='d9effc64c9481f9d445d7d35ba5db4b9',
guessed_toolchain='windows-msvc'
)
Applying RIFT as an IDA Plugin enables us to output the above information in a json file.
{
"commithash": "05f9846f893b09a1be1fc8560e33fc3c815cfecb",
"target_triple": "pc-windows-msvc",
"arch": "x86_64",
"crates": [
"crossbeam-epoch-0.9.18",
"once_cell-1.19.0",
"rayon-1.8.1",
"windows-0.52.0",
"windows-core-0.52.0",
"crossbeam-deque-0.8.5",
"rustc-demangle-0.1.24",
"sysinfo-0.30.5",
"hashbrown-0.15.2",
"crossbeam-utils-0.8.19",
"rayon-core-1.12.1"
]
}
We can create a signature for a standard library with the sign_stdlib command.
Rustbinsign creates a signature file by using idat, idb2pat.py and sigmake on the DLL for a standard library , which is stored in C:\Users\<username>\.rustup\toolchains
.
> rustbinsign sign_stdlib -t 1.84.0-x86_64-pc-windows-msvc
Unless we use an option, including the build-std option described in "No.2 Reducing binary sizes," a compiled standard library is statically linked as is.
Note that rustbinsign also creates a signature using a compiled standard library, this command provides no option that modifies an option, such as one for optimization, to create a signature.
We can use the download_sign or sign_target command to create a signature for a third-party library.
Whereas the download_sign command specifies a target crate and creates a signature for the crate, the sign_target command extracts dependency on external libraries from the executable file to create a signature.
Creating a signature involves downloading a crate, creating a DLL by specifying the crate-type=dylib option and then creating a signature file using
idat, idb2pat.py and sigmake in the same way as for a standard library.
> rustbinsign download_sign --full-compilation windows-core-0.52.0 1.84.0-x86_64-pc-windows-msvc
The sign_target command enables us to specify compilation options for cargo in the --template option.
Alternatively, we can configure a config file for RIFT, run RIFT with the --flirt option specified in a Python script and then create a signature for a third-party library with the release profile applied.
[Default]
PcfPath = <path to pcf.exe for FLAIR that IDA provides>
SigmakePath = <path to sigmake.exe for FLAIR that IDA provides>
DiaphoraPath = <path to diaphora.py that Diaphora provides>
IdatPath = <path to IDAT>
WorkFolder = <path to a location where a signature or the like is output>
CargoProjFolder = <path to a location where a Cargo project created for generating a signature is output>
> py rift.py --cfg rift_config.cfg --input <JSON file output with an IDA Plugin> --flirt --output <output destination>
Aside from the steps above, we can run RIFT with the --binary-diff option specified and specify the execution result on the IDA GUI. This enables us to identify functions from a third-party library from differences in the binaries,
not from a FLIRT signature.
We examined how to improve the rate of signature identification.
We compiled s4killer using the Cargo compilation options described below and the rustc option described in "No.2 Reducing binary sizes," and measured the rate and accuracy of function identification for each option.
[profile.dev]
strip = false
debug = true
[profile.release]
opt-level = "s"
debug-assertions = false
overflow-checks = false
lto = "fat"
panic = "abort"
codegen-unit = 1
strip = false
debug = true
As shown in the table below, the highest rate and accuracy of function identification tend to be achieved when the two compilation options are consistent with each other.
| Target / Signature | dev | release | minsize |
|---|---|---|---|
| s4killer(dev) | 2376 / 72.2% | 352 / 82.6% | 365 / 79.3% |
| s4killer(release) | 97 / 57.6% | 148 / 59.1% | 151 / 57.8% |
| s4killer(minsize) | 2 / 50.0% | 64 / 71.5% | 274 / 59.5% |
