{
	"id": "1d68770e-5d54-4633-8bfe-ca07f5b55250",
	"created_at": "2026-04-06T00:22:01.77938Z",
	"updated_at": "2026-04-10T03:20:34.836004Z",
	"deleted_at": null,
	"sha1_hash": "33ecdf789bed17a47ec2e1d954faa9679ec9c882",
	"title": "AgentTesla dropped via NSIS installer",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 1494846,
	"plain_text": "AgentTesla dropped via NSIS installer\r\nPublished: 2021-11-12 · Archived: 2026-04-05 18:04:12 UTC\r\nLately one of our customers received a suspicious file which was blocked by our sandbox solution but it was\r\nunclear if this was malicious and if so what malware it was so I did an analysis and want to share my results with\r\nyou. The main goal of this article is to show how to extract the final payload.\r\nThe sample is now available on VT: ce8a9bf908ce35bf0c034c61416109a44f015eabf058b12485450cd40af95fc3\r\nIf we do some static analysis via DiE (Detect it Easy) we see that the file is of type NSIS installer. One easy way\r\nto obtain the files is to just simply extract the files with 7-ZIP. Unfortunately I’m not aware of any way to reverse\r\nthe NSIS script (any hint welcome ;-)).\r\nAfter extracting the file we find a folder $PLUGINDIR as expected and another file with a random name and\r\nsome bytes in it.\r\nFirst dll – swfmwfkkeh.dll\r\nInside of the $PLUGINSDIR directory we find one file named swfmwfkkeh.dll (SHA1:\r\n56f3d68f10bde42216634f987b421feee696506e). Once again we open it up in DiE and find out that its written in\r\nC/C++ and some exports which look a little strange.\r\nIn the imports there are some false flags but the VirtualProtect seems to be reasonable\r\nhttp://l1v1ngc0d3.wordpress.com/2021/11/12/agenttesla-dropped-via-nsis-installer/\r\nPage 1 of 19\n\nNow we open up the file in IDA Pro and take a look at the export functions\r\nWe assume that the NSIS installer will start the DLL and call the exported function „zznqqqjqi“ so we start our\r\nanalysis there. After setting up the stack the function intiliazes „var_14“ which then is compared if its above 4722\r\n(in fact the function does a „jump not below“ with the main functionality in the false tree).\r\nWe will start with the right block first because there you can see that the memory address at 10009014 will receive\r\nRWX permissions and after that will be called. So we can assume that at this location there must be some\r\nassembly code. Now lets take a look what happens in the left block because this is where we land after the first\r\ncomparison. At this point „var_14“ is still below 4722. As you can see the variable (now in ecx) is used as a\r\npointer in the marked area by utilizing „byte ptr loc_10009014[ecx]“. So it grabs the first bytes of what ever is at\r\nthis address. If we take a look what is there we see some „strange“ assembly code – this doesn’t look as valid\r\nhttp://l1v1ngc0d3.wordpress.com/2021/11/12/agenttesla-dropped-via-nsis-installer/\r\nPage 2 of 19\n\nassembly code at all. If we look further down in the left block we see some xor, sub, add and mov operations so\r\nwe can assume that this code will be modified.\r\nAfter all the byte manipulations are done the byte is written back to the address 10009014. This time esi receives\r\nthe pointer (mov esi, [ebp+var_14]).\r\nThen we jump back to the comparison if var_14 is already above 4722. If this is the case we change the page\r\npermissions to RWX and execute the now modified code.\r\nLet’s debug some code\r\nThe decryption routine is very very long and we don’t want to go into reversing this algorithm. Below is a\r\nscreenshot of the algorithm – hell no!\r\nhttp://l1v1ngc0d3.wordpress.com/2021/11/12/agenttesla-dropped-via-nsis-installer/\r\nPage 3 of 19\n\nhttp://l1v1ngc0d3.wordpress.com/2021/11/12/agenttesla-dropped-via-nsis-installer/\r\nPage 4 of 19\n\nSo lets open the file up in x64dbg and jump to user code but stop there. We want to start at the function\r\n„zznqqqjqi“. If we switch to text mode in IDA we can see the address. We have two options to change the\r\ninstruction pointer (EIP) to continue from this address. First we can right-click on the EIP register and modify the\r\nvalue.\r\nOr we can jump to the address in the disassembly view (Ctrl + G) and then right-click „Set new origin here“.\r\nhttp://l1v1ngc0d3.wordpress.com/2021/11/12/agenttesla-dropped-via-nsis-installer/\r\nPage 5 of 19\n\nAfter that we can start debugging.\r\nhttp://l1v1ngc0d3.wordpress.com/2021/11/12/agenttesla-dropped-via-nsis-installer/\r\nPage 6 of 19\n\nWe also see as we did in IDA Pro the initialization of the pointer and the comparision with 1272 (hex) = 4722\r\n(dez). Here is on speciality about assembly. Instead of the jnb operation we saw in IDA Pro we see a jae (jump if\r\nabove or equal). In fact the operations are interchangeable because both check if the ZERO flag is set.\r\nWe know from our static analysis that after the decryption loop we change the permission of the page so lets\r\nfollow the jump and place a breakpoint at this point.\r\nNow we want to obverse how the code changes (without reversing the algorithm). Follow the address 10009014 in\r\ndisassembler to see the code.\r\nhttp://l1v1ngc0d3.wordpress.com/2021/11/12/agenttesla-dropped-via-nsis-installer/\r\nPage 7 of 19\n\nBefore decryption\r\nAfter decryption\r\nAs you can see now the code changed fundamentally and it starts with a jmp to another offset in the code. Jump\r\nback to the current instruction by double-clicking the EIP register.\r\nLets single step over the VirtualProtect and then jump into the decrypted code (F7).\r\nWe follow the jump and land at this address where we can see values being push/popped and then moved into a\r\nlocal variable (ebp-28). These values are ASCII codes so we can convert them manually or step over until we\r\nreach 0x10009849 where the string is terminated by a 0 (xor eax,eax = 0; mov memory,ax).\r\nhttp://l1v1ngc0d3.wordpress.com/2021/11/12/agenttesla-dropped-via-nsis-installer/\r\nPage 8 of 19\n\nIf we follow the address ebp-28 we can see the string\r\nAfter that the next call will get us the Magic (4D 5A) of Kernel32.dll\r\nAfter that we see a lot of calls to the same function and what we suspect to be API hashes. So lets dive into this\r\nfunction and try to figure out what algorithm is used to hash the API function names. For easier analysis I dumped\r\nthe DLL again with Scylla and opened it up again in IDA Pro.\r\nAPI Hashing\r\nWe will go into the details of the function but what it mainly does is hashing the function names (exports) of the\r\nDLL and comparing it with the provided hash. If they match the function is found and a pointer to the function is\r\nstored.\r\nhttp://l1v1ngc0d3.wordpress.com/2021/11/12/agenttesla-dropped-via-nsis-installer/\r\nPage 9 of 19\n\nWe know that the function receives the address to the module base of kernel32.dll. The address is passed via the\r\nECX register. Additionally the function receives the precomputed hash via the EDX register. The first steps in the\r\nprogram are to store the passed arguments into edi respectively in var_4. Then the function goes over the memory\r\nregion and reads in the PE structure looking for the export directory. To better unterstand whats going on here and\r\nwhat offsets are used take a look at this (huge) diagram:\r\nhttps://raw.githubusercontent.com/corkami/pics/master/binary/pe102/pe102.svg\r\nNow the function gets the first name from the export directory (edx+esi*4) and calls the function sub_100096B6.\r\nThe function name (better: the address of the function name) is passed via the ECX register.\r\nhttp://l1v1ngc0d3.wordpress.com/2021/11/12/agenttesla-dropped-via-nsis-installer/\r\nPage 10 of 19\n\nAnd here we have find the hashing algorithm. First the address of the function name is stored in ESI. Then we\r\nmove the constant 2326 (hex) into EDX register and jump down to loc_100096CF where we get the first char by\r\nutilizing „movsx edi, byte ptr [esi]“. Then the constant 2326h is copied into EAX. With „test edi, edi“ the function\r\nchecks if there are still characters in the function name left or if the string termination („0“) is reached.\r\nhttp://l1v1ngc0d3.wordpress.com/2021/11/12/agenttesla-dropped-via-nsis-installer/\r\nPage 11 of 19\n\nPseudocode of the hashing algorithm\r\nIf we remove some of the optimizations by the compiler and do the shr / shl operations inline (these operations\r\ncan only be done on registers therefore the compiler had to assemble it this way) then we get a very simple code.\r\nBasically its just starting with a constant (I called iv for Initialization Vector) and then shifts right and left and\r\nadds the current char. This way we go over all the chars in the string until we reach the end of the string. To\r\nsimplify things I use a fixed string of „GetTempPathW“. To account for the EDX register (which is 32 bytes long)\r\nwe have to make sure that we stay inside this range and therefore have to do an AND operation with\r\n0xFFFFFFFF.\r\n1\r\n2\r\n3\r\n4\r\niv = 0x2326\r\nname = \"GetTempPathW\"\r\nhash = 0\r\nfor i in range ( len (name)):\r\nhttp://l1v1ngc0d3.wordpress.com/2021/11/12/agenttesla-dropped-via-nsis-installer/\r\nPage 12 of 19\n\n5\r\n6\r\n7\r\n8\r\n9\r\niv = (iv + (iv \u003e\u003e 1 | iv \u003c\u003c 7 ) + ord (name[i])) \u0026 0xFFFFFFFF\r\nhash = iv\r\nprint ( hex ( hash ))\r\nResolve the called API hashes\r\nIf we jump over one of these function calls we see the pointer in the EAX being returned. So we can jump over all\r\nthe function call and make notes of the resolved API hashes. You could write a IDAPython script if you want but\r\nthere are not many hashes so I decided to do it manually.\r\nFrom what we see we can expect some file operations.\r\nhttp://l1v1ngc0d3.wordpress.com/2021/11/12/agenttesla-dropped-via-nsis-installer/\r\nPage 13 of 19\n\nAfter all necessary APIs are resolved we can see that the malware does the same push/pop trick as before – so we\r\ndo the same and run to the end and jump to the address in the dump\r\nThere we see a strange string „w66zlsqpnyue6“.\r\nReading and decrypting main payload\r\nLets continue debugging. And find out what this string is used for. Next we have a call to a memory region and as\r\nyou might have expected its an API call. We call GetTempPathW.\r\nWith the next call we are able to figure out what the previous strange string means. The string is appended to the\r\nresult of GetTempPathW and therefore it must be a file. If you recall ebp-44 is the string and ebp-480 contains the\r\nstring of the temp folder.\r\nhttp://l1v1ngc0d3.wordpress.com/2021/11/12/agenttesla-dropped-via-nsis-installer/\r\nPage 14 of 19\n\nAfter the call the newly formed string is stored at ebp-480 and we can follow this memory in the dump and see the\r\nfinal result\r\nNext we have a call to CreateFileW. Remember that the parameters are pushed to the stack in reverse order. One\r\ninteresting fact about this call is the value 80000000 which is a constant for GENERIC_READ which means that\r\nthe file must already exist or we will get an error. At this point we can assume that the NSIS installer will copy the\r\nfile over to the temp directory. To proceed with our debugging we have to copy the file ourselves. You can find the\r\nfile in the „root“ folder.\r\nIf the call succeeds EAX will contain the handle to the file. The result of the call is storend and checked in the\r\nnext line.\r\nIf this is successful the malware gets the size of the file with a call to GetFileSize\r\nhttp://l1v1ngc0d3.wordpress.com/2021/11/12/agenttesla-dropped-via-nsis-installer/\r\nPage 15 of 19\n\nThe same logic as above is used to check if the file size is not equal 0. Next we allocate virtual memory with a call\r\nto VirtualAlloc and pass the size (stored at ebp-8) to the function. The allocated memory is stored in ebp-C.\r\nWe do another check if the function succeeded and continue with a call to read file. The destination buffer is the\r\njust allocated memory region. In my case its 0x750000.\r\nAfter the file is read we can take a look at the memory region.\r\nThis does look like encrypted data but lets continue our analysis. After reading the file we close the handle to the\r\nfile and jump into another function at 10009A0E. Notice that the functions receives two parameters. First the file\r\nsize (ebp-8) and the allocated region of memory (ebp-C).\r\nhttp://l1v1ngc0d3.wordpress.com/2021/11/12/agenttesla-dropped-via-nsis-installer/\r\nPage 16 of 19\n\nIn the function we see a familiar code structure – it has great similarities with the first decryption loop. First there\r\nis a pointer initialized with zero stored as local variable at ebp-8. Then the pointer is incremented by one and\r\ncheck against the argument at ebp+C which is the file size. If the pointer value is lower than the size of data (jae)\r\nthe encryption will continue otherwise we jump to 10009C8B. After the jump is NOT taken we see that the value\r\nat ebp+8 (the data itself) is moved into eax and then the pointer (counter) gets added.\r\nAs we did with the previous encryption loop we don’t want to dig into the algorithm and just see whats happening.\r\nWe know that this code will manipulate the read file so go to address 10009C8B and set a breakpoint. After the\r\ndecryption is complete we can see a MZ header in the dump.\r\nhttp://l1v1ngc0d3.wordpress.com/2021/11/12/agenttesla-dropped-via-nsis-installer/\r\nPage 17 of 19\n\nSo right-click on the address 750000 and follow in memory map. Then right-click the address again and choose\r\n„Dump memory to file“. We want to stop our analysis of the initial sample here and continue with the dumped PE.\r\nAnalyzing the dumped PE\r\nWe once again start with some basic static analysis and open the file in DiE.\r\nImmediately we can see that the PE imports APIs to handle resources so lets check if there is something\r\ninteresting.\r\nAnd wow … there is an unencrypted PE inside of the resource section. To dump this PE we will utilize Resource\r\nHacker and dump the file via „Save Resource to a BIN file…“\r\nhttp://l1v1ngc0d3.wordpress.com/2021/11/12/agenttesla-dropped-via-nsis-installer/\r\nPage 18 of 19\n\nAnalyzing the dumped resource\r\nOnce again we open the dumped PE in DiE and see that its written in .NET and obfuscated.\r\nFrom this point on I just dropped this sample into the CAPE and got a hit on AgentTeslaV3 YARA signatures.\r\nMission complete!\r\nIoCs\r\nNSIS Installer ce8a9bf908ce35bf0c034c61416109a44f015eabf058b12485450cd40af95fc3\r\nswfmwfkkeh.dll 6d8bc73c6f2ef4ee700fc8bc4088f73a14dab355a2dd4e3e9aa3ddf52f7e946e\r\nEncrypted\r\nresource (inside\r\nof NSIS data)\r\nw66zlsqpnyue6\r\nc02ff5253bf3930f1ee14e088f50c827bf2209f3a7e9f00ed3994fd417d790b2\r\nDumped PE 9a72e5859b5564cecff5d5a4a929e81595d68aca1972ea2cf0fcf71c518d2cb9\r\nAgentTesla V3 5459e87eb0a39243a35405866b2dca1d57c2c1ee02d24052635fcc48de5d397c\r\nSource: http://l1v1ngc0d3.wordpress.com/2021/11/12/agenttesla-dropped-via-nsis-installer/\r\nhttp://l1v1ngc0d3.wordpress.com/2021/11/12/agenttesla-dropped-via-nsis-installer/\r\nPage 19 of 19\n\nWe follow the jump local variable (ebp-28). and land at this These values address where are ASCII codes we can see values so we can convert being push/popped them manually and or step then moved over until into a we\nreach 0x10009849 where the string is terminated by a 0 (xor eax,eax = 0; mov memory,ax).\n   Page 8 of 19",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"references": [
		"http://l1v1ngc0d3.wordpress.com/2021/11/12/agenttesla-dropped-via-nsis-installer/"
	],
	"report_names": [
		"agenttesla-dropped-via-nsis-installer"
	],
	"threat_actors": [],
	"ts_created_at": 1775434921,
	"ts_updated_at": 1775791234,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/33ecdf789bed17a47ec2e1d954faa9679ec9c882.pdf",
		"text": "https://archive.orkl.eu/33ecdf789bed17a47ec2e1d954faa9679ec9c882.txt",
		"img": "https://archive.orkl.eu/33ecdf789bed17a47ec2e1d954faa9679ec9c882.jpg"
	}
}