{
	"id": "635c7046-ef86-4f28-9ba7-01308cf529af",
	"created_at": "2026-04-10T03:22:06.023989Z",
	"updated_at": "2026-04-10T03:22:19.748448Z",
	"deleted_at": null,
	"sha1_hash": "ae75c2a0b1564fd4d607632644c6ee03e59bfa2b",
	"title": "Navigating QakBot samples with grap",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 1216663,
	"plain_text": "Navigating QakBot samples with grap\r\nArchived: 2026-04-10 02:44:42 UTC\r\nPublished: September 4, 2020\r\ngrap is our tool to match binaries at the assembly code level, matching control flow graphs:\r\nhttps://github.com/QuoSecGmbH/grap/\r\nThis post demonstrates how to use grap to quickly find and analyse documented features (based on public reports)\r\nin published QakBot samples:\r\nAll features and IOC described here are already published (see References)\r\nSamples are from Malpedia: win.qakbot\r\nWe explain both the grap standalone tool and the IDA plugin\r\nThis is a tutorial demonstrating grap’s features with increasing complexity.\r\nReferences\r\n[1] - Reversing Qakbot - https://hatching.io/blog/reversing-qakbot/\r\n[2] - Deep Analysis of QBot Banking Trojan - https://n1ght-w0lf.github.io/malware%20analysis/qbot-banking-trojan/\r\n[3] - Malware Analysis: Qakbot [Part 2] - https://darkopcodes.wordpress.com/2020/06/07/malware-analysis-qakbot-part-2/\r\nQakBot samples are packed and the unpacking process consists in decrypting a buffer that will be written over the\r\nmemory-mapped first-stage PE [1].\r\nThus Malpedia has a 3 types of QakBot samples, they are renamed to not disclose them, for instance:\r\nFirst-stage PE (packed): s10\r\nDumped PE (at some stage during or after the unpacking process): s10_dump_2\r\nUnpacked PE (reconstructed): s10_unpacked\r\n2 - Approach\r\nWe focus on features (decryption, anti-analysis, parsers…) because:\r\nThey will be shared across samples\r\nSome are already documented\r\nRules (grap patterns) detecting features:\r\nhttps://quosecgmbh.github.io/blog/grap_qakbot_navigation.html\r\nPage 1 of 15\n\nHelp navigate (similar) new samples\r\nAllow to study deleted, modified or new features within incoming samples\r\n3 - First-stage PE: PE parsing\r\nQakBot unpacks itself and fixes headers [1], we can thus expect comparison or write of values such as “MZ”\r\n(0x5a4d) or “PE” (0x4550).\r\nThe easiest way is to write a quick pattern that will be automatically converted as a full grap pattern. Looking for\r\nany instruction containing 0x5a4d can be done with grap 0x5a4d * :\r\n$ grap 0x5a4d *\r\ns05.grapcfg - 2035 instructions\r\n1 matches: tmp0 (1)\r\ntmp0 - match 1\r\n1: 0x401933, cmp eax, 0x5a4d\r\n---\r\ns12.grapcfg - 1616 instructions\r\n1 matches: tmp0 (1)\r\ntmp0 - match 1\r\n1: 0x402dfb, cmp word ptr [eax], 0x5a4d\r\n---\r\ns12_unpacked_1.grapcfg - 13891 instructions\r\n1 matches: tmp0 (1)\r\ntmp0 - match 1\r\n1: 0x40451b, mov eax, 0x5a4d\r\nThis first run will disassemble the binaries using grap’s embedded disassembler (recursive disassembler based on\r\nCapstone) and save the disassembly into “.grapcfg” files so there is no need to disassemble them again.\r\nLet’s investigate the generated pattern ( grap -v will output its path, the IDA plugin can also be used) and see\r\nthat this is done through a regex on the full instruction string:\r\nA more precise condition can be written to match any instruction having 0x5a4d or 0x4550 as an argument: arg1\r\nis 0x5a4d or arg2 is 0x5a4d or arg1 is 0x4550 or arg2 is 0x4550 (see trick_PEparsing.grapp):\r\nhttps://quosecgmbh.github.io/blog/grap_qakbot_navigation.html\r\nPage 2 of 15\n\nWithin instruction conditions most fields ( inst , arg1 , arg2 , opcode ) are considered and matched as strings.\r\nThose strings are obtained through Capstone disassembly (even within IDA), patterns shall thus be written with\r\nCapstone syntax in mind.\r\n4 - Unpacked samples\r\n4.1 - cpuid\r\nQakBot uses the cpuid instruction to determine whether it is running in a VM [1]. It first gets the CPU vendor\r\nwith eax=0 and the processor features with eax=1 [2].\r\nLet’s find cpuid usage within our samples:\r\n$ grap -q \"opcode is cpuid\" *.grapcfg\r\ns02_unpacked_1.grapcfg (13898) - tmp0 (2)\r\ns12_unpacked_1.grapcfg (13891) - tmp0 (2)\r\ns06_dump.grapcfg (21194) - tmp0 (2)\r\ns08.grapcfg (22464) - tmp0 (2)\r\ns08_dump.grapcfg (21151) - tmp0 (2)\r\ns10.grapcfg (12479) - tmp0 (2)\r\ns10_dump_2.grapcfg (15130) - tmp0 (2)\r\ns15.grapcfg (10483) - tmp0 (2)\r\nTwo unpacked samples match, both have two ‘cpuid’ matches, let’s investigate those matches by looking also for\r\nthe instruction preceding cpuid :\r\n$ grap \"*-\u003eopcode is cpuid\" s12_unpacked_1.grapcfg\r\ns12_unpacked_1.grapcfg - 13891 instructions\r\n2 matches: tmp0 (2)\r\ntmp0 - match 1\r\n1: 0x406260, mov eax, 1\r\n2: 0x406265, cpuid\r\ntmp0 - match 2\r\n1: 0x4062a7, xor eax, eax\r\n2: 0x4062a9, cpuid\r\nThe pattern *-\u003eopcode is cpuid means: look for any instruction that is sequentially followed by cpuid .\r\nThis sample has the expected behavior: one call with eax=0 ( xor eax, eax ) and one call with eax=1 ( mov\r\neax, 1 ).\r\nhttps://quosecgmbh.github.io/blog/grap_qakbot_navigation.html\r\nPage 3 of 15\n\nWe can use this pattern within IDA using bindings to find where these calls are located:\r\nWe can also use grap to quickly review all instructions preceding cpuid to find possible alternatives:\r\n$ grap \"PRE:* -\u003e opcode is cpuid\" *.grapcfg | grep PRE\r\nPRE: 0x406260, mov eax, 1\r\nPRE: 0x4062a7, xor eax, eax\r\nPRE: 0x40b146, mov eax, 1\r\nPRE: 0x40b19d, xor eax, eax\r\nPRE: 0x404bef, xor eax, eax\r\nPRE: 0x404bff, mov eax, 1\r\nPRE: 0x40b160, mov eax, 1\r\nPRE: 0x40b1ad, xor eax, eax\r\nPRE: 0x4087ee, xor eax, eax\r\nPRE: 0x4087fe, mov eax, 1\r\nPRE: 0x403d27, xor eax, eax\r\nPRE: 0x403db7, mov eax, 1\r\nPRE: 0x406260, mov eax, 1\r\nPRE: 0x4062a7, xor eax, eax\r\nPRE: 0x44a5e9, xor eax, eax\r\nPRE: 0x44a5f9, mov eax, 1\r\nPRE: 0x405610, mov eax, 1\r\nPRE: 0x405657, xor eax, eax\r\nThe pattern \"PRE:* -\u003e opcode is cpuid\" defines the name of the preceding instruction (PRE), allowing for easy\r\ngrepping.\r\nhttps://quosecgmbh.github.io/blog/grap_qakbot_navigation.html\r\nPage 4 of 15\n\nThe cpuid instruction is, as expected, always called with eax=0 or eax=1 .\r\nA simple pattern detecting this technique is (see trick_cpuid.grapp):\r\n4.2 - VMWare detection\r\nReports describe Qakbot’s attempts at detecting VMWare [1] through a technique described by VMWare:\r\nhttps://kb.vmware.com/s/article/1009458 - see “Hypervisor port”\r\nDoing a in on port 0x5658 with:\r\neax = 0x564D5868 (VMware hypervisor magic value, “VMXh”)\r\nebx = 0xFFFFFFFF (UINT_MAX)\r\necx = 0x0a (Getversion command identifier)\r\nedx = 0x5658 (hypervisor port number)\r\nIf you are running on VMWare, it returns ebx = 0x564D5868 (the same VMware hypervisor magic value)\r\nWe can look for a cmp instruction with the VMware hypervisor magic value as argument:\r\nThis leads to a function implementing this VMWare detection\r\nUsing the IDA plugin we can create a detection pattern based on the in instruction and its arguments\r\nThe IDA plugin allows for interactive pattern creation:\r\nDefine the root node and target nodes\r\nChoose general options:\r\nhttps://quosecgmbh.github.io/blog/grap_qakbot_navigation.html\r\nPage 5 of 15\n\n“Generic arguments” will match on opcodes rather than full instructions: mov eax, 0x564D5868 -\u003e\r\nmov\r\n“Lighten memory handling operations” will change some generic memory instructions ( push ,\r\nmov , lea …) into wildcards ( true )\r\n“Standardize jump operations” will streamline conditional jumps ( je , jne …) and calls into the\r\nsame construct ( nchildren==2 )\r\n“Factorize” groups identical conditions with repeat, for instance: repeat=*, lazyrepeat=true\r\nSet the matching precision for each individual instruction (right click): mov eax, 0x564D5868 , mov eax,\r\n* , mov *, 0x564D5868 , mov , or the wildcard *\r\nThe shown generated detection pattern is very specific: if the mov instructions were re-ordered, the code would\r\nstill be identical but the pattern would not detect it.\r\nTo base detection on the magic value and in , we can manually write a pattern that will:\r\nMatch any instruction containing the magic value: inst regex '.*0x564d5868.*'\r\nFollowed by any instructions (0 to 5): cond=\"true\", minrepeat=0, maxrepeat=5, lazyrepeat=true\r\nEnding by an in instruction: opcode is 'in'\r\nWe now have 3 patterns (see trick_vmware_detection.grapp):\r\nLet’s try our patterns on the samples:\r\n$ grap -q trick_vmware_detection.grapp *.grapcfg\r\ns02_unpacked_1.grapcfg (13898) - trick_vmware_detection_cmp (1), trick_vmware_detection_magic (2), trick_vmware_\r\ns06_dump.grapcfg (21194) - trick_vmware_detection_cmp (1), trick_vmware_detection_magic (2)\r\ns08_dump.grapcfg (21151) - trick_vmware_detection_cmp (1), trick_vmware_detection_magic (2)\r\ns10_dump_2.grapcfg (15130) - trick_vmware_detection_cmp (1), trick_vmware_detection_magic (2)\r\nhttps://quosecgmbh.github.io/blog/grap_qakbot_navigation.html\r\nPage 6 of 15\n\ns12_unpacked_1.grapcfg (13891) - trick_vmware_detection_cmp (1), trick_vmware_detection_magic (2), trick_vmware_\r\ns21.grapcfg (11685) - trick_vmware_detection_cmp (1), trick_vmware_detection_magic (1), trick_vmware_detection_v\r\ns23_unpacked.grapcfg (10078) - trick_vmware_detection_magic (2), trick_vmware_detection_v1 (1)\r\n7 samples match:\r\nAs expected the generic trick_vmware_detection_magic has better results than\r\ntrick_vmware_detection_v1\r\nMatching samples all match both trick_vmware_detection_cmp and trick_vmware_detection_magic\r\ntrick_vmware_detection_magic usually matches twice in those samples\r\nLet’s look at the trick_vmware_detection_magic matches ( -m forces to output matching instructions):\r\n$ grap -m trick_vmware_detection.grapp s02_unpacked_1.grapcfg\r\ns02_unpacked_1.grapcfg - 13898 instructions\r\n4 matches: trick_vmware_detection_cmp (1), trick_vmware_detection_magic (2), trick_vmware_detection_v1 (1)\r\ntrick_vmware_detection_magic - match 1\r\n1_magic: 0x406338, mov eax, 0x564d5868\r\n2_other0: 0x40633d, mov ecx, 0x14\r\n2_other1: 0x406342, mov dx, 0x5658\r\n3_in: 0x406346, in eax, dx\r\ntrick_vmware_detection_magic - match 2\r\n1_magic: 0x406d7f, mov eax, 0x564d5868\r\n2_other0: 0x406d84, mov ecx, 0xa\r\n2_other1: 0x406d89, mov dx, 0x5658\r\n3_in: 0x406d8d, in eax, dx\r\nThere is a variant of the technique described by VMWare, this time with ecx=0x14 .\r\nIn this case the in instruction calls the VMWare function to get memory size, this can also be used for VMWare\r\ndetection: https://www.aldeid.com/wiki/VMXh-Magic-Value.\r\nrepeat and lazyrepeat\r\nBy default repeat will match the maximum of sequential instructions which all have a single parent and a single\r\nchild (thus within the same basic-block containing no jump nor call ).\r\nWith lazyrepeat=true , repeat will stop at the first instruction matching the next condition in the pattern:\r\nxor, xor, xor can be matched with cond=\"opcode is xor\", repeat=3\r\nxor, xor, xor can also be matched by cond=\"opcode is xor\", repeat=*\r\nxor, xor, xor, push can be matched by a pattern that has cond=\"opcode is xor\", repeat=*,\r\nlazyrepeat=true followed by cond=\"opcode is push\"\r\nhttps://quosecgmbh.github.io/blog/grap_qakbot_navigation.html\r\nPage 7 of 15\n\nUnfortunately this approach makes some code sequences difficult to match. Let’s try to match all basic blocks\r\nwith this shape:\r\nbetween 1 and 4 of any instructions\r\nxor\r\ncall\r\nA potential pattern would be: \u003c!–\r\ndigraph any_xor_call {\r\nany [cond=true, minrepeat=1, maxrepeat=4, lazyrepeat=true]\r\nxor [cond=\"opcode is xor\"]\r\ncall [cond=\"opcode is call\"]\r\nany−\u003exor\r\nxor−\u003ecall\r\n}\r\n–\u003e\r\nLet’s try to match push, push, xor, xor, call with this pattern.\r\nIf, as shown, we set lazyrepeat=true , candidates for each node are:\r\nany: push, push (stops at the first xor instance)\r\nxor: the first xor\r\ncall: tries to match the second xor (no match)\r\nAlternatively, if we set lazyrepeat=false (“any” node), we get the following candidates:\r\nany: push, push, xor, xor (takes the maximum number of sequential instructions)\r\nxor: tries to match call (no match)\r\nThe pattern can thus not match the wanted instruction sequence.\r\nOne solution is to use repeat=+ with lazyrepeat=true when matching on xor :\r\nhttps://quosecgmbh.github.io/blog/grap_qakbot_navigation.html\r\nPage 8 of 15\n\nCandidates are now:\r\nany: push, push (stops at the first xor instance)\r\nxor: xor, xor (stops at the first call instance)\r\ncall: call\r\nIt matches!\r\nBe aware of the behavior of lazyrepeat when writing and testing patterns with repeated instructions: it leads to\r\nmany unintuitive behavior (no match).\r\n4.3 - Obfuscation with empty loops\r\nQakbot uses unreachable empty loops as an obfuscation method [1]:\r\nBasic blocks at addresses 0x404ce7 and 0x404ceb are unreachable.\r\nA quick pattern can detect the loop: opcode is xor and arg2 is _arg1 -\u003e je -\u003e jmp -2\u003e 1 :\r\narg2 is _arg1 : matches when both arguments are identical\r\nje instead of jz because Capstone outputs je\r\nje -\u003e jmp : je ’s child number 1 (sequentially following) is jmp\r\nhttps://quosecgmbh.github.io/blog/grap_qakbot_navigation.html\r\nPage 9 of 15\n\njmp -2\u003e 1 : jmp ’s child number 2 (the remote one, not sequentially following) is the first matched node\r\n( xor ), defining the loop\r\nThis pattern detects an unreachable empty loop using xor , je and jmp because when both xor arguments\r\nare identical the result is 0 and the condition jump je is always taken.\r\nWe can look for potential variants with xor having different arguments and different conditional jumps,\r\nincluding cases where the jump is not taken : xor -\u003e * -*\u003e * -2\u003e 1 :\r\n-*\u003e : follow both child number 1 and child number 2\r\nMatching both patterns on the samples:\r\n$ grap -q \"opcode is xor and arg2 is _arg1 -\u003e je -\u003e jmp -2\u003e 1\" -p \"xor -\u003e * -*\u003e * -2\u003e 1\" *.grapcfg\r\ns01_unpacked.grapcfg (53616) - tmp0 (224), tmp1 (224)\r\ns02_unpacked_1.grapcfg (13898) - tmp0 (87), tmp1 (87)\r\ns08_dump.grapcfg (21151) - tmp0 (625), tmp1 (625)\r\ns07_dump.grapcfg (48735) - tmp0 (219), tmp1 (219)\r\n[...]\r\nWe find:\r\nGeneralized use of this technique across the samples\r\nBoth patterns (tmp0 and tmp1) match the same number of times: the obfuscation is always the same, no\r\nsimilar alternative was found\r\nThe full patterns for this technique are (see qakbot_trick_emptyloop.grapp):\r\nhttps://quosecgmbh.github.io/blog/grap_qakbot_navigation.html\r\nPage 10 of 15\n\nBack into IDA, the plugin finds and colors matches:\r\n4.4 - RC4 Key Scheduling\r\nQakbot uses RC4 to decrypt an embedded resource [2,3].\r\nRC4’s Key Scheduling algorithm begins with creating a permutation array with all values from 0 to 255: S=[i\r\nfor i in range(0, 0x100)] .\r\nOne documented sample (see “RC4 ENCRYPTION” in [3]) uses a mov eax, 0x100 followed by a push , we\r\ncan find it in our samples with grap -q \"mov eax, 0x100 -\u003e push\" *.grapcfg , exploring the matches leads to\r\nthe following function from which we can create a more precise pattern:\r\nhttps://quosecgmbh.github.io/blog/grap_qakbot_navigation.html\r\nPage 11 of 15\n\nIn many samples the implementation is actually slightly different, without push esi :\r\nOur final pattern will have this push instruction as an optional match with repeat=? (see\r\nqakbot_rc4init.grapp):\r\n5 - Sample navigation\r\nPutting together the previous patterns in a single folder under the right plugin folder (on Linux in “~/idapro-7.5/plugins/idagrap/patterns/test/misc/files/qakbot_patterns”) will make them available within the plugin.\r\nhttps://quosecgmbh.github.io/blog/grap_qakbot_navigation.html\r\nPage 12 of 15\n\nMatching the patterns on unpacked samples ( -sa shows also non-matched samples):\r\ngrap -q -sa qakbot_patterns/ *_unpacked*.grapcfg\r\ns02_unpacked_1.grapcfg (13898) - qakbot_rc4init_gen (1), qakbot_trick_emptyloop_generic (87), qakbot_trick_empty\r\ns12_unpacked_4.grapcfg (5456)\r\ns02_unpacked_2.grapcfg (25461) - qakbot_rc4init_gen (1), qakbot_trick_emptyloop_generic (84), qakbot_trick_empty\r\ns01_unpacked.grapcfg (53616) - qakbot_rc4init_gen (1), qakbot_trick_emptyloop_generic (224), qakbot_trick_emptyl\r\ns03_unpacked.grapcfg (47070) - qakbot_rc4init_gen (1), qakbot_trick_emptyloop_generic (219), qakbot_trick_emptyl\r\ns04_unpacked.grapcfg (52604) - qakbot_rc4init_gen (1), qakbot_trick_emptyloop_generic (224), qakbot_trick_emptyl\r\ns07_unpacked.grapcfg (47281) - qakbot_rc4init_gen (1), qakbot_trick_emptyloop_generic (219), qakbot_trick_emptyl\r\ns09_unpacked.grapcfg (35471) - trick_PEparsing (2)\r\ns12_unpacked_1.grapcfg (13891) - qakbot_rc4init_gen (1), qakbot_trick_emptyloop_generic (87), qakbot_trick_empty\r\ns19_unpacked.grapcfg (36736) - qakbot_rc4init_gen (1), qakbot_trick_emptyloop_generic (176), qakbot_trick_emptyl\r\ns20_unpacked.grapcfg (38455) - qakbot_rc4init_gen (1), qakbot_trick_emptyloop_generic (219), qakbot_trick_emptyl\r\ns22_unpacked.grapcfg (34460) - trick_PEparsing (2)\r\ns23_unpacked.grapcfg (10078) - trick_vmware_detection_magic (2), trick_vmware_detection_v1 (1)\r\n[...]\r\nThe are some differences amonsts unpacked samples:\r\nSome do not have the VM detection features ( cpuid , VMWare detection)\r\nSome miss some data and code (unpacking was unsuccessful?): s22_unpacked\r\nSome are specific modules that do not exhibit the described features (the main module does):\r\ns12_unpacked_4\r\nThough we will not go further now, those differences can be a way to distinguish between versions and sample\r\ntypes.\r\nWithin IDA the patterns help navigate a sample by finding the documented features and leading to their\r\nimplementations:\r\nhttps://quosecgmbh.github.io/blog/grap_qakbot_navigation.html\r\nPage 13 of 15\n\nBesides the previously described patterns, the plugin includes pre-defined patterns such as:\r\nbb_loop: basic block loops (usually small loops)\r\nbb_xor_loop: basic bloc loops containing a xor instruction, can be used to find decryption routines\r\npopular: instructions with at least 10 incoming edges ( nfathers\u003e=10 )\r\ntrick_PEB: access to the process’s Process Environement Block (for instance with mov eax, dword ptr\r\nfs:[0x30] ), this is often used to detect debuggers through the BeingDebugged field\r\n(https://www.aldeid.com/wiki/PEB-Process-Environment-Block)\r\nThe screenshot shows a match of a basic block containing a xor instruction with pattern bb_xor_loop. The\r\nhighlighted instruction ( and ebx, 0x3f ) is actually part of the documented string decryption function [2] (the\r\ndecryption script contains the following operation: idx\u00260x3F ).\r\nThe strings decryption function will be further described in another post, along with a method to automatically\r\ndecrypt the samples’ strings.\r\nConclusion\r\nWe demonstrated how to use grap to find and analyze documented malware features in public samples as a way to\r\nget up-to-speed on a malware family.\r\nCreating grap patterns matching against malware samples is simplified by the IDA plugin and the quick pattern\r\nsyntax. In complex cases you will still need to understand the pattern syntax and write them manually.\r\nPatterns are useful to find implementation alternatives and to navigate and classify unknown samples.\r\nhttps://quosecgmbh.github.io/blog/grap_qakbot_navigation.html\r\nPage 14 of 15\n\nResources\r\nMore documentation on grap can be found here:\r\nInstall (Linux): https://github.com/QuoSecGmbH/grap/#installation\r\nInstall (Windows): https://github.com/QuoSecGmbH/grap/blob/master/WINDOWS.md\r\nInstall (IDA plugin): https://github.com/QuoSecGmbH/grap/blob/master/IDA.md\r\nPattern syntax: https://github.com/QuoSecGmbH/grap/releases/download/v1.1.0/grap_graphs.pdf\r\nSyntax highlighting (vim): https://github.com/QuoSecGmbH/grap/blob/master/doc/syntax_highlighting.md\r\nSource: https://quosecgmbh.github.io/blog/grap_qakbot_navigation.html\r\nhttps://quosecgmbh.github.io/blog/grap_qakbot_navigation.html\r\nPage 15 of 15",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"references": [
		"https://quosecgmbh.github.io/blog/grap_qakbot_navigation.html"
	],
	"report_names": [
		"grap_qakbot_navigation.html"
	],
	"threat_actors": [],
	"ts_created_at": 1775791326,
	"ts_updated_at": 1775791339,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/ae75c2a0b1564fd4d607632644c6ee03e59bfa2b.pdf",
		"text": "https://archive.orkl.eu/ae75c2a0b1564fd4d607632644c6ee03e59bfa2b.txt",
		"img": "https://archive.orkl.eu/ae75c2a0b1564fd4d607632644c6ee03e59bfa2b.jpg"
	}
}