Clone
21
Static analysis with vivisect
Cristiano Maruti edited this page 2016-11-23 15:57:11 +01:00

Vivisect has an huge amount of static analysis features comparable to well known commercial product like IDA. The aim of this article is to introduce novice users to this powerfull python framework and its API with walk-through examples. Let's start with some boilerplate code.

import sys

VDB_ROOT = "C:\\vivisect-master"
sys.path.append(VDB_ROOT)

import vivisect.cli as viv_cli
import vivisect.parsers as viv_parsers

from vivisect.const import *

TARGET_NAME = "C:\\windows\\system32\\notepad.exe"
EXE_FILE_FORMAT = ['elf','pe','macho']

The vivisect.cli.VivCli class inherits most of the helper functions needed to explore executable object.

python vw = viv_cli.VivCli()

The vivisect.parsers class includes a lot of usefull functions to parse various binary format supported by the framework. First guess the exectuable format. Currently vivisect supports mainly PE and ELF binaries with limited MACH-O supports.

parse_mod = viv_parsers.guessFormatFilename(TARGET_NAME)

if parse_mod not in EXE_FILE_FORMAT:
	sys.exit("[!] Executable file format not supported by vivisect...");

Let's start the automated analysis. Vivisect performs a multipass analysis and can be extended with user's plug-ins.

try:
	vw.loadFromFile(TARGET_NAME, fmtname=parse_mod)
	vw.analyze()
except:
	print("[!] Unexpected error: %s", sys.exc_info()[0])

If you want you can see some diagnostic messages increasing verbosity the analisys take place with vw.verbose=4. After vivisect has performed multiple discovery passes, returns an object filled-in with statically data extracted from the binary and dinamically generated information returned by the analysis engine. You can have a more in depth look at all the available methods using the python's dir() function.

dir(vw)

From now on you can query the object for various elements extracted from the target binary. The analysis engine - and also other pieces of the framework - use meta attribute to "label" or add some context and additional information to the vivisect's analysis database.

print("[*] Platform: %s" % vw.getMeta('Platform'))
print("[*] Architecture: %s" % vw.getMeta('Architecture'))

print("[+] Vivisect's static analysis engine recognize the following elements:")

print("\tSegments: %d" % len(vw.getSegments()))
print("\tFunctions: %d" % len(vw.getFunctions()))
print("\tLinked libraries: %d" % len(vw.getLibraryDependancies()))
print("\tRelocations: %d" % len(vw.getRelocations()))
print("\tImports: %d" % len(vw.getImports()))
print("\tExports: %d" % len(vw.getExports()))
print("\tBasic blocks: %d" % len(vw.getCodeBlocks()))
print("\tLocations: %d" % len(vw.getLocations()))
print("\tNames: %d" % len(vw.getNames()))

Now we can choose one arbitrary functions and get some information related to it. For example the basic blocks in the function, the cross references to the current function, the metadatas added by the analysis engine and, the disassembly code of the whole function.

print("[+] Function analysis")
for func in vw.getFunctions():
	print("\tFunction address 0x%08x and name %s" % (func, vw.getName(func)))
	print("\tCross references: %d" % len(vw.getXrefsTo(func)))
	print("\tFunction's disassembled code:")
	
	for va, size, funcva in vw.getFunctionBlocks(func):
		maxva = va + size
		while va < maxva:
			op = vw.parseOpcode(va)
			print("\t\t0x%0X %s" % (va, op))
			va += len(op)
	print("\tFunction's metas added by the analysis engine: %d" % (len(vw.getFunctionMetaDict(func))))
	print("\tNow listing function's meta name and values:")
	for meta in vw.getFunctionMetaDict(func):
		print("\t\t%s %s" % (meta, vw.getFunctionMeta(func, meta)))

I know you like hexdump; with vivisect you can also get a transcript of the function's raw bytes

	bytez = vw.readMemory(func, vw.getFunctionMeta(func, "Size"))
	print("\tFunction's raw bytes:")
	
	while bytez:
		print("\t\t%s" % ' '.join(x.encode('hex') for x in bytez[:32]))
		bytez = bytez[32:]

Now you have seen the power of the framework. It was enough easy isn't it? Let me show how you can extract strings identified by the analysis engine. Vivisect's analysis engine mark locations it discovers with various "tag". The most commons are: LOC_OP for opcode, LOC_NUMBER for immediate numerical value (but not a pointer), LOC_STRING for null terminated string, and LOC_UNI corresponding to null terminated unicode string; see vivisect/vivisect/const.py for the full list.

from vivisect.const import *
	
print("\tEmbedded ASCII strings:")
for ascii in vw.getLocations(LOC_STRING):
	print("\t\t%s" % (vw.reprLocation(ascii)))
	
print("\tEmbedded Unicode strings:")
for uni in vw.getLocations(LOC_UNI):
	print("\t\t%s" % (vw.reprLocation(uni)))

[TO BE CONTINUED]