{
	"id": "9f7292ae-22d7-4b1e-a075-be3f994b4f17",
	"created_at": "2026-04-06T00:09:29.569843Z",
	"updated_at": "2026-04-10T03:29:58.166835Z",
	"deleted_at": null,
	"sha1_hash": "97a0ed3e4e9be9e541f0907302e8f13e4af53367",
	"title": "Internet Explorer and Windows zero-day exploits used in Operation PowerFall",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 140020,
	"plain_text": "Internet Explorer and Windows zero-day exploits used in\r\nOperation PowerFall\r\nBy Boris Larin\r\nPublished: 2020-08-12 · Archived: 2026-04-05 13:41:08 UTC\r\nExecutive summary\r\nIn May 2020, Kaspersky technologies prevented an attack on a South Korean company by a malicious script for\r\nInternet Explorer. Closer analysis revealed that the attack used a previously unknown full chain that consisted of\r\ntwo zero-day exploits: a remote code execution exploit for Internet Explorer and an elevation of privilege exploit\r\nfor Windows. Unlike a previous full chain that we discovered, used in Operation WizardOpium, the new full chain\r\ntargeted the latest builds of Windows 10, and our tests demonstrated reliable exploitation of Internet Explorer 11\r\nand Windows 10 build 18363 x64.\r\nOn June 8, 2020, we reported our discoveries to Microsoft, and the company confirmed the vulnerabilities. At the\r\ntime of our report, the security team at Microsoft had already prepared a patch for vulnerability CVE-2020-0986\r\nthat was used in the zero-day elevation of privilege exploit, but before our discovery, the exploitability of this\r\nvulnerability was considered less likely. The patch for CVE-2020-0986 was released on June 9, 2020.\r\nMicrosoft assigned CVE-2020-1380 to a use-after-free vulnerability in JScript and the patch was released on\r\nAugust 11, 2020.\r\nWe are calling this and related attacks ‘Operation PowerFall’. Currently, we are unable to establish a definitive\r\nlink with any known threat actors, but due to similarities with previously discovered exploits, we believe that\r\nDarkHotel may be behind this attack. Kaspersky products detect Operation PowerFall attacks with verdict\r\nPDM:Exploit.Win32.Generic.\r\nInternet Explorer 11 remote code execution exploit\r\nThe most recent zero-day exploits for Internet Explorer discovered in the wild relied on the vulnerabilities CVE-2020-0674, CVE-2019-1429, CVE-2019-0676 and CVE-2018-8653 in the legacy JavaScript engine jscript.dll. In\r\ncontrast, CVE-2020-1380 is a vulnerability in jscript9.dll, which has been used by default starting with Internet\r\nExplorer 9, and because of this, the mitigation steps recommended by Microsoft (restricting the usage of\r\njscript.dll) cannot protect against this particular vulnerability.\r\nCVE-2020-1380 is a Use-After-Free vulnerability that is caused by JIT optimization and the lack of necessary\r\nchecks in just-in-time compiled code. A proof-of-concept (PoC) that triggers vulnerability is demonstrated below:\r\nhttps://securelist.com/ie-and-windows-zero-day-operation-powerfall/97976/\r\nPage 1 of 9\n\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\r\n10\r\n11\r\n12\r\n13\r\n14\r\n15\r\n16\r\n17\r\n18\r\n19\r\n20\r\n21\r\n22\r\n23\r\n24\r\n25\r\n26\r\nfunction func(O, A, F, O2) {\r\n    arguments.push = Array.prototype.push;\r\n    O = 1;\r\n    arguments.length = 0;\r\n    arguments.push(O2);\r\n    if (F == 1) {\r\n        O = 2;\r\n    }\r\n    // execute abp.valueOf() and write by dangling pointer\r\n    A[5] = O;\r\n};\r\n// prepare objects\r\nvar an = new ArrayBuffer(0x8c);\r\nvar fa = new Float32Array(an);\r\n// compile func\r\nfunc(1, fa, 1, {});\r\nfor (var i = 0; i \u003c 0x10000; i++) {\r\n    func(1, fa, 1, 1);\r\n}\r\nvar abp = {};\r\nabp.valueOf = function() {\r\n    // free\r\n    worker = new Worker('worker.js');\r\n    worker.postMessage(an, [an]);\r\n    worker.terminate();\r\n    worker = null;\r\nhttps://securelist.com/ie-and-windows-zero-day-operation-powerfall/97976/\r\nPage 2 of 9\n\n27\r\n28\r\n29\r\n30\r\n31\r\n32\r\n33\r\n34\r\n35\r\n36\r\n37\r\n38\r\n39\r\n40\r\n41\r\n42\r\n43\r\n44\r\n45\r\n46\r\n    // sleep\r\n    var start = Date.now();\r\n    while (Date.now() - start \u003c 200) {}\r\n    // TODO: reclaim freed memory\r\n    return 0\r\n};\r\ntry {\r\n    func(1, fa, 0, abp);\r\n} catch (e) {\r\n    reload()\r\n}\r\nTo understand this vulnerability, let us take a look at how func() is executed. It is important to understand what\r\nvalue is set to A[5]. According to the code, it should be an O argument. At function start, the O argument is re-assigned to 1, but then the function arguments length is set to 0. This operation does not clear function arguments\r\n(as it would normally do with regular array) but allows to put argument O2 into the arguments list at index zero\r\nusing Array.prototype.push, meaning O = O2 now. Besides that, if the argument F is equal to 1, then O will be re-assigned once again, but to the integer number 2. It means that depending on the value of the F argument, the O\r\nargument is equal to either the value of the O2 argument or the integer number 2. The argument A is a typed array\r\nof 32-bit floating point numbers, and before assigning a value to index 5 of the array, this value should be\r\nhttps://securelist.com/ie-and-windows-zero-day-operation-powerfall/97976/\r\nPage 3 of 9\n\nconverted to a float. Converting an integer to a float is a relatively simple task, but it become less straightforward\r\nwhen an object is converted to a float number. The exploit uses the object abp with an overridden valueOf()\r\nmethod. This method is executed when the object is converted to a float, but inside the method there is code that\r\nfrees ArrayBuffer, which is viewed by Float32Array and where the returned value will be set. To prevent the value\r\nfrom being stored in the memory of the freed object, the JavaScript engine needs to check the status of the object\r\nbefore storing the value in it. To convert and store the float value safely, JScript9.dll uses the function\r\nJs::TypedArray\u003cfloat,0\u003e::BaseTypedDirectSetItem(). You can see decompiled code of this function below:\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\r\n10\r\n11\r\n12\r\n13\r\n14\r\n15\r\n16\r\n17\r\n18\r\n19\r\n20\r\n21\r\nint Js::TypedArray\u003cfloat,0\u003e::BaseTypedDirectSetItem(Js::TypedArray\u003cfloat,0\u003e *this, unsigned int\r\nindex, void *object, int reserved)\r\n{\r\n    Js::JavascriptConversion::ToNumber(object, this-\u003etype-\u003elibrary-\u003econtext);\r\n    if ( LOBYTE(this-\u003eview[0]-\u003eunusable) )\r\n        Js::JavascriptError::ThrowTypeError(this-\u003etype-\u003elibrary-\u003econtext, 0x800A15E4, 0);\r\n    if ( index \u003c this-\u003ecount )\r\n    {\r\n        *(float *)\u0026this-\u003ebuffer[4 * index] = Js::JavascriptConversion::ToNumber(\r\n            object,\r\n            this-\u003etype-\u003elibrary-\u003econtext);\r\n    }\r\n    return 1;\r\n}\r\ndouble Js::JavascriptConversion::ToNumber(void *object, struct Js::ScriptContext *context)\r\n{\r\n    if ( (unsigned char)object \u0026 1 )\r\n        return (double)((int)object \u003e\u003e 1);\r\n    if ( *(void **)object == VirtualTableInfo\u003cJs::JavascriptNumber\u003e::Address[0] )\r\n        return *((double *)object + 1);\r\n    return Js::JavascriptConversion::ToNumber_Full(object, context);\r\nhttps://securelist.com/ie-and-windows-zero-day-operation-powerfall/97976/\r\nPage 4 of 9\n\n22 }\r\nThis function checks the view[0]-\u003eunusable and count fields of the typed float array and when ArrayBuffer is\r\nfreed during execution of the valueOf() method, both of these checks will fail because view[0]-\u003eunusable will be\r\nset to 1 and count will be set to 0 during the first call to Js::JavascriptConversion::ToNumber(). The problem lies\r\nin the fact that the function Js::TypedArray\u003cfloat,0\u003e::BaseTypedDirectSetItem() is used only in interpretation\r\nmode.\r\nWhen the function func() is compiled just in time, the JavaScript engine will use the vulnerable code below.\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\r\n10\r\n11\r\n12\r\n13\r\n14\r\n15\r\n16\r\n17\r\n18\r\n19\r\n20\r\nif ( !((unsigned char)floatArray \u0026 1) \u0026\u0026 *(void *)floatArray == \u0026Js::TypedArray\u003cfloat,0\u003e::vftable )\r\n{\r\n  if ( floatArray-\u003ecount \u003e index )\r\n  {\r\n    buffer = floatArray-\u003ebuffer + 4*index;\r\n    if ( object \u0026 1 )\r\n    {\r\n      *(float *)buffer = (double)(object \u003e\u003e 1);\r\n    }\r\n    else\r\n    {\r\n      if ( *(void *)object != \u0026Js::JavascriptNumber::vftable )\r\n      {\r\n        Js::JavascriptConversion::ToFloat_Helper(object, (float *)buffer, context);\r\n      }\r\n      else\r\n      {\r\n        *(float *)buffer = *(double *)(object-\u003evalue);\r\n      }\r\n    }\r\nhttps://securelist.com/ie-and-windows-zero-day-operation-powerfall/97976/\r\nPage 5 of 9\n\n21\r\n22\r\n  }\r\n}\r\nAnd here is the code of the Js::JavascriptConversion::ToFloat_Helper() function.\r\nvoid Js::JavascriptConversion::ToFloat_Helper(void *object, float *buffer, struct Js::ScriptContext\r\n*context)\r\n{\r\n  *buffer = Js::JavascriptConversion::ToNumber_Full(object, context);\r\n}\r\nAs you can see, unlike in interpretation mode, in just-in-time compiled code, the life cycle of ArrayBuffer is not\r\nchecked, and its memory can be freed and then reclaimed during a call to the valueOf() function. Additionally, the\r\nattacker can control at what index the returned value is written. However, in the case when “arguments.length =\r\n0;”and “arguments.push(O2);” are replaced in PoC with “arguments[0] = O2;” then\r\nJs::JavascriptConversion::ToFloat_Helper() will not trigger the bug because implicit calls will be disabled and it\r\nwill not perform a call to the valueOf() function.\r\nTo ensure that the function func() is compiled just in time, the exploit executes this function 0x10000 times,\r\nperforming a harmless conversion of the integer, and only after that func() is executed once more, triggering the\r\nbug. To free ArrayBuffer, the exploit uses a common technique abusing the Web Workers API. The function\r\npostMessage() can be used to serialize objects to messages and send them to the worker. As a side effect,\r\ntransferred objects are freed and become unusable in the current script context. When ArrayBuffer is freed, the\r\nexploit triggers garbage collection via code that simulates the use of the Sleep() function: it is a while loop that\r\nchecks for the time lapse between Date.now() and the previously stored value. After that, the exploit reclaims the\r\nmemory with integer arrays.\r\nfor (var i = 0; i \u003c T.length; i += 1) {\r\n        T[i] = new Array((0x1000 - 0x20) / 4);\r\n        T[i][0] = 0x666; // item needs to be set to allocate LargeHeapBucket\r\n    }\r\nWhen a large number of arrays is created, Internet Explorer allocates new LargeHeapBlock objects, which are\r\nused by IE’s custom heap implementation. The LargeHeapBlock objects will store the addresses of buffers\r\nhttps://securelist.com/ie-and-windows-zero-day-operation-powerfall/97976/\r\nPage 6 of 9\n\nallocated for the arrays. If the expected memory layout is achieved successfully, the vulnerability will overwrite\r\nthe value at the offset 0x14 of LargeHeapBlock with 0, which happens to be the allocated block count.\r\nLargeHeapBlock structure for jscript9.dll x86\r\n After that, the exploit allocates a huge number of arrays and sets them to another array that was prepared at the\r\ninitial stage of the exploitation. Then this array is set to null, and the exploit makes a call to the CollectGarbage()\r\nfunction. This results in defragmentation of the heap, and the modified LargeHeapBlock will be freed along with\r\nits associated array buffers. At this stage, the exploit creates a large amount of integer arrays in hopes of\r\nreclaiming the previously freed array buffers. The newly created arrays have a magic value set at index zero, and\r\nthis value is checked through a dangling pointer to the previously freed array to detect if the exploitation was\r\nsuccessful.\r\n        for (var i = 0; i \u003c K.length; i += 1) {\r\n            K[i] = new Array((0x1000 - 0x20) / 4);\r\n            K[i][0] = 0x888; // store magic\r\n        }\r\n        for (var i = 0; i \u003c T.length; i += 1) {\r\n            if (T[i][0] == 0x888) { // find array accessible through dangling pointer\r\n                R = T[i];\r\n                break;\r\n            }\r\nhttps://securelist.com/ie-and-windows-zero-day-operation-powerfall/97976/\r\nPage 7 of 9\n\n}\r\nAs a result, the exploit creates two different JavascriptNativeIntArray objects with buffers pointing to the same\r\nlocation. This makes it possible to retrieve the addresses of the objects and even create new malformed objects.\r\nThe exploit takes advantage of these primitives to create a malformed DataView object and get read/write access\r\nto the whole address space of the process.\r\nAfter the building of the arbitrary read/write primitives, it is time to bypass Control Flow Guard (CFG) and get\r\ncode execution. The exploit uses the Array’s vftable pointer to get the module base address of jscript9.dll. From\r\nthere, it parses the PE header of jscript9.dll to get the address of the Import Directory Table and resolves the base\r\naddresses of the other modules. The goal here is to find the address of the function VirtualProtect(), which will be\r\nused to make the shellcode executable. After that, the exploit searches for two signatures in jscript9.dll. Those\r\nsignatures correspond to the address of the Unicode string “split” and the address of the function:\r\nJsUtil::DoublyLinkedListElement\u003cThreadContext\u003e::LinkToBeginning\u003cThreadContext\u003e(). The address of the\r\nUnicode string “split” is used to get a code reference to the string and with its help, to resolve the address of the\r\nfunction Js::JavascriptString::EntrySplit(), which implements the string method split(). The address of the\r\nfunction LinkToBeginning\u003cThreadContext\u003e() is used to obtain the address of the first ThreadContext object in the\r\nglobal linked list. The exploit locates the last entry in the linked list and uses it to get the location of the stack for\r\nthe thread responsible for the execution of the script. After that comes the final stage. The exploit executes the\r\nsplit() method and an object with an overridden valueOf() method is provided as a limit argument. When the\r\noverridden valueOf() method is executed during the execution of the function Js::JavascriptString::EntrySplit(),\r\nthe exploit will search the thread’s stack to find the return address, place the shellcode in a prepared buffer, obtain\r\nits address, and finally build a return-oriented programming (ROP) chain to execute the shellcode by overwriting\r\nthe return address of the function.\r\nNext stage\r\nThe shellcode is a reflective DLL loader for the portable executable (PE) module that is appended to the shellcode.\r\nThe module is very small in size, and the whole functionality is located inside a single function. It creates a file\r\nwithin a temporary folder with the name ok.exe and writes to it the contents of another executable that is present\r\nin the remote code execution exploit. After that, ok.exe is executed.\r\nThe ok.exe executable contains is an elevation of privilege exploit for the arbitrary pointer dereference\r\nvulnerability CVE-2020-0986 in the GDI Print / Print Spooler API. Initially, this vulnerability was reported to\r\nMicrosoft by an anonymous user working with Trend Micro’s Zero Day Initiative back in December 2019. Due to\r\nthe patch not being released for six months since the original report, ZDI posted a public advisory for this\r\nvulnerability as a zero-day on May 19, 2020. The next day, the vulnerability was exploited in the previously\r\nmentioned attack.\r\nThe vulnerability makes it possible to read and write the arbitrary memory of the splwow64.exe process using\r\ninterprocess communication, and use it to achieve code execution in the splwow64.exe process, bypassing the\r\nCFG and EncodePointer protection. The exploit comes with two executables embedded in its resources. The first\r\nexecutable is written to disk as CreateDC.exe and is used to create a device context (DC), which is required for\r\nhttps://securelist.com/ie-and-windows-zero-day-operation-powerfall/97976/\r\nPage 8 of 9\n\nexploitation. The second executable has the name PoPc.dll and if the exploitation is successful, it is executed by\r\nsplwow64.exe with a medium integrity level. We will provide further details on CVE-2020-0986 and its\r\nexploitation in a follow-up post.\r\nExecution of a malicious PowerShell command from splwow64.exe\r\nThe main functionality of PoPc.dll is also located inside a single function. It executes an encoded PowerShell\r\ncommand that proceeds to download a file from www[.]static-cdn1[.]com/update.zip, saves it to the temporary\r\nfolder as upgrader.exe and executes it. We were unable to analyze upgrader.exe because Kaspersky technologies\r\nprevented the attack before the executable was downloaded.\r\nIoCs\r\nwww[.]static-cdn1[.]com/update.zip\r\nB06F1F2D3C016D13307BC7CE47C90594\r\nD02632CFFC18194107CC5BF76AECA7E87E9082FED64A535722AD4502A4D51199\r\n5877EAECA1FE8A3A15D6C8C5D7FA240B\r\n7577E42177ED7FC811DE4BC854EC226EB037F797C3B114E163940A86FD8B078B\r\nB72731B699922608FF3844CCC8FC36B4\r\n7765F836D2D049127A25376165B1AC43CD109D8B9D8C5396B8DA91ADC61ECCB1\r\nE01254D7AF1D044E555032E1F78FF38F\r\n81D07CAE45CAF27CBB9A1717B08B3AB358B647397F08A6F9C7652D00DBF2AE24\r\nSource: https://securelist.com/ie-and-windows-zero-day-operation-powerfall/97976/\r\nhttps://securelist.com/ie-and-windows-zero-day-operation-powerfall/97976/\r\nPage 9 of 9",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"ETDA"
	],
	"references": [
		"https://securelist.com/ie-and-windows-zero-day-operation-powerfall/97976/"
	],
	"report_names": [
		"97976"
	],
	"threat_actors": [
		{
			"id": "1dadf04e-d725-426f-9f6c-08c5be7da159",
			"created_at": "2022-10-25T15:50:23.624538Z",
			"updated_at": "2026-04-10T02:00:05.286895Z",
			"deleted_at": null,
			"main_name": "Darkhotel",
			"aliases": [
				"Darkhotel",
				"DUBNIUM",
				"Zigzag Hail"
			],
			"source_name": "MITRE:Darkhotel",
			"tools": null,
			"source_id": "MITRE",
			"reports": null
		},
		{
			"id": "2008a79d-2f3a-475f-abef-3bc119a1bf38",
			"created_at": "2022-10-25T16:07:24.028651Z",
			"updated_at": "2026-04-10T02:00:04.845194Z",
			"deleted_at": null,
			"main_name": "Operation WizardOpium",
			"aliases": [],
			"source_name": "ETDA:Operation WizardOpium",
			"tools": [],
			"source_id": "ETDA",
			"reports": null
		},
		{
			"id": "5cd3fcb0-eb56-49ac-8125-47ebee93311d",
			"created_at": "2023-01-06T13:46:39.065814Z",
			"updated_at": "2026-04-10T02:00:03.201808Z",
			"deleted_at": null,
			"main_name": "Operation WizardOpium",
			"aliases": [],
			"source_name": "MISPGALAXY:Operation WizardOpium",
			"tools": [],
			"source_id": "MISPGALAXY",
			"reports": null
		},
		{
			"id": "b13c19d6-247d-47ba-86ba-15a94accc179",
			"created_at": "2024-05-01T02:03:08.149923Z",
			"updated_at": "2026-04-10T02:00:03.763147Z",
			"deleted_at": null,
			"main_name": "TUNGSTEN BRIDGE",
			"aliases": [
				"APT-C-06 ",
				"ATK52 ",
				"CTG-1948 ",
				"DUBNIUM ",
				"DarkHotel ",
				"Fallout Team ",
				"Shadow Crane ",
				"Zigzag Hail "
			],
			"source_name": "Secureworks:TUNGSTEN BRIDGE",
			"tools": [
				"Nemim",
				"Tapaoux"
			],
			"source_id": "Secureworks",
			"reports": null
		},
		{
			"id": "2b4eec94-7672-4bee-acb2-b857d0d26d12",
			"created_at": "2023-01-06T13:46:38.272109Z",
			"updated_at": "2026-04-10T02:00:02.906089Z",
			"deleted_at": null,
			"main_name": "DarkHotel",
			"aliases": [
				"T-APT-02",
				"Nemim",
				"Nemin",
				"Shadow Crane",
				"G0012",
				"DUBNIUM",
				"Karba",
				"APT-C-06",
				"SIG25",
				"TUNGSTEN BRIDGE",
				"Zigzag Hail",
				"Fallout Team",
				"Luder",
				"Tapaoux",
				"ATK52"
			],
			"source_name": "MISPGALAXY:DarkHotel",
			"tools": [],
			"source_id": "MISPGALAXY",
			"reports": null
		},
		{
			"id": "c0cedde3-5a9b-430f-9b77-e6568307205e",
			"created_at": "2022-10-25T16:07:23.528994Z",
			"updated_at": "2026-04-10T02:00:04.642473Z",
			"deleted_at": null,
			"main_name": "DarkHotel",
			"aliases": [
				"APT-C-06",
				"ATK 52",
				"CTG-1948",
				"Dubnium",
				"Fallout Team",
				"G0012",
				"G0126",
				"Higaisa",
				"Luder",
				"Operation DarkHotel",
				"Operation Daybreak",
				"Operation Inexsmar",
				"Operation PowerFall",
				"Operation The Gh0st Remains the Same",
				"Purple Pygmy",
				"SIG25",
				"Shadow Crane",
				"T-APT-02",
				"TieOnJoe",
				"Tungsten Bridge",
				"Zigzag Hail"
			],
			"source_name": "ETDA:DarkHotel",
			"tools": [
				"Asruex",
				"DarkHotel",
				"DmaUp3.exe",
				"GreezeBackdoor",
				"Karba",
				"Nemain",
				"Nemim",
				"Ramsay",
				"Retro",
				"Tapaoux",
				"Trojan.Win32.Karba.e",
				"Virus.Win32.Pioneer.dx",
				"igfxext.exe",
				"msieckc.exe"
			],
			"source_id": "ETDA",
			"reports": null
		}
	],
	"ts_created_at": 1775434169,
	"ts_updated_at": 1775791798,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/97a0ed3e4e9be9e541f0907302e8f13e4af53367.pdf",
		"text": "https://archive.orkl.eu/97a0ed3e4e9be9e541f0907302e8f13e4af53367.txt",
		"img": "https://archive.orkl.eu/97a0ed3e4e9be9e541f0907302e8f13e4af53367.jpg"
	}
}