{
	"id": "82e6a19f-c006-436e-a003-52d5992f4676",
	"created_at": "2026-04-06T01:32:34.451612Z",
	"updated_at": "2026-04-10T13:12:57.213868Z",
	"deleted_at": null,
	"sha1_hash": "b81a47fa9064957696f69c1257edc46ff588b5bd",
	"title": "Analyzing KSL0T (Turla’s Keylogger), Part 1 – Reupload | 0ffset Training Solutions",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 618563,
	"plain_text": "Analyzing KSL0T (Turla’s Keylogger), Part 1 – Reupload | 0ffset\r\nTraining Solutions\r\nBy 0verfl0w_\r\nPublished: 2019-07-08 · Archived: 2026-04-06 00:40:35 UTC\r\n(This post is a reupload from my old site which is no longer available – you may have seen it before)\r\nWhilst I’m working through the Hancitor write up and the Flare On challenges, I decided to take a short break and\r\nfocus on a smaller piece of malware – such as a keylogger, which in this case is on a much larger scale than $32\r\nkeylog-as-a-service, as it has been attributed to a Russian Advanced Persistent Threat group known as Turla,\r\nor Waterbug. This APT group has been in the news quite frequently over the past month,\r\nafter compromising European government foreign offices and creating an extremely stealthy backdoor that\r\nutilizes PDF files to exfiltrate data, via emails. I noticed a sample of malware uploaded to VirusBay, tagged\r\nwith Turla and Venomous Bear (yet another moniker given to the group), and decided to analyze it. As I\r\nstatically analyzed a lot of the Flare On challenges that I have completed, I decided I wanted to approach this\r\nsample primarily using static analysis, unless it became too difficult to do so. So, let’s begin cracking this sample\r\nopen!\r\nMD5: 59b57bdabee2ce1fb566de51dd92ec94\r\nAs per usual, I ran the file and strings command on the binary to see the format and if there was anything\r\ninteresting that was visible. The binary is in fact a DLL, and a 64 bit one. The output of strings displayed a lot of\r\njunk, although we are able to see a few error messages and several Windows API calls, such\r\nas IsDebuggerPresent, WriteFile, and dynamic loading\r\ncalls; GetModuleHandle, LoadLibrary and GetProcAddress.\r\nhttps://0ffset.net/reverse-engineering/malware-analysis/analyzing-turlas-keylogger-1/\r\nPage 1 of 10\n\nAfter opening the file in IDA, we are able to view the entry point of the user code, DLLMain. After\r\na cmp operation, the program jumps to 0x1800019BD, where an extremely important function at 0x1800017D0 is\r\ncalled. It might not look like much at a first glance, just multiple calls with 3 arguments passed to them – until you\r\nrealize it is calling the same function each time, with the second argument being what seems to be pointing to\r\nsome encrypted text …\r\nhttps://0ffset.net/reverse-engineering/malware-analysis/analyzing-turlas-keylogger-1/\r\nPage 2 of 10\n\nAs you may have guessed, this is in fact a decryption function. When we view the function called\r\nat 0x180001750, we can determine that it is a decryption function based on both the arguments, and on the xor\r\nedx, eax. Also take note of the for loop, which compares the value in eax (value stored in arg_10) and the value\r\nin var_18.\r\nhttps://0ffset.net/reverse-engineering/malware-analysis/analyzing-turlas-keylogger-1/\r\nPage 3 of 10\n\nWe can further deduce that var_18 is in fact the counter, based off of this section of code:\r\nmoveax, [var_18]\r\nadd   eax, 1\r\nmov   [var_18], eax\r\nAs we now know this, we can rename var_18 to Counter. Click on var_18 and push ‘n‘, and a prompt will\r\nappear, allowing you to rename the variable. We now need to figure out what the counter value is being compared\r\nto, meaning we need to analyze arg_10. As it is arg_* and not var_*, we have to look at the arguments passed to\r\nthis specific function. In this case, the arguments are not passed using the push mnemonic and are\r\ninstead mov’d into the arguments. As the value in the r8d register is being moved into arg_10, let’s jump back the\r\nthe calling function to view what was in r8d before executing the decryption function.\r\nmov r8d, 25Eh\r\nlea rdx, unk_18000F2F0\r\nmov ecx, 47h\r\ncall sub_180001750\r\nWe can convert the hexadecimal number to decimal by pressing ‘H‘ while selecting it, resulting in the decimal\r\nvalue 606. So for this particular call, the XOR algorithm loops 606 times, XORing each character. Now we have\r\nidentified arg_10, we can go ahead and rename it. Next, let’s try and figure out the values that are being XOR’ed\r\ntogether, to see if we can locate the key used and the data being decrypted. The xor mnemonic performs the XOR\r\nhttps://0ffset.net/reverse-engineering/malware-analysis/analyzing-turlas-keylogger-1/\r\nPage 4 of 10\n\noperation on the value in edx, with the value in eax. The result of the operation is always stored in the first\r\nargument, in this case the result is stored in edx. We can assume that edx contains the data to be decrypted,\r\nand eax contains the key. In order to find these values out, we have to see what was moved (mov) or loaded (lea)\r\ninto the edx and eax register.\r\nAs you can see, r8d is moved into edx before the XOR occurs, so we have to then see what was moved\r\ninto r8d beforehand – which is seen in the third instruction of this segment: movsx r8d, byte ptr [rax+rcx]. Just\r\nabove this, [rsp+18h+counter] is moved into rcx, and whatever is stored in arg_8 is moved into rax. As we\r\nknow counter is incremented by 1 each loop, we can determine that the byte ptr [rax+rcx] is iterating\r\nover something 606 times, with that something being encrypted characters. We can double check this by finding\r\nout what is stored in arg_8, the same way we discovered what was in arg_10: unk_18000F2F0, which contains a\r\nlot of encrypted data (specifically, 606 bytes of it).\r\nhttps://0ffset.net/reverse-engineering/malware-analysis/analyzing-turlas-keylogger-1/\r\nPage 5 of 10\n\nNext, let’s find out what is being stored in eax. In this instance, one byte of data at [rax+rdx] is\r\nbeing movsx into eax. Therefore, we need to locate the data stored in rax and rdx. The data in rax is quite easy to\r\nfind out, as there is a mov rax, unk_18000F010 before the movsx. When viewing the data at 0x18000F010, we\r\ncan see what seems to be more encrypted text – the key used to decrypt the data at 18000F2F0. However, it is not\r\nthat simple. Remember the rdx register that is used? Well we can assume that the value in rdx changes on each\r\niteration. In order to figure this value, we need to look at the div instruction.\r\nKey Array:\r\nhttps://0ffset.net/reverse-engineering/malware-analysis/analyzing-turlas-keylogger-1/\r\nPage 6 of 10\n\nmov rax, arg_0\r\nxoredx, edx\r\nmov ecx, 100\r\ndiv ecx\r\nThe div instruction takes one operand – this contains the value to divide rax by. A division of  0x8003 by 0x100 in\r\nx64 Assembly would look something like this:\r\nxor rdx, rdx ; clear dividend\r\nmov rax, 0x8003 ; dividend\r\nmov rcx, 0x100 ; divisor\r\ndiv rcx ; rcx = 0x80, rdx = 0x3\r\nIt is basically a division, however the remainder value is stored in rdx, meaning rdx is equal to 0x3. In the case of\r\nthe keylogger, the XOR key is decided based on the value of rdx, and therefore we need to figure out what rax is,\r\nso we can divide it by 0x64 in order to get the first value of rdx. We know that arg_0 contains the value\r\nof ecx before the function is executed, which is 0x47. When we convert it to decimal format, it is 71 / 100, leaving\r\nhttps://0ffset.net/reverse-engineering/malware-analysis/analyzing-turlas-keylogger-1/\r\nPage 7 of 10\n\nus with 0.71. The value stored in rdx is 71. Simply perform the modulo operation (%) on these two values and\r\nyou will get 71. This means that the 71st byte in the key array is the first byte to be used in the XOR: 0x85. For\r\neach loop, the value inside of arg_0 is incremented by 1, meaning the key byte is always changing – although\r\nnow that we know how the algorithm works, we can automate the decryption statically, rather than relying on a\r\ndebugger.\r\nmov eax, [rsp+18h+arg_0]\r\nadd eax, 1\r\nmov [rsp+18h+arg_0], eax\r\nSo how do we go about the static decryption? Well the answer is IDC, which is a scripting language incorporated\r\ninside of IDA. Another option is IDAPython, however that isn’t available inside the IDA 7 Pro Free version, so\r\nwe’ll stick with IDC. So far, we know that the decryption part is all contained inside of a loop that loops a pre-determined amount of times, using a specific key array and a determined data array. In addition, the value that is\r\nused for the div operation is also passed as an argument. Therefore, we will require 3 arguments for our\r\nfunction: base_data, div, and loop. We will also need 6 variables: index, x1, x2, data, i, and base_xor. index will\r\ncontain the result of the modulo operation, x1 will contain a byte of data from the encrypted text, x2 will contain a\r\nbyte of data from the key, data will contain the result of the XOR, i will be the counter and base_xor will hold the\r\naddress to the key array. To store an address, simply add an 0x to the beginning of said address. The rest of the\r\nscript will contain the necessary incrementations and XOR’s.\r\nstatic decrypt_data(base_data, div, loop) {\r\nauto index, x1, x2, data, i, base_xor;\r\nbase_xor = 0x18000F010;\r\nfor (i = 0; i \u0026lt; loop; i++) {\r\n index = div % 100; // Get value from div % 100\r\n x1 = Byte(base_data); // Get byte from encrypted data\r\n x2 = Byte(base_xor + index); // Get XOR key using value from div / 100\r\n data = x1 ^ x2; // XOR data\r\n PatchByte(base_data, data); // Replace enc. byte with dec. byte\r\n base_data = base_data + 1; // Increment Encrypted Data\r\n div = div + 1; // Increment Divider\r\n}\r\n}\r\nIn order to *install* this script into IDA, click File -\u003e Script Command, and then paste it into the dialog box.To\r\ncall the function, simply type (in the command line at the bottom) decrypt_data(0x18000F2F0, 71, 606) to\r\ndecrypt the first section of data, which should look like the image below.\r\nhttps://0ffset.net/reverse-engineering/malware-analysis/analyzing-turlas-keylogger-1/\r\nPage 8 of 10\n\nSelect all of the data and press A, which should arrange it into more legible data, although every second byte is a\r\n0, so we will need to remove them.\r\nAfter removing the 0’s, we are left with this:\r\n\u003c#RShift\u003e \u003c#LShift\u003e \u003c#RCtrl\u003e \u003c#LCtrl\u003e \u003c!RShift\u003e \u003c!LShift\u003e \u003c!RCtrl\u003e \u003c!LCtrl\u003e - + [] \\ ; / ` ' , . \u003cPag\r\nhttps://0ffset.net/reverse-engineering/malware-analysis/analyzing-turlas-keylogger-1/\r\nPage 9 of 10\n\nWe can assume that this data is used to log keystrokes when pushing certain buttons such as Left Shift and\r\nNumLock, rather than regular characters. To double check that the decryption worked, we can run it in a debugger\r\nand check the output. Now that we have successfully decrypted the first part, we can do the same to each of the 19\r\nsections of data that are encrypted. If you want to view each decrypted string, you can check them out here. One\r\nparticularly interesting string in the data is msimm.dat, which could be the log file. In addition to msimm, one of\r\nthe strings seemed to indicate the version of said keylogger, as well as a possible name for it: KSL0T Ver = 21.0,\r\nalthough I haven’t found anything interesting linked to the name KSL0T – yet.\r\nAs this post is longer than what I planned it to be, I decided to split them into sections, as there is quite a lot of\r\ndecryption and functions to analyze – especially since it is static analysis. I am focusing on this approach mainly\r\nto demonstrate and teach people that you can still get a lot done through static analysis methods, even if you can’t\r\nafford the full version of IDA Pro (which I certainly can’t!), as well as how to use IDC to automate time\r\nconsuming tasks. In the next part we will be decrypting some more stuff, and then actually locating the loop that\r\nperforms the keylogging – this should be out soon!\r\nIOC (MD5):\r\nKeylogger: 59b57bdabee2ce1fb566de51dd92ec94\r\nSource: https://0ffset.net/reverse-engineering/malware-analysis/analyzing-turlas-keylogger-1/\r\nhttps://0ffset.net/reverse-engineering/malware-analysis/analyzing-turlas-keylogger-1/\r\nPage 10 of 10",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"origins": [
		"web"
	],
	"references": [
		"https://0ffset.net/reverse-engineering/malware-analysis/analyzing-turlas-keylogger-1/"
	],
	"report_names": [
		"analyzing-turlas-keylogger-1"
	],
	"threat_actors": [
		{
			"id": "8aaa5515-92dd-448d-bb20-3a253f4f8854",
			"created_at": "2024-06-19T02:03:08.147099Z",
			"updated_at": "2026-04-10T02:00:03.685355Z",
			"deleted_at": null,
			"main_name": "IRON HUNTER",
			"aliases": [
				"ATK13 ",
				"Belugasturgeon ",
				"Blue Python ",
				"CTG-8875 ",
				"ITG12 ",
				"KRYPTON ",
				"MAKERSMARK ",
				"Pensive Ursa ",
				"Secret Blizzard ",
				"Turla",
				"UAC-0003 ",
				"UAC-0024 ",
				"UNC4210 ",
				"Venomous Bear ",
				"Waterbug "
			],
			"source_name": "Secureworks:IRON HUNTER",
			"tools": [
				"Carbon-DLL",
				"ComRAT",
				"LightNeuron",
				"Mosquito",
				"PyFlash",
				"Skipper",
				"Snake",
				"Tavdig"
			],
			"source_id": "Secureworks",
			"reports": null
		},
		{
			"id": "a97cf06d-c2e2-4771-99a2-c9dee0d6a0ac",
			"created_at": "2022-10-25T16:07:24.349252Z",
			"updated_at": "2026-04-10T02:00:04.949821Z",
			"deleted_at": null,
			"main_name": "Turla",
			"aliases": [
				"ATK 13",
				"Belugasturgeon",
				"Blue Python",
				"CTG-8875",
				"G0010",
				"Group 88",
				"ITG12",
				"Iron Hunter",
				"Krypton",
				"Makersmark",
				"Operation Epic Turla",
				"Operation Moonlight Maze",
				"Operation Penguin Turla",
				"Operation Satellite Turla",
				"Operation Skipper Turla",
				"Operation Turla Mosquito",
				"Operation WITCHCOVEN",
				"Pacifier APT",
				"Pensive Ursa",
				"Popeye",
				"SIG15",
				"SIG2",
				"SIG23",
				"Secret Blizzard",
				"TAG-0530",
				"Turla",
				"UNC4210",
				"Venomous Bear",
				"Waterbug"
			],
			"source_name": "ETDA:Turla",
			"tools": [
				"ASPXSpy",
				"ASPXTool",
				"ATI-Agent",
				"AdobeARM",
				"Agent.BTZ",
				"Agent.DNE",
				"ApolloShadow",
				"BigBoss",
				"COMpfun",
				"Chinch",
				"Cloud Duke",
				"CloudDuke",
				"CloudLook",
				"Cobra Carbon System",
				"ComRAT",
				"DoublePulsar",
				"EmPyre",
				"EmpireProject",
				"Epic Turla",
				"EternalBlue",
				"EternalRomance",
				"GoldenSky",
				"Group Policy Results Tool",
				"HTML5 Encoding",
				"HyperStack",
				"IcedCoffee",
				"IronNetInjector",
				"KSL0T",
				"Kapushka",
				"Kazuar",
				"KopiLuwak",
				"Kotel",
				"LOLBAS",
				"LOLBins",
				"LightNeuron",
				"Living off the Land",
				"Maintools.js",
				"Metasploit",
				"Meterpreter",
				"MiamiBeach",
				"Mimikatz",
				"MiniDionis",
				"Minit",
				"NBTscan",
				"NETTRANS",
				"NETVulture",
				"Neptun",
				"NetFlash",
				"NewPass",
				"Outlook Backdoor",
				"Penquin Turla",
				"Pfinet",
				"PowerShell Empire",
				"PowerShellRunner",
				"PowerShellRunner-based RPC backdoor",
				"PowerStallion",
				"PsExec",
				"PyFlash",
				"QUIETCANARY",
				"Reductor RAT",
				"RocketMan",
				"SMBTouch",
				"SScan",
				"Satellite Turla",
				"SilentMoon",
				"Sun rootkit",
				"TTNG",
				"TadjMakhal",
				"Tavdig",
				"TinyTurla",
				"TinyTurla Next Generation",
				"TinyTurla-NG",
				"Topinambour",
				"Tunnus",
				"Turla",
				"Turla SilentMoon",
				"TurlaChopper",
				"Uroburos",
				"Urouros",
				"WCE",
				"WITCHCOVEN",
				"WhiteAtlas",
				"WhiteBear",
				"Windows Credential Editor",
				"Windows Credentials Editor",
				"Wipbot",
				"WorldCupSec",
				"XTRANS",
				"certutil",
				"certutil.exe",
				"gpresult",
				"nbtscan",
				"nbtstat",
				"pwdump"
			],
			"source_id": "ETDA",
			"reports": null
		},
		{
			"id": "a97fee0d-af4b-4661-ae17-858925438fc4",
			"created_at": "2023-01-06T13:46:38.396415Z",
			"updated_at": "2026-04-10T02:00:02.957137Z",
			"deleted_at": null,
			"main_name": "Turla",
			"aliases": [
				"TAG_0530",
				"Pacifier APT",
				"Blue Python",
				"UNC4210",
				"UAC-0003",
				"VENOMOUS Bear",
				"Waterbug",
				"Pfinet",
				"KRYPTON",
				"Popeye",
				"SIG23",
				"ATK13",
				"ITG12",
				"Group 88",
				"Uroburos",
				"Hippo Team",
				"IRON HUNTER",
				"MAKERSMARK",
				"Secret Blizzard",
				"UAC-0144",
				"UAC-0024",
				"G0010"
			],
			"source_name": "MISPGALAXY:Turla",
			"tools": [],
			"source_id": "MISPGALAXY",
			"reports": null
		},
		{
			"id": "d11c89bb-1640-45fa-8322-6f4e4053d7f3",
			"created_at": "2022-10-25T15:50:23.509601Z",
			"updated_at": "2026-04-10T02:00:05.277674Z",
			"deleted_at": null,
			"main_name": "Turla",
			"aliases": [
				"Turla",
				"IRON HUNTER",
				"Group 88",
				"Waterbug",
				"WhiteBear",
				"Krypton",
				"Venomous Bear",
				"Secret Blizzard",
				"BELUGASTURGEON"
			],
			"source_name": "MITRE:Turla",
			"tools": [
				"PsExec",
				"nbtstat",
				"ComRAT",
				"netstat",
				"certutil",
				"KOPILUWAK",
				"IronNetInjector",
				"LunarWeb",
				"Arp",
				"Uroburos",
				"PowerStallion",
				"Kazuar",
				"Systeminfo",
				"LightNeuron",
				"Mimikatz",
				"Tasklist",
				"LunarMail",
				"HyperStack",
				"NBTscan",
				"TinyTurla",
				"Penquin",
				"LunarLoader"
			],
			"source_id": "MITRE",
			"reports": null
		}
	],
	"ts_created_at": 1775439154,
	"ts_updated_at": 1775826777,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/b81a47fa9064957696f69c1257edc46ff588b5bd.pdf",
		"text": "https://archive.orkl.eu/b81a47fa9064957696f69c1257edc46ff588b5bd.txt",
		"img": "https://archive.orkl.eu/b81a47fa9064957696f69c1257edc46ff588b5bd.jpg"
	}
}