#!/usr/bin/python

import os
import re
import sys
import md5

from libutils import *
from config import *

from signatures import check_trick_from_file, DETECTION_TRICKS, DEBUGGING_TRICKS

INTERESTING_CALLS = ["CreateMutex", "CopyFile", "CreateFile.*WRITE", "NtasdfCreateFile", "call shell32", "advapi32.RegOpenKey",
	"KERNEL32.CreateProcess", "shdocvw", "gethostbyname", "ws2_32.bind", "ws2_32.listen", "ws2_32.htons", 
	"advapi32.RegCreate", "advapi32.RegSet", "http://",
	"^([01]?\d\d?|2[0-4]\d|25[0-5])\.([01]?\d\d?|2[0-4]\d|25[0-5])\.([01]?\d\d?|2[0-4]\d|25[0-5])\.([01]?\d\d?|2[0-4]\d|25[0-5])",
	# Common debugger detection techniques
	"OutputDebugString", 
	"FindWindow", # For OllyDbg, i.e.
	"IsDebuggerPresent"
	]

JUNK_CALLS = ['trace:file:CreateFileW L"C:\\\\\\\\windows\\\\\\\\win.ini" GENERIC_READ FILE_SHARE_READ FILE_SHARE_WRITE  creation 3 attributes 0x80',
	'L"Software\\\\\\\\Microsoft\\\\\\\\Windows\\\\\\\\CurrentVersion\\\\\\\\ThemeManager', 'RegSetValueEx.*L"Cache"', 'RegSetValueEx.*L"History"',
	'RegSetValueEx.*L"Cookies"', 'L"Software\\\\\\\\Microsoft\\\\\\\\Windows\\\\\\\\CurrentVersion\\\\\\\\Explorer\\\\\\\\User Shell Folders"',
	'L"Software\\\\\\\\Microsoft\\\\\\\\Windows\\\\\\\\CurrentVersion\\\\\\\\Explorer\\\\\\\\Shell Folders"',
	'L"System\\\\\\\\CurrentControlSet\\\\\\\\Control\\\\\\\\NetworkProvider\\\\\\\\Order"'
	'L"Software\\\\\\\\Microsoft\\\\\\\\Windows NT\\\\\\\\CurrentVersion\\\\\\\\ProfileList"'
	'Software\\\\\\\\Wine', 'L"System\\\\\\\\CurrentControlSet\\\\\\\\Control\\\\\\\\Keyboard Layouts',
	'L"winedbg.exe"', '"RPCSSMasterMutex0x', 'c:!windows!', 
	'L"System\\\\\\\\CurrentControlSet\\\\\\\\Control\\\\\\\\NetworkProvider\\\\\\\\Order',
	'L"Software\\\\\\\\Microsoft\\\\\\\\Windows NT\\\\\\\\CurrentVersion\\\\\\\\ProfileList',
	# The following SHOULD be removed
	'L"DejaVu', 'Font', 'L"__WINE_FONT_MUTEX__"', '() retval', '(TrueType)',
	'Wine', 'WindowMetrics', 'desktop.ini', 'Colors",00000000', '{9D20AAE8-0625-44B0-9CA7-71889C2254D9}',
	'Control Panel\\\\\\\\Colors'
	]

def execute_command(command):
	p = os.popen(command)
	return p.readlines()

def timeout_command(command, timeout):
	import subprocess, datetime, os, time, signal
	cmd = command.split(" ")
	start = datetime.datetime.now()
	process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
	while process.poll() is None:
		time.sleep(0.2)
		now = datetime.datetime.now()
		if (now - start).seconds> timeout:
			showWarning("Killing process %d" % process.pid)
			os.kill(process.pid, signal.SIGKILL)
			os.waitpid(-1, os.WNOHANG)
			return None
		return process.stdout.readlines()
		return process.stdout.read()

def executeMalware(malware, folder, timeout, memory):
	os.environ["DISPLAY"] = CONFIG_X11_DISPLAY
	
	if memory:
		return timeout_command("%s %s %d %s 1" % (LAUNCHER_PATH, malware, timeout, folder), timeout)
	else:
		return timeout_command("%s %s %d %s 0" % (LAUNCHER_PATH, malware, timeout, folder), timeout)

