An Introduction to Detect-It-Easy (DIE) Script
Introduction
After completing my BAD BASS experiment, I have become more interested in ways to identify, extract from memory, and unpack “shellcode-compiled” PE files. Tools such as Monoxgas’s sRDI, TheWover’s Donut, and hashezerade’s pe_to_shellcode make it easy to generate shellcode loaders for PEs that eliminate the need to write code for manual mapping, handling imports, relocations, TLS callbacks, etc. OffSec-minded security folk can simply integrate these tools with their own projects or C2 frameworks, write shellcode to a local or remote process, and trigger a thread to start execution.
In fact, that’s exactly what the popular Sliver C2 does!
Tools exist such as Hashezerade’s pe-sieve or hollows-hunter that can automate the identification and extraction of shellcode from a process’s memory. However, once the shellcode is extracted, how can we figure out what it does?
Emulation tools exist that can run shellcode in a sandbox to help determine its nature. However, attackers are people too, and so may use off-the-shelf or open-source tooling in order to lessen expenditures in developing their TTPs. If we could simply identify if they’re using a known shellcode-generator, we might be able to shorten our own timeline to unpack it for easier analysis.
Detect-It-Easy (DIE) is one of my favorite tools for the initial triage of a malware sample. In addition to a plethora of other features, it has a large signature database to identify the compiler, linker, CPU architecture, format, and any packers/obfuscators that may relate to a given file.
Writing a signature for DIE is a relatively simple process, but it is largely undocumented. So, how can we start?
DIE Script
DIE Script is a simple language based upon Qt Script. Making heavy usage of the Qt framework for development, this makes sense as it allows for easy construction of a domain-specific language tailored towards your application.
Qt Script itself is based upon ECMAScript, a predecessor to Javascript. As such, it’s easy to pick up for anybody already familiar with programming.
The codebase for DIE Script can be found here. Code documentation is really the only documentation that I have found for it. Different objects exist to read and interpret part of the input file given to DIE, such as those for PE files, Mach (OSX) binaries, ELF, and Binary (simply reading and finding byte-values).
To better understand how to write a DIE script, let’s walk through a simple one that I have developed for identifying Hashezerade’s pe_to_shellcode.
Example Signature
// DIE's signature file
// Author: nicholasmckinney
// Detects hashezerade's reflective pe_to_shellcode generator
// https://github.com/hasherezade/pe_to_shellcode
init("shellcode","pe_to_shellcode");
function detect(bShowType,bShowVersion,bShowOptions)
{
bDetected = 0;
// https://github.com/hasherezade/pe_to_shellcode/blob/master/pe2shc/stub2/stub32.bin
var stub32 = "558BEC51518D45F85350E8AA0100005984C0750883C8FFE9A4000000\
56578B7D08B84D5A00006639070F858C0000008B773C03F7813E50450000757F8D86A0\
00000033DB391875046AFDEB71FF76345750E8FB00000083C40C84C075046AFCEB5C39\
9E80000000742357FFB684000000FFB680000000FF75FCFF75F8E86F00000083C41484\
C075046AFBEB318D86C0000000391874095750E82800000059598B4628B90020000003\
C766854E167408536A0157FFD0EB07FFD0EB036AFE585F5E5BC9C204008B4424045657\
8B7C24108B008B74380C85F674128B0E85C9740C6A006A0157FFD183C60475EE5FB001\
5EC3538B5C241855568B7424185703F3EB3C03C350FF5424188BE885ED74408B7E1003\
FBEB1F79050FB7C1EB058D430203C185C074295055FF54242085C0741F890783C7048B\
0F85C975DB83C6148B460C85C075BD85F60F95C05F5E5D5BC332C0EBF78B4424045355\
568B08034C241457EB478B410483F808723F8D68F8D1ED6A005B74320FB7545908668B\
C266C1E80C6685C074216683F803752B8B44241881E2FF0F00002B44241C03D6035424\
180102433BDD72CE0349048B3185F675B3B0015F5E5D5BC332C0EBF7565768029FE66A\
E88B0100008BF05985F67431688DBDC13F56E87C0000008BF8595985FF741E68FF1F7C\
C956E869000000595985C0740D8B4C240C894104B0018939EB0232C05F5EC353568B74\
240C83CAFF8A1E84DB743B8A7C24105784FF750A8D43BF3C19770380C3206A085F8BCA\
0FBEC3D1E933C28BD181F22083B8ED24010F44D1D0FB83EF0175E3468A1E84DB75CB5F\
F7D25E8BC25BC351515355568B742418B84D5A00005766390675618B463C8B44307885\
C074568B54301C33FF8B4C30188954241C8B5430208B443024894C241085C974388D1C\
328D2C308B4C241C0FB745006A018D048103C6894424188B0303C650E84FFFFFFF5959\
3B44242074164783C50283C3043B7C241072CE33C05F5E5D5B5959C38B4424148B0003\
C6EBEF56578B7C240C83CAFF33F60FB7076685C0745B5355807C2418000FB7C8894C24\
1475108D41BF6683F819770783C120894C2414668B5C24146A085D8BC233CAD1E88BD0\
81F22083B8ED80E1010F44D066D1EB66895C241483ED0174068B4C2414EBD9460FB704\
776685C075A95D5BF7D25F8BC25EC364A1300000005356578B780C83C7148B37EB288D\
46F885C074258B581885DB741E8378300074126A00FF7030E85AFFFFFF59593B442410\
740C8B363BF775D433C05F5E5BC38BC3EBF800000000000000";
// https://github.com/hasherezade/pe_to_shellcode/blob/master/pe2shc/stub2/stub64.bin
var stub64 = "56488BF44883E4F04883EC20E805000000488BE65EC348895C240857\
4883EC30488BF9488D4C2420E83702000084C0750883C8FFE9BD000000B84D5A000066\
39070F85AA00000048635F3C4803DF813B504500000F8597000000488D8BB000000083\
3900750AB8FDFFFFFFE9860000004C8B4330488BD7E86201000084C07507B8FCFFFFFF\
EB6F83BB9000000000742A0F28442420488D4C2420488B93900000004C8BC7660F7F44\
2420E89300000084C07507B8FBFFFFFFEB3C488D8BD00000008339007408488BD7E833\
0000008B4328B9002000004803C766854B16740E4533C0488BCF418D5001FFD0EB09FF\
D0EB05B8FEFFFFFF488B5C24404883C4305FC348895C2408574883EC208B01488BFA48\
8B5C10184885DB741B4C8B0B4D85C974134533C0488BCF418D500141FFD14883C30875\
E5488B5C2430B0014883C4205FC3488BC4488958084889681048897018488978204156\
4883EC208BDA498BF04903D84C8BF1EB4A8BC84803CE41FF16488BE84885C074628B7B\
104803FEEB2679050FB7D1EB07488D56024803D14885D27447488BCD41FF56084885C0\
743B4889074883C708488B0F4885C975D24883C3148B430C85C075AF4885DB0F95C048\
8B5C2430488B6C2438488B742440488B7C24484883C420415EC332C0EBE148895C2408\
48897C2410448B09498BD84C03CA4C8BDA33FF458B114585D27459418379040872F141\
8B51044C8BC74883EA0848D1EA7435430FB74C41080FB7C166C1E80C6685C074236683\
F80A752681E1FF0F0000498BC34103CA482BC34903CB49FFC04801014C3BC272CB418B\
41044C03C8EBA332C0EB02B001488B5C2408488B7C2410C348895C2408488974241057\
4883EC20488BD9B9029FE66AE8FD010000488BF84885C07432BA8DBDC13F488BC8E893\
000000488BF04885C0741DBAFF1F7CC9488BCFE87E0000004885C0740B48894308B001\
488933EB0232C0488B5C2430488B7424384883C4205FC3448A09448ADA4C8BD14183C8\
FFEB414584DB750C418D41BF3C1977044180C120BA08000000418BC8410FBEC14133C0\
D1E9448BC14181F02083B8ED2401440F44C141D0F94883EA0175DB49FFC2458A0A4584\
C975BA41F7D0418BC0C3488BC448895808488968104889701848897820415441564157\
B84D5A0000448BFA4C8BC16639010F85870000004863413C8B8C088800000085C97478\
498D040833FF8B68184885ED746A448B4820448B50244D03C8448B601C4D03D0410FB7\
02458B194D03D883CAFF498D3484EB2741BE080000008BCA0FBEC333C2D1E98BD181F2\
2083B8ED24010F44D1D0FB4983EE0175E249FFC3418A1B84DB75D2F7D2413BD7742D48\
FFC74983C2024983C104483BFD72A833C0488B5C2420488B6C2428488B742430488B7C\
2438415F415E415CC3428B04064903C0EBDC48895C240848897C2410440FB7094183C8\
FF33FF8ADA448BD74C8BD9EB4684DB750F418D41BF6683F8197705664183C120BA0800\
0000418BC8410FB7C14133C0D1E9448BC14181F02083B8ED2401440F44C16641D1E948\
83EA0175DA49FFC2470FB70C53664585C975B4488B5C240841F7D0488B7C2410418BC0\
C348895C240848896C24104889742418574883EC2065488B0425600000008BE9488B78\
184883C720488B1F483BDF7430488D43F04885C07427488B70304885F6741E488B4860\
4885C9740B33D2E834FFFFFF3BC57405488B1BEBD0488BC6EB0233C0488B5C2430488B\
6C2438488B7424404883C4205FC300000000000000000000000000";
var redir32_64_Start = "4D5A4552E8000000005B4883EB09534881C3";
var redir32_Start = "4D5A4552E8000000005883E8095005";
var redir64_Start = "4D5A4552E800000000594883E909488BC14805";
var signatures = [redir32_64_Start, redir32_Start, stub32, redir64_Start, stub64];
for (var i = 0; i < signatures.length; i++) {
if (Binary.isSignaturePresent(0, Binary.getSize(), signatures[i])) {
bDetected = 1;
if (i <= 2) {
sOptions = "x86";
} else {
sOptions = "AMD64";
}
}
}
return result(bShowType, bShowVersion, bShowOptions);
}
As you can see, it looks very similar to Javascript. There are several 3 important functions we can see in the example above:
init
detect
result
There are also several important global variables shown:
bDetected
sOptions
But what do these all do?
Understanding DIE Script through the Qt Script Debugger
Since DIE makes use of the Qt Script environment, it is provided with a built-in debugger that will allow us to view loaded scripts and how these functions relate to the final detection output.
To see that, load up DIE and click on the bottom-left signatures button.
In the window that appears, select the pe_to_shellcode.1.sg entry on the left under Binary and then click the Debug button in the top-right corner. If the signature isn’t there (because you have a release that hasn’t been updated since 09-24-2022), you can copy the signature code above and save it to a file named pe_to_shellcode.1.sg under the db/Binary directory in your DIE installation.
Global Variables
The “Loaded Scripts” pane on the left-hand side of the debugger window can show us all the scripts that have been loaded into the script engine to debug this script. The image above shows us our signature file, but selecting the db/_init script below will show us some important information.
We can see 5 global variables (only 2 were set directly in the signature above):
sType
sName
sVersion
sOptions
bDetected
All can be set through the init function, which is automatically called by the script engine. However, we will typicall only set the first 2 or 3, depending upon if there are different versions of the thing we are trying to detect. In the example above, for example, we are only setting the sType to “shellcode” and sName to “pe_to_shellcode”.
Setting all 4 global variables will result in something like the following display:
The bDetected variable is simply a boolean (0 == False, 1 == True) that determines if a signature matches a given file.
Auto-magically called Functions
As mentioned earlier, the init function is automatically called by the scripting engine. The same is also true for the detect function.
So these two functions make up the building blocks of our signature files:
init
detect
For a signature to work, you can include additional functions, but you must have those two functions.
Below is a skeleton signature file you can use for your own scripts:
// DIE's signature file
// Author: <YOUR NAME>
init("<TYPE PLACEHOLDER>","<NAME PLACEHOLDER>", "<VERSION PLACEHOLDER>");
function detect(bShowType,bShowVersion,bShowOptions)
{
bDetected = 0;
// Your logic here
return result(bShowType, bShowVersion, bShowOptions);
}
Conclusion
I hope this post might help you become faster acquainted with writing your own signatures for DIE. It’s an amazing tool and the maintainer is fast to respond and polite. Documentation may be lacking, but at its core the DSL is simple.
Important Links: