{
	"id": "77dcbf7b-f5e3-4c8b-8052-a594be493b30",
	"created_at": "2026-04-06T00:22:19.39067Z",
	"updated_at": "2026-04-10T13:12:22.342636Z",
	"deleted_at": null,
	"sha1_hash": "19b4dfa56ed98c39a44c2da7e99807397e3c0aa7",
	"title": "Defeating VMProtect's Latest Tricks | cyber.wtf",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 651150,
	"plain_text": "Defeating VMProtect's Latest Tricks | cyber.wtf\r\nArchived: 2026-04-05 19:33:46 UTC\r\nA colleague of mine recently came across a SystemBC sample that is protected with VMProtect 3.6 or higher.\r\nVMProtect is a commercial packer that comes with advanced anti-debugging and VM detection capabilities. It\r\nalso employs code virtualization - a technique where normal machine code is translated into a proprietary\r\nbytecode language that is interpreted at runtime - which makes it very hard to determine the exact logic\r\nimplemented by the code. ScyllaHide, our anti-anti-debug tool of choice, was not up to the task of hiding the\r\ndebugger from the packer, so we dove into the unexpectedly deep rabbit hole of figuring out what is going on.\r\nKernel mode tooling such as TitanHide/HyperHide would probably have been up to the task of defeating most of\r\nthe checks, but we prefer user mode tooling, since it is much less complicated to use and easier to debug.\r\nDebugger checks\r\nOn the face of it, VMProtect’s debugger checks don’t use any exotic techniques. We have seen the following\r\nchecks, all of which Scylla has long since had support for:\r\nPEB.BeingDebugged\r\nProcessDebugPort\r\nProcessDebugObjectHandle\r\nNtSetInformationThread ThreadHideFromDebugger\r\nCloseHandle with invalid handle value\r\nNon-zero debug registers in CONTEXT when catching exceptions\r\nIf you’d like to get a deeper understanding of these techniques, please refer to Check Point Research’s excellent\r\nAnti-Debug Tricks page.\r\nThe first problem we encountered is that when debugging a 32-bit executable on a 64-bit system, it is possible for\r\nthe executable to hide code from the debugger by doing a far jump into the 64-bit segment 0x33 (also dubbed\r\n“Heaven’s Gate”). In particular, most of the anti-debug checks will be executed in 64-bit mode. x32dbg/x64dbg\r\ncan only debug their respective bitness, but not both at once.The only debugger we know of that can achieve such\r\na feat is WinDbg, but its interface is rather unpleasant to deal with. So we switched to a native 32-bit system for\r\nthis sample.\r\nThe next problem is that VMProtect has built-in syscall tables for the most common Windows builds. If it finds\r\nthat it runs on a known system version, it uses the sysenter instruction to directly call into kernel mode for its\r\nchecks, bypassing any user mode hooks (there is ProcessInstrumentationCallback , but it has limits - the\r\ncallback is triggered after a syscall has executed but before it returns, which means we cannot prevent changes\r\nsuch as setting the ThreadHideFromDebugger flag). The obvious approach to deal with this is to change the build\r\nnumber to a fake number everywhere, so that the packer has no choice but to call the APIs the regular way.\r\nhttps://cyber.wtf/2023/02/09/defeating-vmprotects-latest-tricks/\r\nPage 1 of 5\n\nEnter the myriad ways of obtaining the Windows build number these days.\r\nScyllaHide already supports patching the OsBuildNumber field in the PEB (Process Environment Block), which is\r\nalso what APIs such as GetVersion / GetVersionEx read:\r\n if (flags \u0026 PEB_PATCH_OsBuildNumber)\r\n {\r\n peb-\u003eOSBuildNumber++;\r\n }\r\nDoing a simple increment comes with a little caveat. What if you happen to be on a recent Windows 10 build such\r\nas 19043 or 19044? 19044/19045 are both valid builds. Oops.\r\nIn a ScyllaHide issue, Mattiwatti, who is one of the maintainers of ScyllaHide, already outlined the next technique\r\nthat VMProtect uses on modern Windows versions. KUSER_SHARED_DATA is a read-only page that is mapped into\r\nevery Windows process. It allows quick access to many commonly needed values, such as versions, current tick\r\ncount, current time, and much more. APIs such as GetTickCount access this page in order to avoid a context\r\nswitch to the kernel. Since Windows 10 the structure also contains the build number. This is unfortunate because\r\nScyllaHide cannot modify this value. But it is possible to set an access hardware breakpoint and change the value\r\nin the register after VMProtect has read it. Alternatively, Windows 8.1 or older can be used.\r\nIf these ways have proven unfruitful for obtaining a known build number, VMProtect will suspect that we’re up to\r\nno good and starts inspecting system library versions. It will parse NTDLL’s version resource, which contains\r\nplenty occurrences of the build number. ScyllaHide patches one of them (the FileVersion string), which apparently\r\nwas sufficient at some point in the past. Not anymore. Nowadays, VMProtect inspects all four build numbers (two\r\nin binary form, two in strings). So we adjusted ScyllaHide to set all of them to a fake version.\r\nMemory breakpoints on other libraries’ resource sections have not been hit, so that should suffice for fooling the\r\npacker, right? Right?! Nope. You can imagine that at this point we were quite confused how it still managed to\r\nfind the correct version.\r\nhttps://cyber.wtf/2023/02/09/defeating-vmprotects-latest-tricks/\r\nPage 2 of 5\n\nAfter some more tracing, we found the call sequence NtQueryVirtualMemory (to get NTDLL’s full path on disk)\r\n→ NtOpenFile → NtCreateSection → NtMapViewOfSection . This maps a fresh copy of the NTDLL image\r\ninto memory. Oh, well. They call APIs, we hook APIs. One somewhat mean detail is that NtCreateSection is\r\ncalled with the flag SEC_IMAGE_NO_EXECUTE . This prevents image load notify routines and debugger events from\r\nbeing raised when the image is loaded, however the flag is only supported since Windows 8. As a result, anything\r\npacked with this VMProtect version will not run on Windows beta builds from before Windows 8, and incidentally\r\nthis also comes to bite us when faking the version on a Windows 7 system - VMProtect knows the usual build\r\nnumbers of Windows 7 (7600/7601) and it would ordinarily never take this code path. Since we’re hooking\r\nanyway, we change this value to SEC_IMAGE when detecting an older OS and everyone is happy.\r\nIf you thought we’re finally rid of the direct syscalls now, think again. VMProtect has one final trick up its sleeve:\r\nit tries to extract syscall numbers from the library code. We expected this all along, but it makes sense that it only\r\nhappens on the fresh mapping from disk. This way, the packer can avoid any hooks and other code patches placed\r\non the regular NTDLL image in memory. That is, until we come in and deliberately destroy some API entrypoints\r\nin the mapping. The packer expects the first instruction to be mov eax, CallNumber , and if it cannot find that, it\r\nfinally gives up and calls the regular NTDLL API export.\r\nVM Checks\r\nOverview of VM checks:\r\nhttps://cyber.wtf/2023/02/09/defeating-vmprotects-latest-tricks/\r\nPage 3 of 5\n\ncpuid hypervisor bit \u0026 hypervisor vendor\r\nTrap Flag tricks in combination with forced VM exit via rdtsc/cpuid\r\nNtQuerySystemInformation with SystemFirmwareTableInformation, TableIDs FIRM and RSMB\r\n(presence of sbiedll.dll in process, for Sandboxie detection)\r\nFor further reading about these checks, please refer to Check Point Research’s Evasion techniques page\r\n(particularly, the “CPU” and “Firmware tables” sections).\r\nThe first is relatively easy to mitigate by disabling paravirtualization, which will remove any hypervisor\r\ninformation from cpuid.\r\nThe second trick is somewhat mean and took us a while to figure out. Consider the following code block:\r\n \u003cprepare flags value with TF bit (0x100) on stack\u003e\r\n popfd ; apply flag change\r\n cpuid ; force VM exit\r\n nop ; filler for EIP check\r\n push ebx ; next regular instruction\r\nThe Trap Flag provides single stepping functionality for debuggers. If you set it, the processor will raise an\r\ninterrupt after executing the following instruction. So we expect the instruction pointer in the exception that the\r\nOS gives us to be at the nop . As it turns out, older VirtualBox versions will rat you out, because they have a bug\r\nthat causes EIP to be at the push instead. This is fixed in version 7.0.4, which was pretty recent at the time of\r\nwriting.\r\nFinally, VMProtect will inspect some firmware bits. The RSMB provider is used to obtain raw SMBIOS values\r\nsuch as BIOS vendor, BIOS version, system family, system UUID, etc. VirtualBox also has custom OEM fields\r\nfor “VBoxRev” and “VBoxVer”. It is possible to change all of these through VM configuration changes\r\n( VBoxManage setextradata ). The FIRM provider is a different story. It allows reading 128K at physical\r\naddresses 0xC0000 and 0xE0000, respectively. These ranges contain BIOS Option ROM code, which may contain\r\nsome strings that give away that virtualization is in use. This cannot be helped without modifying and recompiling\r\nthe VM software.\r\nWhat we can do is hook NtQuerySystemInformation and return empty data. Since ScyllaHide hooks that API\r\nanyway, we simply integrated a code path for the SystemFirmwareTableInformation class.\r\nConclusion\r\nWith the aforementioned counter-measures (or rather, counter-measures for the counter-measures) in place, we can\r\nfinally debug and unpack the sample. Fun fact: The sample is packed twice, so after VMProtect has finished\r\ninitialization, another layer of packer code runs and unpacks the final SystemBC malware. This is not very smart,\r\nbecause this nullifies the effects of VMProtect’s import protection, making it trivial to obtain a fully functional\r\ndump. Malware packers often lack the sophistication of commercial grade packers.\r\nhttps://cyber.wtf/2023/02/09/defeating-vmprotects-latest-tricks/\r\nPage 4 of 5\n\nWe’ve committed all ScyllaHide code changes to GitHub and are currently waiting for them to be accepted\r\nupstream.\r\nIoCs\r\ne21f50a1794acd0a585c86a157e8f70b044adcc860d6d0648d874deccd7ba653 (SystemBC sample)\r\nSource: https://cyber.wtf/2023/02/09/defeating-vmprotects-latest-tricks/\r\nhttps://cyber.wtf/2023/02/09/defeating-vmprotects-latest-tricks/\r\nPage 5 of 5",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"origins": [
		"web"
	],
	"references": [
		"https://cyber.wtf/2023/02/09/defeating-vmprotects-latest-tricks/"
	],
	"report_names": [
		"defeating-vmprotects-latest-tricks"
	],
	"threat_actors": [
		{
			"id": "f8dddd06-da24-4184-9e24-4c22bdd1cbbf",
			"created_at": "2023-01-06T13:46:38.626906Z",
			"updated_at": "2026-04-10T02:00:03.043681Z",
			"deleted_at": null,
			"main_name": "Tick",
			"aliases": [
				"G0060",
				"Stalker Taurus",
				"PLA Unit 61419",
				"Swirl Typhoon",
				"Nian",
				"BRONZE BUTLER",
				"REDBALDKNIGHT",
				"STALKER PANDA"
			],
			"source_name": "MISPGALAXY:Tick",
			"tools": [],
			"source_id": "MISPGALAXY",
			"reports": null
		},
		{
			"id": "54e55585-1025-49d2-9de8-90fc7a631f45",
			"created_at": "2025-08-07T02:03:24.563488Z",
			"updated_at": "2026-04-10T02:00:03.715427Z",
			"deleted_at": null,
			"main_name": "BRONZE BUTLER",
			"aliases": [
				"CTG-2006 ",
				"Daserf",
				"Stalker Panda ",
				"Swirl Typhoon ",
				"Tick "
			],
			"source_name": "Secureworks:BRONZE BUTLER",
			"tools": [
				"ABK",
				"BBK",
				"Casper",
				"DGet",
				"Daserf",
				"Datper",
				"Ghostdown",
				"Gofarer",
				"MSGet",
				"Mimikatz",
				"Netboy",
				"RarStar",
				"Screen Capture Tool",
				"ShadowPad",
				"ShadowPy",
				"T-SMB",
				"down_new",
				"gsecdump"
			],
			"source_id": "Secureworks",
			"reports": null
		},
		{
			"id": "d4e7cd9a-2290-4f89-a645-85b9a46d004b",
			"created_at": "2022-10-25T16:07:23.419513Z",
			"updated_at": "2026-04-10T02:00:04.591062Z",
			"deleted_at": null,
			"main_name": "Bronze Butler",
			"aliases": [
				"Bronze Butler",
				"CTG-2006",
				"G0060",
				"Operation ENDTRADE",
				"RedBaldNight",
				"Stalker Panda",
				"Stalker Taurus",
				"Swirl Typhoon",
				"TEMP.Tick",
				"Tick"
			],
			"source_name": "ETDA:Bronze Butler",
			"tools": [
				"8.t Dropper",
				"8.t RTF exploit builder",
				"8t_dropper",
				"9002 RAT",
				"AngryRebel",
				"Blogspot",
				"Daserf",
				"Datper",
				"Elirks",
				"Farfli",
				"Gh0st RAT",
				"Ghost RAT",
				"HOMEUNIX",
				"HidraQ",
				"HomamDownloader",
				"Homux",
				"Hydraq",
				"Lilith",
				"Lilith RAT",
				"McRAT",
				"MdmBot",
				"Mimikatz",
				"Minzen",
				"Moudour",
				"Muirim",
				"Mydoor",
				"Nioupale",
				"PCRat",
				"POISONPLUG.SHADOW",
				"Roarur",
				"RoyalRoad",
				"ShadowPad Winnti",
				"ShadowWali",
				"ShadowWalker",
				"SymonLoader",
				"WCE",
				"Wali",
				"Windows Credential Editor",
				"Windows Credentials Editor",
				"XShellGhost",
				"XXMM",
				"gsecdump",
				"rarstar"
			],
			"source_id": "ETDA",
			"reports": null
		}
	],
	"ts_created_at": 1775434939,
	"ts_updated_at": 1775826742,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/19b4dfa56ed98c39a44c2da7e99807397e3c0aa7.pdf",
		"text": "https://archive.orkl.eu/19b4dfa56ed98c39a44c2da7e99807397e3c0aa7.txt",
		"img": "https://archive.orkl.eu/19b4dfa56ed98c39a44c2da7e99807397e3c0aa7.jpg"
	}
}