def analyzeMalware(malwareFile, timeout, memory):
	data = malwareFile.file.read()
	folderMd5 = md5.md5(data).hexdigest()
	folderName = MALWARE_FOLDER + os.sep + folderMd5

	if isCgiMode():
		print "<br/>MD5 Sum: %s<br/>" % folderMd5

	if os.path.exists(folderName) == 1:
		showWarning("Folder already exists! File was previously analyzed?")
	else:
		os.mkdir(folderName)
	cleanFilename = cleanFile(malwareFile.filename)
	fileName = folderName + os.sep + cleanFilename

	try:
		f = file(fileName, "wb")
		f.write(data)
		f.close()
	except:
		dieError("Error saving file (%s)" % str(sys.exc_info()[1]))

	if isCgiMode():
		print "<br/>"
		print "File saved as: <b>%s</b>" % (folderMd5 + os.sep + cleanFilename)
		print "<br/>"

	try:
		buf = executeMalware(fileName, folderName + os.sep + "dump", timeout, memory)
	except:
		showWarning("Error running malware!")
		if isCgiMode():
			print "<br/>"
		dieError(str(sys.exc_info()[1]))
	
	if "".join(buf).find("wine: Unhandled page fault") > -1:
		showWarning("One or more spawned processes crashed while running!")
	
	return buf, folderMd5, fileName

def getStrings(filename):
	return execute_command("strings -n 4 %s" % filename)

def getSignatureForPe(pe):
	try:
		import peutils
		signatures = peutils.SignatureDatabase(PE_SIGNATURE_PATH)
		return signatures.match_all(pe)
	except:
		return None

def getHeaders(filename):
	try:
		import pefile
		pe = pefile.PE(filename)
		
		signatureInfo = getSignatureForPe(pe)
		peInfo = pe.dump_info()
		
		if signatureInfo:
			msg = "----------Signature----------\n\n"
			for match in signatureInfo:
				msg += "%s\n" % str(match[0])
			msg += "\n\n"
			msg += "".join(peInfo)
		else:
			msg = "".join(peInfo)

		return msg
	except:
		return "Error getting headers: %s" % str(sys.exc_info()[1])

def getHeadersIfApply(filename):
	f = file(filename, "rb")
	buf = f.read(2)
	f.close()
	
	if buf == "MZ":
		return getHeaders(filename)
	else:
		return "Not a PE Executable"

def junkCall(line):
	for junk in JUNK_CALLS:
		if re.search(junk, line):
			return True
	return False

def analyzeCalls(lines):
	prevLines = []
	for line in lines:
		for mcall in INTERESTING_CALLS:
			if re.search(mcall, line):
				if line not in prevLines and not junkCall(line):
					print line
					prevLines.append(line)

def showMemoryDumps(filename, md5Dir):
	
	theDir = MALWARE_FOLDER + os.sep + md5Dir
	dumps = os.listdir(theDir)
	dumps.sort()

	i = 0
	for dump in dumps:
		if dump != "malware.exe": # Ignore it
			filename = theDir + os.sep + dump
			
			if os.path.getsize(filename) == 0:
				continue # Ignored dumped files with size 0
			
			i += 1

			print """<a href="javascript:toggleShowHide('divDump%d')"><img src="/img/strings.png" height="16" width="16"> Strings</a>&nbsp;""" % i
			print """<a href="javascript:toggleShowHide('divHeaders%d')"><img src="/img/headers.png" height="16" width="16"> Headers</a>&nbsp;""" % i
			print "<a href='/cgi-bin/download.py?md5=%s&dump=%s'>%s</a>&nbsp;" % (cgi.escape(md5Dir), cgi.escape(dump), cgi.escape(dump))
			print """<br/>"""
			
			# Strings
			print """<div id='divDump%d' style="visibility:hidden;display:none;float: center; width: 50%%;">""" % i
			print """<textarea cols='100' rows='30'>"""
			print cgi.escape("".join(getStrings(filename)))
			print "</textarea>"
			print "</div>"
			
			# Headers
			print """<div id='divHeaders%d' style="visibility:hidden;display:none;float: center; width: 50%%;">""" % i
			print """<textarea cols='100' rows='30'>"""
			print cgi.escape(getHeadersIfApply(filename))
			print "</textarea>"
			print "</div>"

	if i == 0:
		print "<i>No memory dumps :(</i><br/>"

def showVMDetectionTricks(filename, md5Dir):
	theDir = MALWARE_FOLDER + os.sep + md5Dir
	dumps = os.listdir(theDir)
	dumps.sort()

	i = 0
	for dump in dumps:
		filename = theDir + os.sep + dump
		tricks = check_trick_from_file(filename)
		
		if len(tricks) > 0:
			for trick in tricks:
				i += 1
				print "<font color='red'>Detected trick %s (%s)</font><br/>" % (trick, repr(DETECTION_TRICKS[trick]))

	if i == 0:
		print "<i>No detection tricks found</i><br/>"

def showDebuggingTricks(filename, md5Dir, msg):
	i = 0
	checkTricks = []

	for line in msg:
		for trick in DEBUGGING_TRICKS:
			if line.lower().find(trick.replace("%", "\\").lower()) > -1:
				
				if not trick in checkTricks:
					i += 1
					print """<font color='red'>Detected trick %s (%s)</font><br/>""" % (trick, DEBUGGING_TRICKS[trick])
					checkTricks.append(trick)

	if i == 0:
		print "<i>No debugger detection trick found</i><br/>"


