MalwareAnalysisReports/WikiLoader/WikiLoader notepad.md at main ยท VenzoV/MalwareAnalysisReports By VenzoV Archived: 2026-04-05 19:26:23 UTC Sample Information I stumbled upon this sample checking the following post: https://github.com/VenzoV/MalwareAnalysisReports/blob/main/WikiLoader/WikiLoader%20notepad.md Page 1 of 22 The following hash is for the malicious .dll "MimeTools.dll" https://github.com/VenzoV/MalwareAnalysisReports/blob/main/WikiLoader/WikiLoader%20notepad.md Page 2 of 22 SHA256 67283e154b86612e325030e5a5f7995a6fe552d20655283ea5de8b53ff405f69 Following the hashes for the .zip file which contains the .dll. SHA256 bef04e3b2b81f2dee39c42ab9be781f3db0059ec722aeee3b5434c2e63512a68 ZIP file contents and "notepad" The zip file initially contains files that mimic the notepad++ file structure and files. The end game is for the "notepad.exe" to side load the malicious .dll "mimetools.dll" Malicious zip file contents: https://github.com/VenzoV/MalwareAnalysisReports/blob/main/WikiLoader/WikiLoader%20notepad.md Page 3 of 22 Legitimate notepade++.exe folder structure. The malicious .dll is located also in the same path notepad++ contains the file. This is of course so that the sideloading can work. The path for the .dll: plugins\mimeTools\ Comparing hashes on VT or any other site like unpac.me, we can observe that they are different and one of them is also detected by many engines. Static Analysis Using IDA or any decompiler won't really help initially. The sample uses a lot of control flow obfuscation essentially changing calls for jmp instructions. This will confuse IDA and we don't get clear disassembly view nor decompiler. So, for the moment we will go with dynamic analysis with x64 dbg. Information Gathering https://github.com/VenzoV/MalwareAnalysisReports/blob/main/WikiLoader/WikiLoader%20notepad.md Page 4 of 22 Some information based on OSINT and other resources just to get ahead and have some hits on what to look for. I was able to find only one report from proofpoint with some useful information. Also online sandboxes gives us some hints of behaviors to expect. We will expect PE parsing to occur: I also observed from IDA some potential stack strings, I ran FLOSS to try and extract some. Following the results, which we will user to help our analysis. Although it may not be useful in the end. FLOSS Stack Strings FLOSS decoded 1 strings !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ FLOSS extracted 67 stackstrings nXm21XOk}Z nXm21XOk}Z VirtualAlloc CryptStringToBinaryA CryptStringToBinaryA ex28mBHjNU7 ex28mBHjNU7 CryptStringToBinaryA 8mBHjNU7 HjGwPX65nXm21XOkX4nMqex28mBHjNU7 ex28mBHjNU7 LoadLibraryA ZBAA Virtu https://github.com/VenzoV/MalwareAnalysisReports/blob/main/WikiLoader/WikiLoader%20notepad.md Page 5 of 22 VirtualAlloc nXm21XOk}Z VirtualAlloc VirtualAlloc 8mBHjNU7 VirtualFreeA VirtualFreeA 8mBHjNU7 VirtualFreeA VirtualAlloc ex28mBHjNU7 HjGwPX65nXm21XOkX4nMqex28mBHjNU7 LoadLibraryA nXm21XOk}Z VirtualFreeA HjGwPX65nXm21XOkX4nMqex28mBHjNU7 HjGwPX65nXm21XOkX4nMqex28mBHjNU7 LoadLibraryA CryptStringToBinaryA LoadLibraryA 8mBHjNU7 Virtu LoadLibraryA VirtualFreeA HjGwPX65nXm21XOkX4nMqex28mBHjNU7 nXm21XOk}Z VirtualFreeA Virtu 8mBHjNU7 Virtu ex28mBHjNU7 CryptStringToBinaryA LoadLibraryA LoadLibraryA 8mBHjNU7 LoadLibraryA HjGwPX65nXm21XOkX4nMqex28mBHjNU7 8mBHjNU7 VirtualAlloc ex28mBHjNU7 CryptStringToBinaryA ex28mBHjNU7 nXm21XOk}Z HjGwPX65nXm21XOkX4nMqex28mBHjNU7 nXm21XOk}Z CryptStringToBinaryA VirtualAlloc https://github.com/VenzoV/MalwareAnalysisReports/blob/main/WikiLoader/WikiLoader%20notepad.md Page 6 of 22 Virtu VirtualFreeA CreateThread VirtualFreeA LoadLibraryA VirtualFreeA Dynamic Analysis Getting Kernel32.dll & GetProcAddress First thing the malware gets a handle to the PEB from the TIB, and then sets up relevant offsets to access the LIST_ENTRY InMemoryOrderModuleList structure. As per microsoft docs it is a double linked list that contains the loaded modules for the process. This will be used the malware to go though the dlls loaded. Ntdll.dll is the first .dll of the process, followed by kernel32.dll. Offset 0x60 is passed to RAX regiser RAX is used with gs: special register to obtain the PEB location. Offset 0x18 is passed to RBX Offset RBX+RAX is used to access PEB_LDR_DATA struct and saved to RBX Offset 0x20 is passed to RAX Offset RBX+RAX now leads to LIST_ENTRY InMemoryOrderModuleList The first of the list InMemoryOrderModuleList is now saved in r12 register, this will be used to check for end of list. Since it is a doubled link list, if the code goes through all the modules, it will eventually end up at the start. Lastly it gets the 0x40 offset to add to the InMemoryOrderModuleList. This is for the Dllbase address. The objective of this first loop is to find kernel32.dll. By walking the PEB, we expect to find the following module order: Executing Process ntdll.dll https://github.com/VenzoV/MalwareAnalysisReports/blob/main/WikiLoader/WikiLoader%20notepad.md Page 7 of 22 kernel32.dll So, the code will have to go through 3 forward pointers. The way the malware checks if it has the right dll reference is through series of jumps. A certain offset is added to the Dllbase address, this will be a letter in the name, then it is compared to a letter. The malware will check these letters for the kernel32.dll 4b->'K' 45->'E' 4c->'L' 32->'2' 4c->'L' 4c->'L' Now that the malware has the base address to Kernel32.dll, it will go through all the functions to search for GetProcAddress. It parses the PE header of kernel32.dll to reach the export table. 1. Offset 3C to reach PE header pointer with value F8. 2. Offset 88 to reach the export table, this value is calculated with: RVA of export table - F8. In our case 0x180-F8=0x88 3. Offset 0x20 to finally reach the exported functions names table. https://github.com/VenzoV/MalwareAnalysisReports/blob/main/WikiLoader/WikiLoader%20notepad.md Page 8 of 22 https://github.com/VenzoV/MalwareAnalysisReports/blob/main/WikiLoader/WikiLoader%20notepad.md Page 9 of 22 To check it performs some calculations to build a hex value which correspond to the first 8 bytes of "GetProcAddress" keeping in mind endianness. It then loops through all the functions in kernel32.dll to perform compare. 1. Calculates the first 8 bytes in hex for GetProcAddress string (GetProcA) 2. Loops through all the functions and compares the 8 bytes to r9 Next, the malware need to retrieve the function address. To to this it will make use of the ordinal name stored in RCX. The ordinal will be searched withing the AddressOfFunctions struct to get the value. https://github.com/VenzoV/MalwareAnalysisReports/blob/main/WikiLoader/WikiLoader%20notepad.md Page 10 of 22 So all this PE parsing is to retrieve finally GetProcAddress and use this API to load others. https://github.com/VenzoV/MalwareAnalysisReports/blob/main/WikiLoader/WikiLoader%20notepad.md Page 11 of 22 String builder and Control flow manipulation Next the malware builds the string "VirtualAlloc" and proceeds to user GetProcAddress to fetch the function from kernel32.dll. This is done through jmp instructions, where the function needed is loaded into the appropriate registry. The string is built by using the lower registry al which contains 1 byte. It simply adds two hex values to generate a char. At the end the null byte is used as terminator. Image The block of code after this is responsible for the following: Sets up the arguments for GetProcAddress in RCX and RDX in this case the module kernel32.dll and API to fetch VirtualAlloc Return address following the jmp is saved to rax which is pushed to the stack, this makes it so that when the jmp returns the return address will be on the stack. This points simply to the code following the jmp rax instruction. This is repeated throughout the next jmp instructions to other API. Finally it jmp to the GetProcAddress API with the args mentioned above returning the address to VirutalAlloc from kernel32.dll Next thing of course, the VirtualAlloc is called to allocate some memory space with PAGE_EXECUTE_READWRITE. Allocated memory buffer: Image Throught the entire sample, all API strings to be fetched or DLLs to be loaded are built in a similar fashion as shown above. All calls to any API are changed with JMP instructions where the return value is pushed onto the stack before execution, this is so following the ret from the JMP, the code can continue. Thread manipulation Using the same string creation as for VirtualAlloc() and same usage of GetProcAddress(), the malware proceeds to call GetCurrentThreadID() API. https://github.com/VenzoV/MalwareAnalysisReports/blob/main/WikiLoader/WikiLoader%20notepad.md Page 12 of 22 Following OpenThread() call. Finally a new thread is created by calling CreateThread(). The arguments passed: Pointer to start address in R8 Pointer to parameter passed to thread in R9, in this case seems to be the ID handle to that thread. Image The malware will then follow by suspending the main thread and leave execution to the newly created one. For debugging purposes, the debugger was acting unpredictably maybe because of race conditions and threads colliding. Once the code reached the new thread start, I manually killed the main thread this seems to have worked but not 100% sure if it is just a fluke. Decrypting Shellcode Shellcode is decrypted from the certificate.pem file. Malware uses CreateFileA() to open the file and then ReadFile() to read the contents. It is then decrypted using the key, with AES in CBC mode. key: HjGwPX65nXm21XOkX4nMqex28mBHjNU7 The decryption functions are not custome, and uses the bcrypt library which is loaded after file is read. https://github.com/VenzoV/MalwareAnalysisReports/blob/main/WikiLoader/WikiLoader%20notepad.md Page 13 of 22 For ReadFile the two arguments correspond to: File Handle Memory buffer to store data that is read. Now to proceed with decryption part some setup is necessary. Crypt32.dll is loaded CryptStringToBinaryA is called on the data of the certificate.pem file saved in the memory. CryptStringToBinaryA is used to convert a formatted string into an array of bytes. The flags supplied is in R8 and is 0x1 which means format of the string to be converted is base64. R9 contains the buffer that will receive the output of the function. https://github.com/VenzoV/MalwareAnalysisReports/blob/main/WikiLoader/WikiLoader%20notepad.md Page 14 of 22 The result is the following: Next setup phase, the malware builds string that will be used later. ObjectLength BlockLength ChainingMode ChainingModeCBC bcrypt.dll module is loaded and proceeds to get addresses for these functions and the jmps to them. https://github.com/VenzoV/MalwareAnalysisReports/blob/main/WikiLoader/WikiLoader%20notepad.md Page 15 of 22 BCryptOpenAlgorithmProvider BCryptGetProperty BCryptSetProperty BCryptGenerateSymmetricKey BCryptDecrypt BCryptDestroyKey BCryptCloseAlgorithmProvider First call of the chain is of course BCryptOpenAlgorithmProvider with "AES" supplied as argument. All these functions are from Microsoft DLL so can be searched on official docs for more information. Second call makes use of the initial strings saved at the first setup stage. It calls on BCryptGetProperty for: ObjectLength BlockLength -> blocks of 4 bytes ChainingMode ChainingModeCBC After the first one, some memory is allocated. After the second, lstrlen is called to get size of the payload that needs to be decrypted but reads only until first null byte so 17 bytes. Also, VirtuaAlloc() is called again and the first 10 bytes of the initial payload are written to it. https://github.com/VenzoV/MalwareAnalysisReports/blob/main/WikiLoader/WikiLoader%20notepad.md Page 16 of 22 lstrlenw() is called to count "ChainingModeCBC" string. This is necessary for the next call, which is BCryptSetProperty(). The arguements relevant are: szProperty -> "A pointer to a null-terminated Unicode string that contains the name of the property to set." In this case "ChainingMode" pbInput -> "The address of a buffer that contains the new property value. The cbInput parameter contains the size of this buffer." In our case this would be "ChainingModeCBC" with size 15 calculated in the step before. https://github.com/VenzoV/MalwareAnalysisReports/blob/main/WikiLoader/WikiLoader%20notepad.md Page 17 of 22 Next call is to get size of the key: symmetric key is generated with BCryptGenerateSymmetricKey(). The fifth arguement is the actual key and is passed as reference onto the stack. Now, the interesting part BCryptDecrypt() is called. This is responsible for decrypting the shellcode. Let's look at the arguments in the debugger. We can use the Microsoft documentation. https://github.com/VenzoV/MalwareAnalysisReports/blob/main/WikiLoader/WikiLoader%20notepad.md Page 18 of 22 First 4 args are passed in: RDX,RCX,R8,R9 The rest are passed as reference onto the stack. The arguments we are interested in are: pbInput The address of a buffer that contains the ciphertext to be decrypted. Second argument. pbIV The address of a buffer that contains the initialization vector (IV) to use during decryption. Fifth argument. pbOutput The address of a buffer to receive the plaintext produced by this function. Seventh argument. Second Argument (RCX) contains the certificate.pem data that was read into memory before. The Fifth argument has the IV. This is on the stack, and it is 10 bytes according the the sixth argument. The pbOutput is null in this case, so no output found. https://github.com/VenzoV/MalwareAnalysisReports/blob/main/WikiLoader/WikiLoader%20notepad.md Page 19 of 22 Following this VirtualAlloc is used to allocate a memory buffer and BCryptDecrypt() is called once again, this time the pbOutput is a pointer to the newly allocated memory. In this case the output can be observed, and we can see the decrypted shellcode. To make memory section which will contain the payload executable, the malware calls on VirtualProtect() onto that section. The call is used to give the section of memory 0x20-> PAGE_EXECUTE_READ. Finally, the malware can jump to the memory location and resume execution! https://github.com/VenzoV/MalwareAnalysisReports/blob/main/WikiLoader/WikiLoader%20notepad.md Page 20 of 22 With this we have finally made it to the first decrypted shellcode! Since this was pretty long I will continue with a part 2 soon! References https://bazaar.abuse.ch/sample/bef04e3b2b81f2dee39c42ab9be781f3db0059ec722aeee3b5434c2e63512a68/ https://github.com/VenzoV/MalwareAnalysisReports/blob/main/WikiLoader/WikiLoader%20notepad.md Page 21 of 22 https://www.unpac.me/results/612d6d2c-c45d-47ba-a2bb-a218ec753d3f https://twitter.com/Cryptolaemus1/status/1747394506331160736 https://www.proofpoint.com/us/blog/threat-insight/out-sandbox-wikiloader-digs-sophisticated-evasion https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/pebteb/peb/index.htm https://mohamed-fakroud.gitbook.io/red-teamings-dojo/shellcoding/leveraging-from-pe-parsing-technique-to-write-x86-shellcode Source: https://github.com/VenzoV/MalwareAnalysisReports/blob/main/WikiLoader/WikiLoader%20notepad.md https://github.com/VenzoV/MalwareAnalysisReports/blob/main/WikiLoader/WikiLoader%20notepad.md Page 22 of 22