{
	"id": "f990c64c-7204-4b5b-8441-37b2f736a4ad",
	"created_at": "2026-04-06T00:13:06.180047Z",
	"updated_at": "2026-04-10T03:21:56.114396Z",
	"deleted_at": null,
	"sha1_hash": "6672c5a91a487b192fe56f2c169ef382eccacb18",
	"title": "Nanocore \u0026 CypherIT",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 1882505,
	"plain_text": "Nanocore \u0026 CypherIT\r\nBy Myrtus 0x0\r\nPublished: 2020-04-04 · Archived: 2026-04-05 22:22:43 UTC\r\nHello everyone! Its been a while since I've posted. There's been some changes in my life that have distracted me\r\nfrom my malware temporarily. One of those updates is a career change. I will officially be working as a security\r\nresearcher and in preparation of that I felt that I needed to keep my reverse engineering skills sharp. So I went to\r\nany.runs malware trends page, and randomly picked a sample. I ended up picking a Nanocore sample to analyze.\r\nNanocore has been around for many years and is one of the simpler and cheaper malware familieis out there but I\r\nnever had the availability during work to look at it. Since I generally focus on targeted malware, I knew this was\r\ngoing to be a good change of pace. The sample can be found here if you wish to follow along.\r\nTechnical Analysis\r\nFirst step as usual, is opening the sample in PE studio for a quick triage.\r\nFrom the output here you can see its a Cpp application with a rather high entropy of 7.5. So there is definitely\r\nsome encrypted or compressed content here. You can also see that there is an embedded resource within the\r\napplication. Immediately the AutoIT caught my eye as that's not something I have dealt with before.\r\nhttps://malwareindepth.com/defeating-nanocore-and-cypherit/\r\nPage 1 of 24\n\nEven more suspicious is that its almost 53% of the file, and a maximum entropy value of 8. Seeing the large\r\nresource immediately leads me to look for resource related calls such as LockResource, SizeOfResource,\r\nLoadResource etc.\r\nFindResource is only called within this function so if we assume that the AutoIT script is part of the malware, this\r\nfunction becomes increasingly important. This function will load the resource make some calls and load the\r\nresource data within [ebp+var_4].\r\nhttps://malwareindepth.com/defeating-nanocore-and-cypherit/\r\nPage 2 of 24\n\nLooking at the call graph shows this is a leaf node for the call graph, which can potentially mean that execution\r\nwill continue outside of the scope of this application or all the information for this chain of calls was acquired.\r\nLooking at the parent function it opens a file passed as an argument.\r\nLooking at calls to this function, there are references to various AutoIT strings.\r\nhttps://malwareindepth.com/defeating-nanocore-and-cypherit/\r\nPage 3 of 24\n\nJumping to the Main function it calls sub_403B3A which has a anti-debugger check. It calls IsDebuggerPresent\r\nand if it is, opens a message box and the process terminates\r\nFollowing sub_408667, eventually the resource will be loaded from memory, and compared against a the\r\ncompiled AutoIT header\r\nhttps://malwareindepth.com/defeating-nanocore-and-cypherit/\r\nPage 4 of 24\n\nExecution only continues if the header is correct, so we can assume it's going to load an AutoIT script. This\r\ncoupled with the fact that it quits if you try to debug the executable, I'm comfortable in assuming this executable is\r\ngoing to load and run the compiled AutoIT script from its resource section.\r\nAutoIT Script\r\nNow that we know the binary file we have been looking at is just a runtime environment for the AutoIT script\r\nresource we can take a look at the script itself. Extracting the resource with Resource Hacker and throwing it in a\r\nhex editor shows that it's a compiled script. Now there are a couple tools out there used to decompile AutoIT\r\nscripts. There is Exe2Aut which is what I went with to handle this compiled script. Although running this script\r\nthrough the application gave the following error...\r\nhttps://malwareindepth.com/defeating-nanocore-and-cypherit/\r\nPage 5 of 24\n\nAut2Exe Error\r\nGoogling around for this I found Hexacorn's post about this exact issue! Following his post we append our\r\ncompiled script to the 32 bit stub and we get a valid decompilation of the script!\r\nCopying the contents to a new file in VSCode and giving it a look over immediately shows something interesting.\r\nThis script is 10901 lines long. The majority of the file looks like the following.\r\nhttps://malwareindepth.com/defeating-nanocore-and-cypherit/\r\nPage 6 of 24\n\nAt the end of the file there is a large data blob that spans 3500 lines just on its own. Generally this means it's some\r\nsort of payload. Loading this data blob into CyberChef shows that it is most likely either compressed or encrypted.\r\nThis rules simpler techniques such as XOR encryption.\r\nhttps://malwareindepth.com/defeating-nanocore-and-cypherit/\r\nPage 7 of 24\n\nWith this information I knew I'd have to give the script a good hard look. After some googling about AutoIT\r\ncrypters I came across CypherIT. CypherIT is a AutoIT crypter that is sold at 5 separate tiers. the first tier is 33$\r\nfor 1 month, 57$ for 2 months and 74$ for 3 months, 175$ for FUD for 2 weeks and finally a 340$ lifetime model.\r\nhttps://malwareindepth.com/defeating-nanocore-and-cypherit/\r\nPage 8 of 24\n\nInterestingly enough they even have a discord server that users can join for troubleshooting and getting updates on\r\nnew versions.\r\nGoing back to the script.... After the large data blob is finished being initialized, it is passed to a function called\r\nskpekamgyg. This function takes the large data blob, a random string and a number as a string.\r\nThere is way too much to go into here for the crypter but these are the basic characteristics of it:\r\n1. unused variables\r\n2. unused functions\r\n3. string decryption\r\nI ended up writing a golang based script that can handle those 3 above cases! For this sample it turned the the\r\n10901 line script into a 6600 line one. There is some more analysis that can happen to remove function calls that\r\nhttps://malwareindepth.com/defeating-nanocore-and-cypherit/\r\nPage 9 of 24\n\naren't actually called by the main payload decryption routine, but that would require actual function call analysis\r\nand that is out of scope for this article. The script can be found here\r\nString Decryption\r\nFor decrypting the strings there are a couple pieces to it.\r\nfunc decryptStrings(lines []string) ([]string) {\r\nvar re = regexp.MustCompile(`(?m)\"\\b[0-9A-F]{2,}\\b\"`)\r\nmodLines := []string{}\r\nfor i, line := range lines {\r\nmatched := false\r\ntempLine := \"\"\r\ntempLine += line\r\nfor _, match := range re.FindAllString(line, -1) {\r\nmatched = true\r\ncleaned := strings.Replace(match, \"\\\"\", \"\", -1)\r\ndec, err := hex.DecodeString(cleaned)\r\nif err != nil {\r\nmodLines = append(modLines, tempLine)\r\nbreak\r\n}\r\ndecodedStr, err := xorBrute(dec)\r\nif err != nil {\r\nmodLines = append(modLines, tempLine)\r\nbreak\r\n}\r\nif len(decodedStr) \u003c 2 {\r\nmodLines = append(modLines, tempLine)\r\nbreak\r\n}\r\nif decodedStr[0:2] == \"0x\" {\r\ntemp, err := hex.DecodeString(strings.Replace(decodedStr, \"0x\", \"\", -1))\r\nif err != nil {\r\nmodLines = append(modLines, tempLine)\r\nbreak\r\n}\r\ndecodedStr = string(temp)\r\n}\r\nif isASCII(decodedStr) {\r\ntempLine += \" ;\" + decodedStr\r\nfmt.Printf(\"[+] decoded string at line %d: %s\\n\", i, decodedStr)\r\n} else {\r\nhttps://malwareindepth.com/defeating-nanocore-and-cypherit/\r\nPage 10 of 24\n\ntempLine += \" ;\" + \"BINARYCONTENT\"\r\n}\r\nmodLines = append(modLines, tempLine)\r\nbreak\r\n}\r\nif !matched {\r\nmodLines = append(modLines, tempLine)\r\n}\r\n}\r\nreturn modLines\r\n}\r\nI look for hex encoded strings with a regex. Then I clean the string removing extraneous characters. Once we have\r\na valid hex string like 307832343639373037393643363836353...33303330333033303232 we pass it to a the\r\nfunction xorBrute.\r\nfunc xor(enc []byte, key byte) (string, error) {\r\nret := []byte{}\r\nfor i := 0; i \u003c len(enc); i++ {\r\ntemp := enc[i] ^ key\r\nret = append(ret, temp)\r\n}\r\nreturn string(ret), nil\r\n}\r\nfunc xorBrute(encodedStr []byte) (string, error) {\r\nswitch string(encodedStr[0]) {\r\ncase \"0\":\r\n// lazy\r\nreturn xor(encodedStr, 0)\r\ncase \"1\":\r\nreturn xor(encodedStr, 1)\r\ncase \"2\":\r\nreturn xor(encodedStr, 2)\r\ncase \"3\":\r\nreturn xor(encodedStr, 3)\r\ncase \"4\":\r\nreturn xor(encodedStr, 4)\r\n}\r\nhttps://malwareindepth.com/defeating-nanocore-and-cypherit/\r\nPage 11 of 24\n\nreturn \"\", errors.New(\"not a valid nanocore encoding\")\r\n}\r\nA neat little property I found about this is that the first character must decode to 0 since the actual string must start\r\nwith 0x for it to be processed properly. Now in the AutoIT script the function that decodes these hex strings takes\r\n2 arguments, a large hex string and a single character that is some number between 0 and 4 which is the XOR key.\r\nSince the value we are looking for here with the first character is 0, we can use the fact that anything XOR'd with\r\nitself is 0. So while the second argument is being passed we can figure out the 1 byte key with the switch\r\nstatement.\r\nOnce we have the decoded string as a large hex value we do a check on the size to make sure we aren't dealing\r\nwith a single byte value that the regex might've picked up. Followed by a check to make sure it starts with 0x, if\r\nall those conditions are met we decode the hex value into ASCII and add it as a comment to the script.\r\nVariable Cleaning\r\nConsidering that these CypherIT scripts generally have thousands of lines, it's pretty clear they have unused\r\nvariables. My technique for removing variables is simplistic but effective. I have a loop that can extract all of the\r\nvariable names via a regex\r\ngetVarName := regexp.MustCompile(`(?m)(Dim|Local|Global Const|Global)\\s\\$(?P\u003cName\u003e\\w+)\\s`)\r\nIf I get a variable if the \"Name\" regex group I scan every line for that name. In the script itself Ive done this step\r\nafter decoding the strings so that all variable names are in the clear.\r\n// count the number of occurences\r\noccurences := 0\r\nfor _, secondLine := range lines {\r\nif strings.Contains(secondLine, result[\"Name\"]) {\r\noccurences++\r\n}\r\n}\r\n// if the variable is used multiple times keep it\r\nif occurences \u003e 1 {\r\nhttps://malwareindepth.com/defeating-nanocore-and-cypherit/\r\nPage 12 of 24\n\nmodLines = append(modLines, line)\r\n}\r\nFunction Cleaning\r\nRemoving functions were a bit more in depth than variables as you need to be able to find the start and end of a\r\nfunction. Functions also have the added complexity that if you are removing a function that isn't being called\r\nanywhere else, you might've isolated another function that isn't going to reached either. So this is function that\r\nworks the best when you call it multiple times. To get started, we define our regex.\r\nvar getFuncName = regexp.MustCompile(`(?m)Func\\s(?P\u003cName\u003e\\w+)`)\r\nThen for every function name we extract, we check if it's being called anywhere else in the script. If it's not being\r\ncalled anywhere else we add it to a list that contains all functions we are going to remove.\r\nfor i, line := range lines {\r\n// If it is a func declaration get the func name\r\nmatch := getFuncName.FindStringSubmatch(line)\r\nif len(match) == 0 {\r\ncontinue\r\n}\r\nresult := make(map[string]string)\r\n// turn the regex groups into a map\r\nfor k, name := range getFuncName.SubexpNames() {\r\nif i != 0 \u0026\u0026 name != \"\" {\r\nresult[name] = match[k]\r\n}\r\n}\r\n// count the number of occurences in the new file\r\noccurences := 0\r\nfor _, secondLine := range lines {\r\nif strings.Contains(secondLine, result[\"Name\"]) {\r\noccurences++\r\n}\r\n}\r\n// if the function is just used once, find it and dont write it to the file\r\nif occurences == 1 {\r\nunusedFuncs = append(unusedFuncs, result[\"Name\"])\r\n}\r\n}\r\nhttps://malwareindepth.com/defeating-nanocore-and-cypherit/\r\nPage 13 of 24\n\nOnce we have this list we iterate over it and find the function start with 2 string.Contains and we iterate over the\r\nlines from that point until we find the EndFunc keyword.\r\n// now that we have all of the unused functions, we need to remove them\r\nfor i := 0; i \u003c len(lines); i++ {\r\nfor _, unusedFunc := range unusedFuncs {\r\nif strings.Contains(lines[i], unusedFunc) \u0026\u0026 strings.Contains(lines[i], \"Func\") {\r\nfor j, secondLine := range lines[i:] {\r\nif strings.Contains(secondLine, \"EndFunc\") {\r\ni = i + j + 1\r\nbreak\r\n}\r\n}\r\n}\r\n}\r\nmodLines = append(modLines, lines[i])\r\n}\r\nAfter running the script against the crypter we have reduced it from 10901 lines to 6195 lines. This function needs\r\nto ran a couple of times to catch code branches that do have child function calls but aren't reachable from the main\r\nfunction. Results will vary from script to script, but I now have a script that only contains used functions, used\r\nvariables and decrypted strings.\r\nThe Final CypherIT Script\r\nThese were the high level concepts I used to simplify my CypherIT crypters, the actual script itself will be listed\r\nhere.\r\nThe Bad News\r\nSadly, even with all of this analysis and development work that made this crypter a lot easier to look at,\r\nreconstructing the shellcode itself that will AES decrypt the actual Nanocore sample is out of scope for this\r\nproject... Luckily the wonderful people over at Unpac.me maintain a incredible service that was actually able to\r\nget the payload for me! If you haven't checked out their service I'd definitely give it a try with some difficult\r\ncrypters.\r\nhttps://malwareindepth.com/defeating-nanocore-and-cypherit/\r\nPage 14 of 24\n\nAs you can see there is the unpacked Nanocore sample! Onto the actual analysis of the sample.  \r\nNanocore Payload Analysis\r\nSo going ahead with the analysis of 80bbde2b38dc19d13d45831e293e009ae71301b67e08b26f9445ad27df2b8ffd,\r\nNanocore is written in .NET so dnSpy will be our tool of choice. Loading it up in dnSpy shows that the internal\r\nclasses are obfuscated.\r\nOne of the first steps I take when I see any sort of obfuscation in .NET malware is run it through de4dot. De4dot\r\nis a .NET deobfuscator for many well known .NET obfuscators.\r\nOutput shows that de4dot was able to identify the obfuscator used, Eazfuscator. This obfuscator can be found free\r\nto use here. Now that we have a cleaned version of the Nanocore sample we are ready to actually analyze it.\r\nhttps://malwareindepth.com/defeating-nanocore-and-cypherit/\r\nPage 15 of 24\n\nStatic Config Decryption\r\nLooking at PE Studio results though there is yet another encrypted resource that we need to deal with.\r\nSearching for function calls within our .NET application that handle resources leads us to the following\r\nhttps://malwareindepth.com/defeating-nanocore-and-cypherit/\r\nPage 16 of 24\n\nPretty standard loading of a resource and checking the xrefs to this function we find\r\nNow we are at the the point where we can recreate this code assuming that its going to decrypt the encrypted\r\nresource. As you can already see I've annotated a lot of the code already to make this blog post a tad shorter.\r\nhttps://malwareindepth.com/defeating-nanocore-and-cypherit/\r\nPage 17 of 24\n\nbyte[] byte_ = binaryReader.ReadBytes(binaryReader.ReadInt32());\r\nThis is the first line that we have to pay attention to. This line will read a 32bit integer from the encrypted\r\nresource. Then get the GUID of the .NET application and pass it to a function that is going to return a Decryptor\r\nobject for us\r\nThis function starts off initializing a Rfc2898DeriveBytes object with the GUID as the password and the salt. That\r\nwill return a Key and IV that is then used in Rijndael in CBC mode to create the next piece in this chain. This\r\nfunction will decrypt the first 8 bytes on the resource and pass that back. Immediately after the 8 bytes is returned,\r\nits passed to this function below where a DES decryptor is created. These 8 bytes and then used as the Key and IV\r\nfor the DES decryptor that will decrypt the rest of the contents of the resource.\r\nAfter this function is called, all we have is a initialized decryptor, and our content is still encrypted. Although a\r\ncouple lines after our init function this function below is called.\r\nbyte_0 = AESCrypto.icryptoTransform_1.TransformFinalBlock(byte_0, 0, byte_0.Length);\r\nhttps://malwareindepth.com/defeating-nanocore-and-cypherit/\r\nPage 18 of 24\n\nThis line will decrypt all the contents. Now as soon as that's finished a boolean is read from the start of the\r\ndecrypted contents. If the boolean is true, the rest of the contents has to be zlib decompressed. In total this breaks\r\ndown to the following python code to re-implement. Now the GUID has to be changed and since I was working\r\nwith a single sample  I didn't write any code to handle the boolean being read to decompress or not, so that will\r\nhave to be modified as well.\r\ndef decrypt_config(coded_config, key):\r\ndata = coded_config[24:]\r\ndecrypt_key = key[:8]\r\ncipher = DES.new(decrypt_key, DES.MODE_CBC, decrypt_key)\r\nraw_config = cipher.decrypt(data)\r\nnew_data = raw_config[5:]\r\ndecompressed_config = zlib.decompress(new_data, -15)\r\nreturn decompressed_config\r\ndef derive_pbkdf2(key, salt, iv_length, key_length, iterations):\r\ngenerator = PBKDF2(key, salt, iterations)\r\nderived_iv = generator.read(iv_length)\r\nderived_key = generator.read(key_length)\r\nreturn derived_iv, derived_key\r\n \r\n# get guid of binary\r\nguid_str = 'a60da4cd-c8b2-44b8-8f62-b12ca6e1251a'\r\nguid = uuid.UUID(guid_str).bytes_le\r\n# AES encrypted key\r\nencrypted_key = raw_config_data[4:20]\r\n# rfc2898 derive IV and key\r\ndiv, dkey = derive_pbkdf2(guid, guid, 16, 16, 8)\r\n# init new rijndael cipher\r\nrjn = new(dkey, MODE_CBC, div, blocksize=len(encrypted_key))\r\n# decrypt the config encryption key\r\nfinal_key = rjn.decrypt(encrypted_key)\r\n# decrypt the config\r\ndecrypted_conf = decrypt_config(raw_config_data, final_key)\r\nLoading the decrypted contents in a hex editor does show in fact that we have a valid decrypted blob.\r\nhttps://malwareindepth.com/defeating-nanocore-and-cypherit/\r\nPage 19 of 24\n\nThis blob contains various PE files being the plugins loaded as well as standard config information below\r\nhttps://malwareindepth.com/defeating-nanocore-and-cypherit/\r\nPage 20 of 24\n\nConfig Parsing\r\nNow that our config blob is properly decrypted, we need to parse it. Running binwalk on our output contents\r\nshows some interesting results.\r\nhttps://malwareindepth.com/defeating-nanocore-and-cypherit/\r\nPage 21 of 24\n\nIn between the zlib compressed contents and the PNGs there are valid PE files. Now Nanocore is a modular RAT\r\nas  I had mentioned earlier. These PE files are the plugins that are loaded immediately after config decryption.\r\nWith the following snippet I was able to dump each individual PE file that Nanocore is going to load.\r\nplugins = decrypted_conf.split(\"\\x00\\x00\\x4D\\x5A\")\r\n# remove first snippet as its junk code\r\nplugins = plugins[1:]\r\n# Add the MZ header back cuz python is hard\r\n# remove the config struct at the end of the file\r\nwhile i \u003c len(plugins):\r\nplugins[i] = '\\x4D\\x5A' + plugins[i]\r\nif \"\\x07\\x3E\\x00\\x00\\x00\" in plugins[i] and i == len(plugins)-1:\r\nplugins[i] = plugins[i].split(\"\\x07\\x3E\\x00\\x00\\x00\")[0]\r\ni += 1\r\nHere we iterate over the config blob that's split by 2 null bytes and the MZ header. With Nanocore's config being\r\nat the end of the file that means the last element in our list from the split is going to contain the config data when it\r\nshouldn't. The config data itself starts with 0x07 0x3E followed by 3 null bytes. Splitting on that when we're at the\r\nlast plugin and selecting the first element keeps the last plugin intact. Once they are split and dumped to a\r\ndirectory we get 8 plugins to analyze.\r\nhttps://malwareindepth.com/defeating-nanocore-and-cypherit/\r\nPage 22 of 24\n\nFor the config values of the sample, each field starts with a 0x0c, a null byte, the field name, another null byte\r\nthen the value of the field name. In the script I search for the hardcoded field names in this specific format.\r\nlogging_rule = re.search(\"\\x0c.KeyboardLogging(?P\u003clogging\u003e.*?)\\x0c\", decrypted_conf)\r\nlogging = logging_rule.group('logging')\r\nif ord(logging[1]):\r\nconfig_dict['KeyboardLogging'] = True\r\nelse:\r\nconfig_dict['KeyboardLogging'] = False\r\nAfter doing this for each configuration field of the sample we can get a clear picture of this sample.\r\nhttps://malwareindepth.com/defeating-nanocore-and-cypherit/\r\nPage 23 of 24\n\nSome of the fields aren't parsed properly but that is mainly due to lack of time. The values are all correct they just\r\nneed to be interpreted correctly.\r\nNanocore as malware is pretty straightforward to analyze and hasn't changed much so I'll be skipping the analysis\r\nof the plugins. If there is demand I can write a follow up on the plugins as well as flaws within Nanocore's\r\nnetwork comms.\r\nIn an effort to keep this post short, I'm going to end the analysis here but there is more work to be done on\r\nNanocore and the CypherIT crypter. If anyone would like to collaborate and make a true unpacker for CypherIT,\r\nplease reach out.\r\nSource: https://malwareindepth.com/defeating-nanocore-and-cypherit/\r\nhttps://malwareindepth.com/defeating-nanocore-and-cypherit/\r\nPage 24 of 24\n\nWith this information crypters I came I knew I'd have across CypherIT. to give the script CypherIT is a AutoIT a good hard crypter that look. After some is sold at 5 separate googling about tiers. the AutoIT first tier is 33$\nfor 1 month, 57$ for 2 months and 74$ for 3 months, 175$ for FUD for 2 weeks and finally a 340$ lifetime model.\n   Page 8 of 24   \n\n  https://malwareindepth.com/defeating-nanocore-and-cypherit/    \nStatic Config Decryption     \nLooking at PE Studio results though there is yet another encrypted resource that we need to deal with.\nSearching for function calls within our .NET application that handle resources leads us to the following\n    Page 16 of 24",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"references": [
		"https://malwareindepth.com/defeating-nanocore-and-cypherit/"
	],
	"report_names": [
		"defeating-nanocore-and-cypherit"
	],
	"threat_actors": [],
	"ts_created_at": 1775434386,
	"ts_updated_at": 1775791316,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/6672c5a91a487b192fe56f2c169ef382eccacb18.pdf",
		"text": "https://archive.orkl.eu/6672c5a91a487b192fe56f2c169ef382eccacb18.txt",
		"img": "https://archive.orkl.eu/6672c5a91a487b192fe56f2c169ef382eccacb18.jpg"
	}
}