{
	"id": "c641f230-02c9-4626-a82e-f49bbb15d2a4",
	"created_at": "2026-04-06T00:06:26.1486Z",
	"updated_at": "2026-04-10T03:22:10.976125Z",
	"deleted_at": null,
	"sha1_hash": "ad024b2eb6e30fec36d320cd71b5eed0d3e980b4",
	"title": "LusyPOS and Tor",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 184073,
	"plain_text": "LusyPOS and Tor\r\nPublished: 2014-12-01 · Archived: 2026-04-02 11:01:40 UTC\r\nIntroduction\r\nAt our dayjobs, as reverse engineers at CBTS, Jeremy and I have been hunting new POS malware.\r\nA new sample appeared on Virustotal this week that had a very interesting name “lusypos.exe”. There have been\r\nvery few references to this particular family and it appears to be fairly new. Google searching was able to give me\r\nthe following information:\r\nThe sample that I’ll be talking about in this post is bc7bf2584e3b039155265642268c94c7.\r\nAt the time of this writing the malware is currently flagged on Virustotal by 7/54 engines.\r\nInterestingly, some of the signatures seem to be hitting on the copy of tor.exe that is in the bundle.\r\nAnalysis\r\nThis malware clocks in around 4.0 MB in size, so it’s not small. For comparison, getmypass POS malware was\r\n17k in size.\r\nhttps://securitykitten.github.io/2014/12/01/lusypos-and-tor.html\r\nPage 1 of 9\n\nThe first thing of note when executing this in a sandbox is that this malware drops a copy of tor.exe, libcurl.dll,\r\nand zlib1.dll. It also copies itself to the %APPDATA% directory on the victim host. The following are the\r\nlocations and MD5’s of the dropped files are below:\r\nThe file mbambservice.exe is the copy of tor.exe\r\nd0f3b3aaa109a1ea8978c83d23055eb1 C:\\Documents and Settings\\\u003cUSER\u003e\\Application Data\\VeriFone32\\libcurl.dll\r\n4407393c1542782bac2ba9d017f27dc9 C:\\Documents and Settings\\\u003cUSER\u003e\\Application Data\\VeriFone32\\mbambservice.exe\r\nbc7bf2584e3b039155265642268c94c7 C:\\Documents and Settings\\\u003cUSER\u003e\\Application Data\\VeriFone32\\verifone32.exe\r\nb8a9e91134e7c89440a0f95470d5e47b C:\\Documents and Settings\\\u003cUSER\u003e\\Application Data\\VeriFone32\\zlib1.dll\r\nThe malware will also create the mutex “prowin32Mutex” and injects code into iexplore.exe. This was a strange\r\nmix of dexter-like behavior mixed with Chewbacca-like techniques.\r\nWhile running in a sandbox, the malware communicated out to\r\n86.59.21.38\r\n212.112.245.170\r\n128.31.0.39\r\n154.35.32.5\r\n193.23.244.244\r\nNow let’s get to the good stuff.\r\nDecoding\r\nThe malware has an interesting method of decoding strings that are statically defined in the binary.\r\nhttps://securitykitten.github.io/2014/12/01/lusypos-and-tor.html\r\nPage 2 of 9\n\nFor the non-asm folks on here, the malware is using a lookup table with structures containing a one byte xor key,\r\npointer to the string, and length of the string. It will perform an additional xor operation at the end.\r\nA decoder for this is written (in python below)\r\n#!/usr/bin/env python\r\n# ----------------------------------------------------------------------------- #\r\n# Author: Jeremy Humble - CBTS ACS\r\n# Description: POC LusyPOC String Extractor. Strings are stored in an array\r\n# of 8 byte structs with the following structure: {short xor_key,\r\n# short length, char* encoded_string}\r\n# ----------------------------------------------------------------------------- #\r\nimport sys\r\nimport struct\r\nimport binascii\r\nimport pefile\r\nimport simplejson as json\r\nfrom pprint import pprint\r\nfrom optparse import OptionParser\r\n# Option Parsing\r\nusage = \"lusypos_parser.py [-j] lusypos_sample1 [lusypos_sample2] ...\"\r\nopt_parser = OptionParser(usage=usage)\r\nopt_parser.add_option(\"-j\", \"--json\", action=\"store_true\",dest=\"json_output\",\r\n help=\"Output all information on each string in json format\")\r\nopt_parser.add_option(\"-p\", \"--pretty\", action=\"store_true\",dest=\"pretty_json_output\",\r\nhttps://securitykitten.github.io/2014/12/01/lusypos-and-tor.html\r\nPage 3 of 9\n\nhelp=\"Output all information on each string in pretty json format\")\r\n(options, args) = opt_parser.parse_args()\r\nif options.json_output and options.pretty_json_output:\r\n sys.stderr.write('Use either -j or -p, not both')\r\n exit()\r\nclass LusyEncodedString:\r\n \r\n def __init__(self,raw_data,file_content,pe):\r\n self.xor_key = struct.unpack('H',raw_data[0:2])[0]\r\n self.length = struct.unpack('H',raw_data[2:4])[0]\r\n self.virtual_offset = struct.unpack('I', raw_data[4:8])[0]\r\n self.raw_offset = pe.get_offset_from_rva(self.virtual_offset - pe.OPTIONAL_HEADER.ImageBas\r\n self.encoded_str = file_content[self.raw_offset:self.raw_offset+self.length]\r\n self._decode()\r\n def _decode(self):\r\n self.decoded_str = \"\"\r\n for i in range(0,self.length):\r\n self.decoded_str += chr(ord(self.encoded_str[i]) ^ self.xor_key ^ i)\r\n def __str__(self):\r\n return str(self.to_dict())\r\n def to_dict(self):\r\n d = {'xor key': hex(self.xor_key), 'length': self.length, 'raw offset': hex(self.raw_offse\r\n 'virtual offset': hex(self.virtual_offset), 'encoded string': self.encoded_str, 'deco\r\n return d\r\n \r\n \r\n \r\n# For now we'll assume the table is always at RVA 401000 (raw 0x400) as hardcoded in bc7bf2584e3b0\r\n# With a little more refinement this could probably be found dynamically. AFAIK it's always locate\r\n# Until I see a sample that shows otherwise, there's no point in doing this\r\ndef parse_table(content,pe,table_rva=0x1000):\r\n encoded_strings = []\r\n raw_offset = pe.get_physical_by_rva(table_rva)\r\n i = 0\r\n while True:\r\n raw_struct = content[raw_offset+i*8:raw_offset+i*8+8]\r\n # The last struct in the table is all null bytes. Stop parsing when we hit it\r\n if struct.unpack('\u003cQ',raw_struct)[0] == 0:\r\n break\r\n else:\r\n try:\r\n encoded_strings.append(LusyEncodedString(raw_struct,content,pe))\r\nhttps://securitykitten.github.io/2014/12/01/lusypos-and-tor.html\r\nPage 4 of 9\n\nexcept Exception as e:\r\n sys.stderr.write('Error processing entry \"%s\" with Exception \"%s\". Ending'\r\n ' table processing\\n' % (binascii.hexlify(raw_struct),e))\r\n i += 1\r\n return encoded_strings\r\nif __name__ == '__main__':\r\n fname_to_lusy_string_map = {}\r\n for arg in args:\r\n try:\r\n pe = pefile.PE(arg)\r\n with open(arg,'r') as fp:\r\n content = fp.read()\r\n fname_to_lusy_string_map[arg] = parse_table(content,pe)\r\n except Exception as e:\r\n sys.stderr.write('Exception processing file %s: \"%s\"\\n' % (arg,e))\r\n \r\n if options.json_output or options.pretty_json_output:\r\n json_dict = {}\r\n # Call to_dict on all of the objects so we can dump json\r\n for fname, lusy_strings in fname_to_lusy_string_map.items():\r\n json_dict[fname] = []\r\n for lusy_str in lusy_strings:\r\n json_dict[fname].append(lusy_str.to_dict())\r\n # If only working on one file, omit the top level filename key since it's obvious\r\n if len(json_dict.keys()) == 1:\r\n json_dict = json_dict[json_dict.keys()[0]]\r\n if options.json_output:\r\n print json.dumps(json_dict)\r\n else:\r\n pprint(json_dict)\r\n else:\r\n for fname, lusy_strings in fname_to_lusy_string_map.items():\r\n for lusy_str in lusy_strings:\r\n print lusy_str.decoded_str\r\nWhich when executed will decode the following strings:\r\nhttp://kcdjqxk4jjwzjopq.onion/d/gw.php\r\nhttp://ydoapqgxeqmvsugz.onion/d/gw.php\r\nVeriFone32\r\nverifone32\r\nprowin32Mutex\r\nb00n v1.1\r\n\\\\Internet Explorer\\\\iexplore.exe\r\nmbambservice.exe\r\nhttps://securitykitten.github.io/2014/12/01/lusypos-and-tor.html\r\nPage 5 of 9\n\ntor.exe\r\nzlib1.dll\r\nlibcurl.dll\r\nSoftware\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Policies\\\\Associations\r\nSoftware\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Internet Settings\\\\Zones\\\\0\r\nLowRiskFileTypes\r\nContent-Type: application/x-www-form-urlencoded\r\n127.0.0.1:9050\r\nMozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0) g00n\r\ncurl_easy_init\r\ncurl_easy_setopt\r\ncurl_easy_cleanup\r\ncurl_easy_perform\r\ncurl_easy_strerror\r\ncurl_slist_append\r\ncurl_easy_getinfo\r\ncurl_slist_free_all\r\npage=\r\n\u0026ump=\r\n\u0026ks=\r\n\u0026opt=\r\n\u0026unm=\r\n\u0026cnm=\r\n\u0026view=\r\n\u0026spec=\r\n\u0026query=\r\n\u0026val=\r\n\u0026var=\r\nDetectShutdownClass\r\ndownload-update-checkin:\r\nscanin:\r\nuninstall\r\nresponse=\r\nUpdateMutex:\r\nSoftware\\\\Verifone32\r\nSoftware\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Run\r\n.DEFAULT\\\\SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Run\r\nmbambservice.exe\r\nwmiprvse.exe\r\nLogonUI.exe\r\nsvchost.exe\r\niexplore.exe\r\nexplorer.exe\r\nSystem\r\nsmss.exe\r\nhttps://securitykitten.github.io/2014/12/01/lusypos-and-tor.html\r\nPage 6 of 9\n\ncsrss.exe\r\nwinlogon.exe\r\nlsass.exe\r\nspoolsv.exe\r\nalg.exe\r\nwuauclt.exe\r\nfirefox.exe\r\nchrome.exe\r\ndevenv.exe\r\nThis contains the C2 information, along with a process whitelist, and registry keys for persistence. One thing to\r\nnote based on these strings, is that it looks like the malware may have taken a cue from dexter.\r\nRAM Scraping\r\nRAM scraping is performed through the common sequence of using CreateToolhelp32Snapshot, then using\r\nProcess32First and Process32Next to iterate. Pseudocode for that would look something like the following:\r\nhandle = CreateToolhelp32Snapshot\r\nProcess32First(handle)\r\ndo\r\nsleep 1000\r\nOpenProcess\r\nVirtualQueryEx\r\nReadProcessMemory\r\n CloseHandle\r\nSleep 5000\r\nwhile Process32Next\r\nThis technique is not new and is commonly used in many different POS Ram scrapers. Truth is, that without\r\nwriting a driver, the malware authors often have their hands tied and only have a few techniques to peer into\r\nprocess memory space.\r\nCC Validation\r\nThe malware also contains methods to search memory for sequences of data that look like credit card track\r\ninformation.\r\nhttps://securitykitten.github.io/2014/12/01/lusypos-and-tor.html\r\nPage 7 of 9\n\nOnce it finds that data, there are checks against the potential credit card number to determine if it is Luhn valid.\r\nLuhn’s algorithm is the defacto algorithm for validating credit card numbers. It can be seen implemented in the\r\nmalware using a lookup table rather than calcuating the digital root. One note, is that this is the same\r\nimplementation of Luhn’s as FrameworkPOS, Dexter, and getmypass.\r\nhttps://securitykitten.github.io/2014/12/01/lusypos-and-tor.html\r\nPage 8 of 9\n\nClosing Thoughts\r\nWhen looking into malware families like Chewbacca and now LusyPOS, one thought comes to mind. Why would\r\na POS machine be allowed to talk to tor? Most PCI audits will attempt to lock this sort of activity down, but there\r\nseems to be devils in the implementation that allow malware like this to be successful.\r\nThis is just a scratch in the surface of a new malware family. We’ll be curious to watch it evolve over the next\r\ncouple years and track its progress.\r\nSource: https://securitykitten.github.io/2014/12/01/lusypos-and-tor.html\r\nhttps://securitykitten.github.io/2014/12/01/lusypos-and-tor.html\r\nPage 9 of 9",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"ETDA",
		"Malpedia"
	],
	"references": [
		"https://securitykitten.github.io/2014/12/01/lusypos-and-tor.html"
	],
	"report_names": [
		"lusypos-and-tor.html"
	],
	"threat_actors": [],
	"ts_created_at": 1775433986,
	"ts_updated_at": 1775791330,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/ad024b2eb6e30fec36d320cd71b5eed0d3e980b4.pdf",
		"text": "https://archive.orkl.eu/ad024b2eb6e30fec36d320cd71b5eed0d3e980b4.txt",
		"img": "https://archive.orkl.eu/ad024b2eb6e30fec36d320cd71b5eed0d3e980b4.jpg"
	}
}