{"id":191,"date":"2023-08-22T09:00:00","date_gmt":"2023-08-22T09:00:00","guid":{"rendered":"http:\/\/justruss.tech\/index.php\/2023\/08\/22\/writing-your-first-yara-rule-from-sample-to-signature\/"},"modified":"2026-05-13T12:59:35","modified_gmt":"2026-05-13T12:59:35","slug":"writing-your-first-yara-rule-from-sample-to-signature","status":"publish","type":"post","link":"https:\/\/justruss.tech\/index.php\/2023\/08\/22\/writing-your-first-yara-rule-from-sample-to-signature\/","title":{"rendered":"Writing Your First Yara Rule: From Sample to Signature"},"content":{"rendered":"<p>Yara is a pattern-matching tool for malware identification. A Yara rule describes characteristics of a file \u2014 byte strings, text strings, PE header values, file size, entropy \u2014 and Yara returns a match when a file satisfies the rule conditions.<br \/>\nThis is a walkthrough of writing a rule from scratch against a real Qakbot loader sample from the obama200 campaign.<\/p>\n<h3>Initial sample analysis<\/h3>\n<pre># Basic file info\nfile Qakbot_loader.exe\n# PE32 executable (GUI) Intel 80386, for MS Windows\n\n# File hash\nsha256sum Qakbot_loader.exe\n# 3a4b5c6d7e8f... (submit to VirusTotal first to check existing coverage)\n\n# Check existing Yara coverage\nyara \/path\/to\/malware_ruleset.yar Qakbot_loader.exe\n# 0 matches - no existing coverage for this variant<\/pre>\n<h3>Extracting strings<\/h3>\n<pre>strings -a -n 8 Qakbot_loader.exe | sort -u | grep -v \"^[A-Z][a-z]\" | head -100\n\n# Interesting output:\nSOFTWARE\\Microsoft\\Dtcpipe\nobama200\nPluginStart\nPluginStop\nPluginCode\nC2List\nEncryptData\nDecryptData<\/pre>\n<p>The registry key <code>SOFTWARE\\Microsoft\\Dtcpipe<\/code> is a known Qakbot persistence location. The string <code>obama200<\/code> is the campaign tag embedded in this specific build. The export function names (PluginStart, PluginStop, etc.) are<br \/>\nconsistent across Qakbot loader variants.<\/p>\n<h3>Identifying byte patterns with a hex editor<\/h3>\n<pre># Use radare2 to look at the decryption routine\nr2 -A Qakbot_loader.exe\n[0x00401000]&gt; s main\n[0x00401234]&gt; pdf\n# Look for the XOR loop - common in Qakbot config decryption:\n# 0x00401240  8b45f8  mov eax, [var_8h]\n# 0x00401243  33c2    xor eax, edx       ; XOR with key\n# 0x00401245  8945f8  mov [var_8h], eax\n\n# Find the XOR key value - look for immediate value in XOR instructions\n[0x00401000]&gt; \/x 35deadc0de\n# hit at 0x00401890<\/pre>\n<p>The 4-byte XOR key <code>DE AD C0 DE<\/code> (0xDEADC0DE) appears as an immediate operand in the decryption routine and is consistent across several samples in the obama200 campaign.<\/p>\n<h3>PE structure analysis<\/h3>\n<pre>python3 -c \"\nimport pefile\npe = pefile.PE('Qakbot_loader.exe')\n\nprint('Exports:')\nif hasattr(pe, 'DIRECTORY_ENTRY_EXPORT'):\n    for exp in pe.DIRECTORY_ENTRY_EXPORT.symbols:\n        print(f'  {exp.name.decode() if exp.name else \\\"\\\"}')\n\nprint(f'\\nTimestamp: {pe.FILE_HEADER.TimeDateStamp:#x}')\nprint(f'Compile time: {pe.FILE_HEADER.dump_dict()[\\\"TimeDateStamp\\\"][\\\"Value\\\"]}')\n\n# Check for anomalous section entropy (packed\/encrypted sections have high entropy)\nfor section in pe.sections:\n    entropy = section.get_entropy()\n    print(f'Section {section.Name.decode().strip(chr(0))}: entropy={entropy:.2f}')\n\"\n\n# Output:\n# Exports:\n#   PluginStart\n#   PluginStop\n#   PluginCode\n# Compile time: Fri Aug 18 14:22:31 2023\n# Section .text:   entropy=6.21\n# Section .rdata:  entropy=4.87\n# Section .data:   entropy=7.94  &lt;-- high entropy, likely packed\/encrypted<\/pre>\n<h3>The rule<\/h3>\n<pre>rule Qakbot_obama200_loader {\n    meta:\n        author      = \"justruss\"\n        description = \"Qakbot loader - obama200 campaign variant\"\n        date        = \"2023-08-20\"\n        hash        = \"3a4b5c6d7e8f...\"\n        reference   = \"https:\/\/justruss.tech\"\n        tlp         = \"WHITE\"\n\n    strings:\n        \/\/ Registry persistence path\n        $reg_path   = \"SOFTWARE\\Microsoft\\Dtcpipe\" wide ascii\n\n        \/\/ Campaign tag\n        $campaign   = \"obama200\" nocase\n\n        \/\/ XOR decryption key as immediate operand\n        $xor_key    = { 35 DE AD C0 DE }\n\n        \/\/ Export function names present in all loader variants\n        $export_1   = \"PluginStart\" ascii\n        $export_2   = \"PluginStop\"  ascii\n        $export_3   = \"PluginCode\"  ascii\n\n    condition:\n        \/\/ Must be a valid PE file\n        uint16(0) == 0x5A4D\n        and uint32(uint32(0x3C)) == 0x00004550\n\n        \/\/ Reasonable size range for this loader family\n        and filesize &gt; 50KB\n        and filesize &lt; 2MB\n\n        \/\/ Must match at least 2 of the 3 export names (some variants missing one)\n        and 2 of ($export_*)\n\n        \/\/ Plus either the registry path, campaign tag, or XOR key\n        and 1 of ($reg_path, $campaign, $xor_key)\n}<\/pre>\n<h3>Testing the rule<\/h3>\n<pre># Test against the original sample\nyara -r qakbot_obama200.yar Qakbot_loader.exe\n# Qakbot_obama200_loader Qakbot_loader.exe\n\n# False positive check against clean Windows binaries\nyara -r qakbot_obama200.yar C:\\Windows\\System32\\ 2&gt;\/dev\/null\n# (no output = no false positives)\n\n# Retrohunt against sample corpus\nyara -r qakbot_obama200.yar \/opt\/malware_samples\/ 2&gt;\/dev\/null | grep Qakbot\n# Lists all matching samples - useful for measuring family coverage<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Yara rules feel mysterious until you write one from<br \/>\nscratch against a real malware sample.<\/p>\n","protected":false},"author":0,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[13],"tags":[],"class_list":["post-191","post","type-post","status-publish","format-standard","hentry","category-threat-hunting"],"_links":{"self":[{"href":"https:\/\/justruss.tech\/index.php\/wp-json\/wp\/v2\/posts\/191","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/justruss.tech\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/justruss.tech\/index.php\/wp-json\/wp\/v2\/types\/post"}],"replies":[{"embeddable":true,"href":"https:\/\/justruss.tech\/index.php\/wp-json\/wp\/v2\/comments?post=191"}],"version-history":[{"count":2,"href":"https:\/\/justruss.tech\/index.php\/wp-json\/wp\/v2\/posts\/191\/revisions"}],"predecessor-version":[{"id":233,"href":"https:\/\/justruss.tech\/index.php\/wp-json\/wp\/v2\/posts\/191\/revisions\/233"}],"wp:attachment":[{"href":"https:\/\/justruss.tech\/index.php\/wp-json\/wp\/v2\/media?parent=191"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/justruss.tech\/index.php\/wp-json\/wp\/v2\/categories?post=191"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/justruss.tech\/index.php\/wp-json\/wp\/v2\/tags?post=191"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}