{
	"id": "39794de9-fd31-4229-930e-cdbc4b26fbe4",
	"created_at": "2026-04-06T00:19:11.7549Z",
	"updated_at": "2026-04-10T13:12:18.392095Z",
	"deleted_at": null,
	"sha1_hash": "9e39c91a8707c1ad2dbbcb7446976f436b07c223",
	"title": "https://gist.githubusercontent.com/quangnh89/41deada8a936a1877a6c6c757ce73800/raw/41f27388a11a606e1d6a7596dcb6469578e79321/sality_extractor.py",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 52092,
	"plain_text": "https://gist.githubusercontent.com/quangnh89/41deada8a936a1877a6c6c757ce73800/raw/4\nArchived: 2026-04-05 22:43:40 UTC\n# Sality Extractor\n# Copyright (C) 2017 quangnh89, develbranch.com\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Affero General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU Affero General Public License for more details.\n#\n# You should have received a copy of the GNU Affero General Public License\n# along with this program. If not, see .\n#\n# blog: https://develbranch.com\n# email: contact[at]develbranch.com\nimport pefile\nimport struct\nimport re\nimport argparse\nfrom unicorn import *\nfrom unicorn.x86_const import *\nfrom capstone import *\nfrom keystone import *\nfrom datetime import datetime\nclass SalityExtractor():\n def __init__(self, sample_file=None, output_file=None):\n self.md = Cs(CS_ARCH_X86, CS_MODE_32)\n self.md.detail = True\n self.sample = sample_file\n self.output = output_file\n self.detected = False\n self.control_server = []\n # utility methods\n @staticmethod\n # display log message\n def log(msg):\n print str(datetime.now()), msg\n # dump all mapped memory to file\n def dump_to_file(self, mu, pe, filename, new_ep_rva=None, runable=True):\n memory_mapped_image = bytearray(mu.mem_read(pe.OPTIONAL_HEADER.ImageBase, pe.OPTIONAL_HEADER.SizeOfIma\n for section in pe.sections:\n va_adj = pe.adjust_SectionAlignment(section.VirtualAddress, pe.OPTIONAL_HEADER.SectionAlignment,\n pe.OPTIONAL_HEADER.FileAlignment)\n if section.Misc_VirtualSize == 0 or section.SizeOfRawData == 0:\n continue\n if section.SizeOfRawData \u003e len(memory_mapped_image):\n continue\n if pe.adjust_FileAlignment(section.PointerToRawData, pe.OPTIONAL_HEADER.FileAlignment) \u003e len(\n memory_mapped_image):\n continue\n pe.set_bytes_at_rva(va_adj, bytes(memory_mapped_image[va_adj: va_adj + section.SizeOfRawData]))\n pe.write(filename)\n # set new entrypoint\n if new_ep_rva is not None:\n self.log(\"New entry point %08x\" % new_ep_rva)\nhttps://gist.githubusercontent.com/quangnh89/41deada8a936a1877a6c6c757ce73800/raw/41f27388a11a606e1d6a7596dcb6469578e79321/sality_extractor.py\nPage 1 of 5\n\nf = open(filename, 'r+b')\r\n f.seek(pe.DOS_HEADER.e_lfanew + 4 + pe.FILE_HEADER.sizeof() + 0x10)\r\n f.write(struct.pack('\u003cI', new_ep_rva))\r\n if not runable:\r\n f.seek(0)\r\n f.write('mz')\r\n f.close()\r\n print ('[+] Save to file {}'.format(filename))\r\n @staticmethod\r\n def assembler(address, assembly):\r\n ks = Ks(KS_ARCH_X86, KS_MODE_32)\r\n encoding, _ = ks.asm(assembly, address)\r\n return ''.join(chr(e) for e in encoding)\r\n # callback for tracing invalid memory access (READ or WRITE)\r\n # noinspection PyUnusedLocal\r\n @staticmethod\r\n def hook_mem_invalid(uc, access, address, size, value, user_data):\r\n # return False to indicate we want to stop emulation\r\n return False\r\n # callback for tracing fake-IAT interrupt\r\n # noinspection PyUnusedLocal\r\n def hook_intr(self, uc, intno, user_data):\r\n # only handle fake-IAT interrupt\r\n if intno != 0xff:\r\n print (\"got interrupt %x ???\" % intno)\r\n uc.emu_stop()\r\n return\r\n eax = uc.reg_read(UC_X86_REG_EAX)\r\n dll_name, address, name, _ = self.import_addrs[eax]\r\n if 'kernel32' in dll_name.lower():\r\n if name == 'LoadLibraryA':\r\n uc.reg_write(UC_X86_REG_EAX, 0xabababab)\r\n elif name == 'GetProcAddress':\r\n uc.reg_write(UC_X86_REG_EAX, 0xbcbcbcbc)\r\n elif name == 'VirtualProtect':\r\n uc.reg_write(UC_X86_REG_EAX, 0x1)\r\n # noinspection PyBroadException\r\n # noinspection PyUnresolvedReferences\r\n def emulate_sality_dll(self, memory):\r\n try:\r\n pe = pefile.PE(data=memory, fast_load=True)\r\n except:\r\n return None\r\n self.log(\"[+] Parse Sality DLL\")\r\n pe.parse_data_directories()\r\n self.import_addrs = []\r\n for entry in pe.DIRECTORY_ENTRY_IMPORT:\r\n for imp in entry.imports:\r\n nparam = 1\r\n if entry.dll.lower() in 'kernel32.dll':\r\n if imp.name == 'LoadLibraryA':\r\n nparam = 1\r\n elif imp.name == 'GetProcAddress':\r\n nparam = 2\r\n elif imp.name == 'VirtualProtect':\r\n nparam = 4\r\n self.import_addrs.append((entry.dll, imp.address, imp.name, nparam))\r\n self.log('[+] Analyze UPX stub code')\r\n entry_point_code = str(pe.get_memory_mapped_image())[pe.OPTIONAL_HEADER.AddressOfEntryPoint:]\r\n begin_addr = pe.OPTIONAL_HEADER.ImageBase + pe.OPTIONAL_HEADER.AddressOfEntryPoint\r\n end_addr = begin_addr\r\n for i in self.md.disasm(str(entry_point_code), begin_addr):\r\n if i.mnemonic.lower() in ['popad', 'popal', 'popa']:\r\n end_addr = i.address + 1\r\n break\r\nhttps://gist.githubusercontent.com/quangnh89/41deada8a936a1877a6c6c757ce73800/raw/41f27388a11a606e1d6a7596dcb6469578e79321/sality_extractor.py\r\nPage 2 of 5\n\nself.log(\"[+] Initialize emulator in X86-32bit mode\")\r\n mu = Uc(UC_ARCH_X86, UC_MODE_32)\r\n # map memory for this emulation\r\n mu.mem_map(pe.OPTIONAL_HEADER.ImageBase, pe.OPTIONAL_HEADER.SizeOfImage)\r\n # stack\r\n stack_addr = 0x1000\r\n stack_size = 0x4000\r\n mu.mem_map(stack_addr, stack_size)\r\n # write machine code to be emulated to memory\r\n mu.mem_write(pe.OPTIONAL_HEADER.ImageBase, pe.get_memory_mapped_image())\r\n # initialize machine registers\r\n mu.reg_write(UC_X86_REG_ESP, stack_addr + stack_size / 2)\r\n # intercept invalid memory events\r\n mu.hook_add(UC_HOOK_MEM_READ_UNMAPPED | UC_HOOK_MEM_WRITE_UNMAPPED, self.hook_mem_invalid)\r\n # build IAT table\r\n iat_addr = 0x10000\r\n e = self.assembler(iat_addr, 'mov eax, 1;int 0xff;ret 0xffff')\r\n iat_size_adj = pe.adjust_SectionAlignment(len(self.import_addrs) * len(e) + pe.OPTIONAL_HEADER.Section\r\n pe.OPTIONAL_HEADER.SectionAlignment, pe.OPTIONAL_HEADER.File\r\n mu.mem_map(iat_addr, iat_size_adj)\r\n for i in range(len(self.import_addrs)):\r\n _, iat_entry, _, nparam = self.import_addrs[i]\r\n func_addr = iat_addr + i * len(e)\r\n if nparam \u003e 1:\r\n c = self.assembler(func_addr, 'mov eax, %x;int 0xff;ret %x' % (i, nparam))\r\n else:\r\n c = self.assembler(func_addr, 'mov eax, %x;int 0xff;ret' % i)\r\n mu.mem_write(func_addr, c)\r\n mu.mem_write(iat_entry, struct.pack('\u003cI', func_addr))\r\n # handle interrupt ourselves\r\n mu.hook_add(UC_HOOK_INTR, self.hook_intr)\r\n self.log('[+] Emulate machine code')\r\n mu.emu_start(begin_addr, end_addr)\r\n decoded_memory = mu.mem_read(pe.OPTIONAL_HEADER.ImageBase, pe.OPTIONAL_HEADER.SizeOfImage)\r\n return decoded_memory\r\n @staticmethod\r\n def check_sality(code):\r\n signature = [(0,\r\n '\\xE8\\x00\\x00\\x00\\x00\\x5D\\x8B\\xC5\\x81\\xED\\x05\\x10\\x40\\x00\\x8A\\x9D\\x73\\x27\\x40\\x00\\x84\\xD\r\n (0x23,\r\n '\\x89\\x85\\x54\\x12\\x40\\x00\\xEB\\x19\\xC7\\x85\\x4D\\x14\\x40\\x00\\x22\\x22\\x22\\x22\\xC7\\x85\\x3A\\x1\r\n for offset, s in signature:\r\n if s != code[offset:offset + len(s)]:\r\n return False\r\n return True\r\n # callback for tracing instructions\r\n # noinspection PyUnusedLocal\r\n def hook_code(self, uc, address, size, user_data):\r\n # I expect 'retn'\r\n if size != 1:\r\n return\r\n if uc.mem_read(address, size) != '\\xc3':\r\n return\r\n esp = uc.reg_read(UC_X86_REG_ESP)\r\n sality_entrypoint = struct.unpack('\u003cI', uc.mem_read(esp, 4))[0]\r\n code = uc.mem_read(sality_entrypoint, 0x100)\r\n if not self.check_sality(code):\r\n return\r\n self.detected = True\r\n uc.emu_stop()\r\n # noinspection PyBroadException\r\n def extract(self):\r\n if self.sample is None:\r\n return\r\n self.log(\"[+] Parse PE File\")\r\n try:\r\n self.sample.seek(0)\r\n except:\r\nhttps://gist.githubusercontent.com/quangnh89/41deada8a936a1877a6c6c757ce73800/raw/41f27388a11a606e1d6a7596dcb6469578e79321/sality_extractor.py\r\nPage 3 of 5\n\npass\r\n pe = pefile.PE(data=self.sample.read(), fast_load=True)\r\n self.log(\"[+] Initialize emulator in X86-32bit mode\")\r\n mu = Uc(UC_ARCH_X86, UC_MODE_32)\r\n # map memory for this emulation\r\n mu.mem_map(pe.OPTIONAL_HEADER.ImageBase, pe.OPTIONAL_HEADER.SizeOfImage)\r\n # stack\r\n stack_addr = 0x1000\r\n stack_size = 0x4000\r\n mu.mem_map(stack_addr, stack_size)\r\n # write machine code to be emulated to memory\r\n mu.mem_write(pe.OPTIONAL_HEADER.ImageBase, pe.get_memory_mapped_image())\r\n # initialize machine registers\r\n mu.reg_write(UC_X86_REG_ESP, stack_addr + stack_size / 2)\r\n # tracing all instructions with customized callback\r\n mu.hook_add(UC_HOOK_CODE, self.hook_code)\r\n # intercept invalid memory events\r\n mu.hook_add(UC_HOOK_MEM_READ_UNMAPPED | UC_HOOK_MEM_WRITE_UNMAPPED, self.hook_mem_invalid)\r\n self.log('[+] Emulate machine code')\r\n begin_addr = pe.OPTIONAL_HEADER.ImageBase + pe.OPTIONAL_HEADER.AddressOfEntryPoint\r\n end_addr = pe.OPTIONAL_HEADER.ImageBase + pe.OPTIONAL_HEADER.SizeOfImage\r\n try:\r\n mu.emu_start(begin_addr, end_addr)\r\n except Exception as e:\r\n self.log('[-] Emulator error: %s' % e)\r\n return\r\n if not self.detected:\r\n self.log('[-] Sality not found')\r\n return\r\n self.log(\"[+] Find Sality section\")\r\n sality_section_addr = None\r\n eip_rva = mu.reg_read(UC_X86_REG_EIP) - pe.OPTIONAL_HEADER.ImageBase\r\n for section in pe.sections:\r\n va_adj = pe.adjust_SectionAlignment(section.VirtualAddress, pe.OPTIONAL_HEADER.SectionAlignment,\r\n pe.OPTIONAL_HEADER.FileAlignment)\r\n if va_adj \u003c= eip_rva \u003c va_adj + section.Misc_VirtualSize:\r\n sality_section_addr = va_adj\r\n break\r\n if sality_section_addr is None:\r\n self.log(\"[-] Sality section not found\")\r\n return\r\n mapped_memory = str(mu.mem_read(pe.OPTIONAL_HEADER.ImageBase + sality_section_addr,\r\n pe.OPTIONAL_HEADER.SizeOfImage - sality_section_addr))\r\n self.detect_control_server(mapped_memory)\r\n for m in re.finditer('MZ', mapped_memory):\r\n sality_dll = mapped_memory[m.start():]\r\n decoded_sality_dll = self.emulate_sality_dll(sality_dll)\r\n if decoded_sality_dll is None:\r\n continue\r\n self.detect_control_server(decoded_sality_dll)\r\n if self.output is not None:\r\n self.output.write(decoded_sality_dll)\r\n self.log(\"[+] Write Sality DLL to file successfully\")\r\n self.log(\"[+] Analyze Sality DLL successfully\")\r\n def detect_control_server(self, memory):\r\n # detect URL\r\n urls = re.findall('http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.\u0026+]|[!*\\(\\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+', me\r\n for _ in urls:\r\n self.control_server.append(str(_))\r\ndef get_args():\r\n \"\"\"This function parses and return arguments passed in\"\"\"\r\n # Assign description to the help doc\r\n parser = argparse.ArgumentParser(description='Script extracts URLs of Win32-Sality variants from a given f\r\n # Add arguments\r\n parser.add_argument('-z', '--zip', action='store_true')\r\n parser.add_argument('-p', '--password', type=str, help='Password to open zip file', required=False,\r\n default=None)\r\n parser.add_argument('-n', '--name', type=str, help='File name in zip file', required=False, default=None)\r\nhttps://gist.githubusercontent.com/quangnh89/41deada8a936a1877a6c6c757ce73800/raw/41f27388a11a606e1d6a7596dcb6469578e79321/sality_extractor.py\r\nPage 4 of 5\n\nparser.add_argument('-d', '--dump', type=str, help='Dump sality DLL to file', required=False, default=None\r\n parser.add_argument('file', nargs='?')\r\n # Array for all arguments passed to script\r\n args = parser.parse_args()\r\n file_name = None\r\n if args.file is not None and len(args.file) \u003e 0:\r\n file_name = args.file\r\n # Return all variable values\r\n return file_name, args.zip, args.password, args.name, args.dump\r\ndef main():\r\n # Match return values from get_args()\r\n # and assign to their respective variables\r\n z = None\r\n file_name, is_zip, password, name, dump = get_args()\r\n if file_name is None:\r\n print \"Enter file name\"\r\n return\r\n if is_zip:\r\n from zipfile import ZipFile\r\n z = ZipFile(file_name)\r\n f = z.open(name, 'r', password)\r\n else:\r\n f = open(file_name, 'rb')\r\n if dump is not None:\r\n d = open(dump, 'wb')\r\n else:\r\n d = None\r\n sd = SalityExtractor(f, d)\r\n sd.extract()\r\n if len(sd.control_server) \u003e 0:\r\n print sd.control_server\r\n else:\r\n print 'Found nothing'\r\n if f is not None:\r\n f.close()\r\n if z is not None:\r\n z.close()\r\n if d is not None:\r\n d.close()\r\nif __name__ == '__main__':\r\n main()\r\nSource: https://gist.githubusercontent.com/quangnh89/41deada8a936a1877a6c6c757ce73800/raw/41f27388a11a606e1d6a7596dcb6469578e79321/sality_e\r\nxtractor.py\r\nhttps://gist.githubusercontent.com/quangnh89/41deada8a936a1877a6c6c757ce73800/raw/41f27388a11a606e1d6a7596dcb6469578e79321/sality_extractor.py\r\nPage 5 of 5",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"origins": [
		"web"
	],
	"references": [
		"https://gist.githubusercontent.com/quangnh89/41deada8a936a1877a6c6c757ce73800/raw/41f27388a11a606e1d6a7596dcb6469578e79321/sality_extractor.py"
	],
	"report_names": [
		"sality_extractor.py"
	],
	"threat_actors": [],
	"ts_created_at": 1775434751,
	"ts_updated_at": 1775826738,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/9e39c91a8707c1ad2dbbcb7446976f436b07c223.pdf",
		"text": "https://archive.orkl.eu/9e39c91a8707c1ad2dbbcb7446976f436b07c223.txt",
		"img": "https://archive.orkl.eu/9e39c91a8707c1ad2dbbcb7446976f436b07c223.jpg"
	}
}