{
	"id": "e819eb60-20d3-4e68-b22c-46b1785a2ff4",
	"created_at": "2026-04-06T00:21:30.593245Z",
	"updated_at": "2026-04-10T03:24:23.843224Z",
	"deleted_at": null,
	"sha1_hash": "4e5b87a5dcdaa227bb7c20351f809f20d590caf3",
	"title": "Lockbit analysis",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 562064,
	"plain_text": "Lockbit analysis\r\nArchived: 2026-04-05 19:04:01 UTC\r\nLockBit Encryption\r\nIntroduction\r\nIn this article, we will talk briefly about the LockBit features and focus on the different parts that have not been\r\nfully covered by other analysts like encryption. The LockBit sample used in this analysis has been extracted\r\nduring an Incident Response. The attack was conducted manually by humans, with the help of Cobalt Strike.\r\nFinally, in the Annexes we will present a way to recover light encrypted stack strings using IDA and the miasm\r\nsymbolic engine.\r\nGeneral description\r\nThe sample was packed, strings \"encrypted\" using XOR operation with the first byte as key. The sample checks\r\nthe process privileges and sidestep Windows UAC using the bypass methods in hfiref0x’s UACME #43.\r\nLockBit stopped undesirable services by checking their names against a skip list using OpenSCManagerA,\r\nenumerate dependent services using EnumDependentServicesA and terminate them using ControlService with\r\nthe SERVICE_CONTROL_STOP control code.\r\nProcesses are enumerated using CreateToolhelp32Snapshot, Process32First, Process32Next and OpenProcess.\r\nIf their names appear on the skip list, the process is killed using TerminateProcess.\r\nThe ransomware can enumerate shares on the current /24 subnet using EnumShare and networks resources using\r\nWNetAddConnection2W and NetShareEnum.\r\nAs usual, Windows snapshots are deleted using the following commands:\r\ncmd.exe /c vssadmin delete shadows /all /quiet \u0026 wmic shadowcopy delete \u0026 bcdedit /set {default}\r\nbootstatuspolicy ignoreallfailures \u0026 bcdedit /set {default} recoveryenabled No \u0026 wbadmin delete\r\ncatalog -quiet\r\nBelow, we will take a closer look at some of its features.\r\nIOCP\r\nTo encrypt quickly and efficiently, LockBit uses the Windows I/O Completion Ports mechanisms. This provides\r\nan efficient threading model for processing multiple asynchronous I/O requests on a multiprocessor system.\r\nInstead of using the traditional API CreateIoCompletionPort and GetQueuedCompletionStatus, they use\r\nNtCreateIoCompletion, NtSetInformationFile and NtRemoveIoCompletion.\r\nhttps://blog.lexfo.fr/lockbit-malware.html\r\nPage 1 of 31\n\nLockBit starts by creating an I/O completion Port using NtCreateIoCompletion API:\r\ncreate IOCP\r\nThen, for each file that does not match entries on the folder and file blacklist, it associates the file handle with the\r\nI/O completion port using NtSetInformationFile with the information class that is set to\r\nFileCompletionInformation:\r\nassociate file handle\r\nThen, reading the file using the OVERLAPPED structure will create an I/O completion packet that is queued in\r\nfirst-in-first-out (FIFO) order to the associated I/O completion port:\r\niocp read file\r\nLater on (in the decryption_thread), instead of calling the “GetQueuedCompletionStatus” to dequeue an I/O\r\ncompletion packet from the specified I/O completion Port, it calls NtRemoveIoCompletion:\r\nNtRemoveIoCompletion\r\nEncryption\r\nIntroduction\r\nThe encryption is based on two algorithms: RSA and AES. First, an RSA session key pair is generated on the\r\ninfected workstations. This key pair is encrypted using the embedded attacker's public key and saved on the\r\nregistry SOFTWARE\\LockBit\\full . An AES key is generated randomly for each file to encrypt. Once the file is\r\nencrypted, the AES key is encrypted using the RSA public session key and appended to the end of the file with the\r\npreviously encrypted session key.\r\nhttps://blog.lexfo.fr/lockbit-malware.html\r\nPage 2 of 31\n\nEncryption detection\r\nTo detect encryption algorithms, public crypto Yara rules can be used. On the unpack binary, you will get the\r\nfollowing results:\r\nBig_Numbers1\r\nPrime_Constants_long\r\nCrypt32_CryptBinaryToString_API\r\nRijnDael_AES_CHAR\r\nRijnDael_AES_LONG\r\nAs you can see, the RSA algorithm has not been detected. Reading codes close to the AES functions make me\r\ngrepping the constant -0x4480 using grep.app application which leads me to the library mbedtls.\r\nWith the source code (or one close to the original one), we can try to load C Header files in IDA, by doing File -\r\n\u003e Load file -\u003e Parse C header files . You may have to set properly the Include directories and the Calling\r\nconvention in the option compiler box:\r\ncompiler dialog box)\r\nAlso, it may be needed to delete external includes in the header files like stddef.h to avoid errors Can't open\r\ninclude file stddef.h.\r\nThis allows the analysts to easily import structure:\r\nhttps://blog.lexfo.fr/lockbit-malware.html\r\nPage 3 of 31\n\nstruct drbg ctx\r\nAnd maybe, provide hints by comparing the structure size (0x140):\r\ndrbg init\r\nEncryption Preparation\r\nLike many ransomware, LockBit uses session keys to encrypt the symmetric key that is used to encrypt files. The\r\nfunction that generates session keys is easy to find because it is just after the debug message \"Generate session\r\nkeys\" and just before the encryption_thread :)\r\nstring generate session keys\r\nBelow the function that generates the RSA session keys:\r\nhttps://blog.lexfo.fr/lockbit-malware.html\r\nPage 4 of 31\n\ngenerate session keys\r\nBecause the private session key needs to be kept secret, LockBit RSA encrypts it with his embedded public key\r\n(named Attacker_Modulus in the code):\r\nimport and encrypt session keys using embedded attacker's RSA public key\r\nFinally, the full encrypted session keys are stored in the registry SOFTWARE\\LockBit\\full while in the\r\nSOFTWARE\\LockBit\\Public the public session key (Modulus and Exponent, respectively 0x100 and 0x3 bytes):\r\nhttps://blog.lexfo.fr/lockbit-malware.html\r\nPage 5 of 31\n\nStored session keys\r\nFiles Encryption\r\nAs we mentioned in the IOCP chapter, each file marked for encryption is passed to the encryption thread using\r\nReadFile and the OVERLAPPED structure. They added to the original structure a random AES 128 bits key that\r\nis generated just before using BCryptGenRandom from bcrypt.dll library:\r\ngenerate aes key\r\nBCryptGenRandom\r\nhttps://blog.lexfo.fr/lockbit-malware.html\r\nPage 6 of 31\n\nIn the decryption thread, the packet is dequeued from the specified I/O completion Port, then the AES key is set\r\nusing aesni_setkey_enc_128 if the processor supports the Advanced Encryption Standard New Instructions (AES-NI) otherwise with the mbedtls_setkey_enc_128 function:\r\nset aes key\r\nThe codes that check if the processor supports AES-NI are done earlier, before generating the RSA session keys:\r\ncheck aesni support\r\nFinally, the file content is encrypted using AES and written back into the file:\r\nhttps://blog.lexfo.fr/lockbit-malware.html\r\nPage 7 of 31\n\nfile encryption\r\nFiles end data\r\nAt the end of each file, 0x610 bytes are appended. This data structure contains the required information for\r\ndecryption:\r\nOffset Description Size (bytes)\r\n0x0 Encrypted AES key 0x100\r\n0x100 Encrypted RSA session keys 0x500\r\n0x600 First 0x10 bytes of the attacker's RSA public key 0x10\r\nhttps://blog.lexfo.fr/lockbit-malware.html\r\nPage 8 of 31\n\nfiles end data\r\nDecryptor and Decryption\r\nTo decrypt a file, the Decryptor needs to:\r\n1. Import the attacker’s RSA keys\r\n2. Get and decrypt the session key pair (placed at the end of the file) using the attackers' private key.\r\n3. Get and decrypt the AES key (placed at the end of the file) used for encryption using the decrypted session\r\nkey pair.\r\n4. Decrypt the file using AES and the previous AES key.\r\nThe decryptor includes in its resource the full RSA attacker's key (0x483 bytes):\r\nload attackers key\r\nImports the RSA key:\r\nhttps://blog.lexfo.fr/lockbit-malware.html\r\nPage 9 of 31\n\nImport RSA attacker's key\r\nGets the last 0x510 file bytes and decrypts the first 0x500 to get the RSA session keys:\r\ndecrypt session key\r\nFrom this, it can decrypt the AES key (stored at the end of the file) with the RSA session key and finally the file.\r\nAnnexes\r\nDynamic Stack Strings\r\nhttps://blog.lexfo.fr/lockbit-malware.html\r\nPage 10 of 31\n\nLockBit builds his strings in the stack dynamically. For example, the function that compares file names to a\r\nblacklist uses stack strings to build the blacklist:\r\nstack strings\r\nBecause there are many strings, and their length can be as big as a ransom note and also because it is a classical\r\nfeature that you may find again in other malware, automating this task may be useful. One solution would have\r\nbeen to create a script using IDA that records write operation on the stack but as we said in the introduction,\r\nstrings are also built in addition to a XOR routine:\r\nhttps://blog.lexfo.fr/lockbit-malware.html\r\nPage 11 of 31\n\nXOR routine\r\nor:\r\nhttps://blog.lexfo.fr/lockbit-malware.html\r\nPage 12 of 31\n\ninline xor loop\r\nIn many malware, there is often a single function that is used to decrypt the strings. Most of the time, the solution\r\nis to get the cross references to this function with its parameter using IDA, and execute the decrypt function by\r\nusing one of them: + Python implementation + Debugger conditional breakpoint + IDA appcall + x86 Emulation\r\n(unicorm, miasm, etc.)\r\nBut because stack strings or often inline we won’t be able to use cross references.\r\nhttps://blog.lexfo.fr/lockbit-malware.html\r\nPage 13 of 31\n\nOne way to do it is by doing static symbolic emulation on a portion of the code by using IDA selection. I did use\r\nthis way because with dynamic emulation (symbolic or not) you may have to handle corner cases when an\r\ninstruction is doing read/write memory operation in an undefined memory zone or API call, etc.\r\nI used miasm framework, because it is easy to install (copy the miasm directory to C:\\...\\IDA\\python\\), and they\r\nalready had a good example on their git.\r\nOriginal miasm example\r\nTaking a simple case:\r\nsimple stack string\r\nBy running the original miasm script, we get the following output:\r\nIRDst = loc_key_2\r\n@32[EBP_init + 0xFFFFFC2C] = 0x730077\r\n@32[EBP_init + 0xFFFFFC20] = 0x770024\r\n@32[EBP_init + 0xFFFFFC34] = 0x740062\r\n@32[EBP_init + 0xFFFFFC28] = 0x6F0064\r\n@32[EBP_init + 0xFFFFFC30] = 0x7E002E\r\n@16[EBP_init + 0xFFFFFC38] = (EAX_init)[0:16]\r\n@32[EBP_init + 0xFFFFFC24] = 0x6E0069\r\nThis gives us a good starting point to implement more features on it.\r\nModified version\r\nThe idea was to modify the script to automatically comment the instruction with the right strings and rename the\r\nlocal variables (see picture above). Below the steps:\r\n1 - Symbolic execution\r\nDo symbolic execution on each instruction and for each write operation in memory, stored:\r\nThe destination memory expr (@32[EBP_init + 0xFFFFFC2C])\r\nThe instruction address (EIP)\r\nThe stack position (to handle case where a local variable is accessed using ESP and not EBP)\r\nhttps://blog.lexfo.fr/lockbit-malware.html\r\nPage 14 of 31\n\ndef run_symb_exec(symb, start, end):\r\n \"\"\" Execute symbolic execution and record memory writes\r\n :param symb : SymbolicEngine\r\n :param start (int) : start address\r\n :param end (int) : end address\r\n return data (dict) : Dictionnary with information needed (instruction offset, data_xrefs, source memory exp\r\n \"\"\"\r\n data = dict()\r\n while True:\r\n irblock = ircfg.get_block(start)\r\n if irblock is None:\r\n break\r\n for assignblk in irblock:\r\n if assignblk.instr:\r\n LOGGER.debug(\"0x%x : %s\" % (assignblk.instr.offset, assignblk.instr))\r\n if assignblk.instr.args:\r\n if assignblk.instr.args[0].is_mem() is True: # write mem operation\r\n data[assignblk.instr.args[0]] = {'to': assignblk.instr.offset, 'd_ref_type': 2, 'expr':\r\n if assignblk.instr.offset == end:\r\n break\r\n symb.eval_updt_assignblk(assignblk)\r\n start = symb.eval_expr(symb.ir_arch.IRDst)\r\n return data\r\n2 - Concretize destination memory\r\nReplace destination memory expression (@32[EBP_init + 0xFFFFFC2C]) with concrete value (0x0 for EBP and\r\nthe stack pointer (spd) for ESP), to get a concrete value.\r\ndef concretizes_stack_ptr(phrase_mem):\r\n \"\"\" replace ESP or EBP with a concrete value\r\n :param phrase_mem (dict)\r\n \"\"\"\r\n stack = dict()\r\n for dst, src in phrase_mem.items():\r\n ptr_expr = dst.ptr\r\n spd = src['spd'] + 0x4\r\n real = expr_simp(ptr_expr.replace_expr({ExprId('ESP', 32): ExprInt(spd, 32), ExprId('EBP', 32): ExprInt(\r\n if real.is_int():\r\n stack[real.arg] = src\r\n return stack\r\n3 - Evaluate the expression\r\nEvaluate the destination memory expression to get the result expression. For example:\r\nhttps://blog.lexfo.fr/lockbit-malware.html\r\nPage 15 of 31\n\n@128[ESP + 0x488] = {@8[0x423EE0] 0 8, @8[0x423EE0] ^ @128[0x423EE0][8:16] 8 16, @128[0x423EE0][16:128] 16 128}\r\n4 - Translate the resulting expression in python\r\nUsing the miasm Translator we can convert the expression in Python code:\r\n{@8[0x423EE0] 0 8, @8[0x423EE0] ^ @128[0x423EE0][8:16] 8 16, @128[0x423EE0][16:128] 16 128} -\u003e (((memory(0x423E\r\nThis will allow us to evaluate the Python code to get the content.\r\n5 - Comment IDA and rename local var\r\nI reproduce the stack frame using a list, and for each string found, I comment IDA to the right offset using the\r\npreviously stored information and rename the local variable with the right size.\r\ndef set_data_to_ida(start, data):\r\n \"\"\" Add comment with the strings, and redefine stack variables in IDA\r\n \"\"\"\r\n size_local_var = idc.get_func_attr(start, idc.FUNCATTR_FRSIZE)\r\n frame_high_offset = 0x100000000\r\n frame_id = idc.get_func_attr(start, idc.FUNCATTR_FRAME)\r\n l_xref = dict()\r\n if size_local_var == 0: # TODO : handle when stack size is undefined\r\n size_local_var = 0x1000 # set stack size to arbitrary value 0x1000\r\n # represent the stack as a list (initialization)\r\n stack = [0 for i in range(0, size_local_var)]\r\n # fill the stack\r\n for offset, value in data.items():\r\n if (offset \u003e\u003e 31) == 1: # EBP based frame\r\n if offset \u003c= frame_high_offset:\r\n stack_offset = twos_complement(offset) # Ex: 0xfffffe00 -\u003e 0x200\r\n frame_offset = stack_to_frame_offset(stack_offset, size_local_var) # frame_offset = member_offs\r\n l_xref[frame_offset] = value\r\n stack[frame_offset] = value['value']\r\n else: # ESP based frame\r\n l_xref[offset] = value\r\n stack_offset = frame_to_stack_offset(offset, size_local_var)\r\n frame_offset = offset\r\n stack[offset] = value['value']\r\n LOGGER.debug(\"offset 0x%x, stack_offset 0x%x, frame_offset 0x%x, value %s\" % (offset, stack_offset, fram\r\n index = 0\r\n while index \u003c len(stack):\r\n if stack[index] != 0: # skip null bytes\r\nhttps://blog.lexfo.fr/lockbit-malware.html\r\nPage 16 of 31\n\nbuff = get_bytes_until_delimiter(stack[index::], [0x0, 0x0]) # TODO: adjust auto ascii/utf16 ?\r\n if buff:\r\n LOGGER.debug(\"raw output = %s\" % repr(buff))\r\n ascii_buff = zeroes_out(buff) # transform to ascii, deletes null bytes\r\n if buff[1] == 0x0: # check if strings is utf16 TODO: check if really usefull\r\n STRTYPE = idc.STRTYPE_C\r\n else:\r\n STRTYPE = idc.STRTYPE_C\r\n var_offset = index\r\n var_name = idc.get_member_name(frame_id, var_offset)\r\n s_string = \"\".join(map(chr, ascii_buff))\r\n LOGGER.info(\"[+] Index 0x%x, var_offset 0x%x, frame_id = 0x%x, var_name %s, strings = %s, clean_\r\n # delete struct member, to create array of char\r\n LOGGER.info(\"[+] Delete structure member\")\r\n for a_var_offset in range(var_offset, var_offset + len(buff)):\r\n # if idc.get_member_name(frame_id, a_var_offset):\r\n ret = idc.del_struc_member(frame_id, a_var_offset)\r\n LOGGER.debug(\"ret del_struc_member = %d\" % ret)\r\n # re add structure member with good size and proper name\r\n LOGGER.info(\"[+] Add structure member\")\r\n ret = idc.add_struc_member(frame_id, replace_forbidden_char(s_string)[:0x10], var_offset, idc.FF\r\n LOGGER.debug(\"ret add_struc_member = %d\" % ret)\r\n # Comment instruction using the xref key\r\n LOGGER.info(\"[+] Comment instruction\")\r\n if l_xref[index]['d_ref_type'] == 2:\r\n if not idc.get_cmt(l_xref[index]['to'], 0):\r\n idc.set_cmt(l_xref[index]['to'], s_string, 0)\r\n index = index + len(buff)\r\n else:\r\n index = index + 1\r\n else:\r\n index = index + 1\r\n LOGGER.info(\"[+] end\")\r\n return stack\r\nScript Output Example\r\nBy selecting instructions and running the script, we get instructions automatically commented and local variable\r\nrenamed:\r\nhttps://blog.lexfo.fr/lockbit-malware.html\r\nPage 17 of 31\n\nxor routine output\r\nxor loop inline\r\nYara\r\nrule malware_first_unpacking_routine {\r\n strings :\r\nhttps://blog.lexfo.fr/lockbit-malware.html\r\nPage 18 of 31\n\n$h1 = { 81 [1-5] A9 0F 00 00 75 ?? C7 ?? ?? ?? ?? ?? 40 2E EB ED }\r\n condition :\r\n $h1\r\n}\r\nIOC\r\nCommands\r\n/c vssadmin Delete Shadows /All /Quiet\r\n/c bcdedit /set {default} recoveryenabled No\r\n/c bcdedit /set {default} bootstatuspolicy ignoreallfailures\r\n/c wbadmin DELETE SYSTEMSTATEBACKUP\r\n/c wbadmin DELETE SYSTEMSTATEBACKUP -deleteOldest\r\n/c wmic SHADOWCOPY /nointeractive\r\n/c wevtutil cl security\r\n/c wevtutil cl system\r\n/c wevtutil cl application\r\n/c vssadmin delete shadows /all /quiet \u0026 wmic shadowcopy delete \u0026 bcdedit /set {default}\r\nbootstatuspolicy ignoreallfailures \u0026 bcdedit /set {default} recoveryenabled no \u0026 wbadmin delete\r\ncatalog -quiet\r\n/C ping 127.0.0.7 -n 3 \u003e Nul \u0026 fsutil file setZeroData offset=0 length=524288 \"%s\" \u0026 Del /f /q\r\n\"%s\"\r\nMutex\r\nGlobal\\{BEF590BE-11A6-442A-A85B-656C1081E04C}\r\nServices\r\nwrapper\r\nDefWatch\r\nccEvtMgr\r\nccSetMgr\r\nSavRoam\r\nSqlservr\r\nsqlagent\r\nsqladhlp\r\nCulserver\r\nRTVscan\r\nsqlbrowser\r\nSQLADHLP\r\nQBIDPService\r\nIntuit.QuickBooks.FCS\r\nhttps://blog.lexfo.fr/lockbit-malware.html\r\nPage 19 of 31\n\nQBCFMonitorService\r\nsqlwriter\r\nmsmdsrv\r\ntomcat6\r\nzhudongfangyu\r\nvmware-usbarbitator64\r\nvmware-converter\r\ndbsrv12\r\ndbeng8\r\nMSSQL$MICROSOFT##WID\r\nMSSQL$VEEAMSQL2012\r\nSQLAgent$VEEAMSQL2012\r\nSQLBrowser\r\nSQLWriter\r\nFishbowlMySQL\r\nMSSQL$MICROSOFT##WID\r\nMySQL57\r\nMSSQL$KAV_CS_ADMIN_KIT\r\nMSSQLServerADHelper100\r\nSQLAgent$KAV_CS_ADMIN_KIT\r\nmsftesql-Exchange\r\nMSSQL$MICROSOFT##SSEE\r\nMSSQL$SBSMONITORING\r\nMSSQL$SHAREPOINT\r\nMSSQLFDLauncher$SBSMONITORING\r\nMSSQLFDLauncher$SHAREPOINT\r\nSQLAgent$SBSMONITORING\r\nSQLAgent$SHAREPOINT\r\nQBFCService\r\nQBVSS\r\nYooBackup\r\nYooIT\r\nsvc$\r\nMSSQL\r\nMSSQL$\r\nmemtas\r\nmepocs\r\nsophos\r\nveeam\r\nbackup\r\nbedbg\r\nPDVFSService\r\nhttps://blog.lexfo.fr/lockbit-malware.html\r\nPage 20 of 31\n\nBackupExecVSSProvider\r\nBackupExecAgentAccelerator\r\nBackupExecAgentBrowser\r\nBackupExecDiveciMediaService\r\nBackupExecJobEngine\r\nBackupExecManagementService\r\nBackupExecRPCService\r\nMVArmor\r\nMVarmor64\r\nstc_raw_agent\r\nVSNAPVSS\r\nVeeamTransportSvc\r\nVeeamDeploymentService\r\nVeeamNFSSvc\r\nAcronisAgent\r\nARSM\r\nAcrSch2Svc\r\nCASAD2DWebSvc\r\nCAARCUpdateSvc\r\nWSBExchange\r\nMSExchange\r\nMSExchange$\r\nProcess\r\nwxServer\r\nwxServerView\r\nsqlmangr\r\nRAgui\r\nsupervise\r\nCulture\r\nDefwatch\r\nwinword\r\nQBW32\r\nQBDBMgr\r\nqbupdate\r\naxlbridge\r\nhttpd\r\nfdlauncher\r\nMsDtSrvr\r\njava\r\n360se\r\n360doctor\r\nhttps://blog.lexfo.fr/lockbit-malware.html\r\nPage 21 of 31\n\nwdswfsafe\r\nfdhost\r\nGDscan\r\nZhuDongFangYu\r\nQBDBMgrN\r\nmysqld\r\nAutodeskDesktopApp\r\nacwebbrowser\r\nCreative Cloud\r\nAdobe Desktop Service\r\nCoreSync\r\nAdobe CEF Helper\r\nnode\r\nAdobeIPCBroker\r\nsync-taskbar\r\nsync-worker\r\nInputPersonalization\r\nAdobeCollabSync\r\nBrCtrlCntr\r\nBrCcUxSys\r\nSimplyConnectionManager\r\nSimply.SystemTrayIcon\r\nfbguard\r\nfbserver\r\nONENOTEM\r\nwsa_service\r\nkoaly-exp-engine-service\r\nTeamViewer_Service\r\nTeamViewer\r\ntv_w32\r\ntv_x64\r\nTitanV\r\nSsms\r\nnotepad\r\nRdrCEF\r\noracle\r\nocssd\r\ndbsnmp\r\nsynctime\r\nagntsvc\r\nisqlplussvc\r\nxfssvccon\r\nhttps://blog.lexfo.fr/lockbit-malware.html\r\nPage 22 of 31\n\nmydesktopservice\r\nocautoupds\r\nencsvc\r\nfirefox\r\ntbirdconfig\r\nmydesktopqos\r\nocomm\r\ndbeng50\r\nsqbcoreservice\r\nexcel\r\ninfopath\r\nmsaccess\r\nmspub\r\nonenote\r\noutlook\r\npowerpnt\r\nsteam\r\nthebat\r\nthunderbird\r\nvisio\r\nwordpad\r\nbedbh\r\nvxmon\r\nbenetns\r\nbengien\r\npvlsvr\r\nbeserver\r\nraw_agent_svc\r\nvsnapvss\r\nCagService\r\nDellSystemDetect\r\nEnterpriseClient\r\nVeeamDeploymentSvc\r\nRegistry\r\nSOFTWARE\\LockBit\r\nSOFTWARE\\LockBit\\full\r\nSOFTWARE\\LockBit\\Public\r\nPersistance\r\nHKCU\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run\\XO1XADpO01\r\nhttps://blog.lexfo.fr/lockbit-malware.html\r\nPage 23 of 31\n\nUAC bypass CLSID\r\n{3E5FC7F9-9A51-4367-9063-A120244FBEC7}\r\n{D2E7041B-2927-42fb-8E9F-7CE93B6DC937}\r\nFull Script\r\nimport idaapi\r\nimport idc\r\nimport logging\r\nfrom math import log\r\ndef unpack_arbitrary(data, word_size=None):\r\n \"\"\" (modified pwntools) unpack arbitrary-sized integer\r\n :param data : String to convert\r\n :word_size : Word size of the converted integer or the string “all” (in bits).\r\n :return : The unpacked number\r\n \"\"\"\r\n number = 0\r\n data = reversed(data)\r\n data = bytearray(data)\r\n for c in data:\r\n number = (number \u003c\u003c 8) + c\r\n number = number \u0026 ((1 \u003c\u003c word_size) - 1)\r\n signbit = number \u0026 (1 \u003c\u003c (word_size-1))\r\n return int(number - 2*signbit)\r\ndef memory(ea, size):\r\n \"\"\" Read memory from IDA\r\n :param ea (int) : Start address\r\n :param size (int) : size of buffer in normal 8-bit bytes\r\n : return int/long\r\n \"\"\"\r\n return unpack_arbitrary(idc.get_bytes(ea, size), size * 8)\r\ndef twos_complement(esp_offset):\r\n return 0x100000000 - esp_offset\r\ndef stack_to_frame_offset(stack_offset, size_local_var):\r\nhttps://blog.lexfo.fr/lockbit-malware.html\r\nPage 24 of 31\n\n\"\"\"\r\n stack_offset \u003c-\u003e frame_offset\r\n \"\"\"\r\n return size_local_var - abs(stack_offset)\r\ndef frame_to_stack_offset(frame_offset, size_local_var):\r\n \"\"\"\r\n stack_offset \u003c-\u003e frame_offset\r\n \"\"\"\r\n return size_local_var - abs(frame_offset)\r\ndef get_bytes_until_delimiter(bytes_list, delimiter):\r\n \"\"\"Get bytes until it reach the delimiter\r\n :param bytes_list (list) : List of bytes\r\n :param delimiter (list) : Delimiter to reach (e.g. [0x0, 0x0])\r\n :return a list of bytes\r\n \"\"\"\r\n len_delimiter = len(delimiter)\r\n iterables = list()\r\n for x in range(len_delimiter):\r\n iterables.append(bytes_list[x:])\r\n abc = zip(*iterables)\r\n if tuple(delimiter) in abc:\r\n index = abc.index(tuple(delimiter))\r\n return (bytes_list[0:index]) + delimiter\r\n else:\r\n return None\r\ndef zeroes_out(bytes_list):\r\n \"\"\" delete null bytes\r\n :param bytes_list (list) : list of bytes\r\n :return : list of bytes without zeros\r\n \"\"\"\r\n return [x for x in bytes_list if x != 0]\r\ndef replace_forbidden_char(my_strings):\r\n \"\"\" replace IDA forbidden char\r\n \"\"\"\r\n clean_string = list()\r\nhttps://blog.lexfo.fr/lockbit-malware.html\r\nPage 25 of 31\n\nfor x in my_strings:\r\n if x.isalnum():\r\n clean_string.append(x)\r\n elif x.isspace():\r\n clean_string.append('_')\r\n else:\r\n clean_string.append('?')\r\n return \"k_\" + \"\".join(clean_string)\r\ndef bytes_needed(n):\r\n \"\"\" get the number of bytes that compose the number\r\n https://stackoverflow.com/questions/14329794/get-size-of-integer-in-python\r\n :pararm n (int) : number\r\n :return (int) : number of bytes\r\n \"\"\"\r\n if n == 0:\r\n return 1\r\n return int(log(n, 256)) + 1\r\ndef extract_byte_x(num, position):\r\n \"\"\" Get bytes from position\r\n :param num (int) : original number\r\n :param position (int) : desired byte position\r\n : return (int) : byte x\r\n \"\"\"\r\n offset = 0x8 * (position - 1)\r\n and_mask = (0xFF \u003c\u003c offset)\r\n return (num \u0026 and_mask) \u003e\u003e offset\r\ndef symbolic_exec(start, end):\r\n \"\"\" Symbolic execution engine (modified of original https://github.com/cea-sec/miasm/blob/master/example/ida\r\n Takes start and end address from IDA selection, do symbolic execution on instructions\r\n Replace stacks registers (ESP, EBP) variable by concrete value 0x0 or current spd instructions\r\n \"\"\"\r\n from miasm.ir.symbexec import SymbolicExecutionEngine\r\n from miasm.expression.expression import ExprId, ExprInt\r\n from miasm.expression.simplifications import expr_simp\r\n from miasm.core.bin_stream_ida import bin_stream_ida\r\n from miasm.ir.translators import Translator\r\n from miasm.analysis.machine import Machine\r\nhttps://blog.lexfo.fr/lockbit-malware.html\r\nPage 26 of 31\n\ndef run_symb_exec(symb, start, end):\r\n \"\"\" Execute symbolic execution and record memory writes\r\n :param symb : SymbolicEngine\r\n :param start (int) : start address\r\n :param end (int) : end address\r\n return data (dict) : Dictionnary with information needed (instruction offset, data_xrefs, source memory\r\n \"\"\"\r\n data = dict()\r\n while True:\r\n irblock = ircfg.get_block(start)\r\n if irblock is None:\r\n break\r\n for assignblk in irblock:\r\n if assignblk.instr:\r\n LOGGER.debug(\"0x%x : %s\" % (assignblk.instr.offset, assignblk.instr))\r\n if assignblk.instr.args:\r\n if assignblk.instr.args[0].is_mem() is True: # write mem operation\r\n data[assignblk.instr.args[0]] = {'to': assignblk.instr.offset, 'd_ref_type': 2, 'exp\r\n if assignblk.instr.offset == end:\r\n break\r\n symb.eval_updt_assignblk(assignblk)\r\n start = symb.eval_expr(symb.ir_arch.IRDst)\r\n return data\r\n def concretizes_stack_ptr(phrase_mem):\r\n \"\"\" replace ESP or EBP with a concrete value\r\n :param phrase_mem (dict)\r\n \"\"\"\r\n stack = dict()\r\n for dst, src in phrase_mem.items():\r\n ptr_expr = dst.ptr\r\n spd = src['spd'] + 0x4\r\n real = expr_simp(ptr_expr.replace_expr({ExprId('ESP', 32): ExprInt(spd, 32), ExprId('EBP', 32): Expr\r\n if real.is_int():\r\n stack[real.arg] = src\r\n return stack\r\n def eval_src_expr(symb, data):\r\n \"\"\" evaluate/resolve the source expression trying to get a concrete value.\r\n Translate to python anbd eval if necessary\r\n Evaluation:\r\n @128[ESP + 0x488] = {@8[0x423EE0] 0 8, @8[0x423EE0] ^ @128[0x423EE0][8:16] 8 16, @128[0x423EE0][16:128]\r\n Translation:\r\n {@8[0x423EE0] 0 8, @8[0x423EE0] ^ @128[0x423EE0][8:16] 8 16, @128[0x423EE0][16:128] 16 128} = (((memory(\r\nhttps://blog.lexfo.fr/lockbit-malware.html\r\nPage 27 of 31\n\n\"\"\"\r\n tmp = dict()\r\n for dst, src in data.items():\r\n tmp_val = symb.eval_expr(src['expr'])\r\n if tmp_val.is_int(): # Case where the source expression is a int\r\n tmp_val = int(tmp_val.arg)\r\n else: # The source expression may by a data reference (read from mem)\r\n # Translate expression to python\r\n py_expr = Translator.to_language('Python').from_expr(tmp_val)\r\n try:\r\n tmp_val = eval(py_expr) # TODO better solution (need to add memory function) ?\r\n except Exception as e:\r\n LOGGER.info(\"[-] Python Translator Exception\")\r\n LOGGER.info(py_expr)\r\n raise e\r\n if isinstance(tmp_val, int) or isinstance(tmp_val, long):\r\n # Set one byte per address\r\n for i in range(0, bytes_needed(tmp_val)):\r\n tmp[dst + i] = {'to': src['to'], 'd_ref_type': 2, 'value': extract_byte_x(tmp_val, i + 1),\r\n return tmp\r\n bs = bin_stream_ida()\r\n machine = Machine(\"x86_32\")\r\n mdis = machine.dis_engine(bs, dont_dis_nulstart_bloc=True)\r\n ir_arch = machine.ira(mdis.loc_db)\r\n # Disassemble the targeted function until end\r\n mdis.dont_dis = [end]\r\n asmcfg = mdis.dis_multiblock(start)\r\n # Generate IR\r\n ircfg = ir_arch.new_ircfg_from_asmcfg(asmcfg)\r\n # Replace ExprID\r\n regs = machine.mn.regs.regs_init\r\n # regs[ExprId('ESP', 32)] = ExprId('ESP', 32)\r\n # regs[ExprId('ESP', 32)] = ExprId('EBP', 32)\r\n # regs[ExprId('EAX', 32)] = ExprId('EAX', 32)\r\n # regs[ExprId('EBX', 32)] = ExprId('EBX', 32)\r\n # regs[ExprId('ECX', 32)] = ExprId('ECX', 32)\r\n # regs[ExprId('EDX', 32)] = ExprId('EDX', 32)\r\n # regs[ExprId('XMM0_init', 32)] = ExprId('XMM0', 32)\r\n LOGGER.info(\"[+] Get symbolic engine\")\r\n symb = SymbolicExecutionEngine(ir_arch, regs)\r\n LOGGER.info(\"[+] Run symbolic execution\")\r\nhttps://blog.lexfo.fr/lockbit-malware.html\r\nPage 28 of 31\n\ndata = run_symb_exec(symb, start, end)\r\n LOGGER.info(\"[+] Concretize stack pointer\")\r\n data = concretizes_stack_ptr(data)\r\n LOGGER.info(\"[+] Resolves source expression\")\r\n data = eval_src_expr(symb, data)\r\n return data\r\ndef set_data_to_ida(start, data):\r\n \"\"\" Add comment with the strings, and redefine stack variables in IDA\r\n \"\"\"\r\n size_local_var = idc.get_func_attr(start, idc.FUNCATTR_FRSIZE)\r\n frame_high_offset = 0x100000000\r\n frame_id = idc.get_func_attr(start, idc.FUNCATTR_FRAME)\r\n l_xref = dict()\r\n if size_local_var == 0: # TODO : handle when stack size is undefined\r\n size_local_var = 0x1000 # set stack size to arbitrary value 0x1000\r\n # represent the stack as a list (initialization)\r\n stack = [0 for i in range(0, size_local_var)]\r\n # fill the stack\r\n for offset, value in data.items():\r\n if (offset \u003e\u003e 31) == 1: # EBP based frame\r\n if offset \u003c= frame_high_offset:\r\n stack_offset = twos_complement(offset) # Ex: 0xfffffe00 -\u003e 0x200\r\n frame_offset = stack_to_frame_offset(stack_offset, size_local_var) # frame_offset = member_offs\r\n l_xref[frame_offset] = value\r\n stack[frame_offset] = value['value']\r\n else: # ESP based frame\r\n l_xref[offset] = value\r\n stack_offset = frame_to_stack_offset(offset, size_local_var)\r\n frame_offset = offset\r\n stack[offset] = value['value']\r\n LOGGER.debug(\"offset 0x%x, stack_offset 0x%x, frame_offset 0x%x, value %s\" % (offset, stack_offset, fram\r\n index = 0\r\n while index \u003c len(stack):\r\n if stack[index] != 0: # skip null bytes\r\n buff = get_bytes_until_delimiter(stack[index::], [0x0, 0x0]) # TODO: adjust auto ascii/utf16 ?\r\n if buff:\r\n LOGGER.debug(\"raw output = %s\" % repr(buff))\r\n ascii_buff = zeroes_out(buff) # transform to ascii, deletes null bytes\r\n if buff[1] == 0x0: # check if strings is utf16 TODO: check if really usefull\r\nhttps://blog.lexfo.fr/lockbit-malware.html\r\nPage 29 of 31\n\nSTRTYPE = idc.STRTYPE_C_16\r\n else:\r\n STRTYPE = idc.STRTYPE_C\r\n var_offset = index\r\n var_name = idc.get_member_name(frame_id, var_offset)\r\n s_string = \"\".join(map(chr, ascii_buff))\r\n LOGGER.info(\"[+] Index 0x%x, var_offset 0x%x, frame_id = 0x%x, var_name %s, strings = %s, clean_\r\n # delete struct member, to create array of char\r\n LOGGER.info(\"[+] Delete structure member\")\r\n for a_var_offset in range(var_offset, var_offset + len(buff)):\r\n # if idc.get_member_name(frame_id, a_var_offset):\r\n ret = idc.del_struc_member(frame_id, a_var_offset)\r\n LOGGER.debug(\"ret del_struc_member = %d\" % ret)\r\n # re add structure member with good size and proper name\r\n LOGGER.info(\"[+] Add structure member\")\r\n ret = idc.add_struc_member(frame_id, replace_forbidden_char(s_string)[:0x10], var_offset, idc.FF\r\n LOGGER.debug(\"ret add_struc_member = %d\" % ret)\r\n # Comment instruction using the xref key\r\n LOGGER.info(\"[+] Comment instruction\")\r\n if l_xref[index]['d_ref_type'] == 2:\r\n if not idc.get_cmt(l_xref[index]['to'], 0):\r\n idc.set_cmt(l_xref[index]['to'], s_string, 0)\r\n index = index + len(buff)\r\n else:\r\n index = index + 1\r\n else:\r\n index = index + 1\r\n LOGGER.info(\"[+] end\")\r\n return stack\r\ndef main():\r\n start, end = idc.read_selection_start(), idc.read_selection_end()\r\n if start != 0xFFFFFFFF and end != 0xFFFFFFFF:\r\n data = symbolic_exec(start, end)\r\n LOGGER.info(\"[+] Set and comment data to IDA\")\r\n stack = set_data_to_ida(start, data)\r\n else:\r\n print(\"Error: Select instructions\")\r\n stack = -0x1\r\n return stack\r\nhttps://blog.lexfo.fr/lockbit-malware.html\r\nPage 30 of 31\n\nif __name__ == '__main__':\r\n idaapi.CompileLine('static key_F3() { RunPythonStatement(\"main()\"); }')\r\n idc.add_idc_hotkey(\"F3\", \"key_F3\")\r\n LOGGER = logging.getLogger(__name__)\r\n if not LOGGER.handlers: # Avoid duplicate handler\r\n console_handler = logging.StreamHandler()\r\n console_handler.setFormatter(logging.Formatter(\"[%(levelname)s] %(message)s\"))\r\n LOGGER.addHandler(console_handler)\r\n LOGGER.setLevel(logging.DEBUG)\r\n print(\"=\" * 50)\r\n print(\"\"\"Available commands:\r\n main() - F3: Symbolic execution of current selection\r\n \"\"\")\r\nSource: https://blog.lexfo.fr/lockbit-malware.html\r\nhttps://blog.lexfo.fr/lockbit-malware.html\r\nPage 31 of 31\n\n0x0 Encrypted 0x100 Encrypted AES key RSA session keys  0x100 0x500\n0x600 First 0x10 bytes of the attacker's RSA public key 0x10\n   Page 8 of 31",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"references": [
		"https://blog.lexfo.fr/lockbit-malware.html"
	],
	"report_names": [
		"lockbit-malware.html"
	],
	"threat_actors": [
		{
			"id": "610a7295-3139-4f34-8cec-b3da40add480",
			"created_at": "2023-01-06T13:46:38.608142Z",
			"updated_at": "2026-04-10T02:00:03.03764Z",
			"deleted_at": null,
			"main_name": "Cobalt",
			"aliases": [
				"Cobalt Group",
				"Cobalt Gang",
				"GOLD KINGSWOOD",
				"COBALT SPIDER",
				"G0080",
				"Mule Libra"
			],
			"source_name": "MISPGALAXY:Cobalt",
			"tools": [],
			"source_id": "MISPGALAXY",
			"reports": null
		}
	],
	"ts_created_at": 1775434890,
	"ts_updated_at": 1775791463,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/4e5b87a5dcdaa227bb7c20351f809f20d590caf3.pdf",
		"text": "https://archive.orkl.eu/4e5b87a5dcdaa227bb7c20351f809f20d590caf3.txt",
		"img": "https://archive.orkl.eu/4e5b87a5dcdaa227bb7c20351f809f20d590caf3.jpg"
	}
}