#!/usr/bin/env python
"""
(c) Adrian Furtuna
seastorm44@yahoo.com
"""

__VERSION__ = '0.1'
import immlib
from immutils import *
import getopt

# Globals
imm = immlib.Debugger()
maxBack 		= 5
maxFw 			= 5
moduleFilter 	= ""
filterRet		= 0
filterExec 		= 0

def banner():
	imm.Log("", address = 0)
	imm.Log("", address = 0)
	imm.Log("========================================", address = 0)
	imm.Log("=  BDASM - a backward disassembler     =", address = 0)
	imm.Log("=  by Adrian Furtuna                   =", address = 0)
	imm.Log("=  http://stormsecurity.wordpress.com  =", address = 0)
	imm.Log("========================================", address = 0)
	imm.Log("", address = 0)
	imm.updateLog()


def usage():
	banner()
	imm.Log("Usage:", address = 0)
	imm.Log("", address = 0)
	imm.Log("!bdasm <operation> [<options>]", address = 0)
	imm.Log("    Operations:", address = 0)
	imm.Log("        -a <address>         Disassemble around <address>", address = 0)
	imm.Log("        -i <instruction>     Find all occurences of <instruction> and disassemble around", address = 0)
	imm.Log("        -o <opcode>          Find all occurences of <opcode> and disassemble around", address = 0)
	imm.Log("    Options:", address = 0)
	imm.Log("        -b <num>             Disassemble maximum <num> instructions backward (default = 5)", address = 0)
	imm.Log("        -f <num>             Disassemble maximum <num> instructions forward (default = 5)", address = 0)
	imm.Log("        -m <module>          Show results only from <module>", address = 0)
	imm.Log("        -r                   Show results only if forward instructions contain RET", address = 0)
	imm.Log("        -e                   Show results only if memory page is executable", address = 0)
	imm.Log("", address = 0)
	
def printInstruction(opcode, emph):
	if emph == 0:
		spaceBack = " " * 18
	else:
		spaceBack = " " * 14
		spaceBack = spaceBack + "*** "
	spaceFw = " " * (40 - len(opcode.getDisasm()))
	logString = spaceBack + "%s" + spaceFw + "%s"
	imm.Log(logString % (opcode.getDisasm(), opcode.getDump()), address = opcode.getAddress())
	

def disasmAtAddress(address):	
	try:			
		page   = imm.getMemoryPagebyAddress( address )
		access = page.getAccess( human = True )
		if filterExec == 1 and not "EXEC" in access:
			#imm.Log("Memory page at address %08x is not executable. Skip" % address, address = 0)
			return
	except:
		imm.Log("Exception at getting access rights for address %08x" % address, address=0)
		return
	
	module = imm.findModule(address)
	if module:
		module = module[0].lower()
	else:
		module = "None"
	
	# Filter result by module name
	if len(moduleFilter) > 0 and module.find(moduleFilter) < 0:  
		return

	nextAddress = address + imm.Disasm(address).getOpSize()

	# Searching for opcodes forward
	opcFw = []
	good = 0
	opcFw.append(imm.Disasm(address))
	for i in range(0,maxFw):
		disasm = imm.Disasm(nextAddress)
		nextAddress = nextAddress + disasm.getOpSize()
		opcFw.append(disasm)
		if disasm.isRet():
			good = 1

	if filterRet == 1 and good == 0:
		#imm.Log("Instructions forward do not contain RET. Skip", address = 0)
		return
	else:
		disasm = imm.Disasm(address)
		disasmStr = disasm.getDisasm()
		imm.Log("Found instruction %s  [opcode: %s]  at address %08x                           %s  %s" % 
		(disasmStr, disasm.getDump(), address, access, module), address = address)		
		imm.Log("", address=0)
	
	#Searching for opcodes before
	opcBack = []
	disasmBack(address, 0, maxBack, opcBack, opcFw)
	imm.Log("", address=0)
	
	
def findOpcode(opcode):
	results = imm.Search(opcode)
	for address in results:
		disasmAtAddress(address)
		
def findInstruction(instr):
	opcode = imm.Assemble(instr)
	if len(opcode) == 0:
		imm.Log("Cannot assemble instruction %s" % instr)
		return
	findOpcode(opcode)
				

def printList(list, size, list2):
	revList = list[0:size]
	revList.reverse()
	for i in range(0, size):
		disasm = revList[i]
		printInstruction(disasm, 0)
		
	if len(list2) > 0:
		disasm = list2[0]
		printInstruction(disasm, 1)
	for i in range(1,len(list2)):
		disasm = list2[i]
		printInstruction(disasm, 0)
		
