{
	"id": "8e6cb1f8-c633-49fa-8bd6-58874faf91ef",
	"created_at": "2026-04-06T00:18:37.942507Z",
	"updated_at": "2026-04-10T13:12:55.376279Z",
	"deleted_at": null,
	"sha1_hash": "24a86863faa8e9d628d0f0d3e124eca0de5823fb",
	"title": "Malware source code investigation: BlackLotus - part 1",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 4906097,
	"plain_text": "Malware source code investigation: BlackLotus - part 1\r\nBy MSSP Research Lab\r\nPublished: 2023-07-15 · Archived: 2026-04-05 23:41:55 UTC\r\nBlackLotus is a UEFI bootkit that targets Windows and is capable of evading security software, persisting once it\r\nhas infected a system, bypassing Secure Boot on fully patched installations of Windows 11, and executing\r\npayloads with the highest level of privileges available in the operating system.\r\nThe source code for the BlackLotus UEFI bootkit has been published on GitHub on July, 12, 2023 .\r\nhttps://mssplab.github.io/threat-hunting/2023/07/15/malware-src-blacklotus.html\r\nPage 1 of 32\n\nSince at least October 2022, BlackLotus is a UEFI bootkit that has been for sale on hacking forums. The\r\ndangerous malware is for sale for $5,000, with payments of $200 per update.\r\nIn this small research we are detailed investigate the source code of BlackLotus and highlights the main features.\r\nArchitecturePermalink\r\nBlack Lotus is written in assembly and C and is only 80kb in size, the malicious code can be configured to avoid\r\ninfecting systems in countries in the CIS region (At the time of writing, these countries are Armenia, Azerbaijan,\r\nBelarus, Kazakhstan, Kyrgyzstan, Moldova, Russia, Tajikistan and Uzbekistan).\r\nSource code structure looks like this:\r\nhttps://mssplab.github.io/threat-hunting/2023/07/15/malware-src-blacklotus.html\r\nPage 2 of 32\n\nThe software consists of two major components: the Agent, which is installed on the target device, and the Web\r\nInterface, which is used by administrators to administer bots. A bot in this context refers to a device with the\r\nAgent installed.\r\nCryptographyPermalink\r\nFirst of all, we paid attention to libraries and cryptographic functions:\r\nhttps://mssplab.github.io/threat-hunting/2023/07/15/malware-src-blacklotus.html\r\nPage 3 of 32\n\nAt first we wanted to focus on the WinAPI hashing method by CRC32 at malware development. As you can see,\r\nnothing out of the ordinary here, CRC32 implementation with constant 0xEDB88320L . You can learn more about\r\nhow to use it for hashing when developing malware, for example, here.\r\nThe implementation of the RC4 algorithm is also standard here, there is nothing complicated about it:\r\nhttps://mssplab.github.io/threat-hunting/2023/07/15/malware-src-blacklotus.html\r\nPage 4 of 32\n\nWhat about XOR? This code appears to implement a custom type of encryption on a given data buffer. The\r\nfunction CryptXor is applied to the buffer using the specified Key and the Cipher Block Chaining (CBC)\r\nmethod. The CBC method is a type of block cipher mode that encrypts plaintext into ciphertext. The encryption\r\nof each block depends on the previous block of data:\r\nhttps://mssplab.github.io/threat-hunting/2023/07/15/malware-src-blacklotus.html\r\nPage 5 of 32\n\nIn summary, this function performs a custom type of encryption on the input buffer. It uses XOR operations with a\r\ngiven key and CBC chaining, with the possibility to skip over pairs of zero DWORD s.\r\nAnd also we have function to decrypt via XOR:\r\nhttps://mssplab.github.io/threat-hunting/2023/07/15/malware-src-blacklotus.html\r\nPage 6 of 32\n\nThen, the next interesting thing is files like ntdll_hash.h , kernel32_hash.h , etc:\r\nhttps://mssplab.github.io/threat-hunting/2023/07/15/malware-src-blacklotus.html\r\nPage 7 of 32\n\nhttps://mssplab.github.io/threat-hunting/2023/07/15/malware-src-blacklotus.html\r\nPage 8 of 32\n\nEach of which contains hashes of WINAPI functions and DLL names:\r\nhttps://mssplab.github.io/threat-hunting/2023/07/15/malware-src-blacklotus.html\r\nPage 9 of 32\n\nhttps://mssplab.github.io/threat-hunting/2023/07/15/malware-src-blacklotus.html\r\nPage 10 of 32\n\nAV evasion tacticPermalink\r\nThen, malware author just use GetModuleHandleByHash (DWORD Hash) function:\r\nThe given C function, GetModuleHandleByHash , is a means of dynamically resolving and obtaining a module\r\nhandle given a hash of the module name. This is typically seen in malware code, as it helps to avoid static strings\r\n(like \"kernel32.dll\" ) that could be easily spotted by antivirus heuristic algorithms. This technique increases the\r\ndifficulty of static analysis.\r\nThe function works as follows:\r\n1. It begins by reading the Thread Environment Block (TEB) via inline assembly code. This is a structure\r\nthat Windows maintains per thread to store thread-specific information. The structure of the TEB and the\r\noffsets used indicate that it’s retrieving the first entry in the InLoadOrderModuleList, which is a doubly\r\nlinked list of loaded modules in the order they were loaded. This is a common way to get a list of loaded\r\nmodules without calling any APIs like EnumProcessModules .\r\n2. Once it has the first module, it enters a loop where it processes each module in turn. For each module, it\r\nconverts the module name to lower case and computes its CRC32 hash (using the Crc32Hash function).\r\n3. If the computed hash matches the input hash, it returns the base address of the module (which is effectively\r\nthe same as the module handle, for the purpose of calling GetProcAddress ).\r\nhttps://mssplab.github.io/threat-hunting/2023/07/15/malware-src-blacklotus.html\r\nPage 11 of 32\n\n4. If the hash does not match, it moves to the next module in the InLoadOrderModuleList and repeats the\r\nprocess.\r\n5. If it has checked all the modules and not found a match, it returns NULL .\r\nNote that LDR_MODULE and its linked list structures are part of the Windows Native API (also known as the “NT\r\nAPI”), which is an internal API used by Windows itself. It’s not officially documented by Microsoft, so using it\r\ncan be risky: it can change between different versions or updates of Windows. However, it also provides a way to\r\ndo things that can’t be done with the standard Windows API, so it’s often used in low-level code like device\r\ndrivers or, in this case, bootkit malware.\r\nAlso we have files like advapi32_functions.h , ntdll_functions.h or user32_functions.h :\r\nhttps://mssplab.github.io/threat-hunting/2023/07/15/malware-src-blacklotus.html\r\nPage 12 of 32\n\nhttps://mssplab.github.io/threat-hunting/2023/07/15/malware-src-blacklotus.html\r\nPage 13 of 32\n\nThis piece of code is a C++ header files that defines function pointers to a Windows API functions like:\r\nVirtualAlloc , OpenProcess , and Process32FirstW or NT API structures and functions:\r\nThese are being defined as function pointers rather than directly calling the functions because this can make it\r\neasier to dynamically load these functions at runtime. This can be useful in a few scenarios, such as when writing\r\ncode that needs to run on multiple versions of Windows and not all functions may be available on all versions, and\r\nin our case when trying to evade detection by anti-malware tools (since these tools often flag direct calls to certain\r\nAPI functions as suspicious).\r\nThe GetProcAddressByHash function in the given code is designed to look up a function in a DLL using the hash\r\nof the function’s name, rather than the name itself. This is typically used in malware to make static analysis harder,\r\nas it avoids leaving clear text strings (like \"CreateProcess\" ) in the binary that can be easily identified:\r\nhttps://mssplab.github.io/threat-hunting/2023/07/15/malware-src-blacklotus.html\r\nPage 14 of 32\n\nThis code also assumes that it’s running on the same architecture as the DLL it’s examining, i.e., if the code is\r\ncompiled for a 64-bit target, it assumes the DLL is also 64-bit , and vice versa for 32-bit .\r\nIt’s worth noting that manipulating the PE file format and using hashed function names like this is a common\r\ntechnique used in malware and rootkits to make analysis and detection more difficult.\r\nAlso interesting file is nzt.h :\r\nhttps://mssplab.github.io/threat-hunting/2023/07/15/malware-src-blacklotus.html\r\nPage 15 of 32\n\nAs you can see, function pointer macro: API(Function) is a macro that expands to NzT.Api.p##Function . This\r\nis likely used to call function pointers stored in an API_FUNCTIONS structure, which is part of the NzT_T struct.\r\nNzT_T is a structure that bundles together various components of the bot’s functionality, including an\r\nAPI_FUNCTIONS structure for API function pointers, an API_MODULES structure for loaded module information,\r\na CRC type (for checksum calculations), and an INFECTION_TYPE field indicating the infection status of the bot.\r\nWindows RegistryPermalink\r\nThen, in the registry.c file implements functions for interacting with the Windows Registry:\r\nhttps://mssplab.github.io/threat-hunting/2023/07/15/malware-src-blacklotus.html\r\nPage 16 of 32\n\nGetRegistryStartPath(INT Hive) - This function is used to get the start path of the registry hive, based on the\r\nhive type passed to it (e.g., HKEY_LOCAL_MACHINE ). The path is formatted into the form expected by the Windows\r\nkernel functions, which is a bit different from what you might usually see (e.g., \"\\Registry\\Machine\" instead of\r\nHKEY_LOCAL_MACHINE ). The function returns this path as a wide character string ( LPWSTR ):\r\nhttps://mssplab.github.io/threat-hunting/2023/07/15/malware-src-blacklotus.html\r\nPage 17 of 32\n\nRegistryOpenKeyEx(CONST LPWSTR KeyPath, HANDLE RegistryHandle, ACCESS_MASK AccessMask) - This function\r\nis used to open a specific key in the registry, given its path, a handle to a pre-existing key (or NULL for the root of\r\nthe registry), and an access mask specifying what type of access the function caller requires to the key (e.g.,\r\nKEY_READ , KEY_WRITE ). It uses the NtOpenKey API function from the Windows Native API to actually open the\r\nkey:\r\nhttps://mssplab.github.io/threat-hunting/2023/07/15/malware-src-blacklotus.html\r\nPage 18 of 32\n\nRegistryReadValueEx(CONST LPWSTR KeyPath, CONST LPWSTR Name, LPWSTR* Value) - This function reads a\r\nvalue from a given key in the registry. It does this by opening the key with RegistryOpenKeyEx , then querying\r\nthe value with NtQueryValueKey . The function reads the value’s data into a buffer, which it then returns to the\r\ncaller. If anything goes wrong (e.g., the key couldn’t be opened, the value couldn’t be queried, there wasn’t\r\nenough memory to store the value’s data), the function returns FALSE :\r\nhttps://mssplab.github.io/threat-hunting/2023/07/15/malware-src-blacklotus.html\r\nPage 19 of 32\n\nRegistryReadValue(INT Hive, CONST LPWSTR Path, CONST LPWSTR Name, LPWSTR* Value) - This function\r\ncombines the functionality of the other functions. It reads a value from a specific key in a specific hive of the\r\nregistry. It constructs the full path to the key by concatenating the start path of the hive (obtained with\r\nGetRegistryStartPath ) and the rest of the key path passed to the function. It then reads the value from this key\r\nwith RegistryReadValueEx :\r\nThere are also two functions, but they are not used anywhere and are commented out:\r\nhttps://mssplab.github.io/threat-hunting/2023/07/15/malware-src-blacklotus.html\r\nPage 20 of 32\n\nFilesystemPermalink\r\nThere are also separate functions for working with files in Windows OS - file.c :\r\nhttps://mssplab.github.io/threat-hunting/2023/07/15/malware-src-blacklotus.html\r\nPage 21 of 32\n\nwhich implements such functions as, for example FileGetInfo , FileGetSize , FileOpen , FileWrite , etc.\r\nFileGetInfo(HANDLE FileHandle, PFILE_STANDARD_INFORMATION Info) - This function retrieves standard\r\ninformation about a file. The NtQueryInformationFile function is used to retrieve the information. It takes a\r\nhandle to an open file and a pointer to a FILE_STANDARD_INFORMATION structure to fill with information. The\r\nMemoryZero function is used to clear these structures before use.\r\nThe FILE_STANDARD_INFORMATION structure includes several file attributes such as the allocation size of the file,\r\nthe end of the file, the number of links to the file, and flags to indicate if the file is a directory or if it is deleted. If\r\nthe operation is successful, the function returns TRUE . If the operation fails, it returns FALSE :\r\nFileGetSize(HANDLE FileHandle, PDWORD FileSize) - This function retrieves the size of a file. It does so by\r\ncalling FileGetInfo to get the standard information of the file, and then sets the value pointed to by FileSize\r\nto the AllocationSize.LowPart of the FILE_STANDARD_INFORMATION structure:\r\nhttps://mssplab.github.io/threat-hunting/2023/07/15/malware-src-blacklotus.html\r\nPage 22 of 32\n\nNote that AllocationSize is a LARGE_INTEGER (which is a 64-bit value), and this function is only returning\r\nthe lower 32 bits of it, which may be incorrect for files larger than 4GB .\r\nInjectionsPermalink\r\nAnother functions from source code of investigated malware, for injection logic:\r\nFor example:\r\nLPVOID InjectData(\r\nHANDLE Process,\r\nLPVOID Data,\r\nhttps://mssplab.github.io/threat-hunting/2023/07/15/malware-src-blacklotus.html\r\nPage 23 of 32\n\nDWORD Size\r\n)\r\nHere’s a breakdown of what the function does:\r\nNzT.Api.pVirtualAllocEx(Process, NULL, Size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE) - It\r\nstarts by allocating memory within the virtual memory space of a target process. The size of the allocated memory\r\nis specified by the Size parameter. The memory is both committed ( MEM_COMMIT ) and reserved ( MEM_RESERVE )\r\nfor future use. The allocated memory has read, write, and execute permissions ( PAGE_EXECUTE_READWRITE ). The\r\naddress of the allocated memory is saved in the Address variable. If this operation fails, the function returns\r\nNULL .\r\nNzT.Api.pWriteProcessMemory(Process, Address, Data, Size, NULL) - If memory allocation is successful, the\r\nfunction proceeds to write data into the allocated memory within the target process. It does this using the\r\nWriteProcessMemory function. This function copies data from a buffer ( Data ) in the current process to the\r\nallocated memory ( Address ) in the target process. If the operation fails, it frees the allocated memory using\r\nVirtualFreeEx and returns NULL .\r\nIf both operations are successful, the function returns the address of the allocated memory in the target process.\r\nThis can then be used for various purposes, such as executing the injected code.\r\nThis type of functionality is often seen in malware that injects malicious code into legitimate processes to hide its\r\nactivities or gain higher privileges.\r\nWhat about this injection logic?\r\nDWORD InjectCode(\r\nHANDLE Process,\r\nLPVOID Function\r\n)\r\nwhich also implemented in this file:\r\nhttps://mssplab.github.io/threat-hunting/2023/07/15/malware-src-blacklotus.html\r\nPage 24 of 32\n\nThis function appears to inject code into a target process by creating a section of memory, copying the code into\r\nthis section, performing relocations, and finally mapping this section into the target process.\r\nOnce all the tasks are performed, the function will clean up by closing any open handles and unmap any mapped\r\nviews of files. Finally, it will return the address of the injected function in the target process.\r\nAs with many other kinds of code injection techniques, this one is also commonly seen in malware.\r\nPseudo-Random GeneratorPermalink\r\nAnd there are several functions in this malware guid.c :\r\nhttps://mssplab.github.io/threat-hunting/2023/07/15/malware-src-blacklotus.html\r\nPage 25 of 32\n\nThese functions are designed to generate a pseudo-random GUID (Globally Unique Identifier) . The GUID is\r\nbuilt from the values produced by a simple linear congruential generator ( LCG ), which is a type of pseudorandom\r\nnumber generator.\r\nHere’s what each function does:\r\nGuidRandom(PDWORD Seed) - This is a linear congruential generator (LCG) function that takes a seed as a\r\nparameter and generates a pseudorandom number. It’s important to note that this LCG function always produces\r\nthe same sequence of numbers if the initial seed is the same:\r\nhttps://mssplab.github.io/threat-hunting/2023/07/15/malware-src-blacklotus.html\r\nPage 26 of 32\n\nGuidGenerate(GUID * Guid, PDWORD Seed) - This function takes a pointer to a GUID structure and a pointer to a\r\nDWORD seed as parameters. It generates a GUID by calling GuidRandom(Seed) to generate pseudorandom\r\nnumbers and assign them to the four parts of the GUID structure ( Data1, Data2, Data3, Data4 ):\r\nGuidGenerateEx(PDWORD Seed) - This function generates a GUID string. It calls GuidGenerate(\u0026Guid, Seed) to\r\ngenerate a GUID and then converts this GUID to a string format with GuidToString(\u0026Guid) . This string is then\r\ncopied to a newly allocated memory block, and a pointer to this block is returned:\r\nhttps://mssplab.github.io/threat-hunting/2023/07/15/malware-src-blacklotus.html\r\nPage 27 of 32\n\nAs for the context of malware, the generated GUIDs might be used for a variety of purposes including marking\r\ninfected systems, communicating with command-and-control (C2) servers, or creating mutexes to avoid multiple\r\ninstances of the malware. In our case, this functions used for generate Bot ID.\r\nUtilsPermalink\r\nThere is also a file with utilities where there are a lot of auxiliary functions utils.c :\r\nhttps://mssplab.github.io/threat-hunting/2023/07/15/malware-src-blacklotus.html\r\nPage 28 of 32\n\nFor example, GetProcessIdByHandle (HANDLE Process) :\r\nThis function, retrieves the unique process ID of a process given a handle to the process.\r\nOr function GetProcessIdByHash(DWORD Hash) :\r\nhttps://mssplab.github.io/threat-hunting/2023/07/15/malware-src-blacklotus.html\r\nPage 29 of 32\n\nwhich returns the Process ID (PID) of a process given its hash. This function scans all running processes on the\r\nsystem and returns the PID of the process whose executable name matches the provided hash.\r\nThe function creates a snapshot of all processes currently running on the system by calling the\r\nCreateToolhelp32Snapshot function. If the snapshot creation fails, it returns -1 to indicate the failure. It then\r\nretrieves the first process in the snapshot using the Process32FirstW function. If this function fails, it closes the\r\nsnapshot handle and returns -1 to indicate the failure. The function then enters a loop, where it calculates the\r\nCRC32 hash of the current process’s executable name ( szExeFile ). It checks whether this calculated hash is\r\nequal to the input hash. If it is, the function breaks out of the loop and returns the Process ID ( th32ProcessID ) of\r\nthe current process. If the hash doesn’t match, it proceeds to the next process in the snapshot using the\r\nProcess32NextW function and repeats previous steps. After the loop, it closes the snapshot handle and returns the\r\nPID of the process with the matching hash. If no matching process was found, it returns -1 .\r\nThe CreateMutexOfProcess(DWORD ProcessID) function is attempting to create a mutex (a synchronization\r\nobject) with a unique name based on the process ID and the serial number of the disk volume (which is obtained\r\nby the GetSerialNumber() function):\r\nhttps://mssplab.github.io/threat-hunting/2023/07/15/malware-src-blacklotus.html\r\nPage 30 of 32\n\nA mutex can be used to prevent multiple instances of a malware or application from running at the same time. In\r\nthis case, the mutex name is generated by concatenating the disk volume’s serial number and the process ID,\r\nwhich should provide a unique mutex for each running instance of the process.\r\nAlso, interesting logic in destroyOS() function:\r\nbut it’s also commented.\r\nThat’s all today. In the next part we will investigate another modules.\r\nWe hope this post spreads awareness to the blue teamers of this interesting malware techniques, and adds a\r\nweapon to the red teamers arsenal.\r\nBy Cyber Threat Hunters from MSSPLab:\r\nhttps://mssplab.github.io/threat-hunting/2023/07/15/malware-src-blacklotus.html\r\nPage 31 of 32\n\n@cocomelonc\r\n@wqkasper\r\nReferencesPermalink\r\nhttps://github.com/ldpreload/BlackLotus\r\nhttps://malpedia.caad.fkie.fraunhofer.de/details/win.blacklotus\r\nhttps://twitter.com/threatintel/status/1679906101838356480\r\nhttps://twitter.com/TheCyberSecHub/status/1680044350820999168\r\nThanks for your time happy hacking and good bye!\r\nAll drawings and screenshots are MSSPLab’s\r\nSource: https://mssplab.github.io/threat-hunting/2023/07/15/malware-src-blacklotus.html\r\nhttps://mssplab.github.io/threat-hunting/2023/07/15/malware-src-blacklotus.html\r\nPage 32 of 32",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"origins": [
		"web"
	],
	"references": [
		"https://mssplab.github.io/threat-hunting/2023/07/15/malware-src-blacklotus.html"
	],
	"report_names": [
		"malware-src-blacklotus.html"
	],
	"threat_actors": [],
	"ts_created_at": 1775434717,
	"ts_updated_at": 1775826775,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/24a86863faa8e9d628d0f0d3e124eca0de5823fb.pdf",
		"text": "https://archive.orkl.eu/24a86863faa8e9d628d0f0d3e124eca0de5823fb.txt",
		"img": "https://archive.orkl.eu/24a86863faa8e9d628d0f0d3e124eca0de5823fb.jpg"
	}
}