def disasmBack(address, instrCrt, instrPrev, list, list2):
	#imm.Log("Dissassembling at %08x      instrCrt=%i" % (address, instrCrt), address = address)
	if instrCrt >= instrPrev:
		#imm.Log("Max instr reached", address=0)
		printList(list, instrCrt, list2)
		imm.Log("", address = 0)
		return
	found = 0
	for numBytes in range (1,10):
		searchAddress = address - numBytes
		disasm = imm.Disasm(searchAddress)
		#imm.Log("try: %s                %s   [%i -- %i]   instrCrt=%i" % 
		#	(disasm.getDisasm(), disasm.getDump(), disasm.getOpSize(), numBytes, instrCrt), address = searchAddress)
		if disasm.getOpSize() == numBytes:
			#imm.Log("ok: %s             %s" % (disasm.getDisasm(), disasm.getDump()), address = searchAddress)
			found = 1
			list.insert(instrCrt, disasm)
			#imm.Log("inserting at pos %i" % instrCrt)
			disasmBack(searchAddress, instrCrt+1, instrPrev, list, list2)
	
	if found == 0:  # No instruction found backward and instrCrt < instrPrev
		#imm.Log("No more instr backw", address = 0)
		printList(list, instrCrt, list2)
		imm.Log("", address = 0)

def hexToByte(hexStr):
	bytes = []
	for i in range(0, len(hexStr), 2):
		bytes.append(chr(int(hexStr[i:i+2], 16)))
	return ''.join( bytes )		

	
	
# #########################################################
#   						MAIN
# #########################################################
def main(args):
	global maxBack
	global maxFw
	global moduleFilter
	global filterRet
	global filterExec

	if len(args) == 0:
		usage()
		return "Please see usage instructions"
	
	banner()
	
	try:
		opts, xxx = getopt.getopt(args, "a:i:o:b:f:m:er")
	except getopt.GetoptError:
		return "Wrong arguments (Check usage on the Log window)"

	# Check first the optional arguments
	for opt, val in opts:
		if opt == '-b':
			try:            
				maxBack = int( val )
			except ValueError:
				usage()                  
				return "Wrong argument (%s) % " % val
		elif opt == '-f':
			try:            
				maxFw = int( val )
			except ValueError:
				usage()                  
				return "Wrong argument (%s) % " % val
		elif opt == '-m':
			imm.Log("    Filter MODULE on", address = 0)
			moduleFilter = val.lower()
		elif opt == '-r':
	 		imm.Log("    Filter RET on", address = 0)
			filterRet = 1
		elif opt == '-e':
			imm.Log("    Filter EXEC on", address = 0)
			filterExec = 1
	imm.Log("", address = 0)

	# Iterate through input arguments and do the desired operation
	for opt, val in opts:
		if opt == '-a':
			try:            
				address = int( val, 16 )
			except ValueError:
				usage()                  
				return "Wrong address (%s) " % val

			imm.Log("Disassembling at address %08x ..." % address, address = address)
			disasmAtAddress(address)
			break

		elif opt == '-i':
			instr = val.replace('_', ' ')
			imm.Log("Searching for instruction: %s ..." % instr, address = 0)
			findInstruction(instr)
			break

		elif opt == '-o':
			if len(val) < 2 or len(val) % 2 != 0:
				usage()
				return "Wrong opcode (%s)" % val
			opcode = hexToByte(val)
			imm.Log("Searching for opcode: %s ..." % val, address = 0)
			findOpcode(hexToByte(val))
			break

	imm.Log("Finished", address = 0)
	return "See results in Log window"
		
	#findOpcode("\x81\xc4", 5, 5)	#add esp, xxxx
	#findOpcode("\x81\xd4", 5, 5)	#adc esp, xxxx
	#findOpcode("\x83\xc4", 10, 10)	#add esp, xx
	#findOpcode("\x83\xd4", 10, 10)	#adc esp, xx
	#findOpcode("\x81\xc5", 5, 5)	#add ebp, xxxx
	#findOpcode("\x83\xc5", 5, 5)	#add ebp, xx
	#opcode1 = "\x8b\xe0";	#mov esp,eax
	#findInstruction("add esp, dword ptr[ebp]", 5, 5)
	#findInstruction("mov esp,eax", 5, 5)
	#findInstruction("mov esp,ebx", 5, 5)
	#findInstruction("mov esp,ecx")
	#findInstruction("mov esp,edx")
	#findInstruction("pop esp", 5, 5)
	#findInstruction("popa", 5, 5)
	#findInstruction("xchg eax,esp", 5, 5)
	#findInstruction("xchg ecx,esp", 5, 5)
	#printAssembled("call dword [esp + 400]");
	#findOpcode("\xff\x94\x24")									#7c90122d ff942400050000  call    dword ptr [esp+500h]
	#findOpcode("\xff\x95")										#7c90123a ff9500050000    call    dword ptr [ebp+500h]
	#findOpcode("\x05\x00", 1, 10)										#7c901240 0500030000      add     eax,300h
	#printAssembled("mov esp,eax")