# plugins

repository: oct0xor/ida-emotionengine
file: ida-emotionengine.py
description: IDA Pro plugin that implements disassembly of PlayStation 2 COP2 MIPS instructions
wanted_name: PS2 Emotion Engine COP2 instructions disassembler
created_at: 2022-07-05 18:10:16+00:00
pushed_at: 2022-07-11 09:54:45+00:00
url:
https://github.com/oct0xor/ida-emotionengine/blob/baadfaa6908b2b7981240af9e6f0ba19ce023427/ida-emotionengine.py

content:
# This plugin helps IDA Pro to disassemble PS2 Emotion Engine COP2 instructions
# Author: oct0xor

import idaapi
import ida_ida
import ida_allins
import ida_idp
import ida_bytes
import ida_ua

ITYPE_START = ida_idp.CUSTOM_INSN_ITYPE + 0x100
MNEM_WIDTH = 13

class COP2_disassemble(idaapi.IDP_Hooks):

	def __init__(self):
		idaapi.IDP_Hooks.__init__(self)

		class idef:
			def __init__(self, opcode, name, ft, dt, dest, cmt):
				self.opcode = opcode
				self.name = name
				self.ft = ft
				self.dt = dt
				self.dest = dest
				self.cmt = cmt

		self.itable = [
			# Coprocessor Calculation Instructions
			idef(0x1FD, "VABS",     3,  1, True,  "Absolute"),
			idef(0x028, "VADD",     1,  2, True,  "Addition"),
			idef(0x022, "VADDi",    1,  3, True,  "ADD broadcast I register"),
			idef(0x020, "VADDq",    1,  4, True,  "ADD broadcast Q register"),
			idef(0x000, "VADDx",    0,  2, True,  "ADD broadcast bc field"),
			idef(0x001, "VADDy",    0,  2, True,  "ADD broadcast bc field"),
			idef(0x002, "VADDz",    0,  2, True,  "ADD broadcast bc field"),
			idef(0x003, "VADDw",    0,  2, True,  "ADD broadcast bc field"),
			idef(0x2BC, "VADDA",    3,  5, True,  "ADD output to ACC"),
			idef(0x23E, "VADDAi",   3,  6, True,  "ADD output to ACC broadcast I register"),
			idef(0x23C, "VADDAq",   3,  7, True,  "ADD output to ACC broadcast Q register"),
			idef(0x03C, "VADDAx",   2,  8, True,  "ADD output to ACC broadcast bc field"),
			idef(0x03D, "VADDAy",   2,  8, True,  "ADD output to ACC broadcast bc field"),
			idef(0x03E, "VADDAz",   2,  8, True,  "ADD output to ACC broadcast bc field"),
			idef(0x03F, "VADDAw",   2,  8, True,  "ADD output to ACC broadcast bc field"),
			idef(0x02C, "VSUB",     1,  2, True,  "Subtraction"),
			idef(0x026, "VSUBi",    1,  3, True,  "SUB broadcast I register"),
			idef(0x024, "VSUBq",    1,  4, True,  "SUB broadcast Q register"),
			idef(0x004, "VSUBx",    0,  2, True,  "SUB broadcast bc field"),
			idef(0x005, "VSUBy",    0,  2, True,  "SUB broadcast bc field"),
			idef(0x006, "VSUBz",    0,  2, True,  "SUB broadcast bc field"),
			idef(0x007, "VSUBw",    0,  2, True,  "SUB broadcast bc field"),
			idef(0x2FC, "VSUBA",    3,  5, True,  "SUB output to ACC"),
			idef(0x27E, "VSUBAi",   3,  6, True,  "SUB output to ACC broadcast I register"),
			idef(0x27C, "VSUBAq",   3,  7, True,  "SUB output to ACC broadcast Q register"),
			idef(0x07C, "VSUBAx",   2,  8, True,  "SUB output to ACC broadcast bc field"),
			idef(0x07D, "VSUBAy",   2,  8, True,  "SUB output to ACC broadcast bc field"),
			idef(0x07E, "VSUBAz",   2,  8, True,  "SUB output to ACC broadcast bc field"),
			idef(0x07F, "VSUBAw",   2,  8, True,  "SUB output to ACC broadcast bc field"),
			idef(0x02A, "VMUL",     1,  2, True,  "Multiply"),
			idef(0x01E, "VMULi",    1,  3, True,  "MUL broadcast I register"),
			idef(0x01C, "VMULq",    1,  4, True,  "MUL broadcast Q register"),
			idef(0x018, "VMULx",    0,  2, True,  "MUL broadcast bc field"),
			idef(0x019, "VMULy",    0,  2, True,  "MUL broadcast bc field"),
			idef(0x01A, "VMULz",    0,  2, True,  "MUL broadcast bc field"),
			idef(0x01B, "VMULw",    0,  2, True,  "MUL broadcast bc field"),
			idef(0x2BE, "VMULA",    3,  5, True,  "MUL output to ACC"),
			idef(0x1FE, "VMULAi",   3,  6, True,  "MUL output to ACC broadcast I register"),
			idef(0x1FC, "VMULAq",   3,  7, True,  "MUL output to ACC broadcast Q register"),
			idef(0x1BC, "VMULAx",   2,  8, True,  "MUL output to ACC broadcast bc field"),
			idef(0x1BD, "VMULAy",   2,  8, True,  "MUL output to ACC broadcast bc field"),
			idef(0x1BE, "VMULAz",   2,  8, True,  "MUL output to ACC broadcast bc field"),
			idef(0x1BF, "VMULAw",   2,  8, True,  "MUL output to ACC broadcast bc field"),
			idef(0x029, "VMADD",    1,  2, True,  "MUL and ADD"),
			idef(0x023, "VMADDi",   1,  3, True,  "MUL and ADD broadcast I register"),
			idef(0x021, "VMADDq",   1,  4, True,  "MUL and ADD broadcast Q register"),
			idef(0x008, "VMADDx",   0,  2, True,  "MUL and ADD broadcast bc field"),
			idef(0x009, "VMADDy",   0,  2, True,  "MUL and ADD broadcast bc field"),
			idef(0x00A, "VMADDz",   0,  2, True,  "MUL and ADD broadcast bc field"),
			idef(0x00B, "VMADDw",   0,  2, True,  "MUL and ADD broadcast bc field"),
			idef(0x2BD, "VMADDA",   3,  5, True,  "MUL and ADD output to ACC"),
			idef(0x23F, "VMADDAi",  3,  6, True,  "MUL and ADD output to ACC broadcast I register"),
			idef(0x23D, "VMADDAq",  3,  7, True,  "MUL and ADD output to ACC broadcast Q register"),
			idef(0x0BC, "VMADDAx",  2,  8, True,  "MUL and ADD output to ACC broadcast bc field"),
			idef(0x0BD, "VMADDAy",  2,  8, True,  "MUL and ADD output to ACC broadcast bc field"),
			idef(0x0BE, "VMADDAz",  2,  8, True,  "MUL and ADD output to ACC broadcast bc field"),
			idef(0x0BF, "VMADDAw",  2,  8, True,  "MUL and ADD output to ACC broadcast bc field"),
			idef(0x02D, "VMSUB",    1,  2, True,  "MUL and SUB"),
			idef(0x027, "VMSUBi",   1,  3, True,  "MUL and SUB broadcast I register"),
			idef(0x025, "VMSUBq",   1,  4, True,  "MUL and SUB broadcast Q register"),
			idef(0x00C, "VMSUBx",   0,  2, True,  "MUL and SUB broadcast bc field"),
			idef(0x00D, "VMSUBy",   0,  2, True,  "MUL and SUB broadcast bc field"),
			idef(0x00E, "VMSUBz",   0,  2, True,  "MUL and SUB broadcast bc field"),
			idef(0x00F, "VMSUBw",   0,  2, True,  "MUL and SUB broadcast bc field"),
			idef(0x2FD, "VMSUBA",   3,  5, True,  "MUL and SUB output to ACC"),
			idef(0x27F, "VMSUBAi",  3,  6, True,  "MUL and SUB output to ACC broadcast I register"),
			idef(0x27D, "VMSUBAq",  3,  7, True,  "MUL and SUB output to ACC broadcast Q register"),
			idef(0x0FC, "VMSUBAx",  2,  8, True,  "MUL and SUB output to ACC broadcast bc field"),
			idef(0x0FD, "VMSUBAy",  2,  8, True,  "MUL and SUB output to ACC broadcast bc field"),
			idef(0x0FE, "VMSUBAz",  2,  8, True,  "MUL and SUB output to ACC broadcast bc field"),
			idef(0x0FF, "VMSUBAw",  2,  8, True,  "MUL and SUB output to ACC broadcast bc field"),
			idef(0x02B, "VMAX",     1,  2, True,  "Maximum"),
			idef(0x01D, "VMAXi",    1,  3, True,  "Maximum broadcast I register"),
			idef(0x010, "VMAXx",    0,  2, True,  "Maximum broadcast bc field"),
			idef(0x011, "VMAXy",    0,  2, True,  "Maximum broadcast bc field"),
			idef(0x012, "VMAXz",    0,  2, True,  "Maximum broadcast bc field"),
			idef(0x013, "VMAXw",    0,  2, True,  "Maximum broadcast bc field"),
			idef(0x02F, "VMINI",    1,  2, True,  "Minimum"),
			idef(0x01F, "VMINIi",   1,  3, True,  "Minimum broadcast I register"),
			idef(0x014, "VMINIx",   0,  2, True,  "Minimum broadcast bc field"),
			idef(0x015, "VMINIy",   0,  2, True,  "Minimum broadcast bc field"),
			idef(0x016, "VMINIz",   0,  2, True,  "Minimum broadcast bc field"),
			idef(0x017, "VMINIw",   0,  2, True,  "Minimum broadcast bc field"),
			idef(0x2FE, "VOPMULA",  3,  9, False, "Outer product MULA"),
			idef(0x02E, "VOPMSUB",  1, 10, False, "Outer product MSUB"),
			idef(0x2FF, "VNOP",     3,  0, False, "No operation"),
			idef(0x17C, "VFTOI0",   3,  1, True,  "Float to integer, fixed point 0 bit"),
			idef(0x17D, "VFTOI4",   3,  1, True,  "Float to integer, fixed point 4 bits"),
			idef(0x17E, "VFTOI12",  3,  1, True,  "Float to integer, fixed point 12 bits"),
			idef(0x17F, "VFTOI15",  3,  1, True,  "Float to integer, fixed point 15 bits"),
			idef(0x13C, "VITOF0",   3,  1, True,  "Integer to float, fixed point 0 bit"),
			idef(0x13D, "VITOF4",   3,  1, True,  "Integer to float, fixed point 4 bits"),
			idef(0x13E, "VITOF12",  3,  1, True,  "Integer to float, fixed point 12 bits"),
			idef(0x13F, "VITOF15",  3,  1, True,  "Integer to float, fixed point 15 bits"),
			idef(0x1FF, "VCLIP",    3, 11, False, "Clipping"),
			idef(0x3BC, "VDIV",     4, 12, False, "Floating divide"),
			idef(0x3BD, "VSQRT",    4, 13, False, "Floating square-root"),
			idef(0x3BE, "VRSQRT",   4, 12, False, "Floating reciprocal square-root"),
			idef(0x030, "VIADD",    1, 14, False, "Integer ADD"),
			idef(0x032, "VIADDI",   5, 15, False, "Integer ADD immediate"),
			idef(0x034, "VIAND",    1, 14, False, "Integer AND"),
			idef(0x035, "VIOR",     1, 14, False, "Integer OR"),
			idef(0x031, "VISUB",    1, 14, False, "Integer SUB"),
			idef(0x33C, "VMOVE",    3, 16, True,  "Move floating register"),
			idef(0x3FD, "VMFIR",    3, 17, True,  "Move from integer register"),
			idef(0x3FC, "VMTIR",    4, 18, False, "Move to integer register"),
			idef(0x33D, "VMR32",    3, 16, True,  "Rotate right 32 bits"),
			idef(0x37E, "VLQD",     3, 17, True,  "Load quadword with pre-decrement"),
			idef(0x37C, "VLQI",     3, 17, True,  "Load quadword with post-increment"),
			idef(0x37F, "VSQD",     3, 19, True,  "Store quadword with pre-decrement"),
			idef(0x37D, "VSQI",     3, 19, True,  "Store quadword with post-increment"),
			idef(0x3FE, "VILWR",    3, 20, True,  "Integer load word register"),
			idef(0x3FF, "VISWR",    3, 20, True,  "Integer store word register"),
			idef(0x43E, "VRINIT",   4, 21, False, "Random-unit init R register"),
			idef(0x43D, "VRGET",    3, 22, True,  "Random-unit get R register"),
			idef(0x43C, "VRNEXT",   3, 22, True,  "Random-unit next M sequence"),
			idef(0x43F, "VRXOR",    4, 21, False, "Random-unit XOR R register"),
			idef(0x3BF, "VWAITQ",   3,  0, False, "Wait Q register"),
		]
		
		self.CFC2_ITABLE_ID  = ida_allins.MIPS_cfc2
		self.CTC2_ITABLE_ID  = ida_allins.MIPS_ctc2
		self.QMFC2_ITABLE_ID = ida_allins.MIPS_qmfc2
		self.QMTC2_ITABLE_ID = ida_allins.MIPS_qmtc2
		self.LQC2_ITABLE_ID  = ida_allins.MIPS_lqc2
		self.SQC2_ITABLE_ID  = ida_allins.MIPS_sqc2

		self.VF_REG = 0
		self.VI_REG = 1
		self.VF_REG_WITH_F = 2
		self.CTL_REG = 3

		self.reg_types = {
			0:  [],
			1:  [self.VF_REG,  self.VF_REG],
			2:  [self.VF_REG,  self.VF_REG, self.VF_REG],
			3:  [self.VF_REG,  self.VF_REG, self.CTL_REG],
			4:  [self.VF_REG,  self.VF_REG, self.CTL_REG],
			5:  [self.CTL_REG, self.VF_REG, self.VF_REG],
			6:  [self.CTL_REG, self.VF_REG, self.CTL_REG],
			7:  [self.CTL_REG, self.VF_REG, self.CTL_REG],
			8:  [self.CTL_REG, self.VF_REG, self.VF_REG],
			9:  [self.CTL_REG, self.VF_REG, self.VF_REG],
			10: [self.VF_REG,  self.VF_REG, self.VF_REG],
			11: [self.VF_REG,  self.VF_REG],
			12: [self.CTL_REG, self.VF_REG_WITH_F, self.VF_REG_WITH_F],
			13: [self.CTL_REG, self.VF_REG_WITH_F],
			14: [self.VI_REG,  self.VI_REG, self.VI_REG],
			15: [self.VI_REG,  self.VI_REG],
			16: [self.VF_REG,  self.VF_REG],
			17: [self.VF_REG,  self.VI_REG],
			18: [self.VI_REG,  self.VF_REG_WITH_F],
			19: [self.VF_REG,  self.VI_REG],
			20: [self.VI_REG,  self.VI_REG],
			21: [self.CTL_REG, self.VF_REG_WITH_F],
			22: [self.VF_REG,  self.CTL_REG],
		}

		self.itable.sort(key=lambda x: x.opcode)

		for entry in self.itable:
			entry.name = entry.name.lower()

		for i in range(len(self.itable)):
			if (self.itable[i].opcode & 0xF00 == 0x100):
				self.pos_0x100 = i
				break

		for i in range(len(self.itable)):
			if (self.itable[i].opcode & 0xF00 == 0x200):
				self.pos_0x200 = i
				break

		for i in range(len(self.itable)):
			if (self.itable[i].opcode & 0xF00 == 0x300):
				self.pos_0x300 = i
				break

		for i in range(len(self.itable)):
			if (self.itable[i].opcode & 0xF00 == 0x400):
				self.pos_0x400 = i
				break

	def set_regs_2(self, insn, a, b):
		insn.Op1.type = ida_ua.o_idpspec1
		insn.Op1.reg = a
		insn.Op2.type = ida_ua.o_idpspec1
		insn.Op2.reg = b

	def set_regs_3(self, insn, a, b, c):
		insn.Op1.type = ida_ua.o_idpspec1
		insn.Op1.reg = a
		insn.Op2.type = ida_ua.o_idpspec1
		insn.Op2.reg = b
		insn.Op3.type = ida_ua.o_idpspec1
		insn.Op3.reg = c

	def decode_type_0(self, insn, dword):
		insn.Op1.type = ida_ua.o_void

	def decode_type_1(self, insn, dword):
		ftreg = (dword >> 0x10) & 0x1F
		fsreg = (dword >> 0xB) & 0x1F
		self.set_regs_2(insn, ftreg, fsreg)

	def decode_type_2(self, insn, dword):
		ftreg = (dword >> 0x10) & 0x1F
		fsreg = (dword >> 0xB) & 0x1F
		fdreg = (dword >> 6) & 0x1F
		self.set_regs_3(insn, fdreg, fsreg, ftreg)

	def decode_type_3(self, insn, dword):
		ftreg = (dword >> 0x10) & 0x1F
		fsreg = (dword >> 0xB) & 0x1F
		fdreg = (dword >> 6) & 0x1F
		self.set_regs_3(insn, fdreg, fsreg, ord('I'))

	def decode_type_4(self, insn, dword):
		ftreg = (dword >> 0x10) & 0x1F
		fsreg = (dword >> 0xB) & 0x1F
		fdreg = (dword >> 6) & 0x1F
		self.set_regs_3(insn, fdreg, fsreg, ord('Q'))

	def decode_type_5(self, insn, dword):
		ftreg = (dword >> 0x10) & 0x1F
		fsreg = (dword >> 0xB) & 0x1F
		self.set_regs_3(insn, ord('A'), fsreg, ftreg)

	def decode_type_6(self, insn, dword):
		ftreg = (dword >> 0x10) & 0x1F
		fsreg = (dword >> 0xB) & 0x1F
		self.set_regs_3(insn, ord('A'), fsreg, ord('I'))

	def decode_type_7(self, insn, dword):
		ftreg = (dword >> 0x10) & 0x1F
		fsreg = (dword >> 0xB) & 0x1F
		self.set_regs_3(insn, ord('A'), fsreg, ord('Q'))

	def decode_type_8(self, insn, dword):
		ftreg = (dword >> 0x10) & 0x1F
		fsreg = (dword >> 0xB) & 0x1F
		self.set_regs_3(insn, ord('A'), fsreg, ftreg)

	def decode_type_9(self, insn, dword):
		ftreg = (dword >> 0x10) & 0x1F
		fsreg = (dword >> 0xB) & 0x1F
		self.set_regs_3(insn, ord('A'), fsreg, ftreg)

	def decode_type_10(self, insn, dword):
		ftreg = (dword >> 0x10) & 0x1F
		fsreg = (dword >> 0xB) & 0x1F
		fdreg = (dword >> 6) & 0x1F
		self.set_regs_3(insn, fdreg, fsreg, ftreg)

	def decode_type_11(self, insn, dword):
		ftreg = (dword >> 0x10) & 0x1F
		fsreg = (dword >> 0xB) & 0x1F
		self.set_regs_2(insn, fsreg, ftreg)

	def decode_type_12(self, insn, dword):
		ftf = (dword >> 0x17) & 3
		fsf = (dword >> 0x15) & 3
		ftreg = (dword >> 0x10) & 0x1F
		fsreg = (dword >> 0xB) & 0x1F
		self.set_regs_3(insn, ord('Q'), fsreg | (fsf << 8), ftreg | (ftf << 8))

	def decode_type_13(self, insn, dword):
		ftf = (dword >> 0x17) & 3
		fsf = (dword >> 0x15) & 3
		ftreg = (dword >> 0x10) & 0x1F
		fsreg = (dword >> 0xB) & 0x1F
		self.set_regs_2(insn, ord('Q'), ftreg | (ftf << 8))

	def decode_type_14(self, insn, dword):
		ftreg = (dword >> 0x10) & 0x1F
		fsreg = (dword >> 0xB) & 0x1F
		fdreg = (dword >> 6) & 0x1F
		self.set_regs_3(insn, fdreg, fsreg, ftreg)

	def decode_type_15(self, insn, dword):
		ftreg = (dword >> 0x10) & 0x1F
		fsreg = (dword >> 0xB) & 0x1F
		imm = (dword >> 6) & 0x1F
		insn.Op1.type = ida_ua.o_idpspec1
		insn.Op1.reg = ftreg
		insn.Op2.type = ida_ua.o_idpspec1
		insn.Op2.reg = fsreg
		insn.Op3.type = ida_ua.o_imm
		insn.Op3.value = imm

	def decode_type_16(self, insn, dword):
		ftreg = (dword >> 0x10) & 0x1F
		fsreg = (dword >> 0xB) & 0x1F
		self.set_regs_2(insn, ftreg, fsreg)

	def decode_type_17(self, insn, dword):
		ftreg = (dword >> 0x10) & 0x1F
		fsreg = (dword >> 0xB) & 0x1F
		self.set_regs_2(insn, ftreg, fsreg)

	def decode_type_18(self, insn, dword):
		ftf = (dword >> 0x17) & 3
		fsf = (dword >> 0x15) & 3
		ftreg = (dword >> 0x10) & 0x1F
		fsreg = (dword >> 0xB) & 0x1F
		self.set_regs_2(insn, ftreg, fsreg | (fsf << 8))

	def decode_type_19(self, insn, dword):
		ftreg = (dword >> 0x10) & 0x1F
		fsreg = (dword >> 0xB) & 0x1F
		self.set_regs_2(insn, fsreg, ftreg)

	def decode_type_20(self, insn, dword):
		ftreg = (dword >> 0x10) & 0x1F
		fsreg = (dword >> 0xB) & 0x1F
		self.set_regs_2(insn, ftreg, fsreg)

	def decode_type_21(self, insn, dword):
		ftf = (dword >> 0x17) & 3
		fsf = (dword >> 0x15) & 3
		ftreg = (dword >> 0x10) & 0x1F
		fsreg = (dword >> 0xB) & 0x1F
		self.set_regs_2(insn, ord('R'), fsreg | (fsf << 8))

	def decode_type_22(self, insn, dword):
		ftreg = (dword >> 0x10) & 0x1F
		fsreg = (dword >> 0xB) & 0x1F
		self.set_regs_2(insn, ftreg, ord('R'))	

	def set_reg_type(self, op, reg_type):
		op.specval = reg_type

	def decode_instruction(self, index, insn, dword):

		insn.itype = ITYPE_START + index

		decoder = getattr(self, 'decode_type_%d' % self.itable[index].dt)
		decoder(insn, dword)

		regs = self.reg_types[self.itable[index].dt]

		if (len(regs) == 2):
			self.set_reg_type(insn.Op1, regs[0])
			self.set_reg_type(insn.Op2, regs[1])

		elif (len(regs) == 3):
			self.set_reg_type(insn.Op1, regs[0])
			self.set_reg_type(insn.Op2, regs[1])
			self.set_reg_type(insn.Op3, regs[2])

		insn.size = 4

	def ev_ana_insn(self, insn):

		dword = ida_bytes.get_wide_dword(insn.ea)

		if (dword >> 0x19 == 0x25):

			if (dword & 0x3C == 0x3C):
				opcode = dword & 0x7FF
			else:
				opcode = dword & 0x3F

			pos = 0
			if (opcode & 0xF00 == 0x100):
				pos = self.pos_0x100
			elif (opcode & 0xF00 == 0x200):
				pos = self.pos_0x200
			elif (opcode & 0xF00 == 0x300):
				pos = self.pos_0x300
			elif (opcode & 0xF00 == 0x400):
				pos = self.pos_0x400

			found = False
			index = 0
			for i in range(pos, len(self.itable)):
				if (self.itable[i].opcode == opcode):
					found = True
					index = i
					break

			if (not found):
				return 0

			self.decode_instruction(index, insn, dword)

		return insn.size

	#def ev_get_autocmt(self, insn):
	#	if (insn.itype >= ITYPE_START and insn.itype < ITYPE_START + len(self.itable)):
	#		return self.itable[insn.itype-ITYPE_START].cmt
	#	return 0

	def ev_emu_insn(self, insn):
		#if (insn.itype >= ITYPE_START and insn.itype < ITYPE_START + len(self.itable)):
		#	return 1
		return 0

	def decode_reg_field(self, val):
		return ["x", "y", "z", "w"][val]

	def get_register(self, op):

		if (op.specval == self.VF_REG):
			return "$vf%d" % op.reg
		elif (op.specval == self.VI_REG):
			return "$vi%d" % op.reg
		elif (op.specval == self.VF_REG_WITH_F):
			return "$vf%d.%s" % (op.reg & 0xFF, self.decode_reg_field(op.reg >> 8))
		elif (op.specval == self.CTL_REG):
			return "$%c" % op.reg
		else:
			return "UNK"

	def ev_out_operand(self, ctx, op):

		if (op.type == ida_ua.o_idpspec1):

			# First we need to fix instructions (badly) disassembled by mips.dll
			if (ctx.insn.itype == self.CFC2_ITABLE_ID and op.n == 1):
				ctx.out_register("$vi%d" % op.reg)
			elif (ctx.insn.itype == self.CTC2_ITABLE_ID and op.n == 1):
				ctx.out_register("$vi%d" % op.reg)
			elif (ctx.insn.itype == self.QMFC2_ITABLE_ID and op.n == 1):
				ctx.out_register("$vf%d" % op.reg)
			elif (ctx.insn.itype == self.QMTC2_ITABLE_ID and op.n == 1):
				ctx.out_register("$vf%d" % op.reg)
			elif (ctx.insn.itype == self.LQC2_ITABLE_ID and op.n == 0):
				ctx.out_register("$vf%d" % op.reg)
			elif (ctx.insn.itype == self.SQC2_ITABLE_ID and op.n == 0):
				ctx.out_register("$vf%d" % op.reg)
			elif (ctx.insn.itype >= ITYPE_START and ctx.insn.itype < ITYPE_START + len(self.itable)):
				ctx.out_register(self.get_register(op))
			else:
				return 0

			return 1

		return 0

	def decode_dest(self, dword):

		dest = (dword >> 0x15) & 0xF 

		s = "."
		if ((dest >> 3) & 1):
			s += "x"
		if ((dest >> 2) & 1):
			s += "y"
		if ((dest >> 1) & 1):
			s += "z"
		if (dest & 1):
			s += "w"

		return s

	def ev_out_mnem(self, ctx):
		if (ctx.insn.itype >= ITYPE_START and ctx.insn.itype < ITYPE_START + len(self.itable)):

			dest = ""
			if (self.itable[ctx.insn.itype-ITYPE_START].dest):
				dest = self.decode_dest(ida_bytes.get_wide_dword(ctx.insn.ea))

			ctx.out_custom_mnem(self.itable[ctx.insn.itype-ITYPE_START].name, MNEM_WIDTH, dest)
			return 1

		# We do this to fix width of other instructions
		ctx.out_mnem(MNEM_WIDTH)
		return 1

class emotionengine_plugin_t(idaapi.plugin_t):
	flags = idaapi.PLUGIN_HIDE
	comment = ""
	help = ""
	wanted_name = "PS2 Emotion Engine COP2 instructions disassembler"
	wanted_hotkey = ""

	def __init__(self):
		self.cop2 = None

	def init(self):
		
		if (idaapi.ph.id == idaapi.PLFM_MIPS and ida_ida.inf_get_procname() == 'r5900l'):
			self.cop2 = COP2_disassemble()
			self.cop2.hook()
			print("PS2 Emotion Engine COP2 instructions disassembler is loaded")
			return idaapi.PLUGIN_KEEP

		return idaapi.PLUGIN_SKIP

	def run(self, arg):
		pass

	def term(self):
		if (self.cop2 != None):
			self.cop2.unhook()
			self.cop2 = None

def PLUGIN_ENTRY():
	return emotionengine_plugin_t()


repository: joeleong/ida-referee
file: referee.py
url: https://github.com/joeleong/ida-referee/blob/ef432253886d7abe9cd5df450f5a9c33d05ad286/referee.py
wanted_name: Referee
comment: Adds struct xref info from decompilation
created_at: 2017-08-18 18:17:31+00:00
pushed_at: 2024-08-11 21:57:12+00:00
content:
# -*- coding: utf-8 -*-
"""
Referee creates struct xrefs for decompiled functions
"""
import logging
import traceback

import idaapi

logging.basicConfig(level=logging.WARN)
log = logging.getLogger("referee")


NETNODE_NAME = '$ referee-xrefs'
NETNODE_TAG = 'X'


def is_assn(t):
    return (
        t == idaapi.cot_asg or
        t == idaapi.cot_asgbor or
        t == idaapi.cot_asgxor or
        t == idaapi.cot_asgband or
        t == idaapi.cot_asgsub or
        t == idaapi.cot_asgmul or
        t == idaapi.cot_asgsshr or
        t == idaapi.cot_asgushr or
        t == idaapi.cot_asgsdiv or
        t == idaapi.cot_asgudiv or
        t == idaapi.cot_asgsmod or
        t == idaapi.cot_asgumod)


def is_incdec(t):
    return (
        t == idaapi.cot_postinc or  # = 53,  ///< x++
        t == idaapi.cot_postdec or  # = 54,  ///< x--
        t == idaapi.cot_preinc  or  # = 55,  ///< ++x
        t == idaapi.cot_predec)     # = 56,  ///< --x


def add_struct_xrefs(cfunc):
    class xref_adder_t(idaapi.ctree_visitor_t):
        def __init__(self, cfunc):
            idaapi.ctree_visitor_t.__init__(self, idaapi.CV_PARENTS)
            self.cfunc = cfunc
            self.node = idaapi.netnode()
            self.clear_struct_xrefs()
            self.xrefs = {}

        def load(self):
            try:
                data = self.node.getblob_ea(self.cfunc.entry_ea, NETNODE_TAG)
                if data:
                    xrefs = eval(data)
                    log.debug('Loaded {} xrefs'.format(len(xrefs)))
                    return xrefs
            except:
                log.error('Failed to load xrefs from netnode')
                traceback.print_exc()
            return {}

        def save(self):
            try:
                self.node.setblob_ea(repr(self.xrefs).encode(),
                                     self.cfunc.entry_ea,
                                     NETNODE_TAG)
            except:
                log.error('Failed to save xrefs to netnode')
                traceback.print_exc()

        def clear_struct_xrefs(self):
            if not self.node.create(NETNODE_NAME):
                xrefs = self.load()
                for (ea, struct_id, member_id) in xrefs.keys():
                    if member_id is None:
                        idaapi.del_dref(ea, struct_id)
                    else:
                        idaapi.del_dref(ea, member_id)
                self.xrefs = {}
                self.save()
                log.debug('Cleared {} xrefs'.format(len(xrefs)))

        def find_addr(self, e):
            if e.ea != idaapi.BADADDR:
                ea = e.ea
            else:
                while True:
                    e = self.cfunc.body.find_parent_of(e)
                    if e is None:
                        ea = self.cfunc.entry_ea
                        break
                    if e.ea != idaapi.BADADDR:
                        ea = e.ea
                        break
            return ea

        def add_dref(self, ea, struct_id, flags, member_id=None):
            if ((ea, struct_id, member_id) not in self.xrefs or
                    flags < self.xrefs[(ea, struct_id, member_id)]):
                self.xrefs[(ea, struct_id, member_id)] = flags
                strname = idaapi.get_struc_name(struct_id)
                if member_id is None:
                    idaapi.add_dref(ea, struct_id, flags)
                    log.debug((" 0x{:X} \t"
                               "struct {} \t"
                               "{}").format(
                               ea, strname, flags_to_str(flags)))
                else:
                    idaapi.add_dref(ea, member_id, flags)
                    log.debug((" 0x{:X} \t"
                               "member {}.{} \t"
                               "{}").format(
                               ea, strname,
                               idaapi.get_member_name(member_id),
                               flags_to_str(flags)))
            self.save()

        def visit_expr(self, e):
            dr = idaapi.dr_R | idaapi.XREF_USER
            ea = self.find_addr(e)

            # We wish to know what context a struct usage occurs in
            # so we can determine what kind of xref to create. Unfortunately,
            # a post-order traversal makes this difficult.

            # For assignments, we visit the left, instead
            # Note that immediate lvalues will be visited twice,
            # and will be eronneously marked with a read dref.
            # However, it is safer to overapproximate than underapproximate
            if is_assn(e.op) or is_incdec(e.op):
                e = e.x
                dr = idaapi.dr_W | idaapi.XREF_USER

            # &x
            if e.op == idaapi.cot_ref:
                e = e.x
                dr = idaapi.dr_O | idaapi.XREF_USER

            # x.m, x->m
            if (e.op == idaapi.cot_memref or e.op == idaapi.cot_memptr):
                moff = e.m

                # The only way I could figure out how
                # to get the structure/member associated with its use
                typ = e.x.type

                if e.op == idaapi.cot_memptr:
                    typ.remove_ptr_or_array()

                strname = typ.dstr()
                if strname.startswith("struct "):
                    strname = strname[len("struct "):]
                if strname.startswith("const "):
                    strname = strname[len("const "):]

                stid = idaapi.get_struc_id(strname)
                struc = idaapi.get_struc(stid)
                mem = idaapi.get_member(struc, moff)

                if struc is not None:
                    self.add_dref(ea, stid, dr)
                    if mem is not None:
                        self.add_dref(ea, stid, dr, mem.id)

                else:
                    log.error(("failure from 0x{:X} "
                               "on struct {} (id: 0x{:X}) {}").format(
                               ea, strname, stid, flags_to_str(dr)))

            elif idaapi.is_lvalue(e.op) and e.type.is_struct():
                strname = e.type.dstr()
                if strname.startswith("struct "):
                    strname = strname[len("struct "):]
                if strname.startswith("const "):
                    strname = strname[len("const "):]

                stid = idaapi.get_struc_id(strname)
                struc = idaapi.get_struc(stid)

                if struc is not None:
                    self.add_dref(ea, stid, dr)

            return 0

    adder = xref_adder_t(cfunc)
    adder.apply_to_exprs(cfunc.body, None)


def callback(*args):
    if args[0] == idaapi.hxe_maturity:
        cfunc = args[1]
        mat = args[2]
        if mat == idaapi.CMAT_FINAL:
            log.debug("analyzing function at 0x{:X}".format(
                cfunc.entry_ea))
            add_struct_xrefs(cfunc)
    return 0


class Referee(idaapi.plugin_t):
    flags = idaapi.PLUGIN_HIDE
    comment = "Adds struct xref info from decompilation"
    help = ""

    wanted_name = "Referee"
    wanted_hotkey = ""

    def init(self):
        if not idaapi.init_hexrays_plugin():
            return idaapi.PLUGIN_SKIP

        idaapi.install_hexrays_callback(callback)
        log.info(("Hex-Rays version {} has been detected; "
                  "{} is ready to use").format(
                  idaapi.get_hexrays_version(), self.wanted_name))
        self.inited = True
        return idaapi.PLUGIN_KEEP

    def run(self, arg):
        # never called
        pass

    def term(self):
        if self.inited:
            idaapi.remove_hexrays_callback(callback)
            idaapi.term_hexrays_plugin()


def PLUGIN_ENTRY():
    return Referee()


def flags_to_str(num):
    match = []
    if num & idaapi.dr_R == idaapi.dr_R:
        match.append('dr_R')
        num ^= idaapi.dr_R
    if num & idaapi.dr_O == idaapi.dr_O:
        match.append('dr_O')
        num ^= idaapi.dr_O
    if num & idaapi.dr_W == idaapi.dr_W:
        match.append('dr_W')
        num ^= idaapi.dr_W
    if num & idaapi.dr_I == idaapi.dr_I:
        match.append('dr_I')
        num ^= idaapi.dr_I
    if num & idaapi.dr_T == idaapi.dr_T:
        match.append('dr_T')
        num ^= idaapi.dr_T
    if num & idaapi.XREF_USER == idaapi.XREF_USER:
        match.append('XREF_USER')
        num ^= idaapi.XREF_USER
    if num & idaapi.XREF_DATA == idaapi.XREF_DATA:
        match.append('XREF_DATA')
        num ^= idaapi.XREF_DATA
    res = ' | '.join(match)
    if num:
        res += ' unknown: 0x{:X}'.format(num)
    return res


def clear_output_window():
    idaapi.process_ui_action('msglist:Clear')


repository: y0ug/gracewire_research
file: tools/ida_grace_core_deobfucation.py
wanted_name: Remove GraceWire obfuscation
created_at: 2022-11-15 11:43:50+00:00
pushed_at: 2022-11-15 14:35:48+00:00
url:
https://github.com/y0ug/gracewire_research/blob/828396c5ff1060b9de2ed56344d71a583f8df209/tools/ida_grace_core_deobfucation.py

content:
import binascii
import hashlib


import idaapi
import idautils
import ida_allins
import idc
from collections import deque

def remove_junk_code(start_ea, end_ea):
    pass

junk_start = 0x18009F000
junk_end = 0x1800A19DA 

def patch_nops(start_ea, end_ea):
    for ea in range(start_ea, end_ea):
        patch_byte(ea, 0x90)

def read_n_ins(ea, n):
    vals = []
    for i in range(n): 
        insn = idaapi.insn_t()
        idaapi.decode_insn(insn, ea)
        dis = idc.GetDisasm(ea)
        insn_mnem = idc.print_insn_mnem(ea)
        vals.append([ea, insn, dis, insn_mnem])
        ea = idc.next_head(ea)
        if not ea: break

    return vals

def color(start, end, color_code):
    ea = start
    while ea <= end:
        idc.set_color(ea, idc.CIC_ITEM, color_code)
        ea = idc.next_head(ea)

def patch(start, end, color_code):
    patch_nops(start, end)
    color(start, end, color_code)

def remove_junk_per_func(ea):
    f = ida_funcs.get_func(ea)
    if f == None:
        print(f'[-] address given - {hex(ea)} does not belong to a function')
        return False

    removal_list = [] # contains all removed code ranges

    print(f'[+] attempting to remove junk code for function {ida_funcs.get_func_name(ea)}') 

    # mov reg, address
    # xor/sub/and/imul, reg, val
    # mov address, reg 
    ea = f.start_ea

    while ea < f.end_ea:
        vals = read_n_ins(ea, 6)
        #print(vals)
        if len(vals) != 6: break 
        #print(f"try {vals[0][0]:x}")

        if vals[0][3].startswith("mov") and vals[1][3].startswith("mov") and \
                vals[2][3] in ['xor', 'add', 'sub', 'and', 'imul', 'or'] and \
                vals[3][3] == "mov" and \
                idc.get_operand_type(vals[3][0], 0) == 0x2 and \
                idc.get_operand_type(vals[0][0], 0) == 0x1:
            print(f"case1 {vals[0][0]:x}")
            ea = idc.next_head(vals[3][0])
            patch(vals[0][0], ea, 0x33FF3F)
        elif vals[0][3].startswith("mov") and vals[1][3].startswith("mov") and \
                vals[2][3] in ['xor', 'add', 'sub', 'and', 'imul', 'or'] and \
                vals[3][3] == "mov" and vals[4][3] == "mov" and \
                idc.get_operand_type(vals[0][0], 0) == 0x1:
            print(f"case2 {vals[0][0]:x}")
            ea = idc.next_head(vals[4][0])
            #patch_nops(vals[0][0], ea)
            patch(vals[0][0], ea, 0xDAFF33)

        #elif vals[0][3].startswith("mov") and \
        #        vals[1][3] in ['xor', 'add', 'sub', 'and', 'imul', 'or'] and \
        #        vals[2][3] == "mov" and vals[3][3] == "mov":
        #    print(f"win2 {vals[0][0]:x}")
        #    patch_nops(vals[0][0], idc.next_head(vals[3][0]))
        #    ea = idc.next_head(vals[3][0])
        elif vals[0][3].startswith("mov") and \
                vals[1][3] in ['xor', 'add', 'sub', 'and', 'imul', 'or'] and \
                vals[2][3] == "mov" and \
                vals[3][3] in ['xor', 'add', 'sub', 'and', 'imul', 'or'] and \
                vals[4][3] == "mov" and \
                idc.get_operand_type(vals[0][0], 0) == 0x1:
            print(f"case3 {vals[0][0]:x}")
            ea = idc.next_head(vals[4][0])
            patch(vals[0][0], ea, 0x33FFFF)

        elif vals[0][3].startswith("mov") and \
                (idc.get_operand_value(vals[0][0], 0) == 0x0 or idc.get_operand_value(vals[0][0], 0) == 0x1) and \
                vals[1][3] in ['xor', 'add', 'sub', 'and', 'imul', 'or'] and \
                vals[2][3] == "mov" and \
                idc.get_operand_type(vals[0][0], 0) == 0x1 and idc.get_operand_type(vals[2][0], 0) == 0x2:
            print(f"case4 {vals[0][0]:x}")
            ea = idc.next_head(vals[2][0])
            patch(vals[0][0], ea, 0x3386FF)

        elif vals[0][3].startswith("imul") and idc.get_operand_type(vals[0][0], 2) == 0x5 and \
                vals[1][3] == "mov":
            print(f"case5 {vals[0][0]:x}")
            ea = idc.next_head(vals[1][0])
            patch(vals[0][0], ea, 0x133760)
        else:
            color(ea, ea, 0xFF33B5)
            ea = idc.next_head(ea)

    ida_bytes.del_items(f.start_ea, 0, f.end_ea - f.start_ea)
    idc.create_insn(f.start_ea)
    ida_funcs.add_func(f.start_ea, f.end_ea)

#------------------------------------------------------------------------------
# IDA Plugin
#------------------------------------------------------------------------------

VERSION = "v1.0"
AUTHORS = ['Hugo Caron']

def PLUGIN_ENTRY():
    """
    Required plugin entry point for IDAPython Plugins.
    """
    return deobf_junk_t()

class deobf_junk_t(idaapi.plugin_t):

    flags = idaapi.PLUGIN_FIX
    comment = ""
    help = ""
    wanted_name = "Remove GraceWire obfuscation"
    wanted_hotkey = "Alt-F3"

    def __init__(self):
        pass

    def init(self):
        idaapi.msg('Init plugin')
        return PLUGIN_OK

    def run(self, arg):
        """
        This is called by IDA when this file is loaded as a script.
        """
        ea = idc.here()
        idaapi.msg(f'plugin run {ea:x}')
        remove_junk_per_func(ea)

    def term(self):
        pass



ea = idc.here()
idaapi.msg(f'plugin run {ea:x}')
for ea in idautils.Functions():
    func = idaapi.get_func(ea)
    print(f"processing {idc.get_func_name(func.start_ea)}")
    remove_junk_per_func(func.start_ea)


repository: danigargu/ida-scripts
file: cyclomatic_complexity.py
description: Misc IDA Pro scripts
wanted_name: Cyclomatic Complexity
comment: Cyclomatic Complexity
created_at: 2016-09-21 20:43:09+00:00
pushed_at: 2016-10-17 14:55:32+00:00
url:
https://github.com/danigargu/ida-scripts/blob/71ab351f9b6f7ae1b1231504595cdf835cae91ed/cyclomatic_complexity.py

content:
#!/usr/bin/python

from idc import *
from idaapi import *
from idautils import *
from sets import Set

def cyclomatic_complexity(function_ea):
  """
  Cyclomatic Complexity function - by Ero Carrera  

  Calculate the cyclomatic complexity measure for a function.
  Given the starting address of a function, it will find all the basic block's boundaries and edges
  between them and will return the cyclomatic complexity, defined as:
    CC = Edges - Nodes + 2
  """

  f_start = function_ea
  f_end = FindFuncEnd(function_ea)
  edges = Set()
  boundaries = Set((f_start,))

  for head in Heads(f_start, f_end):
    if isCode(GetFlags(head)):
      refs = CodeRefsFrom(head, 0)
      refs = Set(filter(lambda x: x>=f_start and x<=f_end, refs))
      
      if refs:
        next_head = NextHead(head, f_end)
        if isFlow(GetFlags(next_head)):
          refs.add(next_head)
        boundaries.union_update(refs)

        for r in refs:
          if isFlow(GetFlags(r)):
            edges.add((PrevHead(r, f_start), r))
          edges.add((head, r))

  return len(edges) - len(boundaries) + 2

class CyclomaticComplexityChoose(Choose2):
  def __init__(self, title):
    Choose2.__init__(self, title, [ 
        ["Address", 8 | Choose2.CHCOL_HEX], 
        ["Name", 30 | Choose2.CHCOL_PLAIN], 
        ["Complexity", 6 | Choose2.CHCOL_DEC],
        ["Library func", 6 | Choose2.CHCOL_PLAIN] ])

    self.title  = title
    self.colors = (0xF3E2A9, 0xCEF6E3, 0xFA2ECC, 0xFFFF)
    self.items  = []
    self.icon   = 41
    self.PopulateItems()

  def OnClose(self):
    return True

  def OnSelectLine(self, n):
    item = self.items[int(n)]
    jumpto(int(item[0], 16))

  def OnGetLine(self, index):
    return self.items[index]

  def OnGetSize(self):
    return len(self.items)

  def OnDeleteLine(self, n):
    del self.items[n]
    return n

  def OnGetLineAttr(self,n):
    complexity = int(self.items[n][2])
    color = self.colors[0]
    if 10 < complexity <= 20:
      color = self.colors[1]
    elif 20 < complexity <= 50:
      color = self.colors[2]
    elif complexity > 50:
      color = self.colors[3]
    return [color, 0]

  def OnRefresh(self, n):
    return n

  def OnCommand(self, n, cmd_id):
    if cmd_id == self.cmd_exc_lib_funcs:
      self.exclude_lib_funcs()
    return n

  def exclude_lib_funcs(self):
    if not len(self.items):
      return False 
    self.items = [i for i in self.items if i[3] != 'True']
    return True

  def show(self):
    n_functions = len(list(Functions()))
    if n_functions > 0:      
      b = self.Show()
      if b == 0:
        self.cmd_exc_lib_funcs = self.AddCommand("Exclude library functions")
        return True
    else:
      warning("IDA has not identified functions.")
      return False

  def PopulateItems(self):
    for function_ea in Functions():
      self.items.append([
        "%08x" % function_ea, 
        GetFunctionName(function_ea), 
        "%d" % cyclomatic_complexity(function_ea),
        "%s" % ((GetFunctionFlags(function_ea) & FUNC_LIB) != 0)
      ])

def show_choose():
  choose = CyclomaticComplexityChoose("Cyclomatic complexity")
  choose.show()

class CyclomaticComplexity_t(plugin_t):
  flags = PLUGIN_UNL
  comment = "Cyclomatic Complexity"
  help = ""
  wanted_name = "Cyclomatic Complexity"
  wanted_hotkey = ""

  def init(self):
    self.icon_id = 0
    return PLUGIN_OK

  def run(self, arg=0):
    show_choose()

  def term(self):
    pass

def PLUGIN_ENTRY():
  return CyclomaticComplexity_t()

if __name__ == '__main__':
  show_choose()
  


repository: 0xba1100n/VxhunterIDAForkForPython3HighVersion
file: vxhunter_core_py3.py
description: 修复停止维护的Vxhunter IDA插件项目在python3高些的版本下运行的若干问题
wanted_name: VxHunter
comment: VxHunter plugin for IDA Pro
created_at: 2024-08-25 12:47:43+00:00
pushed_at: 2024-08-26 03:26:32+00:00
url:
https://github.com/0xba1100n/VxhunterIDAForkForPython3HighVersion/blob/776dfcea9a73bac0e09623f2976f7aabc2c5075d/vxhunter_core_py3.py

content:
# coding=utf-8
import logging
import struct
import idc
import ida_nalt
import ida_bytes
import ida_funcs
import ida_kernwin
import idaapi
import time


default_check_count = 100

known_address = [0x80002000, 0x10000, 0x1000, 0xf2003fe4, 0x100000, 0x107fe0]

function_name_key_words = ['bzero', 'usrInit', 'bfill']

# VxWorks 5.5
vx_5_sym_types = [
    # 0x00,      # Undefined Symbol
    # 0x01,      # Global (external)
    # 0x02,      # Local Absolute
    0x03,      # Global Absolute
    0x04,      # Local .text
    0x05,      # Global .text
    0x06,      # Local Data
    0x07,      # Global Data
    0x08,      # Local BSS
    0x09,      # Global BSS
    0x12,      # Local Common symbol
    0x13,      # Global Common symbol
    0x40,      # Local Symbols related to a PowerPC SDA section
    0x41,      # Global Symbols related to a PowerPC SDA section
    0x80,      # Local symbols related to a PowerPC SDA2 section
    0x81,      # Local symbols related to a PowerPC SDA2 section
]

# VxWorks 6.8
vx_6_sym_types = [
    # 0x00,  # Undefined Symbol
    # 0x01,  # Global (external)
    # 0x02,  # Local Absolute
    0x03,  # Global Absolute
    0x04,  # Local .text
    0x05,  # Global .text
    0x08,  # Local Data
    0x09,  # Global Data
    0x10,  # Local BSS
    0x11,  # Global BSS
    0x20,  # Local Common symbol
    0x21,  # Global Common symbol
    0x40,  # Local Symbols
    0x41,  # Global Symbols
]

need_create_function = [0x04, 0x05]


class VxTarget(object):
    def __init__(self, firmware, vx_version=5, is_big_endian=False, logger=None):
        """
        :param firmware: data of firmware
        :param vx_version: 5 = VxWorks 5.x; 6= VxWorks 6.x
        :param is_big_endian: True = big endian; False = little endian
        :param logger: logger for the target (default: None)
        """
        self.is_big_endian = is_big_endian
        self._vx_version = vx_version
        self.symbol_table_start = None
        self.symbol_table_end = None
        self._string_table = []
        self._symbol_table = []
        self.symbols = []
        self.load_address = None
        self._firmware = firmware
        self._has_symbol = None
        if self._vx_version == 5:
            self._symbol_interval = 16
        elif self._vx_version == 6:
            self._symbol_interval = 20
        self.start_time = None
        self._performance_status = []

        if logger is None:
            self.logger = logging.getLogger(__name__)
            self.logger.setLevel(logging.INFO)
            console_handler = logging.StreamHandler()
            console_format = logging.Formatter('[%(levelname)-8s][%(module)s.%(funcName)s] %(message)s')
            console_handler.setFormatter(console_format)
            self.logger.addHandler(console_handler)
        else:
            self.logger = logger
        self._firmware_info = {
            "load_address": -1,
            "has_symbol": False,
            "vx_version": self._vx_version,
            "big_endian": self.is_big_endian,
            "symbol_table_start": None,
            "symbol_table_end": None,
            "symbols": None
        }

        self.prepare()

    def reset_timer(self):
        self.start_time = time.time()

    def start_timer(self):
        if self.start_time:
            return False
        else:
            self.start_time = time.time()
            return self.start_time

    def get_timer(self):
        if self.start_time:
            return time.time() - self.start_time
        return False

    def prepare(self):
        """ Trying to find symbol from image.

        :return: True if found symbol, False otherwise.
        """
        self.find_symbol_table()
        if self._has_symbol is False:
            return None
        self.logger.debug("has_symbol: {}".format(self._has_symbol))
        self.get_symbol_table()

    def _check_vxworks_endian(self):
        """ Get image endian from image file.

        :return:
        """
        data1 = self._firmware[self.symbol_table_start + 4:self.symbol_table_start + 4 + self._symbol_interval]
        data2 = self._firmware[self.symbol_table_start + 4 + self._symbol_interval:self.symbol_table_start +
                                                                                   4 + self._symbol_interval * 2]
        if data1[0:2] == data2[0:2]:
            self.logger.info("VxWorks endian: Big endian.")
            self.is_big_endian = True
        elif data1[2:4] == data2[2:4]:
            self.logger.info("VxWorks endian: Little endian.")
            self.is_big_endian = False
        else:
            self.logger.info("VxWorks endian unknown. Assuming little endian.")
            self.is_big_endian = False

    def _check_symbol_format(self, offset):
        """ Check offset is symbol table.

        :param offset: offset from image.
        :return: True if offset is symbol table, False otherwise.
        """
        start_offset = offset
        end_offset = offset + self._symbol_interval * default_check_count
        if end_offset > len(self._firmware):
            return False

        check_data = self._firmware[start_offset:end_offset]
        is_big_endian = True
        is_little_endian = True
        # check symbol data match struct
        for i in range(default_check_count):
            check_data_1 = check_data[i * self._symbol_interval:(i + 1) * self._symbol_interval]
            if len(check_data_1) < self._symbol_interval:
                self.logger.debug("check_data_1 length is too small: {}".format(len(check_data_1)))
                break

            if self._check_symbol_format_simple(check_data_1) is False:
                return False

        if self._vx_version == 5:
            self.logger.debug("Check VxWorks 5 symbol format")
            # check is big endian
            for i in range(9):
                check_data_1 = check_data[4 + i * self._symbol_interval:6 + i * self._symbol_interval]
                data2 = check_data[4 + (i + 1) * self._symbol_interval:6 + (i + 1) * self._symbol_interval]
                if check_data_1 != data2:
                    self.logger.debug("VxWorks binary is not big endian.")
                    is_big_endian = False
                    break

            # check is little endian
            for i in range(9):
                check_data_1 = check_data[6 + i * self._symbol_interval:8 + i * self._symbol_interval]
                data2 = check_data[6 + (i + 1) * self._symbol_interval:8 + (i + 1) * self._symbol_interval]
                if check_data_1 != data2:
                    self.logger.debug("VxWorks binary is not little endian.")
                    is_little_endian = False
                    break

            if is_big_endian and is_little_endian:
                return False

            return is_big_endian ^ is_little_endian

        return True

    def _check_symbol_format_simple(self, data):
        """ Check single symbol format is correct.

        :param data: single symbol data.
        :return: True if data is symbol, False otherwise.
        """
        if self._vx_version == 5:
            # Check symbol type is valid
            sym_type = data[14]
            if sym_type not in vx_5_sym_types:
                return False

            # symbol should end with '\x00'
            if data[15] != 0:
                return False

            # Check symbol group is '\x00\x00'
            if data[12:14] != b'\x00\x00':
                return False

            # symbol_name point should not be zero
            if data[4:8] == b'\x00\x00\x00\x00':
                return False

            # symbol value point should not be zero
            if data[8:12] == b'\x00\x00\x00\x00':
                return False

            return True

        elif self._vx_version == 6:
            # Check symbol type is valid
            sym_type = data[18]
            if sym_type not in vx_6_sym_types:
                return False

            # symbol should end with '\x00'
            if data[19] != 0:
                return False

            # Check symbol group is '\x00\x00'
            if data[16:18] != b'\x00\x00':
                return False

            # symbol_name point should not be zero
            if data[4:8] == b'\x00\x00\x00\x00':
                return False

            # TODO: Need handle this problem
            # sometime data[8:12] will be '\x00\x00\x00\x00'
            # if data[8:12] == '\x00\x00\x00\x00':
            #     return False
            return True

        return False

    def find_symbol_table(self):
        """ Find symbol table from image.

        :return:
        """
        self.reset_timer()
        for offset in range(len(self._firmware) - self._symbol_interval):
            if self.symbol_table_start is None:
                # Get first data valid the symbol_format
                if not self._check_symbol_format_simple(self._firmware[offset: offset + self._symbol_interval]):
                    continue

                elif self._check_symbol_format(offset):
                    self.logger.info("symbol table start offset: {:010x}".format(offset))
                    self.symbol_table_start = offset
                    self._has_symbol = True
                    self._firmware_info["has_symbol"] = True
                    break
            else:
                break
        performance_data = "Find symbol table takes {:.3f} seconds".format(self.get_timer())
        self._performance_status.append(performance_data)
        self.logger.debug(performance_data)

        if self.symbol_table_start:
            self.reset_timer()
            for i in range(self.symbol_table_start, len(self._firmware), self._symbol_interval):
                check_data = self._firmware[i:i + self._symbol_interval]

                if len(check_data) < self._symbol_interval:
                    self.logger.debug("check_data length is too small: {}".format(check_data))
                    break

                if self._check_symbol_format_simple(check_data):
                    self.symbol_table_end = i + self._symbol_interval
                    self.logger.debug("self.symbol_table_end: {:010x}".format(self.symbol_table_end))

                else:
                    self.logger.info("Symbol table end offset: {:010x}".format(self.symbol_table_end))
                    break

        else:
            self.logger.error("Didn't find symbol table in this image")
            self._has_symbol = False

        performance_data = "Analyzer symbol table takes {:.3f} seconds".format(self.get_timer())
        self._performance_status.append(performance_data)
        self.logger.debug(performance_data)

    def get_symbol_table(self):
        """ get symbol table data.

        :return: True if get symbol table data successful, False otherwise.
        """
        if self.symbol_table_start and self.symbol_table_end:
            self._check_vxworks_endian()

        else:
            return False

        for i in range(self.symbol_table_start, self.symbol_table_end, self._symbol_interval):
            symbol_name_addr = self._firmware[i + 4:i + 8]
            symbol_dest_addr = self._firmware[i + 8:i + 12]
            symbol_flag = self._firmware[i + self._symbol_interval - 2]
            if self.is_big_endian:
                unpack_format = '>I'
            else:
                unpack_format = '<I'
            symbol_name_addr = int(struct.unpack(unpack_format, symbol_name_addr)[0])
            symbol_dest_addr = int(struct.unpack(unpack_format, symbol_dest_addr)[0])
            self.logger.debug("symbol_name_addr: {}; symbol_dest_addr: {}".format(symbol_name_addr, symbol_dest_addr))
            self._symbol_table.append({'symbol_name_addr': symbol_name_addr, 'symbol_name_length': None, 'symbol_dest_addr': symbol_dest_addr, 'symbol_flag': symbol_flag, 'offset': i})
        self.logger.debug("len(self._symbol_table): %s".format(len(self._symbol_table)))
        self._symbol_table = sorted(self._symbol_table, key=lambda x: x['symbol_name_addr'])
        for i in range(len(self._symbol_table) - 1):
            self._symbol_table[i]['symbol_name_length'] = self._symbol_table[i + 1]['symbol_name_addr'] - \
                                                          self._symbol_table[i]['symbol_name_addr']
        self.logger.debug("len(self._symbol_table): {}".format(len(self._symbol_table)))
        return True

    @staticmethod
    def _is_printable(c):
        """ Check Char is printable.

        :param c: char to check.
        :return: True if char is printable, False otherwise.
        """
        if type(c) is int:
            return 32 <= c <= 126
        elif type(c) is str:
            return 32 <= ord(c) <= 126

    def _is_func_name(self, string):
        """ Check target string is match function name format.

        :param string: string to check.
        :return: True if string is match function name format, False otherwise.
        """
        #
        bad_str = [b'\\', b'%', b'+', b',', b'&', b'/', b')', b'(', b'[', b']']
        # function name length should less than 512 byte
        if len(string) > 512:
            return False

        for data in bad_str:
            if data in string:
                return False

        for c in string:
            if self._is_printable(c) is False:
                return False
        return True

    def _get_prev_string_data(self, offset):
        """ Get previous string from giving offset.

        :param offset: offset of image.
        :return: string data, string start offset, string end offset.
        """
        while offset > 0:
            if self._firmware[offset] != 0:
                start_address = offset
                end_address = offset + 1
                while offset > 0:
                    if self._firmware[offset - 1] == 0:
                        start_address = offset
                        break
                    offset -= 1
                data = self._firmware[start_address:end_address]
                self.logger.debug("data: {}; start_address: {:010x}; end_address: {:010x}".format(data, start_address, end_address))
                return data, start_address, end_address
            else:
                offset -= 1
        self.logger.debug("Done looking for previous string data.")
        return None, None, None

    def _get_next_string_data(self, offset):
        """ Get next string from giving offset.

        :param offset: offset of image.
        :return: string data, string start offset, string end offset.
        """
        while offset < len(self._firmware):
            if self._firmware[offset] != 0:
                start_address = offset
                end_address = offset
                while offset <= len(self._firmware):
                    offset += 1
                    if self._firmware[offset] == 0:
                        end_address = offset
                        break
                data = self._firmware[start_address:end_address]
                return data, start_address, end_address
            else:
                offset += 1
        return None, None, None

    def find_string_table_by_key_function_index(self, key_offset):
        """ Find string table by VxWorks key function name offset in VxWorks image.

        :param key_offset: key function name offset in VxWorks image.
        :return:
        """
        self.logger.debug("Attempting to find string table by key function index with offset {:010x}".format(key_offset))
        temp_str_tab_data = []
        if len(self._symbol_table) > default_check_count:
            count = default_check_count
        else:
            count = len(self._symbol_table)
        start_offset = key_offset
        end_offset = key_offset
        self.logger.debug("Initializing with start_offset = end_offset = {:010x}".format(key_offset))

        while start_offset > 0:
            if self._is_printable(self._firmware[start_offset]) is True:
                # get string from offset
                string, start_address, end_address = self._get_prev_string_data(start_offset)
                self.logger.debug("string: {}; start_address: {:010x}; end_address: {:010x}".format(string, start_address, end_address))
                # check string is function name
                if self._is_func_name(string) is False:
                    if len(temp_str_tab_data) < count:
                        self.logger.error("Can't find any string table with key index: {}".format(string))
                        return None, None
                    else:
                        self.logger.info("Found string table start address at {:010x}".format(start_address))
                        break
                else:
                    temp_str_tab_data.append((string, start_address, end_address))

                # get previous string from offset
                prev_string, prev_start_address, prev_end_address = self._get_prev_string_data(start_address - 1)
                self.logger.debug("prev_string: {}, prev_start_address: {:010x}, prev_end_address: {:010x}".format(prev_string, prev_start_address, prev_end_address))
                if prev_start_address:
                    # strings interval should less than 4
                    if 4 < (start_address - prev_end_address):
                        if len(temp_str_tab_data) < count:
                            self.logger.error("Can't find any string table with key index: {}".format(string))
                            return None, None
                        else:
                            self.logger.info("found string table start address at {:010x}".format(start_address))
                            break
                    else:
                        start_offset = start_address - 1
                        self.logger.debug("start_offset: {}".format(start_offset))
                else:
                    break
            else:
                start_offset -= 1

        while end_offset < len(self._firmware):
            # find first printable char
            if self._is_printable(self._firmware[end_offset]) is True:
                # get string from offset
                string, start_address, end_address = self._get_next_string_data(end_offset)
                # check string is function name
                if self._is_func_name(string) is False:
                    if len(temp_str_tab_data) < count:
                        temp_str_tab_data = []
                        end_offset = end_address
                        continue
                    else:
                        self.logger.info("found string table end at {:010x}".format(end_address))
                        break

                else:
                    temp_str_tab_data.append((string, start_address, end_address))

                # get next string from offset
                next_string, next_start_address, next_end_address = self._get_next_string_data(end_address)
                if next_start_address:
                    # strings interval should less than 4
                    if 4 < (next_start_address - end_address):
                        if len(temp_str_tab_data) < count:
                            self.logger.error("Can't find any string table with key index.")
                            return None, None
                        else:
                            self.logger.info("Found string table end at {:010x}".format(end_address))
                            break
                    else:
                        end_offset = end_address
            else:
                end_offset += 1

        temp_str_tab_data = sorted(temp_str_tab_data, key=lambda x: (x[1]))
        table_start_offset = temp_str_tab_data[0][1]
        table_end_offset = temp_str_tab_data[-1][2]
        self.logger.info("Found a string table at: {:010x} to {:010x}".format(table_start_offset, table_end_offset))
        return table_start_offset, table_end_offset

    def get_string_table(self, str_start_address, str_end_address):
        """ Get string table data from VxWorks image with string table start and end offset.

        :param str_start_address: string table start address.
        :param str_end_address: string table end address.
        :return:
        """
        self._string_table = []
        offset = str_start_address
        address = offset
        str_tab_data = []
        while offset <= str_end_address:
            if self._firmware[offset] == 0:
                while offset <= str_end_address:
                    offset += 1
                    if self._firmware[offset] != 0:
                        next_address = offset
                        string = self._firmware[address:next_address]
                        length = next_address - address
                        str_tab_data.append({'address': address, 'string': string, 'length': length})
                        offset = next_address
                        address = next_address
                        break
            else:
                offset += 1
        self._string_table = str_tab_data

    def _check_fix(self, func_index, str_index):
        """

        :param func_index:
        :param str_index:
        :return:
        """
        try:
            fault_count = 0
            self.logger.debug("Symbol table's first element: {}".format(self._symbol_table[0]))
            if len(self._symbol_table) <= default_check_count:
                count = len(self._symbol_table)
                self.logger.debug("Length of symbol table, {}, is less than default. Setting iteration count to actual length of table, {}.".format(len(self._symbol_table), count))
            else:
                count = default_check_count
                self.logger.debug("Length of symbol table, {}, is greater than default. Setting iteration count to default, {}.".format(len(self._symbol_table), count))
            for i in range(count):

                if (func_index >= len(self._symbol_table)) or (str_index >= len(self._string_table)):
                    self.logger.debug("_check_fix False: func_index greater than length of _symbol_table, or str_index greater than length of _string_table.")
                    return False
                self.logger.debug("str_index: {}; _string_table[str_index]: {}".format(str_index, self._string_table[str_index]))
                self.logger.debug("func_index: {}; _symbol_table[func_index]: {}".format(func_index, self._symbol_table[func_index]))
                if i == count - 1:
                    if fault_count < 10:
                        self.logger.debug("_check_fix True")
                        return True
                    else:
                        self.logger.debug("_check_fix False: Too many faults.")
                        return False

                if self._string_table[str_index]['length'] == self._symbol_table[func_index]['symbol_name_length']:
                    func_index += 1
                    str_index += 1
                    self.logger.debug("_check_fix continue")

                elif self._symbol_table[func_index]['symbol_name_length'] < self._string_table[str_index]['length']:
                    # Sometime Symbol name might point to mid of string.
                    fault_count += 1
                    func_index += 1
                else:
                    self.logger.debug("_check_fix False: symbol_name_length from func_index larger than length from str_index.")
                    return False
        except Exception as e:
            self.logger.debug(e)
            raise

    def find_loading_address(self):
        """ Find VxWorks image load address by automatic analysis.

        :return: Load address if found, None otherwise.
        """

        # Search function keyword in firmware to locate the function string tables.
        self.reset_timer()
        if self._has_symbol is False:
            return None

        for key_word in function_name_key_words:
            prefix_keyword = b'\x00_' + key_word + b'\x00'
            key_word = b'\x00' + key_word + b'\x00'
            if key_word in self._firmware is False and prefix_keyword in self._firmware is False:
                self.logger.info("Firmware does not contain a function named {}".format(key_word))
                return None

        performance_data = "Search function keyword in firmware takes {:.3f} seconds".format(self.get_timer())
        self._performance_status.append(performance_data)
        self.logger.debug(performance_data)

        # Search function keyword in firmware to locate the function string tables.
        self.reset_timer()
        try:
            key_function_index = self._firmware.index(b'\x00' + function_name_key_words[0] + b'\x00')
            self.logger.debug("key_function_index: {}".format(key_function_index))
        except Exception as err:
            # Handler _ prefix symbols
            self.logger.debug("Exception occurred while loading key_function_index: {}. Continuing...".format(err))
            key_function_index = self._firmware.index(b'\x00_' + function_name_key_words[0] + b'\x00')
            self.logger.debug("key_function_index: {}".format(key_function_index))

        str_start_address, str_end_address = self.find_string_table_by_key_function_index(key_function_index)
        self.get_string_table(str_start_address, str_end_address)
        performance_data = "Get function string table takes {:.3f} seconds".format(self.get_timer())
        self._performance_status.append(performance_data)
        self.logger.debug(performance_data)

        # TODO: Need improve performance
        self.reset_timer()
        self.logger.info("Starting loading address analysis")
        for str_index in range(len(self._string_table)):
            for func_index in range(len(self._symbol_table)):
                self.logger.debug("self._string_table[str_index]['length']: {}".format(self._string_table[str_index]['length']))
                self.logger.debug("self._symbol_table[func_index]['symbol_name_length']: {}".format(self._symbol_table[func_index]['symbol_name_length']))
                if self._string_table[str_index]['length'] == self._symbol_table[func_index]['symbol_name_length']:
                    if self._check_fix(func_index, str_index) is True:
                        self.logger.debug("self._symbol_table[func_index]['symbol_name_addr']: {}".format(self._symbol_table[func_index]['symbol_name_addr']))
                        self.logger.debug("self._string_table[str_index]['address']: %s" % self._string_table[str_index]['address'])
                        self.load_address = self._symbol_table[func_index]['symbol_name_addr'] - \
                                            self._string_table[str_index]['address']
                        self._firmware_info["load_address"] = self.load_address
                        self.logger.info('load address is {:010x}'.format(self.load_address))
                        performance_data = "Analyze loading address takes {:.3f} seconds".format(
                            self.get_timer())
                        self._performance_status.append(performance_data)
                        self.logger.debug(performance_data)
                        return self.load_address
                else:
                    continue

        self.logger.error("We didn't find load address in this firmware, sorry!")
        performance_data = "Analyze loading address takes {:.3f} seconds".format(self.get_timer())
        self._performance_status.append(performance_data)
        self.logger.debug(performance_data)

    def _check_load_address(self, address):
        """

        :param address:
        :return:
        """
        if not self._has_symbol:
            return False
        if len(self._symbol_table) > default_check_count:
            self.logger.debug("Length of symbol table greater than default. Setting iteration count to default of {}.".format(default_check_count))
            count = default_check_count
        else:
            count = len(self._symbol_table)
        self.logger.debug("symbol_table length is {}".format(count))
        for i in range(count):
            offset = self._symbol_table[i]['symbol_name_addr'] - address
            if offset <= 0:
                return False
            # TODO: Need improve, currently use string point to check.
            string, str_start_address, str_end_address = self._get_next_string_data(offset)
            if str_start_address != offset:
                self.logger.info("String {} at offset {} didn't match symbol table.".format(string, offset))
                return False
        self.logger.info('Load address is {:010x}'.format(address))
        return True

    def quick_test(self):
        """ Using known load address list to match VxWorks image.

        :return: Load address if match known address, None otherwise.
        """
        if self._has_symbol is False:
            return None
        self.logger.debug("has_symbol: {}".format(self._has_symbol))
        for address in known_address:
            if self._check_load_address(address):
                self.load_address = address
                self._firmware_info["load_address"] = self.load_address
                return self.load_address
            else:
                self.logger.info('Load address is not {:010x}'.format(address))

    def cleanup(self):
        """ Clean up variables.

        :return:
        """
        self.is_big_endian = False
        self.symbol_table_start = None
        self.symbol_table_end = None
        self._string_table = []
        self._symbol_table = []
        self.load_address = None
        self._has_symbol = None

    def get_string_from_firmware_by_offset(self, string_offset):
        symbol_name = ""
        while True:
            if self._firmware[string_offset] != 0x00:
                symbol_name += chr(self._firmware[string_offset])
                string_offset += 1

            else:
                break

        return symbol_name

    def get_symbols(self):
        self.symbols = []
        if self.load_address:
            for symbol in self._symbol_table:
                symbol_name_addr = symbol["symbol_name_addr"]
                symbol_dest_addr = symbol["symbol_dest_addr"]
                symbol_flag = symbol["symbol_flag"]
                symbol_name_firmware_addr = symbol_name_addr - self.load_address
                symbol_name = self.get_string_from_firmware_by_offset(symbol_name_firmware_addr)
                self.symbols.append({
                    "symbol_name": symbol_name,
                    "symbol_name_addr": symbol_name_addr,
                    "symbol_dest_addr": symbol_dest_addr,
                    "symbol_flag": symbol_flag
                })
            return self.symbols

        else:
            return None

    def get_firmware_info(self):
        symbols = self.get_symbols()
        self._firmware_info["symbols"] = symbols
        return self._firmware_info

    def get_performance_status(self):
        return self._performance_status

def demangle_function(demangle_string):
    function_return = None
    function_parameters = None
    function_name_end = len(demangle_string)

    # get parameters
    index = len(demangle_string) - 1
    if demangle_string[-1] == ')':
        # have parameters
        parentheses_count = 0
        while index >= 0:
            if demangle_string[index] == ')':
                parentheses_count += 1

            elif demangle_string[index] == '(':
                parentheses_count -= 1

            index -= 1

            if parentheses_count == 0:
                break

        function_parameters = demangle_string[index + 2:-1]
        function_name_end = index

    # get function name
    while index >= 0:
        if demangle_string[index] == ' ':
            break
        else:
            index -= 1
    function_name_start = index
    function_name = demangle_string[function_name_start + 1:function_name_end + 1]

    # get function return
    function_return = demangle_string[:function_name_start]
    return function_return, function_name, function_parameters


def is_vx_symbol_file(file_data, is_big_endian=True):
    # Check key function names
    for key_function in function_name_key_words:
        if key_function.encode(encoding='utf-8') not in file_data:
            print("key function not found")
            return False

    if is_big_endian:
        return struct.unpack('>I', file_data[:4])[0] == len(file_data)

    else:
        return struct.unpack('<I', file_data[:4])[0] == len(file_data)


# --------------------------------------------------------------------------
# Plugin
# --------------------------------------------------------------------------
class AutoFixIDBForm(idaapi.Form):
    def __init__(self):
        self.invert = False
        self.vx_version = 5
        super(AutoFixIDBForm, self).__init__(
            r"""BUTTON YES* Start analyze

VxHunter Auto Fix IDB will fix IDB with VxWorks symbol table.

Please choose VxWorks main version
{FormChangeCb}
<VxWorks Main Version     :{c_vxversion}>
            """, {
                'FormChangeCb': self.FormChangeCb(self.OnFormChange),
                'c_vxversion': self.DropdownListControl(
                    items=("5", "6"),
                    readonly=True,
                    selval=0),

            }
        )

        self.Compile()

    def OnFormChange(self, fid):
        if fid == -2:
            self.vx_version = (5, 6)[self.GetControlValue(self.c_vxversion)]
            return 1


class FixCodeForm(idaapi.Form):
    def __init__(self):
        self.invert = False
        self.start_address = 0
        self.end_address = 0
        super(FixCodeForm, self).__init__(
            r"""BUTTON YES* Fix Code from image

VxHunter fix code will make all data as code from start_address to end_address. 

Please input start address and end address
{FormChangeCb}
<Start address     :{c_StartAddress}>
<End address     :{c_EndAddress}>
            """, {
                'FormChangeCb': self.FormChangeCb(self.OnFormChange),
                'c_StartAddress': self.NumericInput(value=self.start_address, swidth=40, tp=self.FT_ADDR),
                'c_EndAddress': self.NumericInput(value=self.end_address, swidth=40, tp=self.FT_ADDR),
            }
        )
        self.Compile()

    def OnFormChange(self, fid):
        if fid == -2:
            self.start_address = self.GetControlValue(self.c_StartAddress)
            self.end_address = self.GetControlValue(self.c_EndAddress)
            return 1


class FixAsciiForm(idaapi.Form):
    def __init__(self):
        self.invert = False
        self.string_address = 0
        super(FixAsciiForm, self).__init__(
            r"""BUTTON YES* Fix Code from image

VxHunter fix ascii string. 

Please input string table start address.
{FormChangeCb}
<String Address     :{c_Address}>
            """, {
                'FormChangeCb': self.FormChangeCb(self.OnFormChange),
                'c_Address': self.NumericInput(value=self.string_address, swidth=40, tp=self.FT_ADDR),
            }
        )
        self.Compile()

    def OnFormChange(self, fid):
        if fid == -2:
            self.string_address = self.GetControlValue(self.c_Address)
            return 1


class VxHunter_Plugin_t(idaapi.plugin_t):
    comment = "VxHunter plugin for IDA Pro"
    help = ""
    wanted_name = "VxHunter"
    wanted_hotkey = ""
    flags = idaapi.PLUGIN_KEEP

    def init(self):
        # register popup menu handlers
        try:
            # Register Auto Fix IDB handler
            VxHunterMCFixIDB.register(self, "Auto Fix IDB With symbol table")
            # Register Fix Code handler
            VxHunterMCFixCode.register(self, "Fix Code from start address to end address")
            # Register Fix Ascii handler
            VxHunterMCFixAscii.register(self, "Fix Ascii string table with giving address")
            # Register Load symbol file handler
            VxHunterMCLoadSymbolFile.register(self, "Load VxWorks symbol file")

        except Exception as err:
            print("Got Error!!!: %s" % err)

        # setup popup menu
        if idaapi.IDA_SDK_VERSION >= 700:
            # Add menu IDA >= 7.0
            idaapi.attach_action_to_menu("Edit/VxHunter/", VxHunterMCFixIDB.get_name(), idaapi.SETMENU_APP)
            idaapi.attach_action_to_menu("Edit/VxHunter/", VxHunterMCFixCode.get_name(), idaapi.SETMENU_APP)
            idaapi.attach_action_to_menu("Edit/VxHunter/", VxHunterMCFixAscii.get_name(), idaapi.SETMENU_APP)
            idaapi.attach_action_to_menu("Edit/VxHunter/", VxHunterMCLoadSymbolFile.get_name(), idaapi.SETMENU_APP)
        else:
            # add Vxhunter menu
            menu = idaapi.add_menu_item("Edit/VxHunter/", "Auto Fix IDB1", "", 1, self.handler_auto_fix_idb, None)
            if menu is not None:
                pass

        print("=" * 80)
        return idaapi.PLUGIN_KEEP

    def term(self):
        pass

    # null handler
    def menu_null(self):
        pass

    def handler_auto_fix_idb(self):
        form = AutoFixIDBForm()
        ok = form.Execute()
        if ok == 1:
            vx_version = int(form.vx_version)
            print("vx_version:%s" % vx_version)
            firmware_path = idaapi.get_input_file_path()
            firmware = open(firmware_path, 'rb').read()
            target = VxTarget(firmware=firmware, vx_version=vx_version)
            # target.logger.setLevel(logging.DEBUG)
            target.quick_test()

            if target.load_address:
                print("Load Address is:%s" % target.load_address)
            else:
                target.find_loading_address()
                if target.load_address:
                    print("Load Address is:%s" % target.load_address)
            if not target.load_address:
                return
            symbol_table_start = target.symbol_table_start
            symbol_table_end = target.symbol_table_end
            load_address = target.load_address
            self.fix_vxworks_idb(load_address, vx_version, symbol_table_start, symbol_table_end)
        form.Free()

    def handler_fix_code(self):
        form = FixCodeForm()
        ok = form.Execute()
        if ok == 1:
            start_address = int(form.start_address)
            end_address = int(form.end_address)
            self.fix_code(start_address, end_address)
        form.Free()

    def handler_fix_ascii(self):
        form = FixAsciiForm()
        ok = form.Execute()
        if ok == 1:
            string_address = int(form.string_address)
            self.fix_ascii(string_address)

        form.Free()

    def handler_load_symbol_file(self):
        self.load_symbol_file()

    @staticmethod
    def fix_vxworks_idb(load_address, vx_version, symbol_table_start, symbol_table_end):
        current_image_base = idaapi.get_imagebase()
        symbol_interval = 16
        if vx_version == 6:
            symbol_interval = 20
        symbol_table_start += load_address
        symbol_table_end += load_address
        ea = symbol_table_start
        shift_address = load_address - current_image_base
        while shift_address >= 0x70000000:
            idaapi.rebase_program(0x70000000, 0x0008)
            shift_address -= 0x70000000
        idaapi.rebase_program(shift_address, 0x0008)
        while ea < symbol_table_end:
            # for VxWorks 6 unknown symbol format
            if idc.get_wide_byte(ea + symbol_table_end - 2) == 3:
                ea += symbol_interval
                continue
            offset = 4
            idc.create_strlit(idc.get_wide_dword(ea + offset), idc.BADADDR)
            sName = idc.get_strlit_contents(idc.get_wide_dword(ea + offset), -1, ida_nalt.STRTYPE_C)
            print("Found %s in symbol table" % sName)
            if sName:
                sName_dst = idc.get_wide_dword(ea + offset + 4)
                if vx_version == 6:
                    sName_type = idc.get_wide_byte(ea + 0x12)
                else:
                    sName_type = idc.get_wide_byte(ea + 0x0e)

                idc.set_name(sName_dst, sName.decode('utf-8'), idc.SN_CHECK)
                if sName_type in need_create_function:
                    print("Start fix Function %s at %s" % (sName, hex(sName_dst)))
                    idc.create_insn(sName_dst)  # might not need
                    ida_funcs.add_func(sName_dst, idc.BADADDR)
            ea += symbol_interval
        print("Fix function by symbol table finish.")
        print("Start IDA auto analysis, depending on the size of the firmware this might take a few minutes.")
        idaapi.auto_wait()

    @staticmethod
    def fix_code(start_address, end_address):
        # Todo: There might be some data in the range of codes.
        offset = start_address
        while offset <= end_address:
            offset = ida_bytes.next_addr(offset)
            flags = ida_bytes.get_full_flags(offset)
            if not ida_bytes.is_code(flags):
                # Todo: Check should use MakeCode or MakeFunction
                # idc.create_insn(offset)
                ida_funcs.add_func(offset)

    @staticmethod
    def get_prev_ascii_string_address(address):
        """

        :param address: must be current ascii string start address.
        :return:
        """
        prev_string_start_address = address
        # string table interval should less than 5 bytes.
        if idc.get_wide_dword(address - 5) == 0:
            return None
        else:
            prev_string_start_address -= 5
            # TODO: Need handle short string.
            while idaapi.get_byte(prev_string_start_address) != 0:
                prev_string_start_address -= 1
            return prev_string_start_address + 1

    @staticmethod
    def get_next_ascii_string_address(address):
        """

        :param address: must be current ascii string start address.
        :return:
        """
        next_string_start_address = address
        # find current string end address
        while idaapi.get_byte(next_string_start_address) != 0:
            next_string_start_address += 1

        # string table interval should less than 5 bytes.
        # TODO: need handle short string.
        if idc.get_wide_dword(next_string_start_address + 1) == 0:
            return None

        while idaapi.get_byte(next_string_start_address) == 0:
            next_string_start_address += 1

        return next_string_start_address

    def get_string_table_start_address(self, address):
        string_table_start_address = address
        # find current string start address
        while idaapi.get_byte(string_table_start_address - 1) != 0:
            string_table_start_address -= 1

        while self.get_prev_ascii_string_address(string_table_start_address):
            string_table_start_address = self.get_prev_ascii_string_address(string_table_start_address)

        return string_table_start_address

    def fix_ascii(self, address):
        string_table_start_address = self.get_string_table_start_address(address)
        string_address = string_table_start_address
        while True:
            if string_address:
                print("Start Make string at address: %s" % hex(string_address))
                idc.create_strlit(string_address, idc.BADADDR)
                string_address = self.get_next_ascii_string_address(string_address)
            else:
                break

    def load_symbols(self, file_data, is_big_endian=True):
        symbol_list = []
        if is_big_endian:
            unpack_format = '>I'
        else:
            unpack_format = '<I'

        symbol_count = struct.unpack(unpack_format, file_data[4:8])[0]
        print("symbol_count: %s" % symbol_count)
        symbol_offset = 8
        string_table_offset = 8 + 8 * symbol_count
        print("string_table_offset: %s" % string_table_offset)
        # get symbols
        for i in range(symbol_count):
            offset = i * 8
            symbol_data = file_data[symbol_offset + offset:symbol_offset + offset + 8]
            #print(symbol_data[0])
            flag = ord(chr(symbol_data[0]))
            string_offset = struct.unpack(unpack_format, b'\x00' + symbol_data[1:4])[0]
            string_offset += string_table_offset
            print("string_offset: %s" % string_offset)
            symbol_name = ""
            while True:
                try:
                    if file_data[string_offset] != b'\x00':
                        symbol_name += chr(file_data[string_offset])
                        string_offset += 1
                    else:
                        break
                except IndexError:
                    break
                
            print("symbol_name: %s" % symbol_name)
            symbol_address = struct.unpack(unpack_format, symbol_data[-4:])[0]
            symbol_list.append([flag, symbol_name, symbol_address])
            # Find TP-Link device loading address with symbols
            if "wrs_kernel_text_start" in symbol_name:
                load_address = symbol_address
                current_image_base = idaapi.get_imagebase()
                shift_address = load_address - current_image_base
                while shift_address >= 0x70000000:
                    idaapi.rebase_program(0x70000000, 0x0008)
                    shift_address -= 0x70000000
                idaapi.rebase_program(shift_address, 0x0008)

        # load symbols
        for symbol_data in symbol_list:
            flag, symbol_name, symbol_address = symbol_data
            idc.set_name(symbol_address, symbol_name, idc.SN_CHECK)
            if flag == 0x54:
                if symbol_name:
                    print("Start fix Function %s at %s" % (symbol_name, hex(symbol_address)))
                    idc.create_insn(symbol_address)  # might not need
                    ida_funcs.add_func(symbol_address, idc.BADADDR)

    def load_symbol_file(self):
        symbol_file_path = ida_kernwin.ask_file(0, "*", "Please chose the VxWorks symbol file")
        print("symbol_file_path: {}".format(symbol_file_path))
        symbol_file_data = open(symbol_file_path.encode(encoding='utf-8'), 'rb').read()
        if is_vx_symbol_file(symbol_file_data):
            self.load_symbols(symbol_file_data)
            idaapi.auto_wait()

        else:
            return

    def run(self, arg):
        self.handler_auto_fix_idb()


try:
    class VxHunterMenuContext(idaapi.action_handler_t):

        @classmethod
        def get_name(self):
            return self.__name__

        @classmethod
        def get_label(self):
            return self.label

        @classmethod
        def register(self, plugin, label):
            self.plugin = plugin
            self.label = label
            instance = self()
            return idaapi.register_action(idaapi.action_desc_t(
                self.get_name(),  # Name. Acts as an ID. Must be unique.
                instance.get_label(),  # Label. That's what users see.
                instance  # Handler. Called when activated, and for updating
            ))

        @classmethod
        def unregister(self):
            """Unregister the action.
            After unregistering the class cannot be used.
            """
            idaapi.unregister_action(self.get_name())

        @classmethod
        def activate(self, ctx):
            # dummy method
            return 1

        @classmethod
        def update(self, ctx):
            try:
                if ctx.form_type == idaapi.BWN_DISASM:
                    return idaapi.AST_ENABLE_FOR_FORM
                else:
                    return idaapi.AST_DISABLE_FOR_FORM
            except:
                # Add exception for main menu on >= IDA 7.0
                return idaapi.AST_ENABLE_ALWAYS

    # context menu for Fix idb
    class VxHunterMCFixIDB(VxHunterMenuContext):
        def activate(self, ctx):
            self.plugin.handler_auto_fix_idb()
            return 1

    class VxHunterMCFixCode(VxHunterMenuContext):
        def activate(self, ctx):
            self.plugin.handler_fix_code()
            return 1

    class VxHunterMCFixAscii(VxHunterMenuContext):
        def activate(self, ctx):
            self.plugin.handler_fix_ascii()
            return 1

    class VxHunterMCLoadSymbolFile(VxHunterMenuContext):
        def activate(self, ctx):
            self.plugin.handler_load_symbol_file()
            return 1


except Exception as err:
    # TODO: Add some handle later.
    print("err: {}".format(err))


# register IDA plugin
def PLUGIN_ENTRY():
    return VxHunter_Plugin_t()


repository: alexander-hanel/msdocsviewer
file: ida_plugin/msdocviewida.py
wanted_name: API MSDN Docs
comment: API MSDN Docs
created_at: 2023-10-22 19:33:37+00:00
pushed_at: 2023-12-29 15:21:27+00:00
description:
msdocsviewer is a simple tool that parses Microsoft's win32 API and driver documentation to be used within IDA.

url:
https://github.com/alexander-hanel/msdocsviewer/blob/53aac70cbdf081604da68c2df8400da6bbec18b4/ida_plugin/msdocviewida.py

content:
"""
Author: Alexander Hanel
Version: 1.1
Purpose: Microsoft Document (sdk-api & Driver) document viewer for IDA. 
Updates:
    * Version 1.0   - Release
    * Version 1.1   - Fixed issues with opening and closing widget 
"""

import os
import ida_kernwin
import ida_idaapi
import ida_name

from idaapi import PluginForm
from PyQt5 import QtWidgets

# Path to the Markdown docs. Folder should start with 
API_MD = r"!!CHANGE ME!!"

# global variables used to track initialization/creation of the forms.  
started = False
frm = None 

def get_selected_api_name():
    """
    get selected item and extract function name from it 
    via https://github.com/idapython/src/blob/e1c108a7df4b5d80d14d8b0c14ae73b924bff6f4/Scripts/msdnapihelp.py#L48
    return: api name as string 
    """
    try:
        v = ida_kernwin.get_current_viewer()
        name, ok = ida_kernwin.get_highlight(v)
        if not ok:
            # print("No identifier was highlighted")
            return None 
        t = ida_name.FUNC_IMPORT_PREFIX
        if name.startswith(t):
            return name[len(t):]
        return name
    except:
        return None 

class MSDN(PluginForm):
    def OnCreate(self, form):
        """
        defines widget layout 
        """
        self.parent = self.FormToPyQtWidget(form)
        self.main_layout = QtWidgets.QVBoxLayout()
        self.markdown_viewer_label = QtWidgets.QLabel()
        self.markdown_viewer_label.setText("API MSDN Docs")
        self.markdown_viewer = QtWidgets.QTextEdit()
        self.markdown_viewer.setReadOnly(True)
        self.main_layout.addWidget(self.markdown_viewer)
        self.parent.setLayout(self.main_layout)
        self.load_markdown()

    def load_markdown(self):
        """
        gets api and load corresponding (if present) api markdown 
        """
        api_name = get_selected_api_name()
        if not api_name:
            api_markdown ="#### Invalid Address Selected"
            self.markdown_viewer.setMarkdown(api_markdown)
            return
        md_path = os.path.join(API_MD, api_name + ".md" )
        if os.path.isfile(md_path):
            with open(md_path, "r") as infile:
                api_markdown = infile.read()
        else:
                api_markdown ="#### docs for %s could not be found" % api_name
        self.markdown_viewer.setMarkdown(api_markdown)

    def OnClose(self, form):
        """
        Called when the widget is closed
        """
        global frm
        global started
        del frm 
        started = False

class MSDNPlugin(ida_idaapi.plugin_t):
    flags = ida_idaapi.PLUGIN_MOD
    comment = "API MSDN Docs"
    help = ""
    wanted_name = "API MSDN Docs"
    wanted_hotkey = "Ctrl-Shift-Z"

    def init(self):
        self.options = (ida_kernwin.PluginForm.WOPN_MENU |
            ida_kernwin.PluginForm.WOPN_ONTOP |
            ida_kernwin.PluginForm.WOPN_RESTORE |
            ida_kernwin.PluginForm.WOPN_PERSIST |
            ida_kernwin.PluginForm.WCLS_CLOSE_LATER)
        return ida_idaapi.PLUGIN_KEEP

    def run(self, arg):
        global started
        global frm  
        if not started:
            #API_MD
            if not os.path.isdir(API_MD):
                print("ERROR: API_MD directory could not be found. Make sure to execute python run_me_first.py ")
            frm = MSDN()
            frm.Show("MSDN API Docs: hotkey: Ctrl-Shift-Z", options=self.options)
            started = True
        else:
            frm.load_markdown()
        
    def term(self):
        pass

# -----------------------------------------------------------------------
def PLUGIN_ENTRY():
    return MSDNPlugin()


repository: mrexodia/REToolSync
file: plugins/IDA/REToolSync.py
description: Collaboration platform for reverse engineering tools.
wanted_name: REToolSync
comment: Tool for collaborative reverse engineering
created_at: 2022-08-09 16:27:03+00:00
pushed_at: 2024-12-20 10:23:49+00:00
url:
https://github.com/mrexodia/REToolSync/blob/74b27a24108231a86e522fd14133565eb86958d9/plugins/IDA/REToolSync.py

content:
from __future__ import print_function

import os
import json
import asyncio
import queue
import threading
import traceback
import struct
import time
from typing import Tuple

# pip install tornado
import tornado.ioloop
import tornado.websocket
import tornado.httpclient

import ida_pro
import ida_hexrays
import ida_kernwin
import ida_gdl
import ida_lines
import ida_idaapi
import idc
import idaapi
import idautils
import ida_nalt
import ida_bytes

# Source: https://github.com/OALabs/hexcopy-ida/blob/8b0b2a3021d7dc9010c01821b65a80c47d491b61/hexcopy.py#L30
major, minor = map(int, idaapi.get_kernel_version().split("."))
using_ida7api = (major > 6)
using_pyqt5 = using_ida7api or (major == 6 and minor >= 9)

if using_pyqt5:
    import PyQt5.QtGui as QtGui
    import PyQt5.QtCore as QtCore
    import PyQt5.QtWidgets as QtWidgets
    from PyQt5.Qt import QApplication

else:
    import PySide.QtGui as QtGui
    import PySide.QtCore as QtCore
    QtWidgets = QtGui
    QtCore.pyqtSignal = QtCore.Signal
    QtCore.pyqtSlot = QtCore.Slot
    from PySide.QtGui import QApplication

# A module that helps with writing thread safe ida code.
# Based on:
# https://web.archive.org/web/20160305190440/http://www.williballenthin.com/blog/2015/09/04/idapython-synchronization-decorator/
import logging

import functools
import idaapi

class IDASyncError(Exception):
    pass

# Important note: Always make sure the return value from your function f is a
# copy of the data you have gotten from IDA, and not the original data.
#
# Example:
# --------
#
# Do this:
#
#   @idaread
#   def ts_Functions():
#       return list(idautils.Functions())
#
# Don't do this:
#
#   @idaread
#   def ts_Functions():
#       return idautils.Functions()
#

logger = logging.getLogger(__name__)

# Enum for safety modes. Higher means safer:
class IDASafety:
    SAFE_NONE = 0
    SAFE_READ = 1
    SAFE_WRITE = 2


call_stack = queue.LifoQueue()


def sync_wrapper(ff, safety_mode: IDASafety):
    """
    Call a function ff with a specific IDA safety_mode.
    """
    #logger.debug('sync_wrapper: {}, {}'.format(ff.__name__, safety_mode))

    if safety_mode not in [IDASafety.SAFE_READ, IDASafety.SAFE_WRITE]:
        error_str = 'Invalid safety mode {} over function {}'\
                .format(safety_mode, ff.__name__)
        logger.error(error_str)
        raise IDASyncError(error_str)

    # No safety level is set up:
    res_container = queue.Queue()

    def runned():
        #logger.debug('Inside runned')

        # Make sure that we are not already inside a sync_wrapper:
        if not call_stack.empty():
            last_func_name = call_stack.get()
            error_str = ('Call stack is not empty while calling the '
                'function {} from {}').format(ff.__name__, last_func_name)
            #logger.error(error_str)
            raise IDASyncError(error_str)

        call_stack.put((ff.__name__))
        try:
            res_container.put(ff())
        except Exception:
            traceback.print_exc()
            res_container.put(None)
        finally:
            call_stack.get()
            #logger.debug('Finished runned')

    ret_val = idaapi.execute_sync(runned, safety_mode)
    res = res_container.get()
    return res


def idawrite(f):
    """
    decorator for marking a function as modifying the IDB.
    schedules a request to be made in the main IDA loop to avoid IDB corruption.
    """
    @functools.wraps(f)
    def wrapper(*args, **kwargs):
        ff = functools.partial(f, *args, **kwargs)
        ff.__name__ = f.__name__
        return sync_wrapper(ff, idaapi.MFF_WRITE)
    return wrapper

def idaread(f):
    """
    decorator for marking a function as reading from the IDB.
    schedules a request to be made in the main IDA loop to avoid
      inconsistent results.
    MFF_READ constant via: http://www.openrce.org/forums/posts/1827
    """
    @functools.wraps(f)
    def wrapper(*args, **kwargs):
        ff = functools.partial(f, *args, **kwargs)
        ff.__name__ = f.__name__
        return sync_wrapper(ff, idaapi.MFF_READ)
    return wrapper

# This freezes and doesn't gracefully error
# @idaread
def get_image_size():
    try:
        # https://www.hex-rays.com/products/ida/support/sdkdoc/structidainfo.html
        info = idaapi.get_inf_structure()
        omin_ea = info.omin_ea
        omax_ea = info.omax_ea
    except AttributeError:
        import ida_ida
        omin_ea = ida_ida.inf_get_omin_ea()
        omax_ea = ida_ida.inf_get_omax_ea()
    # Bad heuristic for image size (bad if the relocations are the last section)
    image_size = omax_ea - omin_ea
    # Try to extract it from the PE header
    header = idautils.peutils_t().header()
    if header and header[:4] == b"PE\0\0":
        image_size = struct.unpack("<I", header[0x50:0x54])[0]
    return image_size

@idaread
def get_static_info():
    return {
        'path': idaapi.get_input_file_path(),
        'module': idaapi.get_root_filename(),
        'base': hex(idaapi.get_imagebase()),
        'size': hex(get_image_size()),
        'md5': ida_nalt.retrieve_input_file_md5().hex(),
        'sha256': ida_nalt.retrieve_input_file_sha256().hex(),
        'crc32': hex(ida_nalt.retrieve_input_file_crc32()),
        'filesize': hex(ida_nalt.retrieve_input_file_size()),
    }

@idaread
def get_cursor() -> int:
    return idaapi.get_screen_ea()

@idaread
def get_selection() -> Tuple[bool, int, int]:
    if idaapi.IDA_SDK_VERSION >= 700:
        return idaapi.read_range_selection(None)
    return idaapi.read_selection()

@idaread
def goto_address(address: int) -> None:
    return ida_kernwin.jumpto(address)

# Reference: https://www.georgeho.org/tornado-websockets/
# IDAPython cheat sheet: https://gist.github.com/icecr4ck/7a7af3277787c794c66965517199fc9c
# https://github.com/inforion/idapython-cheatsheet
class WebSocketClient:
    def __init__(self, io_loop):
        self.connection: tornado.websocket.WebSocketClientConnection = None
        self.periodic_cb = tornado.ioloop.PeriodicCallback(self.timeout, 250)
        self.io_loop = io_loop
        self.num_messages = 0
        self.connection_count = 0
        self.reconnect_count = 0
        self.info = get_static_info()
        self.cursor_info = {}
        self.last_cursor = 0
        self.last_selection = None

    def start(self):
        self.connect_and_read()

    def stop(self):
        if self.connection:
            self.connection.close()
            self.connection = None
        if self.periodic_cb.is_running():
            self.periodic_cb.stop()
        self.io_loop.stop()

    def timeout(self):
        cursor_info = {}
        cursor = get_cursor()
        selection = get_selection()

        cursor_info["address"] = hex(cursor)
        ok, start, end = selection
        if ok:
            cursor_info["selection_start"] = hex(start)
            cursor_info["selection_end"] = hex(end)
        else:
            cursor_info["selection_start"] = None
            cursor_info["selection_end"] = None


        if not cursor_info == self.cursor_info:
            print(f"[REToolSync] cursor change: ({json.dumps(self.cursor_info)}) -> ({json.dumps(cursor_info)})")
            if self.connection:
                self.connection.write_message(json.dumps({"cursor": [cursor_info]}))

            self.cursor_info = cursor_info

        return

        # TODO: remove this?
        if not selection == self.last_selection:
            ok, start, end = selection
            if start > end:
                start, end = end, start
            print(f"[REToolSync] range selection: ({ok}, {hex(start)}, {hex(end - 1)})")
            self.last_selection = selection

        cursor = get_cursor()
        if not cursor == self.last_cursor:
            print(f"[REToolSync] sending cursor change {hex(self.last_cursor)} -> {hex(cursor)}")
            self.last_cursor = cursor

    def connect_and_read(self):
        endpoint = os.environ.get("RETOOLSYNC_ENDPOINT", "127.0.0.1:6969")
        request = tornado.httpclient.HTTPRequest(f"ws://{endpoint}/REToolSync", headers={"User-Agent": f"REToolSync IDA {os.getpid()}"})
        print(f"[REToolSync] Connecting to {endpoint} ...")
        tornado.websocket.websocket_connect(
            url=request,
            callback=self.maybe_retry_connection,
            on_message_callback=self.on_message,
            ping_interval=10,
            ping_timeout=30,
        )

    def maybe_retry_connection(self, future) -> None:
        try:
            self.connection = future.result()
            print("[REToolSync] Connected to server!")
            self.periodic_cb.start()
            self.connection.write_message(json.dumps(self.info))
            self.connection_count += 1
        except ConnectionError as x:
            self.reconnect_count += 1
            if self.connection_count > 0 and self.reconnect_count < 10:
                print("[REToolSync] Could not reconnect, retrying in 3 seconds...")
                self.io_loop.call_later(3, self.connect_and_read)
            else:
                print("[REToolSync] Failed to connect, use the menu to retry")
                self.stop()
        except Exception as x:
            print(f"[REToolSync] exception: {x}, {type(x)}")
            raise x

    def on_message(self, message: str):
        if message is None:
            print("[REToolSync] Disconnected, reconnecting in 3 seconds...")
            self.periodic_cb.stop()
            self.io_loop.call_later(3, self.connect_and_read)
            return

        self.num_messages += 1

        msg: dict = json.loads(message)
        request = msg.get("request", None)
        if request == "goto":
            address = int(msg["address"], 16)
            print(f"[REToolSync] Goto: {hex(address)}")
            goto_address(address)
        else:
            print(f"[REToolSync] Unsupported message: {message}")

    def write_message(self, message):
        if self.connection:
            self.connection.write_message(message)

def join_gui_thread(thread: threading.Thread, timeout=None):
    iterations = 0
    iteration_timeout = 0.1
    while True:
        if not thread.is_alive():
            return True
        thread.join(iteration_timeout)
        QtWidgets.QApplication.processEvents()
        if timeout is not None and iteration_timeout * iterations >= timeout:
            return False
        iterations += 1

# jump to cursor: ida_kernwin.jumpto
class Service:
    def __init__(self) -> None:
        self.started = False
        self.client: WebSocketClient = None
        self.wsthread: threading.Thread = None

    def start(self):
        self.stop()
        self.wsthread = threading.Thread(target=self.service_thread)
        self.wsthread.start()
        self.started = True
        print("[REToolSync] Service started")

    def stop(self):
        if not self.started:
            return

        if self.client is not None:
            self.client.stop()

        if not join_gui_thread(self.wsthread, 1.0):
            print("[REToolSync] Waiting for service to stop...")
            if not join_gui_thread(self.wsthread, 5.0):
                print(f"[REToolSync] deadlock while stopping service, please report an issue!\n")
        self.wsthread = None
        self.client = None
        print("[REToolSync] Service stopped")

    def service_thread(self):
        # Create a new event loop for the thread
        # https://github.com/tornadoweb/tornado/issues/2308#issuecomment-372582005
        loop = asyncio.new_event_loop()
        loop.set_debug(False)
        logging.getLogger("asyncio").setLevel(logging.CRITICAL)  # Remove some debug spam
        asyncio.set_event_loop(loop)

        # Before starting the event loop, instantiate a WebSocketClient and add a
        # callback to the event loop to start it. This way the first thing the
        # event loop does is to start the client.
        io_loop = tornado.ioloop.IOLoop.current()
        self.client = WebSocketClient(io_loop)
        io_loop.add_callback(self.client.start)

        # Start the event loop.
        io_loop.start()

        # Signal that the service is finished
        self.started = False

# a plugin interface, boilerplate code
class my_plugin_t(ida_idaapi.plugin_t):
    flags = ida_idaapi.PLUGIN_KEEP
    wanted_name = "REToolSync"
    wanted_hotkey = ""
    comment = "Tool for collaborative reverse engineering"
    help = ""

    def init(self):
        self.service = Service()
        self.service.start()
        return idaapi.PLUGIN_KEEP

    def term(self):
        self.service.stop()

    def run(self, arg):
        self.service.start()

def PLUGIN_ENTRY():
    return my_plugin_t()


repository: tomsons26/tomsons_RE_scripts
file: where_dis.py
wanted_name: WhereDis
comment: Check where this symbol was in
created_at: 2017-04-02 23:01:59+00:00
pushed_at: 2025-01-27 12:14:22+00:00
url:
https://github.com/tomsons26/tomsons_RE_scripts/blob/8329a2b15eb001a055ae728a07407940da3f9489/where_dis.py

content:
# Where Dis
# by tomsons26
#
# Adds the ability to check what module address relates to
#	to do this it uses a premade list, list is a text file in this format
#	(0x00563D10, 0x00564840, "movies.cpp")
#	list can have comments prefixing with #
#
# The option to use it shows up in rightclick menu of the disassembly view
#	'WhereDis' where dis'es
#	'Reload WhereDis' reloads the list
#	'Debug WhereDis' checks for duplicates in list
#
# TODO:
#	cleanup??? surely all this hook stuff isn't needed...?
#
#	Copy the 'where_dis.py' into the plugins directory of IDA
#	modify WHERE_DIS_INFO_PATH as needed

WHERE_DIS_INFO_PATH = "D://Temp//" + "where_info" + ".txt"

import sys
from ast import literal_eval
import ida_kernwin
import ida_lines
import idaapi

popup_action_names = []

class hook_helper(idaapi.UI_Hooks):
	def __init__(self):
		idaapi.UI_Hooks.__init__(self)

	def finish_populating_widget_popup(self, form, popup):
		global popup_action_names
		form_type = idaapi.get_widget_type(form)
		if form_type == idaapi.BWN_DISASM:
			for action_name in popup_action_names:
				idaapi.attach_action_to_popup(form, popup, action_name, None)

class action_helper(idaapi.action_handler_t):
	def __init__(self):
		idaapi.action_handler_t.__init__(self)

	def update(self, ctx):
		return idaapi.AST_ENABLE_FOR_WIDGET if ctx.form_type == idaapi.BWN_DISASM else idaapi.AST_DISABLE_FOR_WIDGET

where_dis = None

def ReloadWhereDisFile():
	global where_dis
	f = open(WHERE_DIS_INFO_PATH, "r")
	if f:
		where_dis = [list(literal_eval(line.strip('#'))) for line in f]
		where_dis.append((0x00000000, 0xFFFFFFFF, "Unknown"))
		f.close()
	else:
		print("WhereDis can't open %s" % WHERE_DIS_INFO_PATH)

class WhereDisPlugin(idaapi.plugin_t):
	flags = idaapi.PLUGIN_HIDE | idaapi.PLUGIN_PROC
	comment = "Check where this symbol was in"
	help = ""
	wanted_name = "WhereDis"
	wanted_hotkey = ""

	def init(self):
		global where_dis
		idaapi.msg("WhereDis init\n")
		AddToPopup('wheredisaction:where_action', 'WhereDis', WhereDisAction(), None, None)
		AddToPopup('wheredisaction:reload_action', 'Reload WhereDis', WhereDisReloadAction(), None, None)
		AddToPopup('wheredisaction:debug_action', 'Debug WhereDis', WhereDisDebugAction(), None, None)
		
		ReloadWhereDisFile()

		self.hooks = hook_helper()
		self.hooks.hook()
		return idaapi.PLUGIN_KEEP

	def run(self):
		idaapi.msg("WhereDis run\n")

	def term(self):
		idaapi.msg("WhereDis term\n")
		if self.hooks:
			self.hooks.unhook()
		idaapi.unregister_action('wheredisaction:where_action')
		idaapi.unregister_action('wheredisaction:reload_action')
		idaapi.unregister_action('wheredisaction:debug_action')

class WhereDisAction(action_helper):
	def activate(self, ctx):
		global where_dis
		
		if len(where_dis) == 0:
			print("where_dis empty!!!!!!")
			return 1
		
		t0, t1, view = idaapi.twinpos_t(), idaapi.twinpos_t(), idaapi.get_current_viewer()
		if idaapi.read_selection(view, t0, t1):
			start, end = t0.place(view).toea(), t1.place(view).toea()
			end += idaapi.get_item_size(end)
		else:
			start = idaapi.get_screen_ea()

			if start == idaapi.BADADDR:
				return 0

			end = start + idaapi.get_item_size(start)

		if start == idaapi.BADADDR:
			return 0

		if start == end:
			return 0

		x = start
		while x < end:
			#print("WhereDis at %x" % x)
			address = x
			for astart, aend, filename in where_dis:
				if address >= astart and address < aend:
					print(format("0x%08X - %s" % (address, filename)))
					break

			isize = idaapi.get_item_size(x)
			if isize != 0:
				x = x + isize
			else:
				x = x + 1

		return 1
		
class WhereDisReloadAction(action_helper):
	def activate(self, ctx):
		global where_dis
		if len(where_dis) != 0:
			del where_dis[:]
		ReloadWhereDisFile()
		#print(where_dis)
		print("WhereDis list reloaded")
		return 1
		
class WhereDisDebugAction(action_helper):
	def activate(self, ctx):
		global where_dis
		if len(where_dis) != 0:
			# check for duplicates
			for i, (s1, en1, f1) in enumerate(where_dis):
				for j, (s2, en2, f2) in enumerate(where_dis):
					if i == j:
						continue
					if s1 > s2 and s1 < en2:
						if s1 != 0 and s2 != 0:
							print("0x%08X overlaps 0x%08X" % (s1, s2))
		else:
			print("where_dis empty!!!!!!")
		
		
		print("WhereDis list debug end")
		return 1

def AddToPopup(action_name, display, handler, shortcut, tooltip, icon=None):
	global popup_action_names

	if tooltip == None:
		tooltip = action_name

	if idaapi.register_action(idaapi.action_desc_t(action_name, display, handler, shortcut, tooltip)):
		popup_action_names.append(action_name)
	else:
		print('WhereDis Error registering action %s' % (action_name))
 
def PLUGIN_ENTRY(*args, **kwargs):
	return WhereDisPlugin()


repository: ri-char/PwnWithIDA
file: remoteIdaPlugin/remoteIDA.py
description: A IDA plugin with a python library which allows you to pwn using IDA more easily in CTF challenge.
wanted_name: RemoteIDA
comment: Control IDA by TCP
created_at: 2020-12-28 13:57:16+00:00
pushed_at: 2021-01-27 10:30:16+00:00
url:
https://github.com/ri-char/PwnWithIDA/blob/bf3bda2e080047b32c672d0bd864d0202a12091e/remoteIdaPlugin/remoteIDA.py

content:
# -*-coding:utf-8 -*-
import inspect
import ctypes
import socket
import threading
import traceback
import sys
import idaapi
import ida_dbg
import ida_kernwin

if sys.version_info.major==2:
    from SocketServer import *
else:
    from socketserver import *

RI_BASE_PORT = 9945


class MainTCPHandler(BaseRequestHandler):
    def handle(self):
        idaapi.msg("Accepting connection from {}\n".format(
            self.client_address[0]))
        while True:
            try:
                data = self.request.recv(1024).decode()
                if len(data) == 0:
                    break
                command = data.strip().split(' ')
                if len(command) == 0:
                    continue
                idaapi.msg('recv> '+' '.join(command)+'\n')
                result = False
                if command[0] == 'attachWithExit':
                    if ida_kernwin.execute_sync(ida_dbg.is_debugger_on, 0):
                        ida_kernwin.execute_sync(ida_dbg.exit_process, 0)
                    if len(command) == 1:
                        pid = ida_kernwin.execute_sync(
                            lambda: ida_dbg.attach_process(-1), 0) == 1
                    else:
                        pid = ida_kernwin.execute_sync(
                            lambda: ida_dbg.attach_process(int(command[1])), 0) == 1
                    result = (pid == 1)
                elif command[0] == 'continue':
                    result = ida_kernwin.execute_sync(
                        ida_dbg.continue_process, 0) == 1
                elif command[0] == 'detach':
                    result = ida_kernwin.execute_sync(
                        ida_dbg.detach_process, 0)
                elif command[0] == 'attach':
                    if len(command) == 1:
                        pid = ida_kernwin.execute_sync(
                            lambda: ida_dbg.attach_process(-1), 0) == 1
                    else:
                        pid = ida_kernwin.execute_sync(
                            lambda: ida_dbg.attach_process(int(command[1])), 0) == 1
                    result = (pid == 1)
                elif command[0] == 'exit':
                    result = ida_kernwin.execute_sync(ida_dbg.exit_process, 0)
                elif command[0] == 'isDebugging':
                    result = ida_kernwin.execute_sync(
                        ida_dbg.is_debugger_on, 0)

                idaapi.msg('res>  '+str(result)+'\n')
                if result:
                    self.request.sendall(b'T\n')
                else:
                    self.request.sendall(b'F\n')
            except:
                traceback.print_exc()

        idaapi.msg("Closing connection from {}\n".format(
            self.client_address[0]))


class RIAction(idaapi.action_handler_t):
    @classmethod
    def get_name(self):
        return 'RemoteIDA:'+self.__name__

    @classmethod
    def get_label(self):
        return self.label

    @classmethod
    def register(self, plugin, label):
        self.plugin = plugin
        self.label = label
        instance = self()
        return idaapi.register_action(idaapi.action_desc_t(
            self.get_name(),  # Name. Acts as an ID. Must be unique.
            instance.get_label(),  # Label. That's what users see.
            instance  # Handler. Called when activated, and for updating
        ))

    @classmethod
    def unregister(self):
        idaapi.unregister_action(self.get_name())

    @classmethod
    def activate(self, ctx):
        return 1

    @classmethod
    def update(self, ctx):
        try:
            if ctx.form_type == idaapi.BWN_DISASM:
                return idaapi.AST_ENABLE_FOR_FORM
            else:
                return idaapi.AST_DISABLE_FOR_FORM
        except Exception as e:
            # Add exception for main menu on >= IDA 7.0
            return idaapi.AST_ENABLE_ALWAYS


class RIStartServer(RIAction):
    def activate(self, ctx):
        self.plugin.startServer()
        return 1


class RIStopServer(RIAction):
    def activate(self, ctx):
        self.plugin.stopServer()
        return 1


class RIRestartServer(RIAction):
    def activate(self, ctx):
        self.plugin.restartServer()
        return 1


def _async_raise(tid, exctype):
    """raises the exception, performs cleanup if needed"""
    tid = ctypes.c_long(tid)
    if not inspect.isclass(exctype):
        exctype = type(exctype)
    res = ctypes.pythonapi.PyThreadState_SetAsyncExc(
        tid, ctypes.py_object(exctype))
    if res == 0:
        raise ValueError("invalid thread id")
    elif res != 1:
        # """if it returns a number greater than one, you're in trouble,
        # and you should call it again with exc=NULL to revert the effect"""
        ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, None)
        raise SystemError("PyThreadState_SetAsyncExc failed")


def stop_thread(thread):
    _async_raise(thread.ident, SystemExit)


class RemoteIDA(idaapi.plugin_t):
    flags = idaapi.PLUGIN_HIDE
    comment = "Control IDA by TCP"
    wanted_name = "RemoteIDA"  # 插件的名称，在IDA界面导航栏中显示 Edit->Plugins->myplugin
    wanted_hotkey = ""
    help = ""
    server = None

    def init(self):
        RIStartServer.register(self, 'Start')
        RIStopServer.register(self, 'Stop')
        RIRestartServer.register(self, 'Restart')

        idaapi.attach_action_to_menu(
            "Edit/RemoteIDA/Start", RIStartServer.get_name(), idaapi.SETMENU_APP)
        idaapi.attach_action_to_menu(
            "Edit/RemoteIDA/Stop", RIStopServer.get_name(), idaapi.SETMENU_APP)
        idaapi.attach_action_to_menu(
            "Edit/RemoteIDA/Restart", RIRestartServer.get_name(), idaapi.SETMENU_APP)

        return idaapi.PLUGIN_KEEP

    def run(self, arg):
        pass

    def term(self):
        self.stopServer()
        idaapi.unregister_action(RIStartServer.get_name())
        idaapi.unregister_action(RIRestartServer.get_name())
        idaapi.unregister_action(RIStopServer.get_name())

    def startServer(self):
        if self.server != None:
            idaapi.msg(">>> There is a running server\n")
            return
        i = 0
        while True:
            try:
                server = TCPServer(
                    ("", RI_BASE_PORT+i), MainTCPHandler)
                self.server = threading.Thread(target=server.serve_forever)
                self.server.start()
                break
            except socket.error:
                i += 1
        idaapi.msg(">>> Start server at port "+str(RI_BASE_PORT+i)+'\n')

    def stopServer(self):
        if self.server != None:
            stop_thread(self.server)
            idaapi.msg(">>> Stop server\n")
            self.server = None
        else:
            idaapi.msg(">>> There is no running server\n")

    def restartServer(self):
        self.stopServer()
        self.startServer()


def PLUGIN_ENTRY():
    return RemoteIDA()


repository: matthewrhannon/IDAPython-Automation-Scripts
file: rdata_fix.py
created_at: 2024-09-24 12:53:57+00:00
pushed_at: 2024-09-24 13:32:30+00:00
description:
Various scripts written in IDAPython to perform various tasks to augment reverse engineering using IDA Pro. Most of the scripts perform some form of automation for signatures, deobfuscation, cleaning sections such as .pdata and RUNTIME_FUNCTION SEH exception handling, LLM Auto-Reversing Application (functions, variables, structures, etc..)

url:
https://github.com/matthewrhannon/IDAPython-Automation-Scripts/blob/cad9d3a5bb5a92ac65736b674a7166348a7ebcc6/rdata_fix.py

content:
#try:
#   import ida_idaapi as idaapi
#except Exception:
#   print("WARNING: ida_idaapi un-importable! Falling back to idaapi...")
#import idaapi
   #pass
   
from idaapi import *
from idautils import *
from idc import *
from ida_bytes import del_items, DELIT_EXPAND

import ida_kernwin
import ida_segment
import ida_hexrays
import ida_funcs
import ida_nalt
import ida_funcs
import ida_bytes
import ida_ua

import idautils
import idc

import sark
import time
import random
import re

from goto import with_goto

print("Script rdata_fix.py 4/12/2024 - updated 4/25/2024")

def wait_for_one_sec():
    idc.qsleep(1000)
    return 1
    
# get the flags
#f = ida_bytes.get_flags(caller)
# no code there?
#if not ida_bytes.is_code(f):
#    ida_ua.create_insn(caller)

def AddressToEA(addr):
    """Converts a Ghidra Address object to an ea"""
    return ea_t.init(addr.getOffset(), addr)

def eaToAddress(ea):
    """Converts an effective address to a Ghidra Address object"""

    if isinstance(ea, ea_t):
        return ea.address
    elif idautils.is_number(ea):
        addrs = idaapi._currentProgram.parseAddress("0x%x" % ea)
        # Return the first memory address
        for addr in addrs:
            if addr.isMemoryAddress():
                return addr
    else:
        # No conversion needed
        return ea
        
    return 0
    
def go_to_address_via_string(string_to_find, retAddr):

    string_to_go = int(string_to_find, 16)
    #string_to_go = ida_bytes.create_data(string_to_find, FF_QWORD, 8, idc.BADADDR)
    #string_to_go = eaToAddress(string_to_go)
    #print("int(str((string_to_go, 16))=" + int(str(string_to_go), 16))
    if string_to_go != idc.BADADDR:
    
        start_ea = 0
        end_ea = 0
        is_func = False
        
        #print("DEBUG: before BEFORE...")
        #print("BEFORE: string_to_go=" + str(string_to_go) + ":" + " string_to_find=" + str(string_to_find))
        #string_to_go = hex(string_to_find)
        #string_to_go = eaToAddress(string_to_find)
        #print("AFTER: string_to_go=" + hex(string_to_go) + ":" + " string_to_find=" + hex(string_to_find))
        
        #print("string_to_go=" + hex(string_to_go) + ":" + " string_to_find=" + str(string_to_find))
        #string_to_go = idc.to_ea(string_to_go)
        #idc.jumpto(string_to_go)
        #string_to_go = hex(string_to_go)
        #string_to_go = AddressToEA(string_to_go)
        ida_kernwin.jumpto(string_to_go)
    
    
    #string_to_go = idaapi._currentProgram.parseAddress(string_to_find)
    if not ida_funcs.get_func(string_to_go):
        #print("NOTE: NOT A FUNCTION - " + hex(string_to_go) + "!")
        #ida_kernwin.jumpto(retAddr)
        #return 1
        
        #now = here()
        #print(('[+] CurPos: ' + hex(now)))     
        
        is_func = False

        if not ida_bytes.del_items(string_to_go, idc.DELIT_EXPAND, string_to_go+9):
            print("ERROR: couldn't delete string_to_go=" + hex(string_to_go) + " throguh string_to_go+9=" + hex(string_to_go+9) + " Returning!")
            ida_kernwin.jumpto(retAddr)
            
            return 0
        else:
        
            ida_kernwin.jumpto(string_to_find)
            
            #ida_bytes.del_items(here()-1, ida_bytes.DELIT_SINGLE, 1)
            ida_bytes.del_items(here()+0, ida_bytes.DELIT_SINGLE, 0x17)
            #ida_bytes.del_items(here()+1, ida_bytes.DELIT_SINGLE, 1)

            ida_kernwin.jumpto(string_to_find)
            
            '''
            ea1 = idc.here()
            #idc.AnalyzeArea(ea1)
            ida_bytes.del_items(here(), 0)
            ida_bytes.del_items(here()+1, 0)
            ida_bytes.del_items(here()-1, 0)
            
            size = 9
            ida_bytes.del_items(here(), DELIT_EXPAND, size)
            ida_bytes.del_items(here()-size, DELIT_EXPAND, size)
            '''
            
            #MakeUnkn(here(), DOUNK_SIMPLE)
            #ida_bytes.del_items(start_ea, DELIT_SIMPLE, end_ea - start_ea)
            #create_insn(here())
            
            ea = idc.here()
            end_ea = ea+1
            while ea < end_ea:
                # Attempt to create an
                insn_len = idc.create_insn(ea)
                if insn_len > 0:
                    #print(f"Instruction created at {hex(ea)}")
                    ea += insn_len  # Move to the next address after the created instruction
                else:
                    print(f"Failed to create instruction at {hex(ea)}")
                    ea += 1  # Move to the next byte and try again
                    
            #idaapi.create_insn(ea1)
            #insn = ida_ua.insn_t()
            #ida_ua.create_insn(insn, string_to_go)
            if not ida_funcs.add_func(string_to_find):
                print(f"Failed to create function at {hex(string_to_go)}")
            #else:
                #print(f"Function successfully created at {hex(string_to_go)}")
                
            ida_kernwin.jumpto(retAddr)
            
            return 1
    else:
        #print("NOTE: IS A FUNCTION - " + hex(string_to_go) + "!")
        
        now = idc.here()
        #print(('[+] CurPos: ' + hex(now)))
        
        cur_func = idc.get_name_ea_simple(idc.get_func_name(idc.here()))
        #print(('[+] CurFunc: ' + hex(cur_func)))
        
        start_ea = idc.get_func_attr(now, idc.FUNCATTR_START)
        end_ea = idc.get_func_attr(now, idc.FUNCATTR_END)
        #print(('[+] FuncStart: ' + hex(func_start)))
        #print(('[+] FuncEnd: ' + hex(func_end)))        
        
        is_func = True
    
        #ida_funcs.del_func(start_ea)
        
        if not ida_bytes.del_items(start_ea, ida_bytes.DELIT_EXPAND, end_ea - start_ea):  
            print("WARNING: couldn't delete function starting at start_ea=" + hex(start_ea) + " Returning!")
            ida_kernwin.jumpto(retAddr)
            
            return 0
        else:
        
            ida_kernwin.jumpto(string_to_find)

            #.del_items(string_to_go.ea, ida_bytes.DELIT_EXPAND, string_to_go.address+9):
            #ida_bytes.del_items(idc.here()-1, ida_bytes.DELIT_EXPAND, 1)
            ida_bytes.del_items(idc.here(), ida_bytes.DELIT_EXPAND, 0x17)
            #ida_bytes.del_items(idc.here()+1, ida_bytes.DELIT_EXPAND, 1)
            
            ida_kernwin.jump(string_to_find)
            
            '''
            ea2 = idc.here()
            #idaapi.create_insn(ea2)
            #idc.AnalyzeArea(ea2)
            #ida_bytes.del_items(here(), 0)
            ida_bytes.del_items(here()+1, 0)
            ida_bytes.del_items(here()-1, 0)
            
            size = 9
            ida_bytes.del_items(here(), DELIT_EXPAND, size)
            ida_bytes.del_items(here()-size, DELIT_EXPAND, size)
            '''
            
            ea = idc.here()
            while ea < end_ea:
                # Attempt to create an
                insn_len = idc.create_insn(ea)
                if insn_len > 0:
                    #print(f"Instruction created at {hex(ea)}")
                    ea += insn_len  # Move to the next address after the created instruction
                else:
                    print(f"Failed to create instruction at {hex(ea)}")
                    ea += 1  # Move to the next byte and try again
            #MakeUnkn(here(), DOUNK_SIMPLE)
            #create_insn(here())
            #insn = ida_ua.insn_t()
            #ida_ua.create_insn(insn, string_to_go)      
            if not ida_funcs.add_func(string_to_find):
                print(f"Failed to create function at {hex(string_to_go)}")
            #else:
                #print(f"Function successfully created at {hex(string_to_go)}")
                
            ida_kernwin.jumpto(retAddr)
            
            return 1
    #else:
    #    print("WARNING: if string_to_go == idc.BADADDR! Returning!") 
    #    return 0  
  
    return 1
    
def find_rdata_start():
    for s in idautils.Segments():
        if idc.get_segm_name(s) == ".rdata":
            #print("Found .rdata start @ 0x" + hex(s) + ".")
            return s
        elif s != "":
            continue
        elif s == "":
            print("s == \"\"")
            return None
        elif s == idc.BADADDR:
            print("s == idc.BADADDR")
            return None
        else:
            print("WARNING: else encounted...")
            return None

#Credit: https://reverseengineering.stackexchange.com/questions/13454/get-a-list-of-global-variables-with-ida-python
#Second option - iteration over lines
def get_segment_names2(name):
    seg = sark.Segment(name=name)
    for line in seg.lines:
        yield line.ea, sark.Line(ea=line.ea)
        #if line.has_name:
        #    yield line.ea, line.name
    return
 
def add_hex(a, b):
  """Adds two hexadecimal numbers.

  Args:
    a: A hexadecimal number.
    b: A hexadecimal number.

  Returns:
    The sum of a and b in hexadecimal.
  """

  # Convert the hexadecimal numbers to integers.
  a = int(a, 16)
  b = int(b, 16)

  # Add the integers.
  sum = a + b

  # Convert the sum back to hexadecimal.
  sum = hex(sum)

  # Return the sum.
  return sum
 
ea = 0
name = 0
Good = False
count_num = 0

#/dq\s+offset\s+(?:(?!\+).)*\+.*/g
#dq\s+offset\s+(?:(?!\+).)*\+.*
regexme = re.compile('dq\s+offset\s+(?:(?!\+).)*\+.*')
UnWantedPattern0 = re.compile(r"\+\+")
UnWantedPattern1 = re.compile(r"\;")

@with_goto
def Main():

    print("NOTE: inside Main...")
    ida_kernwin.show_wait_box("Processing")
    
    global Good
    global ea
    global name
    global count_num
    good_to_go = 0
    
    try:
        rdata_start = find_rdata_start()
        if rdata_start == None:
            print("ERROR: rdata_start == None")
            return False
        #else:
            #print("rdata_start=" + hex(rdata_start) + "...")

        #print("Lines inside .rdata:")
        if good_to_go == 1:
            label .bar_begin
            print("...OUT OF GOTO...")
            good_to_go = 0
            #continue
        else:
            good_to_go = 1
                    
        for ea, name in get_segment_names2('.rdata'):
            if ida_kernwin.user_cancelled():
                #print("0x"+ hex(ea) + ": " + str(name))
                #print("WARNING: ida_kernwin.user_cancelled exit route! count_num="  + str(count_num))
                Good = False
                break
                
            else:

                reggy = regexme.search(str(name))
                if reggy != None:
                    badMatch0 = UnWantedPattern0.search(str(name))
                    
                    if badMatch0 != None:
                        #print("WARNING: ++ found... ea=" + hex(ea))
                        continue
                    else:   
                        badMatch1 = UnWantedPattern1.search(str(name))
                        if badMatch1 != None:
                            #print("WARNING: ; found...ea=" + hex(ea))
                            continue
                        else:
                            third_part = str(str(name).split()[3])
                            #print("third_part=" + str(third_part))
                            address_and_plus_sign_and_number = str(third_part.split("loc_")[1])
                            #print("address_and_plus_sign_and_number=" + str(address_and_plus_sign_and_number))
                            address = str(address_and_plus_sign_and_number.split("+")[0])
                            #print("address=" + str(address))
                            number = str(address_and_plus_sign_and_number.split("+")[1])
                            #print("number=" + str(number))
                            
                            address_int = int("0x" + str(address), 16)
                            address_hex = hex(address_int)
                                                        
                            number_int = int("0x" + str(number), 16)
                            number_hex = hex(number_int)

                            #print("222DEBUG222")
                            #print("address_hex=" + str(address_hex))
                            #print("number_hex=" + str(number_hex))
                            #x1 = int(str(name).split()[3].split("loc_")[1].split('+")[0])
                            #print("x1=" + str(x1))
                            #x2 = int(str(name).split()[3].split("loc_")[1].split('+")[1])
                            #print("x2=" + str(x2))
                            #x = hex(addrsss_hex) + hex(number_hex)
                            x = add_hex(address_hex, number_hex)
                            print(str(x))
                            #if not x:
                            #    print("ERROR: x == bad nummber! hex(x)=" + hex(x))
                            #    break
                            #else:
                            #    print(s
                            #    if go_to_address_via_string(x, ea) == 0:
                            #        print("WARNING: go_to_address_via_hex(" + str(x) + ") == 0!")
                            #       continue
                            #    else:
                            #        print("!!! GOT ONE !!!")
                                    
                            #        count_num = count_num + 1
                            continue
                else:
                    #print("WTF")
                    continue

        #print("Done printing .rdata lines! count_num=" + str(count_num))
        Good = True

    except Exception as e:
        print("WARNING: exception happened! str(e)=" + str(e))
        print("(ea, name, Good)=(" + hex(ea) + ", " + str(name) + ", " + str(Good) + ")")
        #pass
        goto .bar_begin
    finally:
        if Good == False:
            print(hex(ea) + ": " + str(name))
            print("WARNING: finally exit route! count_num="  + str(count_num))
            ida_kernwin.hide_wait_box()
            return False
        else:
            print("SUCCESS: good rdata_fix.py run!")
            ida_kernwin.hide_wait_box()
            return True
    
'''
    now = here()
    print(('[+] CurPos: ' + hex(now)))
    cur_func = get_name_ea_simple(get_func_name(here()))
    print(('[+] CurFunc: ' + hex(cur_func)))
    func_start = idc.get_func_attr(now, FUNCATTR_START)
    func_end = idc.get_func_attr(now, FUNCATTR_END)
    print(('[+] FuncStart: ' + hex(func_start)))
    print(('[+] FuncEnd: ' + hex(func_end)))
    
    curr_addr = func_start
    while curr_addr < func_end:
        disasm = generate_disasm_line(curr_addr, 1)
        print((hex(curr_addr) + '\t' + disasm))
        
        is_obfuscated = False
            
        #Obfuscated Pattern Start
        if ('short near ptr' in disasm):
            next_disasm = generate_disasm_line(next_head(curr_addr), 1)
            if not 'nop' in next_disasm:
                if disasm[0] == 'j':
                    is_obfuscated = True
        elif (', cs:dword' in disasm):
            next_disasm = generate_disasm_line(next_head(curr_addr), 1)
            if 'add' in next_disasm:
                next_disasm = generate_disasm_line(next_head(next_head(next_head(curr_addr))), 1)
                if 'cmp' in next_disasm:
                    start_addr = curr_addr
                    end_addr = 0
                    while end_addr == 0:
                        disasm = generate_disasm_line(start_addr, 1)
                        print((hex(start_addr) + ' - ' + disasm))
                        if ('short' in disasm) and (disasm[0] == 'j'):
                            end_addr = start_addr
                            break
                        start_addr = next_head(start_addr)
                    if end_addr:
                        for i in range(curr_addr, end_addr):
                            idc.patch_byte(i, 0x90)
                        curr_addr = end_addr
                        is_obfuscated = True
        elif ('jz' in disasm):
            prev_disasm = generate_disasm_line(prev_head(curr_addr), 1)
            next_disasm = generate_disasm_line(next_head(curr_addr), 1)
            if not 'nop' in next_disasm:
                if 'cmp' in prev_disasm:
                    if get_operand_value(prev_head(curr_addr), 1) == 0xE8:
                        is_obfuscated = True
        #Obfuscated Pattern End
        
        if (is_obfuscated):
            jmp_addr = get_operand_value(curr_addr,0)
            jmp_next = next_head(jmp_addr)
            print(('[!] Found obfuscated jmp at ' + hex(curr_addr) + ' to ' + hex(jmp_addr)))
            for i in range(curr_addr, jmp_addr):
                idc.patch_byte(i, 0x90)
            break
        curr_addr = next_head(curr_addr)                #is curr_addr already equal to jmp_next value?
'''

    
def PLUGIN_ENTRY():
    print("Starting .rdata fixer...")
    
    retValue = Main()
    if retValue != True:
        print("ERROR: retValue != True!")
    else:
        print("Successfully ran script!")

    #idaapi.compile_idc_text('static fn1() { RunPythonStatement("run()"); }')
    #add_idc_hotkey("F1", 'fn1')


repository: sandin/seida
file: seida.py
description: Symbolic Execution plugin for IDA
url: https://github.com/sandin/seida/blob/43a782f934c870b8603e4ee43c3c53c0d822743e/seida.py
created_at: 2022-06-16 13:58:14+00:00
pushed_at: 2022-09-15 04:43:35+00:00
content:
import re
import xmlrpc.client
import ida_idaapi
import ida_kernwin
import idautils
import idc

SEIDA_USE_AS_SCRIPT = True
SEIDA_SERVER_ADDR = "http://10.37.129.2:50052"  # TODO

seida = None  # global export API
START_EXEC_ACTION_NAME = "execution:start_exec"
STEP_INST_ACTION_NAME = "execution:step_inst"
STEP_BLOCK_ACTION_NAME = "execution:step_block"
STEP_TO_ACTION_NAME = "execution:step_to"
ICON_START = bytes([
    0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52,
    0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x08, 0x06, 0x00, 0x00, 0x00, 0x1F, 0xF3, 0xFF,
    0x61, 0x00, 0x00, 0x00, 0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xAE, 0xCE, 0x1C, 0xE9, 0x00, 0x00,
    0x00, 0xE9, 0x49, 0x44, 0x41, 0x54, 0x38, 0x4F, 0xA5, 0x93, 0x3B, 0x0A, 0xC2, 0x40, 0x10, 0x86,
    0xBF, 0x59, 0x4B, 0x1B, 0x8F, 0xE0, 0x11, 0x04, 0x1F, 0xA9, 0x3D, 0x86, 0x60, 0xA2, 0xBD, 0xB5,
    0x9D, 0x47, 0xF0, 0x00, 0xC1, 0xCA, 0x68, 0x91, 0xD2, 0x42, 0xAF, 0xA0, 0xA0, 0x16, 0x1E, 0xC1,
    0x23, 0x58, 0xF8, 0x0A, 0x58, 0x8C, 0xE0, 0x8B, 0xB8, 0x44, 0x8D, 0x64, 0x61, 0x60, 0x61, 0x77,
    0xBE, 0x99, 0xF9, 0xF7, 0x5F, 0x21, 0xE3, 0x92, 0x8C, 0xF9, 0xBC, 0x03, 0x96, 0xEB, 0x06, 0x4E,
    0x35, 0xFC, 0x07, 0x6A, 0x01, 0x56, 0x73, 0x20, 0x0F, 0xB9, 0x1E, 0x4E, 0x79, 0x96, 0x06, 0x64,
    0x03, 0x36, 0x40, 0xE9, 0x96, 0x28, 0x4C, 0xD1, 0xDC, 0xE0, 0x17, 0xC8, 0x06, 0x6C, 0x81, 0xA2,
    0x55, 0x79, 0x82, 0xD0, 0xA7, 0x56, 0x5B, 0x24, 0x75, 0x64, 0x03, 0x76, 0x40, 0x21, 0xB9, 0x75,
    0xF5, 0x39, 0x9D, 0xBA, 0xD4, 0xEB, 0x87, 0xF8, 0xB9, 0x0D, 0x50, 0xE0, 0x7C, 0x0B, 0x25, 0x42,
    0x1E, 0x7B, 0x88, 0x80, 0xCB, 0x3D, 0x4C, 0x88, 0x53, 0xF1, 0x9F, 0x90, 0x77, 0x40, 0x30, 0x8A,
    0x01, 0x34, 0x42, 0xE4, 0x0E, 0x7B, 0x01, 0xE4, 0x82, 0x68, 0x88, 0xE7, 0x7D, 0x04, 0x7C, 0x1E,
    0x41, 0xF1, 0x39, 0xEE, 0xBB, 0x74, 0x3A, 0x5F, 0x46, 0x08, 0x46, 0x09, 0x22, 0xEA, 0x04, 0x63,
    0xFA, 0xB8, 0x6E, 0x0A, 0x11, 0x83, 0xF1, 0x06, 0x34, 0xF6, 0x8C, 0x66, 0x40, 0xAB, 0xF9, 0xD5,
    0x0F, 0xB6, 0x06, 0x0F, 0x23, 0x99, 0xDE, 0xAF, 0xC4, 0x64, 0x11, 0x87, 0xE3, 0x06, 0x6D, 0x37,
    0x83, 0x95, 0xD3, 0x78, 0xD7, 0xBA, 0x93, 0xF9, 0x37, 0x5E, 0x01, 0xE3, 0xF3, 0x52, 0x11, 0x35,
    0xB6, 0x22, 0xEE, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82
])
ICON_STEP_FORWARD = bytes([
    0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52,
    0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x08, 0x06, 0x00, 0x00, 0x00, 0x1F, 0xF3, 0xFF,
    0x61, 0x00, 0x00, 0x00, 0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xAE, 0xCE, 0x1C, 0xE9, 0x00, 0x00,
    0x00, 0xE0, 0x49, 0x44, 0x41, 0x54, 0x38, 0x4F, 0x63, 0x64, 0x40, 0x03, 0x22, 0xF2, 0x55, 0x92,
    0x6F, 0x1E, 0xB6, 0x3D, 0x47, 0x17, 0xC7, 0xC5, 0x67, 0x44, 0x97, 0x10, 0x56, 0xAC, 0xDC, 0xFF,
    0x9F, 0x81, 0x71, 0xE9, 0xBB, 0xFB, 0x6D, 0x73, 0xB0, 0x69, 0x12, 0x52, 0xAA, 0x6A, 0x78, 0x77,
    0xAF, 0xAD, 0x01, 0x26, 0x87, 0x69, 0x80, 0x52, 0xD5, 0x7F, 0x88, 0xE4, 0xFF, 0xAE, 0xB7, 0xF7,
    0xDA, 0xCB, 0x91, 0x0D, 0x11, 0x51, 0xA8, 0x70, 0xF8, 0xCF, 0xC4, 0xB4, 0xFF, 0xED, 0xBD, 0x36,
    0xB8, 0x3E, 0x3C, 0x06, 0x80, 0xB5, 0xAE, 0xE3, 0xFC, 0xF5, 0x3D, 0xE6, 0xC9, 0x93, 0xFE, 0xEF,
    0x20, 0x0E, 0x39, 0x06, 0x30, 0x30, 0x32, 0x30, 0x5C, 0xFA, 0xFB, 0x9F, 0x21, 0xF6, 0xFD, 0xFD,
    0xB6, 0x4B, 0x64, 0x19, 0x00, 0xF5, 0xC2, 0x77, 0x06, 0x86, 0xFF, 0x31, 0x8C, 0xFF, 0xFE, 0xBF,
    0x23, 0xD5, 0x0B, 0xA8, 0xE1, 0xC8, 0xC8, 0x58, 0xCE, 0xF0, 0xFF, 0x7F, 0x27, 0x29, 0x61, 0x80,
    0x35, 0xF6, 0x06, 0xCE, 0x00, 0x46, 0x46, 0x86, 0xB2, 0xFF, 0xFF, 0x19, 0xBA, 0xC8, 0x71, 0xC1,
    0x57, 0x26, 0x06, 0xA6, 0xB0, 0xFF, 0xFF, 0xFE, 0x7C, 0x23, 0x27, 0x10, 0x4F, 0x33, 0x31, 0x30,
    0x45, 0xBF, 0xBE, 0xD7, 0x72, 0x9B, 0x9C, 0x68, 0x5C, 0x20, 0xC8, 0x24, 0x91, 0x71, 0xE7, 0x4E,
    0xDE, 0x4F, 0x92, 0x13, 0xD2, 0x7F, 0x06, 0x86, 0x46, 0xE4, 0x34, 0x4F, 0xB4, 0x01, 0x22, 0x4A,
    0x55, 0xDB, 0xFE, 0x31, 0x30, 0xAD, 0x7E, 0x77, 0xAF, 0x65, 0x3E, 0x59, 0x99, 0x89, 0xD4, 0xEC,
    0x0C, 0x00, 0xF1, 0x38, 0x8E, 0x11, 0x4D, 0x2F, 0x76, 0x9D, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45,
    0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82
])
ICON_STEP_BLOCK = bytes([
    0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52,
    0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x08, 0x06, 0x00, 0x00, 0x00, 0x1F, 0xF3, 0xFF,
    0x61, 0x00, 0x00, 0x00, 0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xAE, 0xCE, 0x1C, 0xE9, 0x00, 0x00,
    0x00, 0xA5, 0x49, 0x44, 0x41, 0x54, 0x38, 0x4F, 0x63, 0x64, 0xA0, 0x10, 0x30, 0x52, 0xA8, 0x9F,
    0x81, 0x3A, 0x06, 0x08, 0x2B, 0x56, 0xEE, 0x07, 0xB9, 0x84, 0xF1, 0xFF, 0xFF, 0x46, 0x10, 0xFD,
    0x9F, 0x91, 0xB1, 0x1E, 0x44, 0xBF, 0xBD, 0xDF, 0xEE, 0x28, 0xA4, 0x54, 0x55, 0xF8, 0xEE, 0x5E,
    0x5B, 0x3F, 0x2E, 0x97, 0x82, 0x5D, 0x00, 0x36, 0x80, 0x91, 0xD1, 0x81, 0xF1, 0xDF, 0x3F, 0x47,
    0xB0, 0x01, 0x4C, 0x4C, 0xFB, 0x19, 0xFE, 0xFF, 0x3F, 0x00, 0x32, 0x40, 0x58, 0xA9, 0xEA, 0x3F,
    0x03, 0x03, 0xE3, 0x16, 0x86, 0xFF, 0x0C, 0xCD, 0x6F, 0xEF, 0xB7, 0x9E, 0x42, 0x37, 0x88, 0x48,
    0x03, 0xC0, 0xDA, 0xBE, 0x31, 0x30, 0x30, 0xB4, 0xBC, 0xBD, 0xD7, 0xD6, 0x8E, 0x6C, 0x08, 0x29,
    0x06, 0x40, 0xF5, 0xFD, 0xDF, 0xF3, 0x8F, 0xE1, 0x5F, 0xE3, 0xFB, 0x7B, 0x9D, 0x47, 0xC0, 0xDE,
    0x26, 0xCE, 0x0B, 0x18, 0x21, 0xF0, 0xEF, 0x3F, 0xC3, 0xFF, 0xC6, 0x77, 0xF7, 0xDA, 0x9B, 0x06,
    0xC2, 0x00, 0xF2, 0xBD, 0x40, 0x49, 0x20, 0x12, 0x13, 0x8D, 0x94, 0x24, 0x24, 0x4A, 0xF2, 0x03,
    0x75, 0xF2, 0x02, 0x25, 0x2E, 0x00, 0x00, 0x2C, 0xE2, 0xAC, 0x11, 0x8B, 0xAB, 0xCA, 0x8B, 0x00,
    0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82
])
ICON_STEP_TO = bytes([
    0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52,
    0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x08, 0x06, 0x00, 0x00, 0x00, 0x1F, 0xF3, 0xFF,
    0x61, 0x00, 0x00, 0x00, 0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xAE, 0xCE, 0x1C, 0xE9, 0x00, 0x00,
    0x01, 0x25, 0x49, 0x44, 0x41, 0x54, 0x38, 0x4F, 0xA5, 0x93, 0x3D, 0x4E, 0xC4, 0x30, 0x10, 0x85,
    0xDF, 0xF8, 0x06, 0xC4, 0xA6, 0xDF, 0x8D, 0xF7, 0x00, 0xB4, 0x74, 0x29, 0x90, 0x80, 0x12, 0x21,
    0xA0, 0x81, 0x9E, 0x23, 0x04, 0x87, 0x22, 0x69, 0x52, 0x21, 0xD1, 0x41, 0x0F, 0x15, 0x3F, 0xA2,
    0x84, 0x82, 0x82, 0x93, 0xEC, 0x9E, 0x60, 0xB5, 0x0D, 0xB4, 0x78, 0x90, 0x2D, 0xB2, 0x32, 0xC6,
    0x09, 0x12, 0xEB, 0xCA, 0xF2, 0x8C, 0x3F, 0xCD, 0xCC, 0x7B, 0x43, 0x58, 0xF1, 0x90, 0xD2, 0xE6,
    0x6E, 0x3E, 0x6D, 0x8F, 0x62, 0x4E, 0x36, 0x31, 0xDB, 0xB0, 0xD8, 0x5C, 0xCC, 0xDA, 0x3A, 0x8E,
    0xAD, 0x8F, 0xCC, 0x86, 0x25, 0xBE, 0x74, 0xEF, 0x24, 0x73, 0xC3, 0x44, 0x78, 0x98, 0x4F, 0xDB,
    0xC3, 0x30, 0x51, 0xE6, 0xE5, 0x16, 0x20, 0x5E, 0x19, 0x68, 0x62, 0xC8, 0x9A, 0x36, 0xBB, 0x82,
    0xF1, 0xBC, 0x04, 0xF8, 0x4B, 0x04, 0x51, 0x93, 0xF3, 0x82, 0xAD, 0x7D, 0x73, 0xB1, 0x18, 0x92,
    0xE5, 0xD5, 0x3E, 0x81, 0x1F, 0x7F, 0x00, 0x62, 0x88, 0x1A, 0x95, 0x05, 0x0B, 0xE1, 0x01, 0xDF,
    0x90, 0x7A, 0x31, 0x6B, 0x1B, 0x77, 0x97, 0xE3, 0xB3, 0x13, 0x10, 0xDD, 0xFC, 0x02, 0x84, 0x90,
    0x18, 0x10, 0x42, 0x54, 0x5E, 0x9D, 0x32, 0xF8, 0x3A, 0x09, 0xE8, 0x20, 0xF8, 0xB4, 0x57, 0x61,
    0x05, 0x61, 0x25, 0x82, 0xF1, 0xC1, 0x84, 0x8B, 0x5E, 0x40, 0x07, 0x61, 0xC6, 0x41, 0x4A, 0x65,
    0x06, 0x6A, 0x02, 0xBC, 0x3A, 0x5E, 0x85, 0x54, 0x92, 0x1B, 0xEA, 0xBF, 0x01, 0xEE, 0xF3, 0x40,
    0x0B, 0x8D, 0x60, 0xBC, 0xF7, 0xB6, 0xD0, 0xC9, 0xD9, 0x33, 0x44, 0xEF, 0x89, 0xDE, 0x21, 0x86,
    0x5E, 0x48, 0xC8, 0xB8, 0x34, 0x94, 0x1C, 0x9B, 0x63, 0x10, 0x6E, 0x87, 0x8D, 0x14, 0xF8, 0x20,
    0x36, 0x92, 0xD4, 0xD5, 0x1E, 0x98, 0x9F, 0x06, 0xAD, 0xDC, 0x55, 0x90, 0xB2, 0x72, 0xA6, 0xCB,
    0x1D, 0x62, 0xF1, 0xE2, 0x01, 0x4A, 0x9B, 0xFB, 0x78, 0x0F, 0x5C, 0xC0, 0x01, 0xAC, 0x10, 0xC5,
    0x9F, 0xCB, 0xB4, 0xE2, 0x36, 0xE3, 0x0B, 0xA3, 0xD5, 0xB5, 0x11, 0x15, 0x4F, 0x31, 0x37, 0x00,
    0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82
])


class ServiceStub(object):

    def __init__(self, server_addr):
        print("connect to server", server_addr)
        self._proxy = xmlrpc.client.ServerProxy(server_addr)

    def __del__(self):
        pass

    def eval(self, code):
        resp = self._proxy.eval(code)
        lines = resp.split("\n")
        for line in lines:
            print(line)
        return lines


class Emulator(object):
    def __init__(self):
        self._rpc = None

    def connect(self):
        self._rpc = ServiceStub(SEIDA_SERVER_ADDR)
        code = r"""
import angr
import claripy
import pickle

def dump_state(state):
    state = state  # type: angr.sim_state.SimState

    print("address: 0x%X" % state.addr)
    print("registers:")
    if project.arch.name == "AARCH64":
        reg_names = { 29: "fp", 30: "lr", 31: "sp", 32: "pc" }
        reg_count = 32
        reg_prefix = "x"
    else:
        reg_names = { 13: "sp", 14: "lr", 15: "pc" }
        reg_count = 15
        reg_prefix = "r"
    for i in range(0, reg_count + 1):
        r = reg_names[i] if i in reg_names else "%s%d" % (reg_prefix, i)
        print("%4s = %s " % (r, state.regs.get(r)), end="\n" if i != 0 and i % 6 == 0 else "")
    print("")

    sp = state.solver.eval(state.regs.get("sp"))
    stack_len = 0x50
    s = sp - stack_len
    print("stack memory:")
    hexdump(bytearray(state.mem[s].byte.array(stack_len).concrete), s)
    print("")  # empty line

def hexdump(buf, off=0):
    last_bs, last_line = None, None
    for i in range(0, len(buf), 16):
        bs = bytearray(buf[i : i + 16])
        line = "{:08x}  {:23}  {:23}  |{:16}|".format(
            off + i,
            " ".join(("{:02x}".format(x) for x in bs[:8])),
            " ".join(("{:02x}".format(x) for x in bs[8:])),
            "".join((chr(x) if 32 <= x < 127 else "." for x in bs)),
        )
        #if bs == last_bs:
        #    line = "*"
        if bs != last_bs or line != last_line:
            print(line)
        last_bs, last_line = bs, line
    print("{:08x}".format(off + len(buf)))
        """
        self._rpc.eval(code)

    def load(self, filename, base_addr=0):
        code = r"""
if "project" not in globals():  # always use the cache from globals
    if "{filename}".endswith(".dat"):
        with open("{filename}", "rb") as fp:
            project = pickle.load(fp)
    else:
        project = angr.Project("{filename}", main_opts={ "base_addr": {base_addr} })
print("deserialize project from file: %s" % "{filename}")
print("arch:", project.arch)
print("entry:", hex(project.entry))
print("filename:", project.filename)
print("loader:", project.loader)
print("objects:", project.loader.all_objects)
        """
        print("loading...", filename)
        code = code.replace("{filename}", filename)
        code = code.replace("{base_addr}", str(base_addr))
        self._rpc.eval(code)

    def start(self, start_addr):
        code = r"""
if "project" in globals():
    call_state = project.factory.call_state(addr={start_addr})
    call_state.options.add(angr.sim_options.ZERO_FILL_UNCONSTRAINED_MEMORY)
    call_state.options.add(angr.sim_options.ZERO_FILL_UNCONSTRAINED_REGISTERS)

    simulation = project.factory.simulation_manager(call_state)
    print(simulation.active[0])
else:
    print("Error: not load any project")
        """
        code = code.replace("{start_addr}", str(start_addr))
        self._rpc.eval(code)

    @staticmethod
    def _get_addr_from_out(out, index=0):
        addr = -1
        if index >= 0 and len(out) > index:
            line = out[index]  # type: str
            m = re.match(r"address: 0x([A-Fa-f0-9]+)", line)
            if m:
                addr = int(m.group(1), base=16)
        else:
            for line in out:
                m = re.match(r"address: 0x([A-Fa-f0-9]+)", line)
                if m:
                    addr = int(m.group(1), base=16)
        return addr

    def step(self):
        code = r"""
simulation.step(num_inst=1)        
if len(simulation.active) > 0:
    dump_state(simulation.active[0])
else:
    print(simulation)
        """
        out = self._rpc.eval(code)
        return Emulator._get_addr_from_out(out)

    def step_block(self):
        code = r"""
simulation.step()        
if len(simulation.active) > 0:
    dump_state(simulation.active[0])
else:
    print(simulation)
        """
        out = self._rpc.eval(code)
        return Emulator._get_addr_from_out(out)

    def step_to(self, end_addr):
        code = r"""
while True:
    simulation.step(num_inst=1)
    if len(simulation.active) > 0:
        dump_state(simulation.active[0])
        if simulation.active[0].addr == {end_addr}:
            break
    else:
        print(simulation)
        break
        """
        code = code.replace("{end_addr}", str(end_addr))
        out = self._rpc.eval(code)
        return Emulator._get_addr_from_out(out, -1)

    def dump_memory(self, addr, len):
        code = r"""
if len(simulation.active) > 0:
    hexdump(bytearray(simulation.active[0].mem[{addr}].byte.array({len}).concrete), {addr})
else:
    print(simulation)
        """
        code = code.replace("{addr}", str(addr))
        code = code.replace("{len}", str(len))
        return self._rpc.eval(code)

    def set_reg(self, reg_name, val):
        reg_name = reg_name.lower()  # type: str
        size = 32 if reg_name.startswith("w") else 64
        code = r"""
import claripy
if len(simulation.active) > 0:
    simulation.active[0].regs.{reg_name} = claripy.BVV({val}, {size})
        """
        code = code.replace("{reg_name}", reg_name)
        code = code.replace("{val}", str(val))
        code = code.replace("{size}", str(size))
        return self._rpc.eval(code)

    def hook_func(self, addr):
        code = r"""
stub_func = angr.SIM_PROCEDURES['stubs']['ReturnUnconstrained'] 
project.hook({addr}, stub_func())
        """
        code = code.replace("{addr}", str(addr))
        return self._rpc.eval(code)

    def unhook_func(self, addr):
        code = r"""
project.unhook({addr})
        """
        code = code.replace("{addr}", str(addr))
        return self._rpc.eval(code)

    def do_file(self, filename):
        with open(filename, "r") as f:
            code = f.read()
            return self._rpc.eval(code)


class StepActionHandler(ida_kernwin.action_handler_t):

    def __init__(self, emulator):
        super().__init__()
        self._emulator = emulator

    def activate(self, ctx):
        #print("step_action_handler_t()", ctx.action, hex(ctx.cur_ea))
        ea = ctx.cur_ea
        is_thumb = idautils.DecodeInstruction(idc.here()).size == 2
        if is_thumb:
            ea += 1
        if ctx.action == START_EXEC_ACTION_NAME:
            self._emulator.start(ea)
        elif ctx.action == STEP_INST_ACTION_NAME:
            next_ea = self._emulator.step()
            if next_ea > 0:
                if is_thumb:
                    next_ea -= 1
                ida_kernwin.jumpto(next_ea)
        elif ctx.action == STEP_BLOCK_ACTION_NAME:
            next_ea = self._emulator.step_block()
            if next_ea > 0:
                if is_thumb:
                    next_ea -= 1
                ida_kernwin.jumpto(next_ea)
        elif ctx.action == STEP_TO_ACTION_NAME:
            next_ea = self._emulator.step_to(ea)
            if next_ea > 0:
                if is_thumb:
                    next_ea -= 1
                ida_kernwin.jumpto(next_ea)

    def update(self, ctx):
        return ida_kernwin.AST_ENABLE_FOR_WIDGET if ctx.widget_type == ida_kernwin.BWN_DISASM else ida_kernwin.AST_DISABLE_FOR_WIDGET


class SeidaPlugin(ida_idaapi.plugin_t, ida_kernwin.action_handler_t):

    def __init__(self):
        self._emulator = None
        self._start_icon = None
        self._step_icon = None
        self._stepb_icon = None
        self._stept_icon = None
        self._action_handler = None

    def init(self):
        global seida
        seida = self
        self._emulator = Emulator()
        self._emulator.connect()
        self._start_icon = ida_kernwin.load_custom_icon(data=ICON_START, format="png")
        self._step_icon = ida_kernwin.load_custom_icon(data=ICON_STEP_FORWARD, format="png")
        self._stepb_icon = ida_kernwin.load_custom_icon(data=ICON_STEP_BLOCK, format="png")
        self._stept_icon = ida_kernwin.load_custom_icon(data=ICON_STEP_TO, format="png")
        self._action_handler = StepActionHandler(self._emulator)

        ida_kernwin.register_action(
            ida_kernwin.action_desc_t(START_EXEC_ACTION_NAME, "start", self._action_handler, None, "start execution",
                                      self._start_icon))
        ida_kernwin.register_action(
            ida_kernwin.action_desc_t(STEP_INST_ACTION_NAME, "step", self._action_handler, None, "step",
                                      self._step_icon))
        ida_kernwin.register_action(
            ida_kernwin.action_desc_t(STEP_BLOCK_ACTION_NAME, "stepb", self._action_handler, None, "step block",
                                      self._stepb_icon))
        ida_kernwin.register_action(
            ida_kernwin.action_desc_t(STEP_TO_ACTION_NAME, "stept", self._action_handler, None, "step to",
                                      self._stept_icon))

        ida_kernwin.attach_action_to_toolbar("AnalysisToolBar", START_EXEC_ACTION_NAME)
        ida_kernwin.attach_action_to_toolbar("AnalysisToolBar", STEP_INST_ACTION_NAME)
        ida_kernwin.attach_action_to_toolbar("AnalysisToolBar", STEP_BLOCK_ACTION_NAME)
        ida_kernwin.attach_action_to_toolbar("AnalysisToolBar", STEP_TO_ACTION_NAME)
        print("seida inited")

    def term(self):
        self._action_handler = None
        if self._start_icon:
            ida_kernwin.free_custom_icon(self._start_icon)
        if self._step_icon:
            ida_kernwin.free_custom_icon(self._step_icon)
        if self._stepb_icon:
            ida_kernwin.free_custom_icon(self._stepb_icon)
        if self._stept_icon:
            ida_kernwin.free_custom_icon(self._stept_icon)

        ida_kernwin.unregister_action(START_EXEC_ACTION_NAME)
        ida_kernwin.unregister_action(STEP_INST_ACTION_NAME)
        ida_kernwin.unregister_action(STEP_BLOCK_ACTION_NAME)
        ida_kernwin.unregister_action(STEP_TO_ACTION_NAME)

        ida_kernwin.detach_action_from_toolbar("AnalysisToolBar", START_EXEC_ACTION_NAME)
        ida_kernwin.detach_action_from_toolbar("AnalysisToolBar", STEP_INST_ACTION_NAME)
        ida_kernwin.detach_action_from_toolbar("AnalysisToolBar", STEP_BLOCK_ACTION_NAME)
        ida_kernwin.detach_action_from_toolbar("AnalysisToolBar", STEP_TO_ACTION_NAME)

    def run(self):
        pass

    def __getattr__(self, attr):
        if hasattr(self._emulator, attr):  # as proxy of emulator
            return getattr(self._emulator, attr)
        return None


def PLUGIN_ENTRY():
    return SeidaPlugin()


def main():
    global seida
    seida = SeidaPlugin()
    seida.term()
    seida.init()
    seida.run()


if SEIDA_USE_AS_SCRIPT and __name__ == "__main__":
    main()


repository: CMUSTRUDEL/DIRE
file: DIRE/prediction_plugin/decompiler/decompiler_scripts/predict_names.py
wanted_name: Predict variable names
comment: Predicts variable names in decompiled code
created_at: 2019-09-26 14:18:15+00:00
pushed_at: 2025-04-18 16:29:43+00:00
url:
https://github.com/CMUSTRUDEL/DIRE/blob/4c66092f52364ae37114f89676c615006e66581d/DIRE/prediction_plugin/decompiler/decompiler_scripts/predict_names.py

content:
from collections import defaultdict
import idaapi
import ida_hexrays
import json
import jsonlines
import os
import re
import subprocess
import sys

thisScriptFilePath = os.path.realpath(__file__)
utilsPath = os.path.dirname(thisScriptFilePath)
sys.path.insert(0, utilsPath)

from util import UNDEF_ADDR, CFuncGraph, GraphBuilder, hexrays_vars, get_expr_name

try:
    from CStringIO import StringIO ## for Python 2
except ImportError:
    from io import StringIO ## for Python 3

# Dictionary mapping variable ids to (orig, orig) pairs
varnames = dict()
oldvarnames = dict()
var_id = 0
sentinel_vars = re.compile('@@VAR_[0-9]+')

actname = "predict:varnames"

dire_dir = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', '..', '..', '..'))
MODEL = os.path.join(dire_dir, 'data', 'saved_models', 'model.hybrid.bin')

# Rename variables to sentinel representation
class RenamedGraphBuilder(GraphBuilder):
    def __init__(self, cg, func, vuu):
        self.func = func
        self.vuu = vuu
        super(RenamedGraphBuilder, self).__init__(cg)

    def visit_expr(self, e):
        global var_id
        if e.op is ida_hexrays.cot_var:
            # Save original name of variable
            original_name = get_expr_name(e)
            if not sentinel_vars.match(original_name):
                # Rename variables to @@VAR_[id]@@[orig name]@@[orig name]
                new_name = '@@VAR_' + str(var_id) + '@@' + original_name + '@@' + original_name
                self.vuu.rename_lvar(self.vuu.cfunc.get_lvars()[e.v.idx],
                                     str(new_name),
                                     True)
                # This is needed so that the NN graph sees the new
                # name without visiting the entire expression again.
                self.func.get_lvars()[e.v.idx].name = str(new_name)
                varnames[original_name] = new_name
                oldvarnames[new_name] = original_name
                var_id += 1
        return self.process(e)

# Rename variables to predicted names
class FinalRename(ida_hexrays.ctree_visitor_t):
    def __init__(self, renamings, func, vuu):
        super(FinalRename, self).__init__(0)
        self.renamings = renamings
        self.func = func
        self.vuu = vuu

    def visit_expr(self, e):
        if e.op is ida_hexrays.cot_var:
            original_name = get_expr_name(e)
            if original_name in self.renamings:
                new_name = self.renamings[original_name]

                if oldvarnames[original_name] != new_name:
                    print("Renaming %s to %s"%(oldvarnames[original_name],new_name))
                # This one refreshes the pseudo-code window
                self.vuu.rename_lvar(self.vuu.cfunc.get_lvars()[e.v.idx],
                                str(new_name),
                                True)
                # This one stops us from renaming the same variable
                # over and over. Otherwise it's not needed.
                self.func.get_lvars()[e.v.idx].name = str(new_name)
        return 0

# Process a single function given its EA
def func(ea, vuu):
    f = idaapi.get_func(ea)
    function_name = idaapi.get_func_name(ea)
    if f is None:
        print('Please position the cursor within a function')

    cfunc = None
    try:
        cfunc = idaapi.decompile(f)
    except ida_hexrays.DecompilationFailure as e:
        print('Failed to decompile %x: %s!' % (ea, function_name))
        raise e

    # Rename decompilation graph
    cg = CFuncGraph(None)
    gb = GraphBuilder(cg)
    gb.apply_to(cfunc.body, None)
    #ac = AddressCollector(cg)
    #ac.collect()
    rg = RenamedGraphBuilder(cg, cfunc, vuu)
    rg.apply_to(cfunc.body, None)
    
    # Create tree from collected names
    cfunc.build_c_tree()
    new_graph = CFuncGraph(None)
    new_builder = GraphBuilder(new_graph)
    new_builder.apply_to(cfunc.body, None)
    function_info = dict()
    function_info["function"] = function_name
    function_info["ast"] = new_graph.json_tree(0)
    raw_code = ""
    for line in cfunc.get_pseudocode():
        raw_code += idaapi.tag_remove(line.line) + '\n'
    function_info["raw_code"] = raw_code
    return function_info, cfunc

class predict_names_ah_t(idaapi.action_handler_t):
    def __init__(self):
        idaapi.action_handler_t.__init__(self)

    def activate(self, ctx):
        print("Suggesting variable names...")
        idaapi.show_wait_box("Suggesting variable names... please wait")
        ea = idaapi.get_screen_ea()
        vuu = ida_hexrays.get_widget_vdui(ctx.widget)
        if ea is None:
            idaapi.warning("Current function not found.")
        else:
            f = StringIO()
            with jsonlines.Writer(f) as writer:
                try:
                    info, cfunc = func(ea, vuu)
                    # We must set the working directory to the dire dir to open the model correctly
                    os.chdir(dire_dir)
                    p = subprocess.Popen(['python', '-m', 'DIRE.prediction_plugin.run_one', '--model', MODEL], stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE, encoding=sys.getdefaultencoding())
                    #print(info)
                    writer.write(info)
                    comm = p.communicate(input=f.getvalue())
                    json_results = comm[0]
                    stderr = comm[1]
                    if p.returncode != 0:
                        print(stderr)
                        raise ValueError("Variable prediction failed")
                    results = json.loads(json_results)
                    best_results = results[0][0]
                    #print("best: ", best_results)
                    tuples = map(lambda x: (varnames[x[0]] if x[0] in varnames else x[0], x[1]['new_name']), best_results.items())

                    FinalRename(dict(tuples), cfunc, vuu).apply_to(cfunc.body, None)

                    # Force the UI to update
                    #vuu.refresh_ctext()

                except ida_hexrays.DecompilationFailure:
                    idaapi.warning("Decompilation failed")

                except ValueError as e:
                    idaapi.warning(str(e) + ". See output window for more details.")

        idaapi.hide_wait_box()
        return 1

    def update(self, ctx):
        return idaapi.AST_ENABLE_FOR_WIDGET if \
            ctx.widget_type == idaapi.BWN_PSEUDOCODE else \
            idaapi.AST_DISABLE_FOR_WIDGET

class name_hooks_t(idaapi.Hexrays_Hooks):
    def __init__(self):
        idaapi.Hexrays_Hooks.__init__(self)
    def populating_popup(self, widget, phandle, vu):
        idaapi.attach_action_to_popup(vu.ct, None, actname)
        return 0

if idaapi.init_hexrays_plugin():
    idaapi.register_action(
        idaapi.action_desc_t(
            actname,
            "Predict variable names",
            predict_names_ah_t(),
            "P"))
    name_hooks = name_hooks_t()
    name_hooks.hook()
else:
    print('Predict variable names: hexrays is not available.')

class plugin(idaapi.plugin_t):
    flags = 0
    comment = "Predicts variable names in decompiled code"

    wanted_name = "Predict variable names"
    #wanted_hotkey = "P"

    def init(self):
        return idaapi.PLUGIN_KEEP

def PLUGIN_ENTRY():
    return plugin()


repository: hoangcuongflp/Stuff
file: ida-master/plugins/alleycat/alleycat.py
description: because I've orderliness obsession :(
wanted_name: AlleyCat
created_at: 2015-10-12 16:35:32+00:00
pushed_at: 2014-12-22 00:32:06+00:00
url:
https://github.com/hoangcuongflp/Stuff/blob/ee100760a05f524ff73837222d80f1dd12da3cee/ida-master/plugins/alleycat/alleycat.py

content:
import idc
import idaapi
import idautils
import time

# This limits the depth of any individual path, as well as the maximum
# number of paths that will be searched; this is needed for practical
# reasons, as IDBs with tens of thousands of functions take a long time
# to exhaust all possible paths without some practical limitation.
#
# This is global so it's easy to change from the IDAPython prompt.
ALLEYCAT_LIMIT = 10000

class AlleyCatException(Exception):
    pass

class AlleyCat(object):
    '''
    Class which resolves function paths. This is where most of the work is done.
    '''

    def __init__(self, start_ea, end_ea):
        '''
        Class constructor.

        @start_ea - An address in the head function.
        @end_ea   - An address in the tail funciton.

        Returns None.
        '''
        global ALLEYCAT_LIMIT
        self.limit = ALLEYCAT_LIMIT

        self.paths = []

        # We work backwards via xrefs, so we start at the end and end at the start
        try:
            start = idaapi.get_func(end_ea).startEA
        except:
            raise AlleyCatException("Address 0x%X is not part of a function!" % end)
        try:
            end = idaapi.get_func(start_ea).startEA
        except:
            end = idc.BADADDR

        print "Generating call paths from %s to %s..." % (idc.Name(end), idc.Name(start))
        self._build_paths(start, end)

    def _build_paths(self, start, end=idc.BADADDR):
        partial_paths = [[start]]

        # Loop while there are still unresolve paths and while all path sizes have not exceeded ALLEYCAT_LIMIT
        while partial_paths and len(self.paths) < self.limit and len(partial_paths) < self.limit:
            # Initialize a unique set of callers for this iteration
            callers = set()

            # Callee is the last entry of the first path in partial paths.
            # The first path list will change as paths are completed and popped from the list.
            callee = partial_paths[0][-1]

            # Find all unique functions that reference the callee, assuming this path has not
            # exceeded ALLEYCAT_LIMIT.
            if len(partial_paths[0]) < self.limit:
                for xref in idautils.XrefsTo(callee):
                    caller = idaapi.get_func(xref.frm)
                    if caller and caller.startEA not in callers:
                        callers.add(caller.startEA)

            # If there are callers to the callee, remove the callee's current path
            # and insert new ones with the new callers appended.
            if callers:
                base_path = partial_paths.pop(0)
                for caller in callers:
                    # If we've reached the desired end node, don't go any further down this path
                    if caller == end:
                        self.paths.append((base_path + [caller])[::-1])
                    else:
                        partial_paths.append(base_path + [caller])
            # Else, our end node is not in this path, so don't include it in the finished path list.
            elif end not in partial_paths[0]:
                partial_paths.pop(0)
            # If there were no callers then this path has been exhaused and should be
            # popped from the partial path list into the finished path list.
            elif end in partial_paths[0]:
                # Paths start with the end function and end with the start function; reverse it.
                self.paths.append(partial_paths.pop(0)[::-1])


### Everything below here is just IDA UI/Plugin stuff ###


class AlleyCatGraphHistory(object):
    '''
    Manages include/exclude graph history.
    '''

    INCLUDE_ACTION = 0
    EXCLUDE_ACTION = 1

    def __init__(self):
        self.reset()

    def reset(self):
        self.history = []
        self.includes = []
        self.excludes = []
        self.history_index = 0
        self.include_index = 0
        self.exclude_index = 0

    def update_history(self, action):
        if self.excludes and len(self.history)-1 != self.history_index:
            self.history = self.history[0:self.history_index+1]
        self.history.append(action)
        self.history_index = len(self.history)-1

    def add_include(self, obj):
        if self.includes and len(self.includes)-1 != self.include_index:
            self.includes = self.includes[0:self.include_index+1]
        self.includes.append(obj)
        self.include_index = len(self.includes)-1
        self.update_history(self.INCLUDE_ACTION)

    def add_exclude(self, obj):
        if len(self.excludes)-1 != self.exclude_index:
            self.excludes = self.excludes[0:self.exclude_index+1]
        self.excludes.append(obj)
        self.exclude_index  = len(self.excludes)-1
        self.update_history(self.EXCLUDE_ACTION)

    def get_includes(self):
        return set(self.includes[0:self.include_index+1])

    def get_excludes(self):
        return set(self.excludes[0:self.exclude_index+1])

    def undo(self):
        if self.history:
            if self.history[self.history_index] == self.INCLUDE_ACTION:
                if self.include_index >= 0:
                    self.include_index -= 1
            elif self.history[self.history_index] == self.EXCLUDE_ACTION:
                if self.exclude_index >= 0:
                    self.exclude_index -= 1

            self.history_index -= 1
            if self.history_index < 0:
                self.history_index = 0

    def redo(self):
        self.history_index += 1
        if self.history_index >= len(self.history):
            self.history_index = len(self.history)-1

        if self.history[self.history_index] == self.INCLUDE_ACTION:
            if self.include_index < len(self.includes)-1:
                self.include_index += 1
        elif self.history[self.history_index] == self.EXCLUDE_ACTION:
            if self.exclude_index < len(self.excludes)-1:
                self.exclude_index += 1

class AlleyCatGraph(idaapi.GraphViewer):
    '''
    Displays the graph and manages graph actions.
    '''

    def __init__(self, results, title="AlleyCat Graph"):
        idaapi.GraphViewer.__init__(self, title)
        self.results = results

        self.nodes_ea2id = {}
        self.nodes_id2ea = {}
        self.edges = {}
        self.end_nodes = []
        self.edge_nodes = []
        self.start_nodes = []

        self.history = AlleyCatGraphHistory()
        self.include_on_click = False
        self.exclude_on_click = False

    def Show(self):
        '''
        Display the graph.

        Returns True on success, False on failure.
        '''
        if not idaapi.GraphViewer.Show(self):
            return False
        else:
            self.cmd_undo = self.AddCommand("Undo", "U")
            self.cmd_redo = self.AddCommand("Redo", "R")
            self.cmd_reset = self.AddCommand("Reset graph", "G")
            self.cmd_exclude = self.AddCommand("Exclude node", "X")
            self.cmd_include = self.AddCommand("Include node", "I")
            return True

    def OnRefresh(self):
        # Clear the graph before refreshing
        self.Clear()
        self.nodes_ea2id = {}
        self.nodes_id2ea = {}
        self.edges = {}
        self.end_nodes = []
        self.edge_nodes = []
        self.start_nodes = []

        includes = self.history.get_includes()
        excludes = self.history.get_excludes()

        for path in self.results:
            parent_node = None

            # Check to see if this path contains all nodes marked for explicit inclusion
            if (set(path) & includes) != includes:
                continue

            # Check to see if this path contains any nodes marked for explicit exclusion
            if (set(path) & excludes) != set():
                continue

            for ea in path:
                # If this node already exists, use its existing node ID
                if self.nodes_ea2id.has_key(ea):
                    this_node = self.nodes_ea2id[ea]
                # Else, add this node to the graph
                else:
                    this_node = self.AddNode(self.get_name_by_ea(ea))
                    self.nodes_ea2id[ea] = this_node
                    self.nodes_id2ea[this_node] = ea

                # If there is a parent node, add an edge between the parent node and this one
                if parent_node is not None:
                    self.AddEdge(parent_node, this_node)
                    if this_node not in self.edges[parent_node]:
                        self.edges[parent_node].append(this_node)

                # Update the parent node for the next loop
                parent_node = this_node
                if not self.edges.has_key(parent_node):
                    self.edges[parent_node] = []

            try:
                # Track the first, last, and next to last nodes in each path for
                # proper colorization in self.OnGetText.
                self.start_nodes.append(self.nodes_ea2id[path[0]])
                self.end_nodes.append(self.nodes_ea2id[path[-1]])
                self.edge_nodes.append(self.nodes_ea2id[path[-2]])
            except:
                pass

        return True

    def OnGetText(self, node_id):
        color = idc.DEFCOLOR

        if node_id in self.edge_nodes:
            color = 0x00ffff
        elif node_id in self.start_nodes:
            color = 0x00ff00
        elif node_id in self.end_nodes:
            color = 0x0000ff

        return (self[node_id], color)

    def OnHint(self, node_id):
        hint = ""

        try:
            for edge_node in self.edges[node_id]:
                hint += "%s\n" % self[edge_node]
        except Exception as e:
            pass

        return hint

    def OnCommand(self, cmd_id):
        if self.cmd_undo == cmd_id:
            if self.include_on_click or self.exclude_on_click:
                self.include_on_click = False
                self.exclude_on_click = False
            else:
                self.history.undo()
            self.Refresh()
        elif self.cmd_redo == cmd_id:
            self.history.redo()
            self.Refresh()
        elif self.cmd_include == cmd_id:
            self.include_on_click = True
        elif self.cmd_exclude == cmd_id:
            self.exclude_on_click = True
        elif self.cmd_reset == cmd_id:
            self.include_on_click = False
            self.exclude_on_click = False
            self.history.reset()
            self.Refresh()

    def OnClick(self, node_id):
        if self.include_on_click:
            self.history.add_include(self.nodes_id2ea[node_id])
            self.include_on_click = False
        elif self.exclude_on_click:
            self.history.add_exclude(self.nodes_id2ea[node_id])
            self.exclude_on_click = False
        self.Refresh()

    def OnDblClick(self, node_id):
        xref_locations = []
        node_ea = self.get_ea_by_name(self[node_id])

        if self.edges.has_key(node_id):
            for edge_node_id in self.edges[node_id]:

                edge_node_name = self[edge_node_id]
                edge_node_ea = self.get_ea_by_name(edge_node_name)

                if edge_node_ea != idc.BADADDR:
                    for xref in idautils.XrefsTo(edge_node_ea):
                        # Is the specified node_id the source of this xref?
                        if self.match_xref_source(xref, node_ea):
                            xref_locations.append((xref.frm, edge_node_ea))

        if xref_locations:
            xref_locations.sort()

            print ""
            print "Path Xrefs from %s:" % self[node_id]
            print "-" * 100
            for (xref_ea, dst_ea) in xref_locations:
                print "%-50s  =>  %s" % (self.get_name_by_ea(xref_ea), self.get_name_by_ea(dst_ea))
            print "-" * 100
            print ""

            idc.Jump(xref_locations[0][0])
        else:
            idc.Jump(node_ea)

    def match_xref_source(self, xref, source):
        # TODO: This must be modified if support for graphing function blocks is added.
        return ((xref.type != idc.fl_F) and (idc.GetFunctionAttr(xref.frm, idc.FUNCATTR_START) == source))

    def get_ea_by_name(self, name):
        '''
        Get the address of a location by name.

        @name - Location name

        Returns the address of the named location, or idc.BADADDR on failure.
        '''
        # This allows support of the function offset style names (e.g., main+0C)
        # TODO: Is there something in the IDA API that does this already??
        if '+' in name:
            (func_name, offset) = name.split('+')
            base_ea = idc.LocByName(func_name)
            if base_ea != idc.BADADDR:
                try:
                    ea = base_ea + int(offset, 16)
                except:
                    ea = idc.BADADDR
        else:
            ea = idc.LocByName(name)
            if ea == idc.BADADDR:
                try:
                    ea = int(name, 0)
                except:
                    ea = idc.BADADDR

        return ea

    def get_name_by_ea(self, ea):
        '''
        Get the name of the specified address.

        @ea - Address.

        Returns a name for the address, one of idc.Name, idc.GetFuncOffset or 0xXXXXXXXX.
        '''
        name = idc.Name(ea)
        if not name:
            name = idc.GetFuncOffset(ea)
            if not name:
                name = "0x%X" % ea
        return name

class idapathfinder_t(idaapi.plugin_t):

    flags = 0
    comment = ''
    help = ''
    wanted_name = 'AlleyCat'
    wanted_hotkey = ''

    def init(self):
        ui_path = "View/Graphs/"
        self.menu_contexts = []
        self.graph = None

        self.menu_contexts.append(idaapi.add_menu_item(ui_path,
                                "Find paths to the current function from...",
                                "Alt-6",
                                0,
                                self.FindPathsFromMany,
                                (None,)))
        self.menu_contexts.append(idaapi.add_menu_item(ui_path,
                                "Find paths from the current function to...",
                                "Alt-5",
                                0,
                                self.FindPathsToMany,
                                (None,)))

        return idaapi.PLUGIN_KEEP

    def term(self):
        for context in self.menu_contexts:
            idaapi.del_menu_item(context)
        return None

    def run(self, arg):
        pass

    def _current_function(self):
        return idaapi.get_func(ScreenEA()).startEA

    def _find_and_plot_paths(self, sources, targets):
        results = []

        for target in targets:
            for source in sources:
                s = time.time()
                r = AlleyCat(source, target).paths
                e = time.time()
                print "Found %d paths in %f seconds." % (len(r), (e-s))

                if r:
                    results += r
                else:
                    name = idc.Name(target)
                    if not name:
                        name = "0x%X" % target
                    print "No paths found to", name

        if results:
            # Be sure to close any previous graph before creating a new one.
            # Failure to do so may crash IDA.
            try:
                self.graph.Close()
            except:
                pass

            self.graph = AlleyCatGraph(results, 'Path Graph')
            self.graph.Show()

    def _get_user_selected_functions(self, many=False):
        functions = []
        ea = idc.ScreenEA()
        try:
            current_function = idc.GetFunctionAttr(ea, idc.FUNCATTR_START)
        except:
            current_function = None

        while True:
            function = idc.ChooseFunction("Select a function and click 'OK' until all functions have been selected. When finished, click 'Cancel' to display the graph.")
            # ChooseFunction automatically jumps to the selected function
            # if the enter key is pressed instead of clicking 'OK'. Annoying.
            if idc.ScreenEA() != ea:
                idc.Jump(ea)

            if not function or function == idc.BADADDR or function == current_function:
                break
            elif function not in functions:
                functions.append(function)

            if not many:
                break

        return functions

    def FindPathsToMany(self, arg):
        source = self._current_function()

        if source:
            targets = self._get_user_selected_functions(many=True)
            if targets:
                self._find_and_plot_paths([source], targets)

    def FindPathsFromMany(self, arg):
        target = self._current_function()

        if target:
            sources = self._get_user_selected_functions(many=True)
            if sources:
                self._find_and_plot_paths(sources, [target])

def PLUGIN_ENTRY():
    return idapathfinder_t()


repository: vAlerainTech/vAlerain-Ark
file: cmake-build-debug/IDA_Pro_7.7/plugins/BinCAT/python/idabincat/bcplugin.py
wanted_name: BinCAT
comment: Interface to the BinCAT analyzer
created_at: 2024-03-25 14:26:36+00:00
pushed_at: 2024-07-27 05:07:46+00:00
url:
https://github.com/vAlerainTech/vAlerain-Ark/blob/df8bcafeda091d30ceade7bb2c7c73b5ae351a38/cmake-build-debug/IDA_Pro_7.7/plugins/BinCAT/python/idabincat/bcplugin.py

content:
# -*- coding: utf-8 -*-
# version IDA 6.9
# runs the "bincat" command from ida
"""
    This file is part of BinCAT.
    Copyright 2014-2017 - Airbus Group

    BinCAT is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, either version 3 of the License, or (at your
    option) any later version.

    BinCAT is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with BinCAT.  If not, see <http://www.gnu.org/licenses/>.
"""

from collections.abc import MutableSequence
import hashlib
import logging
import os
import shutil
import sys
import tempfile
import traceback
import zlib
try:
    from configparser import NoSectionError
except ImportError:
    from ConfigParser import NoSectionError

# Ugly but IDA Python Linux doesn't have it !
try:
    import distutils.spawn
    no_spawn = False
except ImportError:
    no_spawn = True
try:
    import requests
except ImportError:
    # log message will be displayed later
    pass

import idaapi
from idaapi import NW_OPENIDB, NW_CLOSEIDB, NW_TERMIDA, NW_REMOVE
import idabincat.netnode
import idabincat.npkgen
from idabincat.plugin_options import PluginOptions
from idabincat.analyzer_conf import AnalyzerConfig, AnalyzerConfigurations, ConfigHelpers
from idabincat.gui import GUI, taint_color
import pybincat

from PyQt5 import QtCore
# used in idaapi
from PyQt5 import QtWidgets

# Logging
logging.basicConfig(level=logging.DEBUG)
bc_log = logging.getLogger('bincat.plugin')
bc_log.setLevel(logging.INFO)


def dedup_loglines(loglines, max=None):
    res = []
    staging = None
    n = 0

    def flush_staging():
        if n > 0:
            res.append(staging)
            if max and len(res) >= max:
                return True
        if n == 2:
            res.append(staging)
            if max and len(res) >= max:
                return True
        if n > 2:
            res.append("== (same line repeated %i times) ==" % n)
            if max and len(res) >= max:
                return True

    while True:
        if not loglines:
            flush_staging()
            break
        l = loglines.pop()
        if l == staging:
            n += 1
        else:
            if flush_staging():
                break
            staging = l
            n = 1
    res.reverse()
    return res


class AnalyzerUnavailable(Exception):
    pass


class BincatPlugin(idaapi.plugin_t):
    # variables required by IDA
    flags = 0  # normal plugin
    wanted_name = "BinCAT"
    wanted_hotkey = "Ctrl-Shift-B"
    comment = "Interface to the BinCAT analyzer"
    help = ""
    initialized = False

    def __init__(self):
        super(BincatPlugin, self).__init__()
        self.state = None

    # IDA API methods: init, run, term
    def init(self):
        info = idaapi.get_inf_structure()
        procname = info.procname
        if procname != 'metapc' and procname != 'ARM' and procname != 'ARMB' and procname != 'PPC':
            bc_log.info("CPU '%s' is not supported, not loading BinCAT", procname)
            return idaapi.PLUGIN_SKIP
        try:
            from pybincat import cfa as cfa_module
            global cfa_module
        except:
            bc_log.warning(
                "Failed to load 'pybincat.cfa' python module\n%s",
                repr(sys.exc_info()))
            return idaapi.PLUGIN_SKIP
        PluginOptions.init()

        # add plugin's 'bin' dir to PATH
        userdir = idaapi.get_user_idadir()
        bin_path = os.path.join(userdir, "plugins", "idabincat", "bin")
        if os.path.isdir(bin_path):
            path_env_sep = ';' if os.name == 'nt' else ':'
            os.environ['PATH'] += path_env_sep+bin_path
        if no_spawn:
            bc_exe = None
        else:
            # Check if bincat is available
            bc_exe = distutils.spawn.find_executable('bincat')
        if bc_exe is None:
            if no_spawn:
                bc_exe = os.path.join(bin_path, "bincat")
            else:
                bc_exe = distutils.spawn.find_executable('bincat')
        if bc_exe is None and no_spawn is False:
            bc_log.warning('Could not find bincat binary, will not be able to run local analysis')

        if PluginOptions.get("autostart") != "True":
            # will initialize later
            return idaapi.PLUGIN_OK

        bc_log.info("Autostarting")

        self.state = State()
        self.initialized = True
        bc_log.info("IDABinCAT ready.")
        return idaapi.PLUGIN_KEEP

    def run(self, args):
        if self.initialized:
            return
        self.state = State()
        # XXX move
        self.initialized = True
        self.state.gui.show_windows()
        bc_log.info("IDABinCAT ready.")

    def term(self):
        if self.state:
            bc_log.debug("Terminating BinCAT")
            self.state.clear_background()
            self.state.gui.term()
            self.state.gui = None
            self.state = None


class Analyzer(object):
    def __init__(self, path, finish_cb):
        self.path = path
        self.finish_cb = finish_cb

    def generate_tnpk(self, fname=None, destfname=None):
        """
        Generates TNPK file for provided fname. If None, generate one for the
        binary that is currently being analyzed in IDA, using IDA-provided
        headers.

        Returns file path to generated tnpk (string), or None if generation was
        not successful.
        """
        return None

    @property
    def initfname(self):
        return os.path.join(self.path, "init.ini")

    @property
    def outfname(self):
        return os.path.join(self.path, "out.ini")

    @property
    def cfainfname(self):
        return os.path.join(self.path, "cfain.marshal")

    @property
    def cfaoutfname(self):
        return os.path.join(self.path, "cfaout.marshal")

    @property
    def logfname(self):
        return os.path.join(self.path, "analyzer.log")

class LocalAnalyzerTimer(object):
    """
    IDA timer used to kill the BinCAT analyzer if the user
    cancels the analysis
    """
    def __init__(self, qprocess):
        self.interval = 500  # ms
        self.qprocess = qprocess
        self.timer = idaapi.register_timer(self.interval, self)
        if self.timer is None:
            raise RuntimeError("Failed to register timer")

    def __call__(self):
        if idaapi.user_cancelled():
            bc_log.info("Analysis cancelled by user, terminating process")
            self.qprocess.terminate()
            return -1
        else:
            return self.interval

    def destroy(self):
        idaapi.unregister_timer(self.timer)


class LocalAnalyzer(Analyzer, QtCore.QProcess):
    """
    Runs BinCAT locally using QProcess.
    """

    def __init__(self, *args, **kwargs):
        QtCore.QProcess.__init__(self)
        Analyzer.__init__(self, *args, **kwargs)
        # Qprocess signal handlers
        self.error.connect(self.procanalyzer_on_error)
        self.stateChanged.connect(self.procanalyzer_on_state_change)
        self.started.connect(self.procanalyzer_on_start)
        self.finished.connect(self.procanalyzer_on_finish)

    def generate_tnpk(self, fname=None, destfname=None):
        if fname:
            imports_data = open(fname, 'r').read()
        else:
            imports_data = ""
        try:
            npk_fname = idabincat.npkgen.NpkGen().generate_tnpk(
                imports_data=imports_data, destfname=destfname)
            return npk_fname
        except idabincat.npkgen.NpkGenException:
            bc_log.warning("Could not compile header file, "
                           "types from IDB will not be used for type propagation")
            return

    def run(self):
        idaapi.show_wait_box("Running analysis")

        # Create a timer to allow for cancellation
        self.timer = LocalAnalyzerTimer(self)

        cmdline = ["bincat",
                   [self.initfname, self.outfname, self.logfname]]
        # start the process
        bc_log.debug(
            "Analyzer cmdline: [%s %s]", cmdline[0], " ".join(cmdline[1]))
        try:
            self.start(*cmdline)
        except Exception as e:
            bc_log.error("BinCAT failed to launch the analyzer.py")
            bc_log.warning("Exception: %s\n%s", str(e), traceback.format_exc())
        else:
            bc_log.info("Analyzer started.")

    def procanalyzer_on_error(self, err):
        errors = ["Failed to start", "Crashed", "TimedOut", "Read Error",
                  "Write Error", "Unknown Error"]
        try:
            errtxt = errors[err]
        except IndexError:
            errtxt = "Unspecified error %s" % err
        bc_log.error("Analyzer error: %s", errtxt)
        self.process_output()

    def procanalyzer_on_state_change(self, new_state):
        states = ["Not running", "Starting", "Running"]
        bc_log.debug("Analyzer new state: %s", states[new_state])

    def procanalyzer_on_start(self):
        bc_log.info("Analyzer: starting process")

    def procanalyzer_on_finish(self):
        bc_log.info("Analyzer process finished")
        exitcode = self.exitCode()
        if exitcode != 0:
            bc_log.error("analyzer returned exit code=%i", exitcode)
        try:
            self.process_output()
        except Exception as e:
            idaapi.hide_wait_box()
            bc_log.error("Caught exception, hiding wait box", exc_info=True)

    def process_output(self):
        """
        Try to process analyzer output.
        """
        self.timer.destroy()
        bc_log.info("---- stdout ----------------")
        bc_log.info(str(self.readAllStandardOutput()))
        bc_log.info("---- stderr ----------------")
        bc_log.info(str(self.readAllStandardError()))
        bc_log.info("---- logfile ---------------")
        if os.path.exists(self.logfname):
            with open(self.logfname, 'r') as f:
                log_lines = f.readlines()
            log_lines = dedup_loglines(log_lines, max=100)
            if len(log_lines) > 100:
                bc_log.info("---- Only the last 100 log lines (deduped) are displayed here ---")
                bc_log.info("---- See full log in %s ---" % self.logfname)
            for line in log_lines:
                bc_log.info(line.rstrip())

        bc_log.info("====== end of logfile ======")
        idaapi.hide_wait_box()
        self.finish_cb(self.outfname, self.logfname, self.cfaoutfname)


class WebAnalyzer(Analyzer):
    API_VERSION = "1.2"

    def __init__(self, *args, **kwargs):
        Analyzer.__init__(self, *args, **kwargs)
        self.server_url = PluginOptions.get("server_url").rstrip("/")
        self.reachable_server = False
        self.check_version()  # raises exception if server is unreachable
        self.reachable_server = True

    def check_version(self):
        try:
            version_req = requests.get(self.server_url + "/version")
            srv_api_version = str(version_req.text)
        except requests.ConnectionError as e:
            raise AnalyzerUnavailable(
                "BinCAT server at %s could not be reached : %s" % (self.server_url, str(e)))
        if srv_api_version != WebAnalyzer.API_VERSION:
            raise AnalyzerUnavailable(
                "API mismatch: this plugin supports version %s, while server "
                "supports version %s." % (WebAnalyzer.API_VERSION,
                                          srv_api_version))
        return True

    def generate_tnpk(self, fname=None, destfname=None):
        if not self.reachable_server:
            return
        if not fname:
            headers_data = idabincat.npkgen.NpkGen().get_header_data()
            fname = os.path.join(self.path, "ida_generated_headers.h")
            with open(fname, 'w') as f:
                f.write(headers_data)
        sha256 = self.sha256_digest(fname)
        if not self.upload_file(fname, sha256):
            return
        npk_res = requests.post(self.server_url + "/convert_to_tnpk/" + sha256)
        if npk_res.status_code != 200:
            bc_log.warning("Error while compiling file to tnpk "
                           "on BinCAT analysis server, types from IDB will "
                           "not be used for type propagation")
            return
        res = npk_res.json()
        if 'status' not in res or res['status'] != 'ok':
            return
        sha256 = res['sha256']
        npk_contents = self.download_file(sha256)
        npk_fname = os.path.join(self.path, "headers.npk")
        with open(npk_fname, 'w') as f:
            f.write(npk_contents)
        return npk_fname

    def run(self):
        if not self.reachable_server:
            return
        if 'requests' not in sys.modules:
            bc_log.error("python module 'requests' could not be imported, "
                         "so remote BinCAT cannot be used.")
            return
        # create temporary AnalyzerConfig to replace referenced file names with
        # sha256 of their contents
        with open(self.initfname, 'r') as f:
            temp_config = AnalyzerConfig.load_from_str(f.read())
        # patch filepath - set filepath to sha256, upload file
        sha256 = self.sha256_digest(temp_config.binary_filepath)
        if not self.upload_file(temp_config.binary_filepath, sha256):
            return
        temp_config.binary_filepath = sha256
        # patch [imports] headers - replace with sha256, upload files
        files = temp_config.headers_files.split(',')
        h_shalist = []
        for f in files:
            if not f:
                continue
            try:
                f_sha256 = self.sha256_digest(f)
                h_shalist.append('"%s"' % f_sha256)
            except IOError as e:
                bc_log.error("Could not open file %s" % f, exc_info=1)
                return
            if not self.upload_file(f, f_sha256):
                bc_log.error("Could not upload file %s" % f, exc_info=1)
                return
        temp_config.headers_files = ','.join(h_shalist)

        # patch in_marshalled_cfa_file - replace with file contents sha256
        if os.path.exists(self.cfainfname):
            cfa_sha256 = self.sha256_digest(self.cfainfname)
            temp_config.in_marshalled_cfa_file = '"%s"' % cfa_sha256
        else:
            temp_config.in_marshalled_cfa_file = '"no-input-file"'
        temp_config.out_marshalled_cfa_file = '"cfaout.marshal"'
        # write patched config file
        init_ini_str = str(temp_config)
        # --- Run analysis
        run_res = requests.post(
            self.server_url + "/analyze",
            files={'init.ini': ('init.ini', init_ini_str)})
        if run_res.status_code != 200:
            bc_log.error("Error while uploading analysis configuration file "
                         "to BinCAT analysis server (%r)" % run_res.text)
            return
        files = run_res.json()
        if files["errorcode"]:
            bc_log.error("Error while analyzing file. Bincat output is:\n"
                         "----------------\n%s\n----------------"
                         % self.download_file(files["stdout.txt"]))
            if not files["out.ini"]:  # try to parse out.ini if it exists
                return
        with open(self.outfname, 'w') as outfp:

            outfp.write(self.download_file(files["out.ini"]))
        analyzer_log_contents = self.download_file(files["analyzer.log"])
        with open(self.logfname, 'w') as logfp:
            logfp.write(analyzer_log_contents)
        bc_log.info("---- stdout+stderr ----------------")
        bc_log.info(self.download_file(files["stdout.txt"]))
        bc_log.info("---- logfile ---------------")
        log_lines = analyzer_log_contents.split('\n')

        log_lines = dedup_loglines(log_lines, max=100)
        if len(log_lines) > 100:
            bc_log.info("---- Only the last 100 log lines (deduped) are displayed here ---")
            bc_log.info("---- See full log in %s ---" % self.logfname)
        for line in log_lines:
            bc_log.info(line.rstrip())

        bc_log.info("----------------------------")
        if "cfaout.marshal" in files:
            # might be absent (ex. when analysis failed, or not requested)
            with open(self.cfaoutfname, 'w') as cfaoutfp:
                cfaoutfp.write(self.download_file(files["cfaout.marshal"]))
        self.finish_cb(self.outfname, self.logfname, self.cfaoutfname)

    def download_file(self, fname):
        r = requests.get(self.server_url + '/download/' + fname + '/zlib')
        try:
            return zlib.decompress(r.content)
        except Exception as e:
            bc_log.error("Error uncompressing downloaded file [%s] (%s)" %
                         (fname, e))

    def sha256_digest(self, path):
        with open(path, 'rb') as f:
            h = hashlib.new('sha256')
            h.update(f.read())
            sha256 = h.hexdigest().lower()
        return sha256

    def upload_file(self, path, sha256):
        # check whether file has already been uploaded
        try:
            check_res = requests.head(
                self.server_url + "/download/%s" % sha256)
        except requests.exceptions.ConnectionError as e:
            bc_log.error("Error when contacting the BinCAT server: %s", e)
            return
        if check_res.status_code == 404:
            # Confirmation dialog before uploading file?
            msgBox = QtWidgets.QMessageBox()
            msgBox.setText("Do you really want to upload %s to %s?" %
                           (path, self.server_url))
            msgBox.setStandardButtons(QtWidgets.QMessageBox.Yes |
                                      QtWidgets.QMessageBox.No)
            msgBox.setIcon(QtWidgets.QMessageBox.Question)
            ret = msgBox.exec_()
            if ret == QtWidgets.QMessageBox.No:
                bc_log.info("Upload aborted.")
                return
            # upload
            upload_res = requests.put(
                self.server_url + "/add",
                files={'file': ('file', open(path, 'rb').read())})
            if upload_res.status_code != 200:
                bc_log.error("Error while uploading file %s "
                             "to BinCAT analysis server.", path)
                return
        return True


class State(object):
    """
    Container for (static) plugin state related data & methods.
    """
    def __init__(self):
        self.current_ea = None
        self.cfa = None
        self.current_node = None
        #: list of node ids (int)
        self.current_node_ids = []
        #: cfa.Unrel object or None
        self.current_unrel = None
        #: last run config
        self.current_config = None
        #: config to be edited
        self.edit_config = None
        #: Analyzer instance - protects against merciless garbage collector
        self.analyzer = None
        self.hooks = None
        self.netnode = idabincat.netnode.Netnode("$ com.bincat.bcplugin")
        #: acts as a List of (eip (int), "register name", "taint mask")
        #: XXX store in IDB?
        self.overrides = CallbackWrappedList()
        #: list of [@ or function names (str)]
        self.nops = CallbackWrappedList()
        #: list of [@ or function names (str), arg_nb, ret_val]
        self.skips = CallbackWrappedList()
        #: list of (name, config)
        self.configurations = AnalyzerConfigurations(self)
        # XXX store in idb after encoding?
        self.last_cfaout_marshal = None
        #: filepath to last dumped remapped binary
        self.remapped_bin_path = None
        self.remap_binary = True
        self.remapped_sections = None
        # for debugging purposes, to interact with this object from the console
        global bc_state
        bc_state = self

        self.gui = GUI(self)
        if PluginOptions.get("load_from_idb") == "True":
            self.load_from_idb()

    def new_analyzer(self, *args, **kwargs):
        """
        returns current Analyzer class (web or local)
        """
        if (PluginOptions.get("web_analyzer") == "True" and
                PluginOptions.get("server_url") != ""):
            if 'requests' not in sys.modules:
                bc_log.error("Trying to launch a remote analysis without the 'requests' module !")
                raise AnalyzerUnavailable()
            return WebAnalyzer(*args, **kwargs)
        else:
            return LocalAnalyzer(*args, **kwargs)

    def load_from_idb(self):
        if "out.ini" in self.netnode and "analyzer.log" in self.netnode:
            bc_log.info("Loading analysis results from idb")
            path = tempfile.mkdtemp(suffix='bincat')
            outfname = os.path.join(path, "out.ini")
            logfname = os.path.join(path, "analyzer.log")
            with open(outfname, 'w') as outfp:
                outfp.write(self.netnode["out.ini"])
            with open(logfname, 'w') as logfp:
                logfp.write(self.netnode["analyzer.log"])
            if "current_ea" in self.netnode:
                ea = self.netnode["current_ea"]
            else:
                ea = None
            self.analysis_finish_cb(outfname, logfname, cfaoutfname=None,
                                    ea=ea)
        if "remapped_bin_path" in self.netnode:
            fname = self.netnode["remapped_bin_path"]
            if os.path.isfile(fname):
                self.remapped_bin_path = fname
                if "remapped_sections" in self.netnode:
                    self.remapped_sections = self.netnode["remapped_sections"]
            else:
                bc_log.warning("cannot find remapped binary %s", fname)
        if "remap_binary" in self.netnode:
            self.remap_binary = self.netnode["remap_binary"]

    def clear_background(self):
        """
        reset background color for previous analysis
        """
        if self.cfa:
            color = idaapi.calc_bg_color(idaapi.NIF_BG_COLOR)
            for v in self.cfa.addr_nodes:
                ea = v.value
                idaapi.set_item_color(ea, color)

    def analysis_finish_cb(self, outfname, logfname, cfaoutfname, ea=None):
        idaapi.show_wait_box("HIDECANCEL\nParsing BinCAT analysis results")
        bc_log.debug("Parsing analyzer result file")
        # Here we can't check for user_cancelled because the UI is
        # unresponsive when parsing.
        try:
            cfa = cfa_module.CFA.parse(outfname, logs=logfname)
        except (pybincat.PyBinCATException, NoSectionError):
            idaapi.hide_wait_box()
            bc_log.error("Could not parse result file")
            return None
        self.clear_background()
        self.cfa = cfa
        if cfa:
            # XXX add user preference for saving to idb? in that case, store
            # reference to marshalled cfa elsewhere
            bc_log.info("Storing analysis results to idb...")
            with open(outfname, 'r') as f:
                self.netnode["out.ini"] = f.read()
            with open(logfname, 'r') as f:
                self.netnode["analyzer.log"] = f.read()
            if self.remapped_bin_path:
                self.netnode["remapped_bin_path"] = self.remapped_bin_path
                if self.remapped_sections:
                    self.netnode["remapped_sections"] = self.remapped_sections
            self.netnode["remap_binary"] = self.remap_binary
            if cfaoutfname is not None and os.path.isfile(cfaoutfname):
                with open(cfaoutfname, 'rb') as f:
                    self.last_cfaout_marshal = f.read()
            bc_log.info("Analysis results have been stored idb.")
        else:
            bc_log.info("Empty or unparseable result file.")
        bc_log.debug("----------------------------")
        idaapi.replace_wait_box("Updating IDB with BinCAT results")
        # Update current RVA to start address (nodeid = 0)
        # by default, use current ea - e.g, in case there is no results (cfa is
        # None) or no node 0 (happens in backward mode)
        current_ea = self.current_ea
        if ea is not None:
            current_ea = ea
        else:
            try:
                node0 = cfa['0']
                if node0:
                    current_ea = node0.address.value
            except (KeyError, TypeError):
                # no cfa is None, or no node0
                pass
        try:
            self.set_current_ea(current_ea, force=True)
        except TypeError as e:
            bc_log.warn("Could not load results from IDB")
            bc_log.warn("------ BEGIN EXCEPTION -----")
            bc_log.exception(e)
            bc_log.warn("------ END EXCEPTION -----")
            idaapi.hide_wait_box()
            return None
        self.netnode["current_ea"] = current_ea
        if not cfa:
            return
        for addr, nodeids in list(cfa.addr_nodes.items()):
            if hasattr(idaapi, "user_cancelled") and idaapi.user_cancelled() > 0:
                bc_log.info("User cancelled!")
                idaapi.hide_wait_box()
                return None
            ea = addr.value
            tainted = False
            taint_id = 1
            for n_id in nodeids:
                # is it tainted?
                # find child nodes
                node = cfa[n_id]
                if node.tainted:
                    tainted = True
                    if node.taintsrc:
                        # Take the first one
                        try:
                            taint_id = int(node.taintsrc[0].split("-")[1])
                        except IndexError: # Taint is TOP
                            taint_id = 0
                    break

            if tainted:
                idaapi.set_item_color(ea, taint_color(taint_id))
            else:
                idaapi.set_item_color(ea, 0xF0F0F0)
        idaapi.hide_wait_box()
        self.gui.focus_registers()

    def set_current_node(self, node_id, unrel_id=None):
        if self.cfa:
            node = self.cfa[node_id]
            if node:
                self.set_current_ea(
                    node.address.value,
                    force=True, node_id=node_id, unrel_id=unrel_id)

    def set_current_ea(self, ea, force=False, node_id=None, unrel_id=None):
        """
        :param ea: int or long
        """
        if not (force or ea != self.current_ea):
            return
        self.gui.before_change_ea()
        self.current_ea = ea
        empty_node = True
        if self.cfa:
            node_ids = self.cfa.node_id_from_addr(ea)
            if node_ids:
                if node_id in node_ids:
                    self.current_node = self.cfa[node_id]
                else:
                    self.current_node = self.cfa[node_ids[0]]
                self.current_node_ids = node_ids
                # find unrel_id
                if not unrel_id:
                    unrel_id = self.current_node.default_unrel_id()
                if unrel_id and unrel_id in self.current_node.unrels:
                    self.current_unrel = self.current_node.unrels[unrel_id]
                    empty_node = False
        if empty_node:
            self.current_node = None
            self.current_node_ids = []
            self.current_unrel = None

        self.gui.after_change_ea()

    def guess_filepath(self):
        filepath = self.current_config.binary_filepath
        if os.path.isfile(filepath):
            return filepath
        filepath = ConfigHelpers.guess_file_path()
        if os.path.isfile(filepath):
            return filepath
        # give up
        return None

    def start_analysis(self):
        """
        Creates new temporary dir. File structure:
        input files: init.ini, cfain.marshal
        output files: out.ini, cfaout.marshal
        """
        binary_filepath = self.guess_filepath()
        if not binary_filepath:
            bc_log.error(
                "File %s does not exit. Please fix path in configuration.",
                self.current_config.binary_filepath)
            return
        bc_log.debug("Using %s as source binary path", binary_filepath)
        self.current_config.binary_filepath = binary_filepath

        path = tempfile.mkdtemp(suffix='bincat')

        # instance variable: we don't want the garbage collector to delete the
        # *Analyzer instance, killing an unlucky QProcess in the process
        try:
            self.analyzer = self.new_analyzer(path, self.analysis_finish_cb)
        except AnalyzerUnavailable:
            bc_log.error("Analyzer is unavailable", exc_info=True)
            return

        bc_log.info("Current analyzer path: %s", path)

        # Update overrides
        self.current_config.update_overrides(self.overrides, self.nops,
                                             self.skips)
        analysis_method = self.current_config.analysis_method
        if analysis_method in ("forward_cfa", "backward"):
            if self.last_cfaout_marshal is None:
                bc_log.error("No marshalled CFA has been recorded - run a "
                             "forward analysis first.")
                return
            with open(self.analyzer.cfainfname, 'w') as f:
                f.write(self.last_cfaout_marshal)
        # Set correct file names
        # Note: bincat_native expects a filename for in_marshalled_cfa_file -
        # may not exist if analysis mode is forward_binary
        self.current_config.set_cfa_options('true', self.analyzer.cfainfname,
                                            self.analyzer.cfaoutfname)
        bc_log.debug("Generating .no files...")

        headers_filenames = self.current_config.headers_files.split(',')
        # compile .c files for libs, if there are any
        bc_log.debug("Initial header files: %r", headers_filenames)
        new_headers_filenames = []
        for f in headers_filenames:
            f = f.strip()
            if not f:
                continue
            if f.endswith('.c'):
                # generate .npk from .c file
                if not os.path.isfile(f):
                    bc_log.warning(
                        "header file %s could not be found, continuing", f)
                    continue
                new_npk_fname = f[:-2] + '.no'
                headers_filenames.remove(f)
                if not os.path.isfile(new_npk_fname):
                    # compile
                    self.analyzer.generate_tnpk(fname=f,
                                                destfname=new_npk_fname)
                    if not os.path.isfile(new_npk_fname):
                        bc_log.warning(
                            ".no file containing type data for the headers "
                            "file %s could not be generated, continuing", f)
                        continue
                    f = new_npk_fname
            # Relative paths are copied
            elif f.endswith('.no') and os.path.isfile(f):
                if f[0] != os.path.sep:
                    temp_npk_fname = os.path.join(path, os.path.basename(f))
                    shutil.copyfile(f, temp_npk_fname)
                    f = temp_npk_fname
            else:
                bc_log.warning(
                    "Header file %s does not exist or does not match expected "
                    "extensions (.c, .no), ignoring.", f)
                continue

            new_headers_filenames.append(f)
        headers_filenames = new_headers_filenames
        # generate npk file for the binary being analyzed (unless it has
        # already been generated)
        if not any(
                [s.endswith('pre-processed.no') for s in headers_filenames]):
            npk_filename = self.analyzer.generate_tnpk()
            if not npk_filename:
                bc_log.warning(
                    ".no file containing type data for the file being "
                    "analyzed could not be generated, continuing. The "
                    "ida-generated header could be invalid.")
            else:
                headers_filenames.append(npk_filename)
            bc_log.debug("Final npk files: %r", headers_filenames)
        self.current_config.headers_files = ','.join(headers_filenames)

        self.current_config.write(self.analyzer.initfname)
        self.analyzer.run()

    def re_run(self):
        """
        Re-run analysis, taking new overrides settings into account
        """
        if self.current_config is None:
            # XXX upload all required files in Web Analyzer
            # XXX Store all required files in IDB
            bc_log.error(
                "You have to run the analysis first using the 'Analyze From "
                "here (Ctrl-Shift-A)' menu - reloading previous results from "
                "IDB is not yet supported.")
        else:
            self.start_analysis()

    def add_or_replace_override(self, ea, htext, mask):
        for idx, (e, h, _) in enumerate(self.overrides):
            if e == ea and h == htext:
                del self.overrides[idx]
        self.overrides.append((ea, htext, mask))


class CallbackWrappedList(MutableSequence):
    """
    Acts as a List object, wraps write access with calls to properly invalidate
    models associated with View GUI objects.
    Should store only immutable objects.
    """
    def __init__(self):
        self._data = []
        #: list of functions to be called prior to updating list
        self.pre_callbacks = []
        #: list of functions to be called after updating list
        self.post_callbacks = []
        #: cache

    def register_callbacks(self, pre_cb, post_cb):
        if pre_cb:
            self.pre_callbacks.append(pre_cb)
        if post_cb:
            self.post_callbacks.append(post_cb)

    def __getitem__(self, index):
        # no need for wrappers - values should be immutable
        return self._data[index]

    def _callback_wrap(f):
        def wrap(self, *args, **kwargs):
            for cb in self.pre_callbacks:
                cb()
            f(self, *args, **kwargs)
            for cb in self.post_callbacks:
                cb()
        return wrap

    @_callback_wrap
    def __setitem__(self, index, value):
        self._data[index] = tuple(value)

    @_callback_wrap
    def __delitem__(self, item):
        del self._data[item]

    @_callback_wrap
    def clear(self):
        self._data = []

    def __len__(self):
        return len(self._data)

    @_callback_wrap
    def insert(self, index, value):
        self._data.insert(index, tuple(value))


def PLUGIN_ENTRY():
    return BincatPlugin()


repository: ZetaTwo/binja-experiments
file: script_flareon8_evil/flareon8-evil.py
description: Experiments, snippets and other things related to Binary Ninja
created_at: 2021-10-05 13:29:15+00:00
pushed_at: 2024-10-22 23:18:02+00:00
url:
https://github.com/ZetaTwo/binja-experiments/blob/ca5ba019f1346604aeaa21aff313f139b519db0f/script_flareon8_evil/flareon8-evil.py

content:
#!/usr/bin/env python3

import binaryninja
from binaryninja.mediumlevelil import MediumLevelILOperation
from binaryninja import RegisterValueType

LOAD_FUNC_ADDR =  0x4054B0
# Executable location containing padding where we can place the small trampoline
TRAMPOLINE_ADDR = 0X4039D3

# Created by taking the output of step 2 and running in a debugger
LibraryFunctions = {
    (0x176684, 0x314fa299): "ntdll.ZwSetLdtEntries",
    (0x176684, 0xc0ec38c1): "ntdll.Unknown_1",
    (0x176684, 0xfe76fb3a): "ntdll.DbgBreakPoint",

    (0x234324, 0x2cae18a6): "user32.ReleaseDC",
    (0x234324, 0x7c1b5535): "user32.GetDC",

    (0x246132, 0x1c5d2c5e): "kernel32.CreateMutexA",
    (0x246132, 0x277d84bb): "kernel32.WaitForSingleObject",
    (0x246132, 0x307d41cd): "kernel32.GetCurrentThread",
    (0x246132, 0x30fb5637): "kernel32.GetModuleHandleA",
    (0x246132, 0x3ea79291): "kernel32.ExitProcess",
    (0x246132, 0x3f2eef6c): "kernel32.CheckRemoteDebuggerPresent",
    (0x246132, 0x4f2e84b7): "kernel32.Sleep",
    (0x246132, 0x53805498): "kernel32.ReleaseMutex",
    (0x246132, 0x5d22746):  "kernel32.CreateThread",
    (0x246132, 0x66fff672): "kernel32.GetSystemTime",
    (0x246132, 0xa31beaa4): "kernel32.VirtualProtect",
    (0x246132, 0xb243fe0c): "kernel32.GetCurrentProcess",
    (0x246132, 0xb3d0027c): "kernel32.GetLastError",
    (0x246132, 0xb87be91c): "kernel32.GetTickCount",
    (0x246132, 0xcfe0d62f): "kernel32.GetThreadContext",
    (0x246132, 0xd22f881e): "kernel32.Unknown_3",
    (0x246132, 0xd6c07d79): "kernel32.CreateSemaphoreA",
    (0x246132, 0xedf7e920): "kernel32.Unknown_5",
    (0x246132, 0xf5d407d0): "kernel32.WaitForMultipleObjects",
    (0x246132, 0xf8395491): "kernel32.GetConsoleWindow",

    (0x24df32, 0x24e0b26e): "Unknown_4",

    (0x43493856, 0x52e698ca): "gdi32.DeleteDC",
    (0x43493856, 0x6cdb8a4):  "gdi32.CreateDIBSection",
    (0x43493856, 0xc496854f): "gdi32.CreateCompatibleDC",
    (0x43493856, 0xdc5bd1aa): "gdi32.DeleteObject",

    (0x52325, 0x3ad795fd): "ws2_32.bind",
    (0x52325, 0x774bbdd0): "ws2_32.recvfrom",
    (0x52325, 0x88efa52b): "ws2_32.closesocket",
    (0x52325, 0x97e90ebc): "ws2_32.inet_addr",
    (0x52325, 0xa0b3da21): "ws2_32.WSAGetLastError",
    (0x52325, 0xa4e84503): "ws2_32.sendto",
    (0x52325, 0xa572514d): "ws2_32.setsockopt",
    (0x52325, 0xc3e4c63f): "ws2_32.WSACleanup",
    (0x52325, 0xd5af7bf3): "ws2_32.socket",
    (0x52325, 0xddc03158): "ws2_32.WSAStartup",
    (0x52325, 0xeb68c9d0): "ws2_32.WSAIoctl",
    (0x52325, 0xf0b9c6a8): "ws2_32.htons",

    (0x523422, 0x151b52df): "advapi32.CryptDecrypt",
    (0x523422, 0x4c9945c7): "advapi32.LookupPrivilegeValueA",
    (0x523422, 0x4fbdc973): "advapi32.CryptDestroyKey",
    (0x523422, 0x539dda96): "advapi32.CryptReleaseContext",
    (0x523422, 0x59504677): "advapi32.PrivilegeCheck",
    (0x523422, 0x613a1fc5): "advapi32.OpenThreadToken",
    (0x523422, 0x94f7a04c): "advapi32.CryptImportKey",
    (0x523422, 0xcac815fa): "advapi32.CryptAcquireContextA",
    (0x523422, 0xf0271154): "advapi32.OpenProcessToken",

    (0x7468951, 0x15cf2779): "oleaut32.SysAllocString",
    (0x7468951, 0x7492cdd5): "oleaut32.VariantInit",

    (0x832325, 0x8f04bc74): "Unknown_2",
}


def plugin_entry(bv):
    # Deobfuscate step 0 - Create ecx/edx swap trampoline
    
    trampoline = bv.functions[0].arch.assemble(f"xchg ecx, edx\njmp {LOAD_FUNC_ADDR:#x}", TRAMPOLINE_ADDR)
    bv.write(TRAMPOLINE_ADDR, trampoline)

    # Deobfuscate step 1 - Patch null derefs to call trampoline
    patch_locations = set()
    for func in bv.functions:
        #for (ins1, ins1_addr), (ins2, ins2_addr) in zip(func.instructions, itertools.islice(func.instructions, 1, None)): # Does not work properly for some reason (BUG?)
        for ins1, ins1_addr in func.instructions:
            ins2_addr = ins1_addr + bv.get_instruction_length(ins1_addr)
            ins2_len = bv.get_instruction_length(ins2_addr)
            ins2_data = bv[ins2_addr:ins2_addr+ins2_len]
            ins2, ins2_len2 = func.arch.get_instruction_text(ins2_data, ins2_addr)
            assert ins2_len == ins2_len2, (ins2, ins2_len, ins2_len2)

            # Example: (['mov', '     ', 'eax', ', ', 'dword ', '[', 'eax', ']'], 4203616
            if ins1[0].text == 'xor' and ins1[2] == ins1[4]:
                if (len(ins2) == 3 and ins2[0].text == 'div' and ins2[2] == ins1[2]) or (len(ins2) == 8 and ins2[0].text == 'mov' and ins2[5].text == '[' and ins2[6] == ins1[2]):
                    binaryninja.log_info(f'Patching at {ins1_addr:#x}: {ins1}, {ins2}')
                    patch_locations.add(ins1_addr)
    
    
    for patch_addr in patch_locations:
        patch = func.arch.assemble(f"call {TRAMPOLINE_ADDR:#x}\n call eax", patch_addr)
        assert len(patch) == 7
        bv.write(patch_addr, patch)
    binaryninja.log_info(f'Patched {len(patch_locations)} locations')


def get_load_func_calls(bv):
    func_loads = set()
    for func in bv.functions:
        for mlil_ins in func.mlil.instructions: # Ugly, I should be able to do xref to mlil calls
            if mlil_ins.operation != MediumLevelILOperation.MLIL_CALL:
                continue
        
            if mlil_ins.dest.value.type != RegisterValueType.ConstantPointerValue:
                continue

            if len(mlil_ins.params) != 2 or not all(param.possible_values.type == RegisterValueType.ConstantValue for param in mlil_ins.params):
                continue

            func_addr = mlil_ins.dest.value.value
            arg1 = mlil_ins.params[0].possible_values.value
            arg2 = mlil_ins.params[1].possible_values.value

            if func_addr == LOAD_FUNC_ADDR:
                #print(f'LoadFunc({arg1:#x}, {arg2:#x})')
                yield mlil_ins, arg1, arg2
            elif func_addr == TRAMPOLINE_ADDR:
                yield mlil_ins, arg2, arg1
                #print(f'LoadFunc({arg2:#x}, {arg1:#x})')


def plugin_entry2(bv):
    load_func = bv.get_function_at(LOAD_FUNC_ADDR)
    load_func2 = bv.get_function_at(TRAMPOLINE_ADDR)

    func_loads = set()
    for _, arg1, arg2 in get_load_func_calls(bv):
        func_loads.add(arg1, arg2)

    for arg1, arg2 in func_loads:
        #print(f'LoadFunc({arg1:#x}, {arg2:#x})')
        print(f"mov ecx, {arg1:#x}\nmov, edx {arg2:#x}\ncall 0x4054b0")
    print('ret')

def plugin_entry3(bv):
    load_func = bv.get_function_at(LOAD_FUNC_ADDR)
    load_func2 = bv.get_function_at(TRAMPOLINE_ADDR)

    
    for mlil_ins, arg1, arg2 in get_load_func_calls(bv):
        func_key = (arg1, arg2)
        if func_key in LibraryFunctions:
            bv.set_comment_at(mlil_ins.address, LibraryFunctions[func_key])

binaryninja.plugin.PluginCommand.register("FlareOn8-Evil\\Step 1 - Deobfuscate function loads", "Evil - Replace exception obfuscation with call to function loader. Run multiple times until no more patches are found", plugin_entry)
binaryninja.plugin.PluginCommand.register("FlareOn8-Evil\\Step 2 - Dump loader arguments", "Evil - Generate code for dumping loaded functions", plugin_entry2)
binaryninja.plugin.PluginCommand.register("FlareOn8-Evil\\Step 3 - Apply loader comments", "Evil - Add comments to show which function is loaded", plugin_entry3)


repository: psyplot/psyplot
file: psyplot/utils.py
description: Python package for interactive data visualization
url: https://github.com/psyplot/psyplot/blob/d603bc0810a9af8a0918ed80a659afaf43027b99/psyplot/utils.py
created_at: 2017-04-11 14:28:42+00:00
pushed_at: 2024-07-15 11:03:26+00:00
content:
"""Miscallaneous utility functions for the psyplot package."""

# SPDX-FileCopyrightText: 2016-2024 University of Lausanne
# SPDX-FileCopyrightText: 2020-2021 Helmholtz-Zentrum Geesthacht

# SPDX-FileCopyrightText: 2021-2024 Helmholtz-Zentrum hereon GmbH
#
# SPDX-License-Identifier: LGPL-3.0-only

import inspect
import re
import sys
from difflib import get_close_matches
from itertools import chain, filterfalse

import six

from psyplot.docstring import dedent, docstrings


def get_default_value(func, arg):
    argspec = inspect.getfullargspec(func)
    return next(
        default
        for a, default in zip(reversed(argspec[0]), reversed(argspec.defaults))
        if a == arg
    )


def isstring(s):
    return isinstance(s, str)


def plugin_entrypoints(group="psyplot", name="name"):
    """This utility function gets the entry points of the psyplot plugins"""
    if sys.version_info[:2] > (3, 7):
        from importlib.metadata import entry_points

        try:
            eps = entry_points(group=group, name=name)
        except TypeError:  # python<3.10
            eps = [
                ep for ep in entry_points().get(group, []) if ep.name == name
            ]
    else:
        from pkg_resources import iter_entry_points

        eps = iter_entry_points(group=group, name=name)
    return eps


class Defaultdict(dict):
    """An ordered :class:`collections.defaultdict`

    Taken from http://stackoverflow.com/a/6190500/562769"""

    def __init__(self, default_factory=None, *a, **kw):
        if default_factory is not None and not callable(default_factory):
            raise TypeError("first argument must be callable")
        dict.__init__(self, *a, **kw)
        self.default_factory = default_factory

    def __getitem__(self, key):
        try:
            return dict.__getitem__(self, key)
        except KeyError:
            return self.__missing__(key)

    def __missing__(self, key):
        if self.default_factory is None:
            raise KeyError(key)
        self[key] = value = self.default_factory()
        return value

    def __reduce__(self):
        if self.default_factory is None:
            args = tuple()
        else:
            args = (self.default_factory,)
        return type(self), args, None, None, self.items()

    def copy(self):
        """Return a shallow copy of the dictionary"""
        return self.__copy__()

    def __copy__(self):
        return type(self)(self.default_factory, self)

    def __deepcopy__(self, memo):
        import copy

        return type(self)(self.default_factory, copy.deepcopy(self.items()))

    def __repr__(self):
        return "Defaultdict(%s, %s)" % (
            self.default_factory,
            dict.__repr__(self),
        )


class _TempBool(object):
    """Wrapper around a boolean defining an __enter__ and __exit__ method

    Notes
    -----
    If you want to use this class as an instance property, rather use the
    :func:`_temp_bool_prop` because this class as a descriptor is ment to be a
    class descriptor"""

    #: default boolean value for the :attr:`value` attribute
    default = False

    #: boolean value indicating whether there shall be a validation or not
    value = False

    def __init__(self, default=False):
        """
        Parameters
        ----------
        default: bool
            value of the object"""
        self.default = default
        self.value = default
        self._entered = []

    def __enter__(self):
        self.value = not self.default
        self._entered.append(1)

    def __exit__(self, type, value, tb):
        self._entered.pop(-1)
        if not self._entered:
            self.value = self.default

    if six.PY2:

        def __nonzero__(self):
            return self.value

    else:

        def __bool__(self):
            return self.value

    def __repr__(self):
        return repr(bool(self))

    def __str__(self):
        return str(bool(self))

    def __call__(self, value=None):
        """
        Parameters
        ----------
        value: bool or None
            If None, the current value will be negated. Otherwise the current
            value of this instance is set to the given `value`"""
        if value is None:
            self.value = not self.value
        else:
            self.value = value

    def __get__(self, instance, owner):
        return self

    def __set__(self, instance, value):
        self.value = value


def _temp_bool_prop(propname, doc="", default=False):
    """Creates a property that uses the :class:`_TempBool` class

    Parameters
    ----------
    propname: str
        The attribute name to use. The _TempBool instance will be stored in the
        ``'_' + propname`` attribute of the corresponding instance
    doc: str
        The documentation of the property
    default: bool
        The default value of the _TempBool class"""

    def getx(self):
        if getattr(self, "_" + propname, None) is not None:
            return getattr(self, "_" + propname)
        else:
            setattr(self, "_" + propname, _TempBool(default))
        return getattr(self, "_" + propname)

    def setx(self, value):
        getattr(self, propname).value = bool(value)

    def delx(self):
        getattr(self, propname).value = default

    return property(getx, setx, delx, doc)


def unique_everseen(iterable, key=None):
    """List unique elements, preserving order. Remember all elements ever seen.

    Function taken from https://docs.python.org/2/library/itertools.html"""
    # unique_everseen('AAAABBBCCDAABBB') --> A B C D
    # unique_everseen('ABBCcAD', str.lower) --> A B C D
    seen = set()
    seen_add = seen.add
    if key is None:
        for element in filterfalse(seen.__contains__, iterable):
            seen_add(element)
            yield element
    else:
        for element in iterable:
            k = key(element)
            if k not in seen:
                seen_add(k)
                yield element


def is_remote_url(path):
    patt = re.compile(r"^https?\://")
    if not isinstance(path, six.string_types):
        return all(map(patt.search, (s or "" for s in path)))
    return bool(re.search(r"^https?\://", path))


@docstrings.get_sections(
    base="check_key", sections=["Parameters", "Returns", "Raises"]
)
@dedent
def check_key(
    key,
    possible_keys,
    raise_error=True,
    name="formatoption keyword",
    msg=("See show_fmtkeys function for possible formatopion " "keywords"),
    *args,
    **kwargs,
):
    """
    Checks whether the key is in a list of possible keys

    This function checks whether the given `key` is in `possible_keys` and if
    not looks for similar sounding keys

    Parameters
    ----------
    key: str
        Key to check
    possible_keys: list of strings
        a list of possible keys to use
    raise_error: bool
        If not True, a list of similar keys is returned
    name: str
        The name of the key that shall be used in the error message
    msg: str
        The additional message that shall be used if no close match to
        key is found
    *args, **kwargs
        They are passed to the :func:`difflib.get_close_matches` function
        (i.e. `n` to increase the number of returned similar keys and
        `cutoff` to change the sensibility)

    Returns
    -------
    str
        The `key` if it is a valid string, else an empty string
    list
        A list of similar formatoption strings (if found)
    str
        An error message which includes

    Raises
    ------
    KeyError
        If the key is not a valid formatoption and `raise_error` is True"""
    if key not in possible_keys:
        similarkeys = get_close_matches(key, possible_keys, *args, **kwargs)
        if similarkeys:
            msg = ("Unknown %s %s! Possible similiar " "frasings are %s.") % (
                name,
                key,
                ", ".join(similarkeys),
            )
        else:
            msg = ("Unknown %s %s! ") % (name, key) + msg
        if not raise_error:
            return "", similarkeys, msg
        raise KeyError(msg)
    else:
        return key, [key], ""


def sort_kwargs(kwargs, *param_lists):
    """Function to sort keyword arguments and sort them into dictionaries

    This function returns dictionaries that contain the keyword arguments
    from `kwargs` corresponding given iterables in ``*params``

    Parameters
    ----------
    kwargs: dict
        Original dictionary
    ``*param_lists``
        iterables of strings, each standing for a possible key in kwargs

    Returns
    -------
    list
        len(params) + 1 dictionaries. Each dictionary contains the items of
        `kwargs` corresponding to the specified list in ``*param_lists``. The
        last dictionary contains the remaining items"""
    return chain(
        (
            {key: kwargs.pop(key) for key in params.intersection(kwargs)}
            for params in map(set, param_lists)
        ),
        [kwargs],
    )


def hashable(val):
    """Test if `val` is hashable and if not, get it's string representation

    Parameters
    ----------
    val: object
        Any (possibly not hashable) python object

    Returns
    -------
    val or string
        The given `val` if it is hashable or it's string representation"""
    if val is None:
        return val
    try:
        hash(val)
    except TypeError:
        return repr(val)
    else:
        return val


@docstrings.get_sections(base="join_dicts")
def join_dicts(dicts, delimiter=None, keep_all=False):
    """Join multiple dictionaries into one

    Parameters
    ----------
    dicts: list of dict
        A list of dictionaries
    delimiter: str
        The string that shall be used as the delimiter in case that there
        are multiple values for one attribute in the arrays. If None, they
        will be returned as sets
    keep_all: bool
        If True, all formatoptions are kept. Otherwise only the intersection

    Returns
    -------
    dict
        The combined dictionary"""
    if not dicts:
        return {}
    if keep_all:
        all_keys = set(chain(*(d.keys() for d in dicts)))
    else:
        all_keys = set(dicts[0])
        for d in dicts[1:]:
            all_keys.intersection_update(d)
    ret = {}
    for key in all_keys:
        vals = {hashable(d.get(key, None)) for d in dicts} - {None}
        if len(vals) == 1:
            ret[key] = next(iter(vals))
        elif delimiter is None:
            ret[key] = vals
        else:
            ret[key] = delimiter.join(map(str, vals))
    return ret


def is_iterable(iterable):
    """Test if an object is iterable

    Parameters
    ----------
    iterable: object
        The object to test

    Returns
    -------
    bool
        True, if the object is an iterable object"""
    try:
        iter(iterable)
    except TypeError:
        return False
    else:
        return True


repository: sfinktah/idarest75
file: idarest/idarest.py
wanted_name: IDA Rest API (Terminate)
comment: Interface to IDA Rest API
created_at: 2021-09-19 14:01:35+00:00
pushed_at: 2022-06-29 09:49:58+00:00
url:
https://github.com/sfinktah/idarest75/blob/7a68452ce71ee6017d3f10a0c8caa3721dd905d3/idarest/idarest.py

content:
import sys
import os
import cgi
import errno
import itertools
import json
import re
import requests
import socket
import threading
import time
import traceback
import atexit
import inspect
import itertools
import urllib.request, urllib.error, urllib.parse as urlparse
from code import InteractiveInterpreter
from http.server import BaseHTTPRequestHandler, HTTPServer
from queue import Queue, Empty, Full
from socketserver import ThreadingMixIn

# pip install superglobal split_paren
from superglobals import setglobal, getglobal, superglobals
from split_paren import paren_multisplit

try:
    from .idarest_mixins import IdaRestConfiguration, BorrowStdOut, IdaRestLog
except:
    from idarest_mixins import IdaRestConfiguration, BorrowStdOut, IdaRestLog

# for testing outside ida
try:
    import idc
    import ida_idaapi
    import ida_kernwin
    import ida_loader
    import idaapi
    import idautils
    import ida_pro
    from PyQt5 import QtWidgets, QtCore
    delayed_exec_timer = QtCore.QTimer()
except:
    class idc:
        @staticmethod
        def msg(s):
            print(s)
        def get_idb_path():
            return '.'

    class ida_idaapi:
        plugin_t = object
        PLUGIN_UNL = PLUGIN_KEEP = 0

_API_IDB = idc.get_idb_path()
_API_FILE = __file__

def static_vars(**kwargs):
    def decorate(func):
        for k in kwargs:
            setattr(func, k, kwargs[k])
        return func
    return decorate

def _classname(instance):
    return getattr(getattr(instance, '__class__', object), '__name__', '')

def _asBytes(s):
    if isinstance(s, str):
        return s.encode('utf-8')
    return s

def _asStringRaw(o):
    return o.decode('raw_unicode_escape') if isinstance(o, (bytes, bytearray)) else o

def _execute_sync(cmd, *args):
    if ida_pro.is_main_thread():
        return cmd()
    request = ida_kernwin.execute_sync(cmd, ida_kernwin.MFF_WRITE)
    #  request = ida_kernwin.execute_sync(cmd, ida_kernwin.MFF_NOWAIT)
    #  print("[_execute_sync] request: {}".format(request))
    #  HTTPRequestHandler.delayed_call(lambda: ida_kernwin.cancel_exec_request(request))

"""
IDA Pro Plugin Interface

Define an IDA Python plugin required class and function.
"""

# If used as a plugin everything just becomes harder, though it does clean-up used ports
# properly. 
MENU_PATH = 'Edit/Other'
class idarest_plugin_t(IdaRestConfiguration, IdaRestLog, ida_idaapi.plugin_t):
    count = 0
    flags = ida_idaapi.PLUGIN_UNL
    comment = "Interface to IDA Rest API"
    help = "IDA Rest API for basic RE tool interoperability"
    wanted_name = "IDA Rest API (Terminate)"
    wanted_hotkey = ""

    @staticmethod
    def test_bind_port(port):
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
            try:
                # s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
                s.bind((idarest_plugin_t.config['api_host'], port))
            except socket.error as e:
                if e.errno != errno.EADDRINUSE:
                    if idarest_plugin_t.config['api_debug']: idc.msg(e)
                else:
                    if idarest_plugin_t.config['api_debug']: idc.msg("[idarest_plugin_t::test_bind_port] port in use: {}\n".format(port))
                return False
        return True

    def init(self):
        if idarest_plugin_t.count:
            return ida_idaapi.PLUGIN_KEEP
        idarest_plugin_t.count += 1
        if idarest_plugin_t.config['api_verbose']: idc.msg("[idarest_plugin_t::init]\n")
        super(idarest_plugin_t, self).__init__()
        #  self.load_configuration()

        # 10 queues for standard operation, 1 queue for iter/generator usage
        
        self.state = None
        # I replaced the previous menu contexts with hotkey contexts, but then
        # decided they were pretty useless
        #  new_ctx1 = ida_kernwin.add_hotkey("Alt-7", lambda *a: self.start())
        #  new_ctx2 = ida_kernwin.add_hotkey("Alt-8", lambda *a: self.stop())
        #  new_ctx2 = ida_kernwin.add_hotkey("Alt-9", lambda *a: self.term())
        #  self.ctxs = [new_ctx1, new_ctx2]
        self.worker = None
        self.timer = None
        for port in range(idarest_plugin_t.config['api_port'], 65535):
            if idarest_plugin_t.test_bind_port(port):
                self.port = port
                break
        self.host = idarest_plugin_t.config['api_host']
        # IDA 7 used totally new menu contexts, and I cba translating them.
        #  ret = self._add_menus()
        if idarest_plugin_t.config['api_info']: idc.msg("[idarest_plugin_t::init] done\n")
        return self.start()

    def is_alive(self):
        return self.worker and self.worker.is_alive()

    def start(self, *args):
        # load_and_run_plugin
        if idarest_plugin_t.config['api_verbose']: idc.msg("[idarest_plugin_t::start]\n")
        if self.worker and self.worker.is_alive():
            if idarest_plugin_t.config['api_info']: idc.msg("[idarest_plugin_t::start] already running\n")
            return ida_idaapi.PLUGIN_SKIP

        worker = Worker(self.host, self.port)
        self.worker = worker
        self.worker.start()

        if idarest_plugin_t.config['api_info']: idc.msg("[idarest_plugin_t::start] worker started: port {}\n".format(self.port))

        timer = Timer(self.host, self.port)
        self.timer = timer
        self.timer.start()

        if idarest_plugin_t.config['api_info']: idc.msg("[idarest_plugin_t::start] timer started\n")

        def cleanup():
            self.register(self.host, self.port, unregister=True)
            self.log("**atexit** cleanup")
            if worker and worker.is_alive():
                self.log("[idarest_plugin_t::start::cleanup] stopping..\n")
                worker.stop()
                self.log("[idarest_plugin_t::start::cleanup] joining..\n")
                worker.join()
                self.log("[idarest_plugin_t::start::cleanup] stopped\n")

            if timer and timer.is_alive() and not timer.stopped():
                self.log("[idarest_plugin_t::start::cleanup] stopping..\n")
                timer.stop()
                self.log("[idarest_plugin_t::start::cleanup] joining..\n")
                timer.join()
                self.log("[idarest_plugin_t::start::cleanup] stopped\n")

        print('[idarest_plugin_t::start] registered atexit cleanup')
        atexit.register(cleanup)


        #  idarest_plugin_t.register(self.host, self.port)

        return ida_idaapi.PLUGIN_KEEP

    def run(self, *args):
        if idarest_plugin_t.config['api_info']: idc.msg("[idarest_plugin_t::run] {}\n".format(args))
        pass

    def stop(self):
        if not self.timer or not self.timer.is_alive():
            if idarest_plugin_t.config['api_info']: idc.msg("[idarest_plugin_t::stop] timer was not running\n")
        else:
            if idarest_plugin_t.config['api_info']: idc.msg("[idarest_plugin_t::stop] stopping master timer..\n")
            self.timer.stop()
            #  idc.msg("[idarest_plugin_t::stop] joining..\n")
            #  self.timer.join()
        if not self.worker or not self.worker.is_alive():
            if idarest_plugin_t.config['api_info']: idc.msg("[idarest_plugin_t::stop] worker was not running\n")
        else:
            if idarest_plugin_t.config['api_info']: idc.msg("[idarest_plugin_t::stop] stopping RESTful service..\n")
            self.worker.stop()
            if idarest_plugin_t.config['api_info']: idc.msg("[idarest_plugin_t::stop] joining..\n")
            self.worker.join()
            if idarest_plugin_t.config['api_info']: idc.msg("[idarest_plugin_t::stop] stopped\n")
        idarest_plugin_t.register(self.host, self.port, unregister=True)
        idarest_main.instance = None
        if 'ir' in globals():
            globals().pop('ir')
        if idarest_plugin_t.count > 0:
            idarest_plugin_t.count -= 1


    def term(self):
        try:
            self.stop()
        except Exception as e:
            if idarest_plugin_t.config['api_info']: idc.msg("[idarest_plugin_t::term] {}: {}\n".format(e.__class__.__name__, str(e)))
        #  for ctx in self.ctxs:
            #  ida_kernwin.del_hotkey(ctx)
            #
            #

    @staticmethod
    def register(host, port, unregister=False):
        if unregister:
            url = 'http://{}:{}{}/unregister'.format(idarest_plugin_t.config['master_host'], idarest_plugin_t.config['master_port'], idarest_plugin_t.config['api_prefix'])
        else:
            url = 'http://{}:{}{}/register'.format(idarest_plugin_t.config['master_host'], idarest_plugin_t.config['master_port'], idarest_plugin_t.config['api_prefix'])
        if idarest_plugin_t.config['api_verbose']: idc.msg("[idarest_plugin_t::register] trying to connect to master at {}\n".format(url))
        master_plugin = _API_FILE.replace('idarest.py', 'idarest_master.py')
        connect_timeout = 1
        read_timeout = 1
        try:
            p = _API_IDB
            idb = os.path.split(os.path.split(p)[0])[1] + '/' + os.path.splitext(os.path.split(os.path.split(p)[1])[1])[0]
            
            r = requests.get(
                    url, 
                    params={ 
                        'host': host,
                        'port': port,
                        'idb': idb,
                    }, 
                    timeout=(connect_timeout, read_timeout))
            if r.status_code == 200:
                if r.content:
                    if idarest_plugin_t.config['api_verbose']: idc.msg("[idarest_plugin_t::register] master responded correctly: {}, {}\n".format(url, r.content))
                else:
                    if idarest_plugin_t.config['api_info']: idc.msg("[idarest_plugin_t::register] master failed to return data: {}, {}\n".format(url, r.content))
                return
            if idarest_plugin_t.config['api_info']: idc.msg("[idarest_plugin_t::register] master returned status_code: {}\n".format(r.status_code))
        except Exception as e:
            if idarest_plugin_t.config['api_info']: idc.msg("[idarest_plugin_t::register] failed to connect to master: {}\n".format(e.__class__.__name__))
            if not unregister:
                if e.__class__.__name__ in ('ConnectTimeout', 'ConnectionError'):
                    if idarest_plugin_t.config['api_info']: idc.msg("[idarest_plugin_t::register] launching new master\n")
                    _execute_sync(lambda: ida_loader.load_plugin(master_plugin), ida_kernwin.MFF_WRITE)
                else:
                    if idarest_plugin_t.config['api_info']: idc.msg("[idarest_plugin_t::register] exception not of a type to trigger loading new master\n")

                
        except requests.exceptions.ConnectionError as e:
            if idarest_plugin_t.config['api_info']: 
                idc.msg("[idarest_plugin_t::register] failed to connect to master: {}: {}\n".format(e.__class__.__name__, str(e)))
            if not unregister:
                if idarest_plugin_t.config['api_info']: idc.msg("[idarest_plugin_t::register] launching new master\n")
                _execute_sync(lambda: ida_loader.load_plugin(master_plugin), ida_kernwin.MFF_WRITE)
        # ConnectionAbortedError
            if idarest_plugin_t.config['api_info']: idc.msg("[idarest_plugin_t::register] failed to connect to master: {}: {}\n".format(e.__class__.__name__, str(e)))

    @property
    def handler(self):
        return self.worker.httpd.RequestHandlerClass

    def add_route(self, route_pattern, f):
        self.handler.add_route(route_pattern, f)

    def remove_route(self, route_pattern):
        self.handler.remove_route(self.handler, route_pattern)

idarest_plugin_t.load_configuration()


class HTTPRequestError(BaseException):
    def __init__(self, msg, code):
        self.msg = msg
        self.code = code

class UnknownApiError(HTTPRequestError):
    pass

class HTTPRequestHandler(BaseHTTPRequestHandler):
    routes = {}
    docs = {}
    prefns = {}
    postfns = {}
    idarest_queue = [Queue() for x in range(11)]
    uid_iterator = itertools.count()

    @property
    def uid(self):
        # is probably guaranteed to be atomic https://stackoverflow.com/a/27062830/912236
        value = next(HTTPRequestHandler.uid_iterator)
        return value % 10

    def log_message(self, format, *args):
        return

    @staticmethod
    def _get_params(f):
        if idarest_plugin_t.config['api_debug']: idc.msg(inspect.getsource(ir.handler.routes['call'][1]))

    @staticmethod
    def set_result(uid, value):
        if idarest_plugin_t.config['api_verbose']: idc.msg("[set_result] {}: {}\n".format(uid, value))
        HTTPRequestHandler.idarest_queue[uid].put(value)
        return uid

    @staticmethod
    def synced_next(value):
        try:
            v = next(value)
            if idarest_plugin_t.config['api_debug']: idc.msg("next value: {}".format(v))
            HTTPRequestHandler.set_result(10, v)
        except StopIteration as e:
            HTTPRequestHandler.set_result(10, e)

    @staticmethod
    def wrapped_iter(value):
        while True:
            _execute_sync(lambda: HTTPRequestHandler.synced_next(value), ida_kernwin.MFF_WRITE)
            yield HTTPRequestHandler.get_result(10)
        yield None

    @staticmethod
    def get_result(uid):
        try:
            value = HTTPRequestHandler.idarest_queue[uid].get(timeout=2)
            if str(type(value)) == "<class 'generator'>":
                if idarest_plugin_t.config['api_debug']: idc.msg("[get_result] return wrapped_iter")
                return HTTPRequestHandler.wrapped_iter(value)
        except Empty:
            value = {'code': 400, 'msg': 'No response: timeout',
                    "traceback": traceback.format_exc()}
        except Exception as e:
            value = {'code': 400, 'msg': 'Unhandled Exception: ({}) {}'.format(type(e), str(e)),
                    "traceback": traceback.format_exc()}
        except:
            value = "timeout"
        if idarest_plugin_t.config['api_verbose']: idc.msg("[get_result] {}: {}\n".format(uid, value))
        return value

    @staticmethod
    def build_route_pattern(route):
        return re.compile("^{0}$".format(route))

    @staticmethod
    def route(route_str):
        def decorator(f):
            #  if idarest_plugin_t.config['api_debug']: print("[route] {}: {}".format(route_str, f.__doc__))
            route_path = idarest_plugin_t.config['api_prefix'] + '/' + route_str + '/?'
            route_pattern = HTTPRequestHandler.build_route_pattern(route_path)
            HTTPRequestHandler.routes[route_str] = (route_pattern, f)
            HTTPRequestHandler.docs[route_str] = f.__doc__
            # HTTPRequestHandler.params[route_str] = HTTPRequestHandler._get_params(f)
            return f
        return decorator

    @classmethod
    def add_route(cls, route_str, f):
        #  print("[HTTPRequestHandler::add_route] cls: {}".format(cls))
        route_path = idarest_plugin_t.config['api_prefix'] + '/' + route_str + '/?'
        route_pattern = cls.build_route_pattern(route_path)
        cls.routes[route_str] = (route_pattern, f)
        cls.docs[route_str] = f.__doc__
        return f

    def remove_route(self, route_str):
        if route_str in self.routes:
            self.routes.pop(route_str)
            return True
        return False


    @staticmethod
    def prefn(route_str):
        def decorator(f):
            HTTPRequestHandler.prefns.setdefault(route_str, []).append(f)
            return f
        return decorator

    @staticmethod
    def postfn(route_str):
        def decorator(f):
            HTTPRequestHandler.postfns.setdefault(route_str, []).append(f)
            return f
        return decorator

    def _get_route_match(self, path):
        for (key, (route_pattern, view_function)) in self.routes.items():
            if False: print("[debug] route_pattern:{}, path:{}".format(route_pattern, path))

            m = route_pattern.match(path)
            if m:
                return key, view_function
        return None

    def _get_route_prefn(self, key):
        try:
            return self.prefns[key]
        except:
            return []

    def _get_route_postfn(self, key):
        try:
            return self.postfns[key]
        except:
            return []

    def _write(self, s):
        self.wfile.write(_asBytes(s))

    def _write_chunk(self, r):
        l = len(r)
        self._write('{:X}\r\n{}\r\n'.format(l, r))


    def _serve_route(self, args):
        path = urlparse.urlparse(self.path).path
        route_match = self._get_route_match(path)
        if route_match:
            key, view_function = route_match
            # these won't run in the main thread, so could cause issues if they try to interact with the idb
            for prefn in self._get_route_prefn(key):
                args = prefn(self, args)

            uid = self.uid
            #  while not HTTPRequestHandler.idarest_queue[uid].empty():
                #  print("[_serve_route] flushing queue...")
                #  HTTPRequestHandler.idarest_queue[uid].get()
                #

            def _exec():
                try:
                    HTTPRequestHandler.set_result(uid, view_function(self, args))
                except Exception as e:
                    HTTPRequestHandler.set_result(uid, e)

            # ida_kernwin.execute_sync(lambda: HTTPRequestHandler.set_result(uid, view_function(self, args)), ida_kernwin.MFF_WRITE)
            _execute_sync(_exec, ida_kernwin.MFF_WRITE)
            results = HTTPRequestHandler.get_result(uid)

            # these won't run in the main thread, so could cause issues if they try to interact with the idb
            for postfn in self._get_route_postfn(key):
                results = postfn(self, results)

            return results
        else:
            raise UnknownApiError('Route "{0}" has not been registered'.format(path), 404)

    def _serve_queue(self, q):
        response = {
            'code':  200,
            'msg':   'CONTROL',
            'control': {'queue': 'stop'},
            'data': False,
        }

        jsonp_callback = self._extract_callback()
        if jsonp_callback:
            content_type = 'application/javascript'
            response_fmt = jsonp_callback + '({0});'
        else:
            content_type = 'application/json'
            response_fmt = '{0}'

        self.send_response(200)
        self.send_header('Content-Type', content_type)
        self.send_origin_headers()
        self.send_header('Transfer-Encoding', 'chunked')
        self.end_headers()

        try:
            for i in itertools.count(start=0):
                self._write_chunk(response_fmt.format(json.dumps(response)))

                if idarest_plugin_t.config['api_debug']: idc.msg("[HTTPRequestHandler::_serve_queue] wrote: {}\n".format(response))
                data = q.get(timeout=idarest_plugin_t.config['api_queue_result_qget_timeout'])
                if data is None:
                    if idarest_plugin_t.config['api_debug']: idc.msg("[HTTPRequestHandler::_serve_queue] Queue returned None\n")
                    break
                response = {
                    'code': 200,
                    'msg':  'OK',
                    'oob':  {'count': i},
                    'data': data,
                }
                if isinstance(response['data'], dict):
                    if 'error' in response['data']:
                        response['msg'] = 'FAIL'
        except Empty:
            pass

        response = {
            'code':    200,
            'msg':     'CONTROL',
            'control': {'queue': 'stop'},
            'data':    False,
        }
        self._write_chunk(response_fmt.format(json.dumps(response)))
        self._write_chunk('')
        # self._write('0\r\n\r\n')

    def _serve_generator(self, it):
        iterable = True
        exception = True
        queue = True

        response = {
            'code' : 200,
            'msg'  : 'CONTROL',
            'data' : False,
            'control': {'iterable': 'start'},
        }

        jsonp_callback = self._extract_callback()
        if jsonp_callback:
            content_type = 'application/javascript'
            response_fmt = jsonp_callback + '({0});'
        else:
            content_type = 'application/json'
            response_fmt = '{0}'

        self.send_response(200)
        self.send_header('Content-Type', content_type)
        self.send_origin_headers()
        self.send_header('Transfer-Encoding', 'chunked')
        self.end_headers()

        try:
            for i in itertools.count(start=0):
                self._write_chunk(response_fmt.format(json.dumps(response)))

                if idarest_plugin_t.config['api_debug']: idc.msg("[HTTPRequestHandler::_serve_generator] wrote: {}".format(r))
                data = next(it)
                if data is None:
                    break
                if isinstance(data, StopIteration):
                    break
                response = {
                    'code' : 200,
                    'msg'  : 'OK',
                    'oob': {'count': i},
                    'data' : data,
                }
                if isinstance(response['data'], dict):
                    if 'error' in response['data']:
                        response['msg'] = 'FAIL'
        except StopIteration:
            pass

        response = {
            'code' : 200,
            'msg'  : 'CONTROL',
            'control': {'iterable': 'stop'},
            'data' : False,
        }
        self._write_chunk(response_fmt.format(json.dumps(response)))
        self._write_chunk('')
        #  self._write('0\r\n\r\n')

    def send_origin_headers(self):
        if self.headers.get('Origin', '') == 'null':
            self.send_header('Access-Control-Allow-Origin', self.headers.get('Origin'))
        self.send_header('Vary', 'Origin')

    def _serve(self, args):
        error = False
        try:
            it = self._serve_route(args)
            # it = sleeping_generator_test({}, {})
            if issubclass(it.__class__, Exception):
                response = {'code': 400, 'msg': 'returned exception {}: {}'.format(it.__class__, str(it))}
                error = True
                print('returned exception {}: {}'.format(it.__class__, str(it)))
            elif str(type(it)) == "<class 'generator'>":
                #  if idarest_plugin_t.config['api_debug']: print("[_serve] Generator!")
                return self._serve_generator(it)
            elif it.__class__.__name__ == "Queue":
                #  if idarest_plugin_t.config['api_debug']: print("[_serve] Queue!")
                return self._serve_queue(it)
            else:
                if isinstance(it, dict) and it.get('code', None):
                    response = it
                else:
                    response = {
                        'code' : 200,
                        'msg'  : 'OK',
                        'data' : it,
                    }

                    if isinstance(response['data'], dict):
                        if 'error' in response['data']:
                            response['code'] = 400
                            response['msg'] = response['data']['error']
                            if 'error_trace' in response['data']:
                                response['traceback'] = response['data']['error_trace']
                            response.pop('data')

        except UnknownApiError as e:
            self.send_error(e.code, e.msg)
            return
        except HTTPRequestError as e:
            response = {'code': e.code, 'msg' : e.msg}
        except ValueError as e:
            response = {'code': 400, 'msg': 'ValueError: ' + str(e)}
        except KeyError as e:
            response = {'code': 400, 'msg': 'KeyError: ' + str(e)}
        except StopIteration as e:
            response = {'code': 400, 'msg': 'StopIteration: ' + str(e)}
        except Exception as e:
            response = {
                    'code': 400, 
                    'msg': '{}: {}'.format(_classname(e), str(e)),
                    "traceback": traceback.format_exc()
            }

        

        jsonp_callback = self._extract_callback()
        if jsonp_callback:
            content_type = 'application/javascript'
            response_fmt = jsonp_callback + '({0});'
        else:
            content_type = 'application/json'
            response_fmt = '{0}'

        self.send_response(200)
        self.send_header('Content-Type', content_type)
        self.send_origin_headers()
        self.end_headers()

        self._write(response_fmt.format(json.dumps(response)))
        return

    def _extract_post_map(self):
        content_type, _t = cgi.parse_header(self.headers.get('content-type'))
        #  if content_type != 'application/json':
            #  raise HTTPRequestError(
                    #  'Bad content-type, use application/json',
                    #  400)
        length = int(self.headers.get('content-length'))
        try:
            return json.loads(self.rfile.read(length))
        except ValueError as e:
            raise HTTPRequestError(
                    'Bad or malformed json content',
                    400)

    def _extract_query_map(self):
        query = urlparse.urlparse(self.path).query
        qd = urlparse.parse_qs(query)
        args = {}
        for k, v in qd.items():
            if len(v) != 1:
                raise HTTPRequestError(
                    "Query param specified multiple times : " + k,
                    400)
            args[k.lower()] = v[0]
        return args

    def _extract_callback(self):
        try:
            args = self._extract_query_map()
            return args['callback']
        except:
            return ''

    def process_headers(self):
        self.header_dict = dict()
        for a, c in self.headers.items():
            if a and c:
                self.header_dict[a.strip()] = c.strip()

    def do_POST(self):
        self.process_headers()
        try:
            args = self._extract_post_map()
        except TypeError as e:
            # thrown on no content, just continue on
            args = '{}'
        except HTTPRequestError as e:
            self.send_error(e.code, e.msg)
            return
        self._serve(args)

    def do_GET(self):
        self.process_headers()
        try:
            HTTPRequestHandler._request = self
            args = self._extract_query_map()
        except HTTPRequestError as e:
            self.send_error(e.code, e.msg)
            return
        try:
            self._serve(args)
        except ConnectionAbortedError as e:
            if idarest_plugin_t.config['api_debug']: idc.msg("[HTTPRequestHandler::do_GET] {}: {}\n".format(e.__class__.__name__, str(e)))

    @staticmethod
    def delayed_call(f):
        """
        call function (delayed)

        allows us to start a function that will fill a Queue, while still being
        able to return that Queue before the function blocks
        """
        with ida_kernwin.disabled_script_timeout_t():

            def delayed_exec():
                f()

            # delayed_exec_timer = QtCore.QTimer()
            delayed_exec_timer.singleShot(0, delayed_exec)

    @staticmethod
    def fake_cli(text):
        """
        fake text input into cli

        TODO: copy ida stdout perhaps?  see ipyida's Zmq console tee.
        """
        with ida_kernwin.disabled_script_timeout_t():

            # We'll now have to schedule a call to the standard
            # 'execute' action. We can't call it right away, because
            # the "Output window" doesn't have focus, and thus
            # the action will fail to execute since it requires
            # the "Output window" as context.

            def delayed_exec(*args):
                output_window_title = "Output window"
                tw = ida_kernwin.find_widget(output_window_title)
                if not tw:
                    raise Exception("Couldn't find widget '%s'" % output_window_title)

                # convert from a SWiG 'TWidget*' facade,
                # into an object that PyQt will understand
                w = ida_kernwin.PluginForm.TWidgetToPyQtWidget(tw)

                line_edit = w.findChild(QtWidgets.QLineEdit)
                if not line_edit:
                    raise Exception("Couldn't find input")
                line_edit.setFocus() # ensure it has focus
                QtWidgets.QApplication.instance().processEvents() # and that it received the focus event

                # inject text into widget
                line_edit.setText(text)

                # and execute the standard 'execute' action
                ida_kernwin.process_ui_action("cli:Execute")

            # delayed_exec_timer = QtCore.QTimer()
            delayed_exec_timer.singleShot(0, delayed_exec)


"""
API handlers for IDA

"""
def check_ea(f):
    def wrapper(self, args):
        if 'ea' in args:
            try:
                ea = int(args['ea'], 16)
            except ValueError:
                raise IDARequestError(
                        'ea parameter malformed - must be 0xABCD', 400)
            if ea > idc.MaxEA():
                raise IDARequestError(
                        'ea out of range - MaxEA is 0x%x' % idc.MaxEA(), 400)
            args['ea'] = ea
        return f(self, args)
    return wrapper

def check_color(f):
    def wrapper(self, args):
        if 'color' in args:
            color = args['color']
            try:
                color = color.lower().lstrip('#').rstrip('h')
                if color.startswith('0x'):
                    color = color[2:]
                # IDA Color is BBGGRR, we need to convert from RRGGBB
                color = color[-2:] + color[2:4] + color[:2]
                color = int(color, 16)
            except:
                raise IDARequestError(
                        'color parameter malformed - must be RRGGBB form', 400)
            args['color'] = color
        return f(self, args)
    return wrapper

# this doesn't presently work, as it ends up being executed in IDA's main
# working thread. but let's leave it in so we can still use the decorator
# and maybe it will be fixed.
def require_params(*params):
    def decorator(f):
        def require_params_wrapper(self, args):
            for x in params:
                if ':' not in x and ' ' not in x and x not in args:
                    raise IDARequestError('missing parameter {0}'.format(x), 400)
            return f(self, args)
        require_params_wrapper.__doc__ = f.__doc__
        require_params_wrapper._params = getattr(f, '_params', [])
        require_params_wrapper._params.append(params)
        return require_params_wrapper
    return decorator

class IDARequestError(HTTPRequestError):
    pass

class EvalInterpreter(InteractiveInterpreter):
    def __init__(self, locals=None):
        super(EvalInterpreter, self).__init__(locals)
        self.buffer  = []
        self.stdout_list  = []
        self.stderr_list  = []

    def write(self, data):
        self.buffer.append(data)

    def eval(self, cmd):
        is_help = cmd.startswith('help(')
        result = None
        r = None
        with BorrowStdOut(self.stdout_list, self.stderr_list, is_help):
            r = self.runsource(cmd)
            if isinstance(self.stderr_list, list): 
                if self.stderr_list:
                    start = False
                    result = []
                    for line in self.stderr_list:
                        if 'File "<input>"' in line:
                            start = True
                        if start:
                            result.append(line)
                    if result:
                        self.stderr_list = [self.stderr_list[0]] + result
                        return {
                                'code': 400,
                                'msg': ''.join(self.stderr_list),
                        }

                result = ''.join(self.stdout_list + self.buffer)
            
        if r != False:
            raise SyntaxError(cmd)
        if isinstance(self.stdout_list, list): 
            return result

class EvalInterpreterQueue(EvalInterpreter):
    def __init__(self, locals, queue):
        super(EvalInterpreterQueue, self).__init__(locals)
        self.buffer  = queue
        self.stdout_list  = self.buffer
        self.stderr_list  = self.buffer

        self.buffer.append = self.buffer.put

class IDARequestHandler(HTTPRequestHandler):
    @staticmethod
    def _hex(v):
        return hex(v).rstrip('L')

    @staticmethod
    def _from_hex(v):
        if re.match(r'0x[0-9a-fA-F]+$', v):
            return int(v, 16)
        if re.match(r'\d+$', v):
            return int(v, 10)
        if re.match(r'".*"$', v):
            return v[1:-1]
        if re.match(r"'.*'$", v):
            return v[1:-1]
        return v

    def paren_split(s, delim):
        """ split string, respecting quotes """
        result = paren_multisplit(s, delim, '\'"', '\'"')
        if idarest_plugin_t.config['api_debug']: print("[paren_split] in: {}".format(s))
        if idarest_plugin_t.config['api_debug']: print("[paren_split] out: {}".format(result))
        return result


    @staticmethod
    def _dotted(key):
        pieces = key.split('.')
        return pieces

    @staticmethod
    def _ensure_path(_dict, path):
        if not path:
            if idarest_plugin_t.config['api_debug']: idc.msg("[_ensure_path] empty path\n")
            return None
        for piece in path:
            try:
                if piece in _dict:
                    _dict = _dict[piece]
            except TypeError:
                if hasattr(_dict, piece):
                    _dict = getattr(_dict, piece)
                else:
                    return None
        return _dict


    @staticmethod
    def _getplus(key):
        return getglobal(key)
        #        _globals = superglobals()
        #
        #        if isinstance(key, list):
        #            path = key
        #        else:
        #            path = IDARequestHandler._dotted(key)
        #
        #        base = IDARequestHandler._ensure_path(_globals, path)
        #        return base

    @staticmethod
    def error(e):
        if issubclass(e.__class__, Exception):
            _class = _classname(e)
            _message = str(e)
            result = {
                    "error": "{}: {}".format(_class, _message),
                    "error_trace": traceback.format_exc(),
            }
        else:
            result = {
                    "error": e,
            }
        return result


    @HTTPRequestHandler.route('info')
    def info(self, args):
        # No args, Return everything we can meta-wise about the ida session
        # file crcs
        result = {
                'md5' : _asStringRaw(idc.GetInputMD5()),
                'idb_path' : idc.GetIdbPath(),
                'file_path' : idc.GetInputFilePath(),
                'ida_dir' : idc.GetIdaDirectory(),
                'min_ea' : self._hex(idc.MinEA()),
                'max_ea' : self._hex(idc.MaxEA()),
                'segments' : self.segments({})['segments'],
                # idaapi.cvar.inf
                'procname' : idc.GetLongPrm(idc.INF_PROCNAME),
            }
        return result

    @HTTPRequestHandler.route('query')
    def query(self, args):
        # multiple modes
        # with address return everything about that address
        # with name, return everything about that name
        if 'ea' in args:
            idc.jumpto(args['ea'])
            return idc.here()
        if 'name' in args:
            return idc.get_name_ea_simple(args['name'])



    @HTTPRequestHandler.route('cursor')
    @check_ea
    def cursor(self, args):
        # XXX - Doesn't work
        #if 'window' in args:
        #    tform = idaapi.find_tform(args['window'])
        #    if tform:
        #        idaapi.switchto_tform(tform, 1)
        #    else:
        #        raise IDARequestError(
        #            'invalid window - {0}'.format(args['window']), 400)
        result = {}
        if idarest_plugin_t.config['api_debug']: idc.msg(args)
        if 'ea' in args:
            success = idc.jumpto(args['ea'])
            result['moved'] = success
        else:
            result['error'] = "missing argument: ea"
        result['ea'] = self._hex(idc.here())
        return result

    @HTTPRequestHandler.route('color')
    @check_color
    @check_ea
    @require_params('ea')
    def color(self, args):
        ea = args['ea']
        if 'color' in args:
            color = args['color']
            def f():
                idc.SetColor(ea, idc.CIC_ITEM, color)
            # idaapi.execute_sync(f, idaapi.MFF_WRITE)
            f()
            idc.Refresh()
            return {}
        else:
            return {'color' : str(GetColor(ea, idc.CIC_ITEM))}


    @HTTPRequestHandler.route('get_type')
    @require_params('type', 'comma seperated list of types')
    def get_type(self, args):
        """
        get type definition(s)

        :param type: comma seperated list of types
        :return

        $ echo -e 'GET /ida/api/v1.0/get_type?type=Vehicle,Entity HTTP/1.1\n\n' | nc localhost 2245
        {
            'code': 200,
            'msg': 'OK',
            'data': [{
                'name': 'Vehicle',
                'msg': 'OK',
                'data': '/* 11472 */\ntypedef int Vehicle;\n\n'
            }, {
                'name': 'Entity',
                'msg': 'OK',
                'data': '/* 1124 */\ntypedef int Entity;\n\n'
            }]
        }
        """
        def get_tinfo_by_parse(name):
            result = idc.parse_decl(name, idc.PT_SILENT)
            if result is None:
                return
            _, tp, fld = result
            tinfo = idaapi.tinfo_t()
            tinfo.deserialize(idaapi.cvar.idati, tp, fld, None)
            return tinfo

        def my_print_decls(name, flags=0):
            names = name if isinstance(name, list) else [name]
            ordinals = []
            for name in names:
                ti = get_tinfo_by_parse(name)
                if ti:
                    ordinal = ti.get_ordinal()
                    if ordinal:
                        ordinals.append(ordinal)
                        continue
                if idarest_plugin_t.config['api_debug']: print("[warn] couldn't get ordinal for type '{}'".format(name))
            if not ordinals:
                if idarest_plugin_t.config['api_debug']: print("[warn] couldn't get ordinals for types '{}'".format(name))
                return ''
            else:
                if idarest_plugin_t.config['api_debug']: print("[info] ordinals: {}".format(ordinals))

            result = ''
            if ordinals:
                result = idc.print_decls(','.join([str(x) for x in ordinals if x > -1]), flags)
            if idarest_plugin_t.config['api_debug']: print(result)
            return result

        types = IDARequestHandler.paren_split(args['type'], ',')
        #  flags = idc.PDF_INCL_DEPS | idc.PDF_DEF_FWD
        flags = 3
        if 'flags' in args:
            flags = int(args['flags'], 0)
        if idarest_plugin_t.config['api_debug']: print("request for type definitions: {}".format(types))
        result = []
        if types:
            for t in set(types):
                if t != 'void':
                    et = my_print_decls(t, flags=flags)
                    response = {
                        'name' : t,
                        'msg'  : 'OK',
                        'data' : et,
                    }
                    if not et:
                        if idarest_plugin_t.config['api_debug']: print("**** Type Error ****\n{}\n".format(t))
                        response['msg'] = 'FAIL'
                    result.append(response)
        return result


    def _get_segment_info(self, s):
        return {
            'name' : idaapi.get_true_segm_name(s),
            'ida_name' : idaapi.get_segm_name(s),
            'start' : self._hex(s.startEA),
            'end' : self._hex(s.endEA),
            'size' : self._hex(s.size())
        }

    @HTTPRequestHandler.route('segments')
    @check_ea
    def segments(self, args):
        if 'ea' in args:
            s = idaapi.getseg(args['ea'])
            if not s:
                raise IDARequestError('Invalid address', 400)
            return {'segment': self._get_segment_info(s)}
        else:
            m = {'segments': []}
            for i in range(idaapi.get_segm_qty()):
                s = idaapi.getnseg(i)
                m['segments'].append(self._get_segment_info(s))
            return m

    @HTTPRequestHandler.route('get')
    @require_params('var', 'name of variable to retrieve')
    def get_var(self, args):
        """get global variable

        :param var: var name

        """
        if idarest_plugin_t.config['api_debug']: idc.msg("[get_var]\n")
        try:
            if not 'var' in args:
                return IDARequestHandler.error('missing parameter \'var\'')
            var = args.pop('var')
            value = IDARequestHandler._getplus(var)
            if idarest_plugin_t.config['api_debug']:
                print({
                    'var': var,
                    'value': value,
                })
            if value is None:
                return IDARequestHandler.error(NameError("name '{}' is not defined or is None".format(var)))
            result = value
            if idarest_plugin_t.config['api_debug']: idc.msg('result: {}\n'.format(result))
            return result

        except Exception as e:
            return IDARequestHandler.error(e)
        except:
            return IDARequestHandler.error("Unknown Exception")

    @HTTPRequestHandler.route('call')
    @require_params('cmd',             'name of callable')
    @require_params('args:optional',   'comma seperated list of positional arguments')
    @require_params('kwargs:optional', 'object of keyword arguments')
    def call(self, args):
        """run callable and return result

        :param cmd: callable
        :param args: [optional] comma seperated list of positional arguments
        :param *: [optional] keyword arguments

        $ wget 'http://127.0.0.1:2000/ida/api/v1.0/call?cmd=_type=idc.GetType(0x1412E9E98)&return=_type' -O - -q
        {
            'code': 200,
            'msg': 'OK',
            'data': 'void __fastcall(uint8_t *buffer, uint32_t data, uint32_t bits, int32_t offset)'
        }

        """
        if idarest_plugin_t.config['api_debug']: idc.msg("[call]\n")
        try:
            if not 'cmd' in args:
                return IDARequestHandler.error('missing parameter \'cmd\'')
            cmd = args.pop('cmd')
            _args = []
            _kwargs = {}
            if 'args' in args:
                _args = IDARequestHandler.paren_split(args.pop('args'), ',')
                _args = [ IDARequestHandler._from_hex(x) for x in _args ]
                if idarest_plugin_t.config['api_debug']: idc.msg('_args: {}\n'.format(_args))
            for k, v in args.items():
                _kwargs[k] = IDARequestHandler._from_hex(v)
                if idarest_plugin_t.config['api_debug']: idc.msg('_kwarg: {}: {}\n'.format(k, v))
            fn = IDARequestHandler._getplus(cmd)
            if idarest_plugin_t.config['api_debug']:
                print({
                    'cmd': cmd,
                    'fn': fn,
                    'args': _args,
                    'kwargs': _kwargs,
                })
            if fn is None:
                return IDARequestHandler.error(NameError("name '{}' is not defined".format(cmd)))
            if not callable(fn):
                return IDARequestHandler.error(NameError("name '{}' is not callable".format(cmd)))
            result = fn(*_args, **_kwargs)
            if idarest_plugin_t.config['api_debug']: idc.msg('result: {}\n'.format(result))
            return result

        except Exception as e:
            return IDARequestHandler.error(e)
        except:
            return IDARequestHandler.error("Unknown Exception")

    @HTTPRequestHandler.route('eval')
    @HTTPRequestHandler.route('exec')
    @require_params('cmd',             'string to evaluate')
    @require_params('return:optional', 'variable to return, else return stdout')
    def eval(self, args):
        """evaluate expression via python exec() and return stdout/stderr (or variable)

        :param cmd: string to evaluate
        :param return: [optional] name of variable to return

        $ wget 'http://127.0.0.1:2000/ida/api/v1.0/eval?cmd=idc.GetType(0x1412E9E98)' -O - -q
        {
            'code': 200,
            'msg': 'OK',
            'data': 'void __fastcall(uint8_t *buffer, uint32_t data, uint32_t bits, int32_t offset)'
        }

        """
        if idarest_plugin_t.config['api_debug']: idc.msg("Hello Eval\n")
        try:
            if not 'cmd' in args:
                return IDARequestHandler.error('missing parameter \'cmd\'')
            cmd = args['cmd']
            if idarest_plugin_t.config['api_debug']: idc.msg('cmd: {}\n'.format(cmd))
            i = EvalInterpreter(superglobals())
            _result = i.eval(cmd)

            if 'return' in args:
                return getglobal(args['return'], None)
            else:
                return _result
        except Exception as e:
            return IDARequestHandler.error(e)

    @HTTPRequestHandler.route('evalq')
    @require_params('cmd',             'string to evaluate')
    def evalq(self, args):
        """evaluate expression via python exec() and return stdout via chunked queue

        :param cmd: string to evaluate

        $ wget 'http://127.0.0.1:2000/ida/api/v1.0/evalq?cmd=idc.GetType(0x1412E9E98)' -O - -q
        {
            'code': 200,
            'msg': 'OK',
            'data': 'void __fastcall(uint8_t *buffer, uint32_t data, uint32_t bits, int32_t offset)'
        }

        """
        if idarest_plugin_t.config['api_debug']: idc.msg("Hello EvalQ\n")
        try:
            if not 'cmd' in args:
                return IDARequestHandler.error('missing parameter \'cmd\'')
            cmd = args['cmd']
            if idarest_plugin_t.config['api_debug']: idc.msg('cmd: {}\n'.format(cmd))
            q = Queue(maxsize=1)
            i = EvalInterpreterQueue(superglobals(), q)
            def delayed():
                i.eval(cmd)
            HTTPRequestHandler.delayed_call(delayed)

            #  _result = i.eval(cmd)

            return q
        except Exception as e:
            return IDARequestHandler.error(e)

    @HTTPRequestHandler.route('cli')
    @require_params('cmd', 'string to evaluate')
    def cli(self, args):
        """fake input to CLI

        :param cmd: string to evaluate
        """
        if idarest_plugin_t.config['api_debug']: idc.msg("Hello CLI\n")
        try:
            if not 'cmd' in args:
                return IDARequestHandler.error('missing parameter \'cmd\'')
            cmd = args['cmd']
            if idarest_plugin_t.config['api_debug']: idc.msg('cmd: {}\n'.format(cmd))
            HTTPRequestHandler.fake_cli(cmd)
        except Exception as e:
            return IDARequestHandler.error(e)

class _IDARestStdOut:
    """
    Dummy file-like class that receives stout and stderr
    """
    buffer = ''
    def __init__(self):
        pass
        #  self.buffer = ''

    def write(self, text):
        # NB: in case 'text' is Unicode, msg() will decode it
        # and call msg() to print it
        self.buffer += text

    def flush(self):
        pass

    def isatty(self):
        return False

"""
Threaded HTTP Server and Worker

Use a worker thread to manage the server so that we can run inside of
IDA Pro without blocking execution.

"""
class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
    allow_reuse_address = True


class Worker(threading.Thread):
    def __init__(self, host=idarest_plugin_t.config['api_host'], port=idarest_plugin_t.config['api_port']):
        threading.Thread.__init__(self)
        self.httpd = ThreadedHTTPServer((host, port), IDARequestHandler)
        self.host = host
        self.port = port

    def run(self):
        self.httpd.serve_forever()

    def stop(self):
        self.httpd.shutdown()
        self.httpd.server_close()
        if idarest_plugin_t.config['api_info']: idc.msg("httpd shutdown...\n")
        if idarest_plugin_t.config['api_info']: idc.msg("httpd server_close...\n")

class Timer(threading.Thread):
    def __init__(self, host, port):
        super(Timer, self).__init__()
        self._stop_event = threading.Event()
        self.host = host
        self.port = port

    def run(self):
        if idarest_plugin_t.config['api_info']: print("[idarest::Timer::run] started\n")
        while True:
            result = idarest_plugin_t.register(self.host, self.port)
            if self._stop_event.wait(60.0):
                break
        if idarest_plugin_t.config['api_info']: print("[idarest::Timer::run] stopped")

        #  if not self.running:
            #  self.running = True
            #  while self.running:
                #  time.sleep(60.0 - ((time.time() - self.starttime) % 60.0))
                #  if idarest_plugin_t.config['api_debug']: print(Handler.get_json(Handler.hosts, {'ping': time.time()}))
            #  if idarest_plugin_t.config['api_info']: print("[idarest::Timer::run] stopped")

    def stop(self):
        if self.is_alive():
            if self.stopped():
                if idarest_plugin_t.config['api_info']: print("[idarest::Timer::stop] already stopping...")
            else:
                if idarest_plugin_t.config['api_info']: print("[idarest::Timer::stop] stopping...")
                self._stop_event.set()
        else:
            if idarest_plugin_t.config['api_info']: print("[idarest::Timer::stop] not running")

    def stopped(self):
        return self._stop_event.is_set()

# This has been redesigned **not** to require running as a plugin, but if you
# really insist on trying it does work.  To dynamically add routes if loaded
# as a plugin, you will have to do something like this:
#
# >>> ir = sys.modules['__plugins__idarest'].instance
# >>> ir.add_route(...)
def PLUGIN_ENTRY():
    globals()['instance'] = idarest_main() # idarest_plugin_t()
    return globals()['instance']

def idarest_main(*args):
    # pretend to be a plugin
    if idarest_main.instance is None:
        idarest_main.instance = ir = idarest_plugin_t()
        idarest_main.instances.append(ir)
        ir.init()
        #  globals()['ir'] = ir
        #  def cleanup():
            #  ir.term()
        #  atexit.register(cleanup)
    else:
        idc.msg('idarest_main.instance was not None!!!!!\n')
        return idarest_main.instance

    ### example dnamic routes
    def name_generator(self, args):
        """return all extant names and addresses via generator"""
        m = {'names' : []}
        for n in idautils.Names():
            yield {n[1]: self._hex(n[0])}

    def names(self, args):
        """return all extant names and addresses"""
        m = {'names' : []}
        for n in idautils.Names():
            m['names'].append([self._hex(n[0]), n[1]])
        return m

    def sleeping_generator_test(self, args):
        for r in range(5):
            if idarest_plugin_t.config['api_debug']: idc.msg("[sleeping_generator_test] {}".format(r))
            yield r
            time.sleep(1)

    def queue_test(self, args):
        _q = Queue()
        for r in range(10):
            _q.put(r)
        return _q

    def sleeping_queue_test(self, args):
        q = Queue()
        def test():
            for r in range(60):
                q.put(r)
                time.sleep(1)
        HTTPRequestHandler.delayed_call(test)
        return q

    @static_vars(q=Queue())
    def tap(self, args):
        if BorrowStdOut.is_default_stdout(sys.stdout):
            sys.stdout = BorrowStdOut.IDARestStdOutTee(sys.stdout, BorrowStdOut.IDARestStdOutQueue(tap.q))
            # sys.stderr = BorrowStdOut.IDARestStdOutTee(sys.stderr, BorrowStdOut.IDARestStdOutQueue(tap.q))
        else:
            pass
            # return IDARequestHandler.error('stdout is already tapped')


        return tap.q


    ir.add_route('sleep', sleeping_generator_test)
    ir.add_route('name_generator', name_generator)
    ir.add_route('names', names)
    ir.add_route('ea', lambda o, *a: idc.here())
    ir.add_route('echo', lambda o, a: {'args': a})
    ir.add_route('name', lambda o, a: idc.get_name(eax(a['ea'])))
    ir.add_route('q', queue_test)
    ir.add_route('q2', sleeping_queue_test)
    ir.add_route('tap', tap)
    ### end example route


    ### some stuff I use that won't work for you
    def relist_iter(self, args):
        return iter_retrace_list(q, once=1)

    def relist_queue(self, args):
        setglobal("_q", Queue())
        HTTPRequestHandler.fake_cli("retrace_list(q, output=_q)")
        return getglobal("_q")

    ir.add_route('list1', relist_iter)
    ir.add_route('list2', relist_queue)
    ### end "my stuff"

    return ir


if not hasattr(idarest_main, 'instance'):
    idarest_main.instance = None
    idarest_main.instances = []

if idarest_plugin_t.config['api_verbose']:
    print("[idarest]: __name__: ", __name__)
    print("[idarest]: __file__: ", __file__)


_load_method = None
if __name__ == "__main__":
    # loaded directly
    _load_method = 'direct'
elif __name__.startswith("__plugins__"):
    _load_method = 'plugin'
    # loaded as a plugin
elif __name__ == "idarest" or __name__ == "_idarest":
    _load_method = 'module'
elif __name__ == "idarest.idarest":
    _load_method = 'package'
else:
    # unknown load method (filename could be changed?)
    _load_method = 'unknown'
    print("[idarest]: unknown load method '{}'".format(__name__))

# find existing instance of idarest (unless we're loading as an ida plugin)
def get_ir():
    if idarest_plugin_t.config['api_debug']: idc.msg("[get_ir] attempting to find existing idarest instance...\n")
    # check if idarest is loaded as a plugin
    ir = None
    if idarest_plugin_t.config['api_verbose']: idc.msg('[get_ir] sys.modules.__plugins__\n')
    for k in [x for x in sys.modules if x.startswith('__plugins__')]:
        if idarest_plugin_t.config['api_verbose']: idc.msg('[get_ir]     ' + k + '\n')
    ir = ir or getglobal('sys.modules.__plugins__idarest_plugin.instance', None)
    if ir:
        if idarest_plugin_t.config['api_info']: idc.msg("[get_ir] got reference to idarest from sys.modules.__plugins__idarest_plugin.instance\n")
    else:
        ir = ir or getglobal('sys.modules.__plugins__idarest.instance', None)
        if ir:
            if idarest_plugin_t.config['api_info']: idc.msg("[get_ir] got reference to idarest from sys.modules.__plugins__idarest.instance\n")
        # check if idarest is loaded as a module
        else:
            ir = ir or getglobal('sys.modules.idarest.instance', None)
            if ir:
                if idarest_plugin_t.config['api_info']: idc.msg("[get_ir] got reference to idarest from sys.modules.idarest.instance\n")
            else:
                # check if idarest is loaded in global context
                ir = ir or getglobal('idarest_main.instance', None)
                if ir:
                    if idarest_plugin_t.config['api_info']: idc.msg("[get_ir] got reference to idarest from idarest_main.instance\n") 
                    # else start a new idarest instance
                    if ir and _load_method == "direct":
                        if idarest_plugin_t.config['api_info']: idc.msg("[get_ir] will (re)start idarest\n")
                        ir.term()
                        ir = None

    if not ir or not ir.is_alive():
        if idarest_plugin_t.config['api_info']: idc.msg("[get_ir] starting\n")
        ir = idarest_main()

    return ir

def get_ir_plugin():
    if '__plugins__idarest_plugin' in sys.modules:
        return sys.modules['__plugins__idarest_plugin']

def find_plugin(pattern):
    for name in sys.modules.copy():
        if name.startswith('__plugins__') and re.match(pattern, name[11:]):
            yield name[11:]

def get_plugin(name):
    return sys.modules['__plugins__' + name]

def unload_module(pattern):
    for x in [x for x in sys.modules.keys() if re.match(pattern, x)]:
        print("unloading {}".format(x))
        del sys.modules[x]

def unload_plugin(pattern, reload=False):
    for name in find_plugin(pattern):
        l = ida_loader.load_plugin(get_plugin(name).__file__)
        ida_loader.run_plugin(l, 0)
        unload_module(pattern)
        if reload:
            plugin = get_ir_plugin()
            if plugin:
                l = ida_loader.load_plugin(plugin.__file__)

def reload_idarest():
    plugin = get_ir_plugin()
    if plugin:
        l = ida_loader.load_plugin(plugin.__file__)
        ida_loader.run_plugin(l, 0)
    unload_module('idarest')
    if plugin:
        l = ida_loader.load_plugin(plugin.__file__)
    else:
        print("idarest was not loaded as a plugin, cannot reload")

def test_eval(cmd):
    ir = getglobal('ir', None)
    if ir:
        ir.term()
    # unload_module('idarest')
    # _from('idarest.idarest import *')
    try:
        ida_idaapi.require('idarest.idarest')
        ida_idaapi.require('idarest.idarest_mixins')
        from idarest.idarest import get_ir
    except ModuleNotFoundError:
        ida_idaapi.require('idarest')
        ida_idaapi.require('idarest_mixins')
        from idarest import get_ir
    setglobal('ir', get_ir())
    e = EvalInterpreter(superglobals())
    r = e.eval(cmd)
    return r


repository: 19h/ida-plugins
file: comdump/comdump.py
url: https://github.com/19h/ida-plugins/blob/b045b6f6bc9ae2a53104986fb74f3fe1198e5d2e/comdump/comdump.py
wanted_name: Function Decompiler
comment: Decompiles all functions reachable from the current function.
created_at: 2025-04-08 22:54:27+00:00
pushed_at: 2025-04-08 23:07:17+00:00
content:
import idaapi
import idautils
import idc
import ida_hexrays
import ida_kernwin

def count_total_xrefs_to(ea):
    """Counts total code and data cross-references to a given address."""
    count = 0
    # Count code references
    cref = idc.get_first_cref_to(ea)
    while cref != idaapi.BADADDR:
        count += 1
        cref = idc.get_next_cref_to(ea, cref)
    
    # Count data references
    dref = idc.get_first_dref_to(ea)
    while dref != idaapi.BADADDR:
        count += 1
        dref = idc.get_next_dref_to(ea, dref)
        
    return count

def decompile_reachable_functions():
    """
    Performs a depth-first search starting from the current function to identify all
    reachable functions in the call graph. Then decompiles each function and writes
    the decompiled code to a text file, sorted by the total number of incoming 
    cross-references from anywhere in the binary, with prettified output including 
    detailed headers and separators.
    """
    output_file = ida_kernwin.ask_file(True, "*.txt", "Select or create output file for decompiled functions")
    if not output_file:
        print("No output file selected.")
        return

    # Get the current effective address and the function containing it
    ea = idaapi.get_screen_ea()
    func = idaapi.get_func(ea)

    if not func:
        print("No function at the current address.")
        return

    # Initialize data structures
    start_ea = func.start_ea
    visited = set()  # Set of reachable function start addresses

    # Perform DFS to build the set of reachable functions
    dfs(start_ea, visited)

    # Convert visited set to a list
    reachable_funcs = list(visited)
    
    # Sort by the total number of cross-references to the function start
    reachable_funcs.sort(key=lambda fea: count_total_xrefs_to(fea))

    try:
        with open(output_file, "w") as f:
            for fea in reachable_funcs: # Use fea (function effective address) for clarity
                # Get function name or use address if unnamed
                func_name = idc.get_func_name(fea)
                # Calculate total incoming references for the header
                total_incoming_xrefs = count_total_xrefs_to(fea)

                # Create a detailed header using total xrefs
                if func_name:
                    header = f"// Function: {func_name} at 0x{fea:X}, Total incoming xrefs: {total_incoming_xrefs}\n"
                else:
                    header = f"// Function at 0x{fea:X}, Total incoming xrefs: {total_incoming_xrefs}\n"

                # Write the header
                f.write(header)

                # Attempt to decompile the function
                try:
                    cfunc = ida_hexrays.decompile(fea)
                    if cfunc:
                        decompiled_code = str(cfunc)
                        f.write(decompiled_code)
                        f.write("\n")
                    else:
                        f.write("// Decompilation failed.\n")
                except Exception as e:
                    f.write(f"// Decompilation error: {str(e)}\n")

                # Add a separator after each function
                f.write("// -----------------------------\n\n")

        print(f"[*] Decompiled functions written to {output_file}")
    except Exception as e:
        print(f"Error writing output file: {e}")


def dfs(func_ea, visited):
    """Recursively traverse the call graph starting from func_ea."""
    if func_ea in visited:
        return
    visited.add(func_ea)
    func = idaapi.get_func(func_ea)
    if not func:
        return

    # Iterate over the function's instructions using idc.next_head
    current_ea = func.start_ea
    while current_ea < func.end_ea:
        # Ensure the current address is recognized as code by IDA
        if idaapi.is_code(idaapi.get_flags(current_ea)):
            if idaapi.is_call_insn(current_ea):
                # Get the target address from the call instruction's operand
                for i in range(idaapi.UA_MAXOP):
                    op_type = idc.get_operand_type(current_ea, i)
                    # Check if the operand is a code address (near or far)
                    if op_type == idc.o_near or op_type == idc.o_far:
                        ref = idc.get_operand_value(current_ea, i)
                        if ref != idaapi.BADADDR: # Ensure we got a valid address
                            called_func = idaapi.get_func(ref)
                            # Ensure the reference is the start of a function
                            if called_func and called_func.start_ea == ref:
                                callee_ea = ref
                                # Recurse to the called function (no callers dict needed)
                                dfs(callee_ea, visited)
                                # Assuming a call instruction targets only one function directly
                                break 
        
        # Move to the next instruction or data head
        current_ea = idc.next_head(current_ea, func.end_ea)
        if current_ea == idaapi.BADADDR: # Check if next_head failed or went past the end
             break


class DecompilerPlugin(idaapi.plugin_t):
    flags = idaapi.PLUGIN_KEEP
    comment = "Decompiles all functions reachable from the current function."
    help = ("This plugin performs a depth-first search from the current function, "
            "identifies all reachable functions, and decompiles them to a text file.")
    wanted_name = "Function Decompiler"
    wanted_hotkey = ""

    def init(self):
        print("[*] Function Decompiler plugin initialized.")
        return idaapi.PLUGIN_OK

    def run(self, arg):
        decompile_reachable_functions()

    def term(self):
        print("[*] Function Decompiler plugin terminated.")


def PLUGIN_ENTRY():
    return DecompilerPlugin()


repository: Finch4/Malware-Analysis-Reports
file: IDA_Preview_Function_Capabilities.py
description: My Malware Analysis Reports
wanted_name: Functions Capabilities Preview
comment: Automatically renames a function to get a preview of its capabilities
created_at: 2021-01-03 15:32:05+00:00
pushed_at: 2022-05-24 13:30:18+00:00
url:
https://github.com/Finch4/Malware-Analysis-Reports/blob/4f3baae07575e799db97ec22cb271d89c0fb0879/IDA_Preview_Function_Capabilities.py

content:
# Idea from: https://github.com/AGDCservices/Ghidra-Scripts/blob/master/Preview_Function_Capabilities.py
# If you have some "Key Error" logs, open the "functions_error.txt", you will find a ready copy and paste key:value to add in the dictionary!


import idaapi
import ida_kernwin
import ida_ida
import idc
import idautils
import ida_nalt
import ida_funcs
class myplugin_t(idaapi.plugin_t):
    flags = idaapi.PLUGIN_UNL
    comment = "Automatically renames a function to get a preview of its capabilities"
    help="https://github.com/Finch4/Malware-Analysis-Reports"
    wanted_name = "Functions Capabilities Preview"
    wanted_hotkey = "Alt-F4"

    
    def init(self):
        idaapi.msg("init() called!\n")
        return idaapi.PLUGIN_OK
    


    def run(self, arg):
        apiPurposeDict = {
        'socket':'netwB',

        'WSAStartup':'netwC',
        'connect':'netwC',
        'InternetOpen':'netwC',
        'InternetConnect':'netwC',
        'InternetOpenURL':'netwC',
        'HttpOpenRequest':'netwC',
        'WinHttpConnect':'netwC',
        'WinHttpOpenRequest':'netwC',

        'bind':'netwL',
        'listen':'netwL',
        'accept':'netwL',

        'send':'netwS',
        'sendto':'netwS',
        'InternetWriteFile':'netwS',
        'HttpSendRequest':'netwS',
        'WSASend':'netwS',
        'WSASendTo':'netwS',
        'WinHttpSendRequest':'netwS',
        'WinHttpWriteData':'netwS',

        'recv':'netwR',
        'recvfrom':'netwR',
        'InternetReadFile':'netwR',
        'HttpReceiveHttpRequest':'netwR',
        'WSARecv':'netwR',
        'WSARecvFrom':'netwR',
        'WinHttpReceiveResponse':'netwR',
        'WinHttpReadData':'netwR',
        'URLDownloadToFile':'netwR',

        'inet_addr':'netwM',
        'htons':'netwM',
        'htonl':'netwM',
        'ntohs':'netwM',
        'ntohl':'netwM',


        'RegOpenKey':'regH',

        'RegQueryValue':'regR',
        'RegGetValue':'regR',
        'RegEnumValue':'regR',

        'RegSetValue':'regW',
        'RegSetKeyValue':'regW',

        'RegDeleteValue':'regD',
        'RegDeleteKey':'regD',
        'RegDeleteKeyValue':'regD',

        'RegCreateKey':'regC',

        'CreateFile':'fileH',
        'fopen':'fileH',

        'fscan':'fileR',
        'fgetc':'fileR',
        'fgets':'fileR',
        'fread':'fileR',
        'ReadFile':'fileR',

        'flushfilebuffers':'fileW',
        'fprintf':'fileW',
        'fputc':'fileW',
        'fputs':'fileW',
        'fwrite':'fileW',
        'WriteFile':'fileW',

        'DeleteFile':'fileD',

        'CopyFile':'fileC',

        'MoveFile':'fileM',

        'FindFirstFile':'fileE',
        'FindNextFile':'fileE',

        'strcmp':'strC',
        'strncmp':'strC',
        'stricmp':'strC',
        'wcsicmp':'strC',
        'mbsicmp':'strC',
        'lstrcmp':'strC',
        'lstrcmpi':'strC',

        'OpenService':'servH',

        'QueryServiceStatus':'servR',
        'QueryServiceConfig':'servR',

        'ChangeServiceConfig':'servW',
        'ChangeServiceConfig2':'servW',

        'CreateService':'servC',

        'DeleteService':'servD',

        'StartService':'servS',

        'CreateToolhelp32Snapshot':'procE',
        'Process32First':'procE',
        'Process32Next':'procE',

        'OpenProcess':'procH',

        'CreateProcess':'procC',
        'CreateProcessAsUser':'procC',
        'CreateProcessWithLogon':'procC',
        'CreateProcessWithToken':'procC',
        'ShellExecute':'procC',


        'ReadProcessMemory':'procR',

        'WriteProcessMemory':'procW',

        'CreateThread':'threadC',
        'beginthread':'threadC',

        'OpenThread':'threadO',

        'SuspendThread':'threadS',

        'ResumeThread':'threadR',
    }
        
        for function in idautils.Functions():
            function_name = idc.get_func_name(function)
            if function_name.startswith("sub"):
                function_start = idc.get_func_attr(function, idc.FUNCATTR_START)
                function_end = idc.get_func_attr(function, idc.FUNCATTR_END)
                function_name_new = ""
                functions_error = set()
                #idaapi.msg(f"{function_name} - {hex(function_start)} - {hex(function_end)}\n")
                for i in range(function_start, function_end):
                    if idc.print_insn_mnem(i) == "call" and not "word" in str(idc.print_operand(i, 0)) and not "sub" in str(idc.print_operand(i, 0)):
                        #idaapi.msg(f"{idc.print_insn_mnem(i)} - {idc.print_operand(i, 0)}\n")
                        try:
                            #idaapi.msg(str(idc.print_operand(i, 0)).replace("Ex","").replace("_","").replace("cs:","").replace("A","").replace("W","")+"\n")
                            key = str(idc.print_operand(i, 0)).replace("Ex","").replace("_","").replace("cs:","").replace("ds:","")
                            if key[-1] == "W" or key[-1] == "A":
                                key = key[:-1]
                            if apiPurposeDict[key]:
                                function_name_new += "_"+apiPurposeDict[str(idc.print_operand(i, 0)).replace("Ex","").replace("_","").replace("cs:","").replace("ds:","")]
                        except KeyError:
                            idaapi.msg(f' Key Error for {key}\n')
                            open("functions_error.txt","a").write("'"+key+"'"+":"+"'"+key.lower()+"',")
                idc.set_name(function_start, function_name_new, idc.SN_NOWARN)
                idaapi.msg(f"{function_name} -> {function_name_new}\n")
                

            
        
        

    def term(self):
        idaapi.msg("term() called!\n")
        
def PLUGIN_ENTRY():
    return myplugin_t()


repository: williballenthin/idawilli
file: scripts/add_segment/add_segment.py
description: IDA Pro resources, scripts, and configurations
wanted_name: AddSegment
comment: Add a segment to an IDA .idb from a file.
created_at: 2016-12-14 02:23:46+00:00
pushed_at: 2025-05-12 10:45:42+00:00
url:
https://github.com/williballenthin/idawilli/blob/063417207eebd061fbd58f6441e6b5e9ffc43f8e/scripts/add_segment/add_segment.py

content:
'''
IDAPython plugin that adds the contents of a file as a new segment in an existing idb.
Prompts the user for:
  - file path
  - segment name
  - segment starting offset
  
Useful for reversing engineering packed software and shellcode.

Author: Willi Ballenthin <william.ballenthin@fireeye.com>
Licence: Apache 2.0
'''
import logging
from collections import namedtuple

import idc
import idaapi
import idautils


logger = logging.getLogger(__name__)


class BadInputError(Exception):
    pass


Segment = namedtuple('SegmentBuffer', ['path', 'name', 'addr'])


def prompt_for_segment():
    ''' :returns: a Segment instance, or raises BadInputError '''
    class MyForm(idaapi.Form):
        def __init__(self):
            idaapi.Form.__init__(self, """STARTITEM 0
add segment by buffer

<##buffer path:{path}>
<##segment name:{name}>
<##segment start address:{addr}>
""",
                                 {
                                     'path': idaapi.Form.FileInput(open=True),
                                     'name': idaapi.Form.StringInput(),
                                     'addr': idaapi.Form.NumericInput(tp=Form.FT_ADDR),
                                 })

        def OnFormChange(self, fid):
            return 1

    f = MyForm()
    f.Compile()
    f.path.value = ""
    f.name.value = ""
    f.addr.value = 0x0
    ok = f.Execute()
    if ok != 1:
        raise BadInputError('user cancelled')

    path = f.path.value
    if path == "" or path is None:
        raise BadInputError('bad path provided')

    if not os.path.exists(path):
        raise BadInputError('file doesn\'t exist')

    name = f.name.value
    if name == "" or name is None:
        raise BadInputError('bad name provided')

    addr = f.addr.value
    f.Free()
    return Segment(path, name, addr)


def main(argv=None):
    if argv is None:
        argv = sys.argv[:]

    try:
        seg = prompt_for_segment()
    except BadInputError:
        logger.error('bad input, exiting...')
        return -1

    with open(seg.path, 'rb') as f:
        buf = f.read()

    seglen = len(buf)
    if seglen % 0x1000 != 0:
        seglen = seglen + (0x1000 - (seglen % 0x1000))

    if not idc.AddSeg(seg.addr, seg.addr + seglen, 0, 1, 0, idaapi.scPub):
        logger.error('failed to add segment: 0x%x', seg.addr)
        return -1

    if not idc.set_segm_name(seg.addr, seg.name):
        logger.warning('failed to rename segment: %s', seg.name)

    if not idc.set_segm_class(seg.addr, 'CODE'):
        logger.warning('failed to set segment class CODE: %s', seg.name)

    if not idc.set_segm_attr(seg.addr, SEGATTR_ALIGN, idc.saRelPara):
        logger.warning('failed to align segment: %s', seg.name)

    ida_bytes.patch_bytes(seg.addr, buf)


class AddSegmentPlugin(idaapi.plugin_t):
    flags = idaapi.PLUGIN_KEEP
    comment = "Add a segment to an IDA .idb from a file."

    help = "Add a segment to an IDA .idb from a file."
    wanted_name = "AddSegment"
    wanted_hotkey = "Alt-F8"

    def init(self):
        return idaapi.PLUGIN_OK

    def run(self, arg):
        main()

    def term(self):
        pass


def PLUGIN_ENTRY():
    return AddSegmentPlugin()


if __name__ == '__main__':
    logging.basicConfig(level=logging.DEBUG)
    main()


repository: Introspelliam/ida-python
file: Scripts/VirusTotal.py
wanted_name: VirusTotal report
comment: VirusTotal plugin for IDA
created_at: 2019-03-17 16:34:44+00:00
pushed_at: 2019-03-17 17:41:26+00:00
url:
https://github.com/Introspelliam/ida-python/blob/e3f6e6a763f442add7c2422cd410b2b03fd3e1fb/Scripts/VirusTotal.py

content:
from __future__ import print_function
# -----------------------------------------------------------------------
# VirusTotal IDA Plugin
# By Elias Bachaalany <elias at hex-rays.com>
# (c) Hex-Rays 2011
#
# Special thanks:
# - VirusTotal team
# - Bryce Boe for his VirusTotal Python code
#
import idaapi
import idc
from idaapi import plugin_t
from ida_kernwin import Choose
import BboeVt as vt
import webbrowser
import urllib
import os


PLUGIN_TEST = 0

# -----------------------------------------------------------------------
# Configuration file
VT_CFGFILE = os.path.join(idaapi.get_user_idadir(), "virustotal.cfg")

# -----------------------------------------------------------------------
# VirusTotal Icon in PNG format
VT_ICON = (
    "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A\x00\x00\x00\x0D\x49\x48\x44\x52"
    "\x00\x00\x00\x10\x00\x00\x00\x10\x04\x03\x00\x00\x00\xED\xDD\xE2"
    "\x52\x00\x00\x00\x30\x50\x4C\x54\x45\x03\x8B\xD3\x5C\xB4\xE3\x9C"
    "\xD1\xED\xF7\xFB\xFD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
    "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
    "\x00\x00\x00\x00\x00\x00\x00\x00\x00\xD3\xF2\x42\x61\x00\x00\x00"
    "\x4B\x49\x44\x41\x54\x78\x9C\x2D\xCA\xC1\x0D\x80\x30\x0C\x43\x51"
    "\x27\x2C\x50\x89\x05\x40\x2C\x40\xEB\xFD\x77\xC3\x76\xC9\xE9\xEB"
    "\xC5\x20\x5F\xE8\x1A\x0F\x97\xA3\xD0\xE4\x1D\xF9\x49\xD1\x59\x29"
    "\x4C\x43\x9B\xD0\x15\x01\xB5\x4A\x9C\xE4\x70\x14\x39\xB3\x31\xF8"
    "\x15\x70\x04\xF4\xDA\x20\x39\x02\x8A\x0D\xA8\x0F\x94\xA7\x09\x0E"
    "\xC5\x16\x2D\x54\x00\x00\x00\x00\x49\x45\x4E\x44\xAE\x42\x60\x82")


# -----------------------------------------------------------------------
class VirusTotalConfig(object):
    def __init__(self):
        self.Default()


    def Default(self):
        self.md5sum = retrieve_input_file_md5()
        self.infile = idaapi.dbg_get_input_path()
        if not self.infile:
            self.infile = ""

        # Persistent options
        self.apikey = ""
        self.options = 1 | 2


    def Read(self):
        """
        Read configuration from file
        """
        if not os.path.exists(VT_CFGFILE):
            return
        f = open(VT_CFGFILE, 'r')
        lines = f.readlines()
        for i, line in enumerate(lines):
            line = line.strip()
            if i == 0:
                self.apikey = line
            elif i == 1:
                self.options = int(line)
            else:
                break


    def Write(self):
        """
        Write configuration to file
        """
        lines = (self.apikey.strip(), str(self.options))
        try:
            f = open(VT_CFGFILE, 'w')
            f.write("\n".join(lines))
            f.close()
        except:
            pass


# -----------------------------------------------------------------------
def VtReport(apikey, filename=None, md5sum=None):
    if filename is None and md5sum is None:
        return (False, "No parameters passed!")

    # Check filename existance
    if filename is not None and not os.path.exists(filename):
        return (False, "Input file '%s' does not exist!" % filename)

    #print("fn=%s md5=%s" % (filename, md5sum))
    # Get file report from VirusTotal
    try:
        vt.set_apikey(apikey)
        result = vt.get_file_report(filename=filename, md5sum=md5sum)
    except Exception as e:
        return (False, "Exception:\n%s" % str(e))

    # Already analyzed?
    if result is not None:
        # Transform the results
        items = []
        for av, mwname in result.items():
            mwname = str(mwname) if mwname else "n/a"
            av = str(av)
            items.append([av, mwname])
        result = items

    return (True, result)


# -----------------------------------------------------------------------
class VirusTotalChooser(Choose):
    """
    Chooser class to display results from VT
    """
    def __init__(self, title, items, icon, embedded=False):
        Choose.__init__(self,
                        title,
                        [ ["Antivirus", 20], ["Result", 40] ],
                        embedded=embedded)
        self.items = items
        self.icon = icon


    def GetItems(self):
        return self.items


    def SetItems(self, items):
        self.items = [] if items is None else items


    def OnGetLine(self, n):
        return self.items[n]


    def OnGetSize(self):
        return len(self.items)


    def OnSelectLine(self, n):
        # Google search for the malware name and the antivirus name
        s = urllib.urlencode({"q" : " ".join(self.items[n])})
        webbrowser.open_new_tab("http://www.google.com/search?%s" % s)
        return (NOTHING_CHANGED, )


# --------------------------------------------------------------------------
class VirusTotalForm(Form):
    def __init__(self, icon):
        self.EChooser = VirusTotalChooser("E1", [], icon, embedded=True)
        Form.__init__(self, r"""STARTITEM {id:txtInput}
VirusTotal - IDAPython plugin v1.0 (c) Hex-Rays

{FormChangeCb}
<#API key#~A~pi key:{txtApiKey}>

Options:
<#Open results in a chooser when form closes#~P~opout results on close:{rOptRemember}>
<#Use MD5 checksum#~M~D5Sum:{rOptMD5}>
<#Use file on disk#~F~ile:{rOptFile}>{grpOptions}>

<#Type input (file or MD5 string)#~I~nput:{txtInput}>
<Results:{cEChooser}>
<#Get reports from VT#~R~eport:{btnReport}>
""", {
            'FormChangeCb': Form.FormChangeCb(self.OnFormChange),
            'txtApiKey'   : Form.StringInput(swidth=80),
            'grpOptions'  : Form.ChkGroupControl(("rOptRemember", "rOptMD5", "rOptFile")),
            'txtInput'    : Form.FileInput(open=True),
            'btnReport'   : Form.ButtonInput(self.OnReportClick),
            'cEChooser'   : Form.EmbeddedChooserControl(self.EChooser)
        })



    def OnReportClick(self, code=0):
        pass


    def OnFormChange(self, fid):
        if fid == self.rOptMD5.id or fid == self.rOptFile.id:
            input = (self.cfg.md5sum, self.cfg.infile)
            if fid == self.rOptMD5.id:
                c1 = self.rOptMD5
                c2 = self.rOptFile
                idx = 0
            else:
                c1 = self.rOptFile
                c2 = self.rOptMD5
                idx = 1

            v = not self.GetControlValue(c1)
            if v: idx = not idx

            # Uncheck the opposite input type
            self.SetControlValue(c2, v)

            # Set input field depending on input type
            self.SetControlValue(self.txtInput, input[idx])
        #
        # Report button
        #
        elif fid == self.btnReport.id:
            input = self.GetControlValue(self.txtInput)
            as_file = self.GetControlValue(self.rOptFile)
            apikey = self.GetControlValue(self.txtApiKey)

            ok, r = VtReport(self.cfg.apikey,
                        filename=input if as_file else None,
                        md5sum=None if as_file else input)

            # Error?
            if not ok:
                idc.warning(r)
                return 1

            # Pass the result
            self.EChooser.SetItems(r)

            # We have results and it was a file? Print its MD5
            if r and as_file:
                print("%s: %s" % (vt.LAST_FILE_HASH, input))

            # Refresh the embedded chooser control
            # (Could also clear previous results if not were retrieved during this run)
            self.RefreshField(self.cEChooser)

            # Store the input for the caller
            self.cfg.input = input

            # No results and file as input was supplied?
            if r is None:
                if as_file:
                    # Propose to upload
                    if idc.ask_yn(0, "HIDECANCEL\nNo previous results. Do you want to submit the file:\n\n'%s'\n\nto VirusTotal?" % input) == 0:
                        return 1

                    try:
                        r = vt.scan_file(input)
                    except Exception as e:
                        idc.warning("Exceptio during upload: %s" % str(e))
                    else:
                        if r is None:
                            idc.warning("Failed to upload the file!")
                        else:
                            idc.warning("File uploaded. Check again later to get the analysis report. Scan id: %s" % r)
                else:
                    idc.warning("No results found for hash: %s" % input)

        return 1


    def Show(self, cfg):
        # Compile the form once
        if not self.Compiled():
            _, args = self.Compile()
            #print args[0]

        # Populate the form
        self.txtApiKey.value  = cfg.apikey
        self.grpOptions.value = cfg.options
        self.txtInput.value   = cfg.infile if self.rOptFile.checked else cfg.md5sum

        # Remember the config
        self.cfg = cfg

        # Execute the form
        ok = self.Execute()

        # Forget the cfg
        del self.cfg

        # Success?
        if ok != 0:
            # Update config
            cfg.options = self.grpOptions.value
            cfg.apikey  = self.txtApiKey.value

            # Popup results?
            if self.rOptRemember.checked:
                ok = 2

        return ok


# -----------------------------------------------------------------------
class VirusTotalPlugin_t(plugin_t):
    flags = idaapi.PLUGIN_UNL
    comment = "VirusTotal plugin for IDA"
    help = ""
    wanted_name = "VirusTotal report"
    wanted_hotkey = "Alt-F8"


    def init(self):
        # Some initialization
        self.icon_id = 0
        return idaapi.PLUGIN_OK


    def run(self, arg=0):
        # Load icon from the memory and save its id
        self.icon_id = idaapi.load_custom_icon(data=VT_ICON, format="png")
        if self.icon_id == 0:
            raise RuntimeError("Failed to load icon data!")

        # Create config object
        cfg = VirusTotalConfig()

        # Read previous config
        cfg.Read()

        # Create form
        f = VirusTotalForm(self.icon_id)

        # Show the form
        ok = f.Show(cfg)
        if ok == 0:
            f.Free()
            return

        # Save configuration
        cfg.Write()

        # Spawn a non-modal chooser w/ the results if any
        if ok == 2 and f.EChooser.GetItems():
            VirusTotalChooser(
                "VirusTotal results [%s]" % cfg.input,
                f.EChooser.GetItems(),
                self.icon_id).Show()

        f.Free()
        return


    def term(self):
        # Free the custom icon
        if self.icon_id != 0:
            idaapi.free_custom_icon(self.icon_id)


# -----------------------------------------------------------------------
def PLUGIN_ENTRY():
    return VirusTotalPlugin_t()

# --------------------------------------------------------------------------
if PLUGIN_TEST:
    # Create form
    f = PLUGIN_ENTRY()
    f.init()
    f.run()
    f.term()


repository: DSecurity/crauEmu
file: crauEmu.py
description: crauEmu is an uEmu extension for developing and analyzing payloads for code-reuse attacks
url: https://github.com/DSecurity/crauEmu/blob/a8da80a7d19d3f35b4ee329ec8e59df5df5c2366/crauEmu.py
created_at: 2019-11-11 15:08:24+00:00
pushed_at: 2019-12-11 10:18:44+00:00
content:
#
# Copyright (c) Digital Security. All rights reserved.
# Licensed under the MIT License. See LICENSE file in the project root for full license information.
#

from uEmu import *

CRAUEMU_USE_AS_SCRIPT = True    # Set to `False` if you want to load crauEmu automatically as IDA Plugin


from PyQt5.QtWidgets import (
    QStyledItemDelegate, QTableView, QWidget,
    QTextEdit, QTabWidget, QTabBar, QToolButton,
    QAbstractItemView, QAction, QLineEdit, QLabel,
    QVBoxLayout,
)

from PyQt5.QtCore import (
    Qt, QAbstractTableModel, QVariant, QModelIndex, pyqtSignal
)

from PyQt5.QtGui import QBrush, QColor

def crauemu_log(entry):
    uemu_log(entry, name = "crauEmu")


class uEmuRopTraceView(simplecustviewer_t):

    def __init__(self, owner):
        super(uEmuRopTraceView, self).__init__()
        self.owner = owner
        ue = owner.unicornEngine
        arch = UEMU_HELPERS.get_arch()
        _, self.uc_reg_sp = UEMU_HELPERS.get_stack_register(arch)
        self.uc_reg_pc = ue.uc_reg_pc

    def Create(self, title):
        if not simplecustviewer_t.Create(self, title):
            return False
        return True

    def SetContent(self, rop_tracer):
        self.ClearLines()
        self.AddLine(COLSTR('  [ Rop Tracer View ]', SCOLOR_AUTOCMT))
        self.AddLine('')
        if rop_tracer is None:
            return
        for line_addr, line_disas, line_changes in rop_tracer.get_trace():
            if len(line_changes) > 0:
                self.AddLine('%s: %s  %s' % (line_addr, COLSTR(line_disas, SCOLOR_INSN), COLSTR('# ' + line_changes, SCOLOR_DREF)))
            else:
                self.AddLine('%s: %s' % (line_addr, COLSTR(line_disas, SCOLOR_INSN)))

    def OnClose(self):
        self.owner.rop_trace_view_closed()

def parse_int(value, default=0):
    try:
        return int(value)
    except Exception as ex:
        pass
    try:
        return int(value, 16)
    except Exception as ex:
        pass
    return default

class RopModel(QAbstractTableModel):

    nameChanged = pyqtSignal()
    
    def __init__(self, name, addr, size, prev_size, values=[]):
        super(RopModel, self).__init__()
        self.name = name
        self.addr = addr
        self.init_addr = None
        self.size = size
        self.prev_size = prev_size
        self.values = values
        self.highlights = {}

    def rowCount(self, parent):
        return len(self.values) + 1

    def columnCount(self, parent):
        return 4
    
    def headerData(self, section, orientation, role):
        if role == Qt.DisplayRole:
            return ['Address', 'Value', 'Comments', 'Context'][section]

    def data(self, index, role):
        try:
            row = index.row()
            col = index.column()
            if role == Qt.DisplayRole:
                if row == len(self.values):
                    return QVariant()
                elif col == 0:
                    arch = UEMU_HELPERS.get_arch()
                    reg_bit_size = UEMU_HELPERS.get_register_bits(arch)
                    reg_byte_size = reg_bit_size // 8
                    if type(self.addr) in (str, unicode):
                        addr_str = '{}+{:x}'.format(self.addr,  row * reg_byte_size)
                    else:
                        addr_str = '0x{:x}'.format(self.addr + row * reg_byte_size)
                    if 0 <= row < len(self.values) and row in self.highlights:
                        return '{} ({})'.format(addr_str, ', '.join(self.highlights[row]))
                    else:
                        return addr_str
                elif col == 1:
                    return self.values[row].get('addr', 0)
                elif col == 2:
                    return self.values[row].get('cmt', '')
                elif col == 3:
                    addr = self.values[row].get('addr', 0)
                    func = ida_funcs.get_func(addr)
                    if func is None or func.start_ea > addr:
                        return QVariant()
                    name = ida_name.get_name(func.start_ea)
                    off = addr - func.start_ea
                    return '{} + 0x{:x}'.format(name, off)
            elif role == Qt.BackgroundRole:
                if 0 <= row < len(self.values) and row in self.highlights:
                    return QBrush(QColor(Qt.yellow))
            return QVariant()
        except Exception as ex:
            print ex
    
    def setData(self, index, value, role):
        row = index.row()
        col = index.column()
        if role in (Qt.EditRole, Qt.DisplayRole):
            if row == len(self.values):
                self.insertRows(len(self.values), 1, QModelIndex())
            if col == 1:
                self.values[row]['addr'] = parse_int(value)
            elif col == 2:
                self.values[row]['cmt'] = value

        return True

    def flags(self, index):
        row = index.row()
        col = index.column()

        flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsDragEnabled
        if col in (1, 2):
            flags |= Qt.ItemIsEditable
        if row == -1 and col == -1:
            flags |= Qt.ItemIsDropEnabled
        return flags

    def insertRows(self, row, count, parent):
        self.beginInsertRows(parent, row, row + count - 1)
        self.values = self.values[:row] + [{} for _ in range(count)] + self.values[row:]
        self.endInsertRows()
        return True

    def removeRows(self, row, count, parent):
        if row < len(self.values) and row + count <= len(self.values):
            self.beginRemoveRows(parent, row, row + count - 1)
            self.values = self.values[:row] + self.values[row + count:]
            self.endRemoveRows()
            return True
        return False

    def dropMimeData(self, data, action, row, col, parent):
        if row == -1 or row > len(self.values):
            return False
        return super(RopModel, self).dropMimeData(data, action, row, 0, parent)

    def supportedDropActions(self):
        return Qt.MoveAction | Qt.CopyAction


class HexDelegate(QStyledItemDelegate):

    def displayText(self, value, locale):
        try:
            return '0x%x' % value
        except TypeError:
            return value

    def setEditorData(self, editor, index):
        editor.setText(self.displayText(index.data(), None))


class RopView(QTableView):

    def __init__(self, parent):
        super(RopView, self).__init__(parent)
        self.verticalHeader().hide()
        self.verticalHeader().setDefaultSectionSize(20)
        #self.horizontalHeader().hide()
        self.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.setDropIndicatorShown(True)
        self.setDragDropMode(self.DragDrop);
        self.setDragDropOverwriteMode(False);
        self.setDefaultDropAction(Qt.MoveAction)
        self.setItemDelegateForColumn(1, HexDelegate())
        self.setContextMenuPolicy(Qt.CustomContextMenu)
        self.customContextMenuRequested.connect(self.contextMenu)

        # actions
        self.del_act = QAction('Delete', self)
        self.del_act.triggered.connect(self.deleteSelectedRows)
        self.del_act.setShortcut('Delete')
        self.addAction(self.del_act)

        self.jump_act = QAction('Jump to', self)
        self.jump_act.triggered.connect(self.jumpTo)
        self.jump_act.setShortcut('G')
        self.addAction(self.jump_act)

    def contextMenu(self, point):

        select = self.selectionModel()

        menu = QMenu(self)
        menu.addAction(self.del_act)
        menu.addAction(self.jump_act)
        menu.popup(self.viewport().mapToGlobal(point))

    def deleteSelectedRows(self, checked):
        select = self.selectionModel()
        if select.hasSelection():
            selection = select.selectedRows()
            rows = sorted([i.row() for i in selection], reverse=True)
            for idx in rows:
                self.model().removeRow(idx)

    def jumpTo(self):
        select = self.selectionModel()
        if select.hasSelection():
            values = self.model().values
            selection = select.selectedRows()
            idx = [i.row() for i in selection][0]
            if 0 <= idx < len(values):
                addr = values[idx].get('addr', -1)
                IDAAPI_Jump(addr)


class RopTracer:

    def __init__(self, owner):
        self.owner = owner
        self.contexts = []

    def clear(self):
        self.contexts = []

    def trace(self):

        if len(self.contexts) > 0 and self.contexts[-1]['pc'] == self.owner.pc:
            return

        line = UEMU_HELPERS.trim_spaces(IDAAPI_GetDisasm(self.owner.pc, 0))
        context = {'pc': self.owner.pc, 'line': line, 'regs': {}}

        arch = UEMU_HELPERS.get_arch()
        regs_map = UEMU_HELPERS.get_register_map(arch)
        for name, ue_reg_id in regs_map:
            context['regs'][name] = self.owner.mu.reg_read(ue_reg_id)

        self.contexts.append(context)

    def get_trace(self):

        arch = UEMU_HELPERS.get_arch()
        reg_map = {i[1]: i[0] for i in UEMU_HELPERS.get_register_map(arch)}

        _, uc_reg_sp = UEMU_HELPERS.get_stack_register(arch)
        uc_reg_pc = self.owner.uc_reg_pc
        

        reg_sp_mnem = reg_map[uc_reg_sp]
        reg_pc_mnem = reg_map[uc_reg_pc]

        n = len(self.contexts)

        lines = []
        for i in range(n):
            context = self.contexts[i]
            next_context = self.contexts[i + 1] if i + 1 < n else None

            line_disas = context['line']
            regs = context['regs']

            reg_changes = []
            if next_context is not None:
                reg_names = regs.keys()
                next_regs = next_context['regs']
                for reg_name in reg_names:
                    if reg_name in (reg_sp_mnem, reg_pc_mnem):
                        continue
                    if next_regs[reg_name] != regs[reg_name]:
                        reg_changes.append('%s = %016X' % (reg_name, next_regs[reg_name]))
            line_addr = '%016X' % context['pc']
            line_changes = ', '.join(reg_changes)

            lines.append((line_addr, line_disas, line_changes))

        return lines


class RopTab(QWidget):

    def __init__(self, parent, model):
        super(RopTab, self).__init__(parent)
        self.model = model
        self.init_ui()

    def init_ui(self):
        self.rop_view = RopView(self)
        self.rop_view.setModel(self.model)

        self.name_edit = QLineEdit(self.model.name, self)
        if type(self.model.addr) in (unicode, str):
            self.addr_edit = QLineEdit(self.model.addr, self)
        else:
            self.addr_edit = QLineEdit('0x{:x}'.format(self.model.addr), self)
        self.size_edit = QLineEdit('0x{:x}'.format(self.model.size), self)
        self.psize_edit = QLineEdit('0x{:x}'.format(self.model.prev_size), self)

        self.name_edit.editingFinished.connect(self.name_edited)
        self.addr_edit.editingFinished.connect(self.addr_edited)
        self.size_edit.editingFinished.connect(self.size_edited)
        self.psize_edit.editingFinished.connect(self.psize_edited)

        edit_layout = QHBoxLayout()
        edit_layout.addWidget(QLabel('Name:'))
        edit_layout.addWidget(self.name_edit)
        edit_layout.addWidget(QLabel('Address:'))
        edit_layout.addWidget(self.addr_edit)
        edit_layout.addWidget(QLabel('Size:'))
        edit_layout.addWidget(self.size_edit)
        edit_layout.addWidget(QLabel('Prev. Size:'))
        edit_layout.addWidget(self.psize_edit)

        layout = QVBoxLayout()
        layout.addLayout(edit_layout)
        layout.addWidget(self.rop_view)
        self.setLayout(layout)

    def name_edited(self):
        self.model.name = self.name_edit.text()
        self.model.nameChanged.emit()

    def addr_edited(self):
        self.model.addr = parse_int(self.addr_edit.text(), None)
        if self.model.addr is None:
            self.model.addr = self.addr_edit.text()
        if type(self.model.addr) in (str, unicode):
            self.addr_edit.setText(self.model.addr)
        else:
            self.addr_edit.setText('0x{:x}'.format(self.model.addr))
        self.model.dataChanged.emit(QModelIndex(), QModelIndex())

    def size_edited(self):
        self.model.size = parse_int(self.size_edit.text())
        self.size_edit.setText('0x{:x}'.format(self.model.size))
        self.model.dataChanged.emit(QModelIndex(), QModelIndex())

    def psize_edited(self):
        self.model.prev_size = parse_int(self.psize_edit.text())
        self.psize_edit.setText('0x{:x}'.format(self.model.prev_size))
        self.model.dataChanged.emit(QModelIndex(), QModelIndex())

class RopEditorView(PluginForm):

    DEFAULT_SP = 0xffff1000
    DEFAULT_PC = 0xdeadbeef
    DEFAULT_STACK_SIZE = 0xf000
    DEFAULT_STACK_PREV_SIZE = 0x1000

    def __init__(self, owner):
        self.owner = owner
        super(RopEditorView, self).__init__()
        arch = UEMU_HELPERS.get_arch()
        self.sp_mnem, self.uc_reg_sp = UEMU_HELPERS.get_stack_register(arch)
        self.uc_reg_pc = self.owner.unicornEngine.uc_reg_pc
        reg_map = UEMU_HELPERS.get_register_map(arch)
        self.pc_mnem = {i[1]: i[0] for i in reg_map}[self.uc_reg_pc]
        self.rop_models = [
            RopModel('Stack', self.sp_mnem,
                     self.DEFAULT_STACK_SIZE,
                     self.DEFAULT_STACK_PREV_SIZE,
            )
        ]

    def OnCreate(self, form):
        self.parent = self.FormToPyQtWidget(form)
        self.init_ui()

    def get_default_ctx_src(self):
        src = ''
        src += '{} = 0x{:x} # stack pointer\n'.format(self.sp_mnem, self.DEFAULT_SP)
        src += '{} = 0x{:x} # program counter\n'.format(self.pc_mnem, self.DEFAULT_PC)
        return src

    def init_ui(self):
        self.text_edit = QTextEdit(self.parent)
        src = self.get_default_ctx_src()
        self.text_edit.setPlainText(src)

        self.init_btn = QPushButton("Initiate", self.parent)
        self.rop_save_btn = QPushButton("Save", self.parent)
        self.rop_load_btn = QPushButton("Load", self.parent)
        self.dump_trace_btn = QPushButton("Dump trace", self.parent)
        self.dump_rop_btn = QPushButton("Dump rop", self.parent)

        self.init_btn.clicked.connect(self.OnInitialize)
        self.rop_save_btn.clicked.connect(self.OnRopSave)
        self.rop_load_btn.clicked.connect(self.OnRopLoad)
        self.dump_trace_btn.clicked.connect(self.OnDumpTrace)
        self.dump_rop_btn.clicked.connect(self.OnDumpRop)

        button_layout = QHBoxLayout()
        button_layout.addWidget(self.init_btn)
        button_layout.addWidget(self.rop_save_btn)
        button_layout.addWidget(self.rop_load_btn)
        button_layout.addWidget(self.dump_trace_btn)
        button_layout.addWidget(self.dump_rop_btn)

        self.tabs = QTabWidget()
        self.tabs.setTabsClosable(True)
        self.tabs.addTab(self.text_edit, 'Context')
        self.tabs.tabBar().tabButton(0, QTabBar.RightSide).deleteLater()
        self.tabs.tabBar().setTabButton(0, QTabBar.RightSide, None)
        self.tabs.tabCloseRequested.connect(self.handle_tab_close)


        tb = QToolButton()
        tb.setText('+')
        tb.clicked.connect(self.OnNewTab)
        self.tabs.addTab(QWidget(), '')
        self.tabs.setTabEnabled(1, False)
        self.tabs.tabBar().setTabButton(1, QTabBar.RightSide, tb)

        self.update_rop_views()

        layout = QVBoxLayout()
        layout.addWidget(self.tabs)
        layout.addLayout(button_layout)

        self.parent.setLayout(layout)

    def update_rop_views(self):
        n = max(0, self.tabs.count() - 2)
        for i in range(n):
            self.tabs.removeTab(1)
        for i, rop_model in enumerate(self.rop_models):
            self.tabs.insertTab(i + 1, RopTab(self.parent, rop_model), rop_model.name)

            class TabNameSetter:

                def __init__(self, parent, i):
                    self.parent = parent
                    self.i = i

                def __call__(self):
                    self.parent.tabs.setTabText(self.i + 1, self.parent.rop_models[self.i].name)

            rop_model.nameChanged.connect(TabNameSetter(self, i))

    def handle_tab_close(self, index):
        # tab index to rop_model index
        index = index - 1
        del self.rop_models[index]
        self.update_rop_views()

    def update_context(self):
        mu = self.owner.unicornEngine.mu
        arch = UEMU_HELPERS.get_arch()
        reg_map = dict(UEMU_HELPERS.get_register_map(arch))
        reg_bit_size = UEMU_HELPERS.get_register_bits(arch)
        reg_byte_size = reg_bit_size // 8
        for rop_model in self.rop_models:
            rop_model.highlights = {}
        for reg in reg_map:
            value = self.owner.unicornEngine.mu.reg_read(reg_map[reg])
            for rop_model in self.rop_models:
                if rop_model.init_addr is not None and\
                        rop_model.init_addr <= value <= rop_model.init_addr + rop_model.size:
                    index = (value - rop_model.init_addr) // reg_byte_size
                    if index in rop_model.highlights:
                        rop_model.highlights[index].append(reg)
                    else:
                        rop_model.highlights[index] = [reg]
        for rop_model in self.rop_models:
            rop_model.dataChanged.emit(QModelIndex(), QModelIndex())

    def OnNewTab(self):
        self.rop_models.append(
            RopModel('Tab', self.sp_mnem,
                 self.DEFAULT_STACK_SIZE,
                 self.DEFAULT_STACK_PREV_SIZE,
            )
        )
        self.update_rop_views()

    def OnInitialize(self):

        self.follow_regs = {}

        ue = self.owner.unicornEngine
        if ue.is_active():
            ue.reset()

        self.owner.emu_hooks = {}

        # evaluate context
        ctx = {
            self.sp_mnem: self.DEFAULT_SP,
            self.pc_mnem: self.DEFAULT_PC,
            'hook': self.owner.set_emu_hook,
        }
        src = self.text_edit.toPlainText()
        try:
            exec(
                src,
                {k: v for k, v in globals().items() if k.startswith('UC_')},
                ctx
            )
        except Exception as ex:
            crauemu_log('[!] context evaluation error: {}'.format(ex))

        arch = UEMU_HELPERS.get_arch()
        reg_map = dict(UEMU_HELPERS.get_register_map(arch))
        reg_bit_size = UEMU_HELPERS.get_register_bits(arch)

        pc = ctx[self.pc_mnem]

        self.owner.rop_tracer = RopTracer(ue)
        self.owner.init_from_crauemu = True
        ue.run_from(pc)
        self.owner.init_from_crauemu = False

        sp = ctx[self.sp_mnem]

        for mnem, reg_value in ctx.items():
            if mnem in reg_map:
                ue.mu.reg_write(reg_map[mnem], reg_value)

        for rop_model in self.rop_models:
            addr = rop_model.addr
            values = rop_model.values
            if type(addr) in (str, unicode):
                addr = ctx.get(addr, None)
            if addr is None:
                crauemu_log('[!] Error address at {} rop tab: {}'.format(rop_model.name, rop_model.addr))
                continue
            rop_model.init_addr = addr
            start = addr - rop_model.prev_size
            end = addr + rop_model.size

            ue.map_memory(start, end - start)
            ue.mu.mem_write(addr, struct.pack(
                ('%dQ' if reg_bit_size == 64 else '%dI')  % len(values),
                *[i.get('addr', 0) for i in values]))

        self.owner.update_context(ue.pc, ue.mu)

    def OnRopSave(self):
        file_path = IDAAPI_AskFile(1, "*.rop", "Rop Save")
        if file_path is not None:
            dump = {'ctx': self.text_edit.toPlainText(), 'rops': []}
            for rop_model in self.rop_models:
                dump['rops'].append({
                    'name': rop_model.name,
                    'addr': rop_model.addr,
                    'size': rop_model.size,
                    'psize': rop_model.prev_size,
                    'values': rop_model.values
                })
            with open(file_path, 'wb') as f:
                json.dump(dump, f)

    def OnRopLoad(self):
        file_path = IDAAPI_AskFile(0, "*.rop", "Rop Load")
        if file_path is not None:
            with open(file_path, 'rb') as f:
                dump = json.load(f)
            self.text_edit.setPlainText(dump['ctx'])
            # load old save
            if 'rop' in dump:
                values = dump['rop']
                self.rop_models = [
                    RopModel('Stack', self.sp_mnem,
                             self.DEFAULT_STACK_SIZE,
                             self.DEFAULT_STACK_PREV_SIZE,
                             values)
                ]
            else:
                self.rop_models = []
                for rop in dump['rops']:
                    name = rop['name']
                    addr = rop['addr']
                    size = rop['size']
                    psize = rop['psize']
                    values = rop['values']
                    self.rop_models.append(RopModel(name, addr, size, psize, values))
            self.update_rop_views()


    def OnDumpTrace(self):
        if self.owner.unicornEngine.rop_tracer is None:
            crauemu_log('[!] Rop tracer not found')
            return
        file_path = IDAAPI_AskFile(1, "*.log", "Trac(e dump")
        if file_path is not None:
            with open(file_path, 'w') as f:
                for line_addr, line_disas, line_changes in self.owner.unicornEngine.rop_tracer.get_trace():
                    if len(line_changes) > 0:
                        f.write('%s: %s  %s\n' % (line_addr, line_disas, '# ' + line_changes))
                    else:
                        f.write('%s: %s\n' % (line_addr, line_disas))
                
    def OnDumpRop(self):
        file_path = IDAAPI_AskFile(1, "*.py", "Rop dump")
        if file_path is not None:
            with open(file_path, 'w') as f:
                f.write('import struct\n\n\n')
                f.write('REBASE =  0x0\n')
                f.write('rop = b\'\'\n')
                arch = UEMU_HELPERS.get_arch()
                reg_bit_size = UEMU_HELPERS.get_register_bits(arch)
                fmt = 'I' if reg_bit_size == 32 else 'Q'
                for rop_model in self.rop_models:
                    name = rop_model.name
                    values = rop_model.values
                    for i in values:
                        addr = i['addr']
                        if IDAAPI_IsLoaded(addr):
                            f.write('rop_%s += struct.pack(\'%s\', REBASE + 0x%x)\n' % (name, fmt, addr))
                        else:
                            f.write('rop_%s += struct.pack(\'%s\', 0x%x)\n' % (name, fmt, addr))

    def OnClose(self, form):
        self.owner.rop_editor_view_closed()

class crauEmuPlugin(uEmuPlugin):

    def __init__(self):
        hooks = uEmuExtensionHooks()
        hooks.init_context = self.hook_init_context
        hooks.trace_log = self.hook_trace_log
        hooks.emu_step = self.hook_emu_step
        self.ropEditorView = None
        self.ropTraceView = None
        self.rop_tracer = None
        self.init_from_crauemu = False
        self.emu_hooks = {}

        self.settings['trace_inst'] = True
        self.settings['follow_pc'] = True
        self.settings['lazy_mapping'] = True

        super(crauEmuPlugin, self).__init__("crauEmu", hooks)

    def add_custom_menu(self):
        self.MENU_ITEMS.append(UEMU_HELPERS.MenuItem(self.plugin_name + ":rop_trace_view",    self.show_rop_trace_view,   "Show Rop Trace View",        "Show Rop Trace View",       "SHIFT+CTRL+ALT+T",     True    ))
        self.MENU_ITEMS.append(UEMU_HELPERS.MenuItem(self.plugin_name + ":rop_editor_view",   self.show_rop_editor_view,  "Show Rop Editor View",       "Show Rop Editor View",      "SHIFT+CTRL+ALT+R",     True    ))
        self.MENU_ITEMS.append(UEMU_HELPERS.MenuItem("-",                                     self.do_nothing,            "",                           None,                        None,                   True    ))

    ### uEmu hooks

    def hook_init_context(self):
        return self.init_from_crauemu

    def hook_trace_log(self, pc):
        if self.rop_tracer is not None:
            self.rop_tracer.trace()
        return True

    def hook_emu_step(self, pc):
        if pc in self.emu_hooks:
            self.emu_hooks[pc](self.unicornEngine.mu)
            return True
        return False

    def rop_editor_view_closed(self):
        self.ropEditorView = None

    def rop_trace_view_closed(self):
        self.ropTraceView = None

    def show_rop_trace_view(self):
        if not self.unicornEngine.is_active():
            crauemu_log("Emulator is not active")
            return

        if self.ropTraceView is None:
            self.ropTraceView = uEmuRopTraceView(self)
            self.ropTraceView.Create("uEmu Rop Trace View")
            self.ropTraceView.SetContent(self.rop_tracer)
            self.ropTraceView.Show()
            self.ropTraceView.Refresh()

    def show_rop_editor_view(self):
        if self.ropEditorView is None:
            self.ropEditorView = RopEditorView(self)
            self.ropEditorView.Show('RopEditor')

    def update_context(self, address, context):
        super(crauEmuPlugin, self).update_context(address, context)

        if self.ropEditorView is not None:
            self.ropEditorView.update_context()
        if self.ropTraceView is not None:
            self.ropTraceView.SetContent(self.rop_tracer)

    def close_windows(self):
        super(crauEmuPlugin, self).close_windows()

        if self.ropEditorView is not None:
            self.ropEditorView.Close(0)
            self.ropEditorView = None

        if self.ropTraceView is not None:
            self.ropTraceView.Close()
            self.ropTraceView = None

    def set_emu_hook(self, addr, func):
        self.emu_hooks[addr] = func


def PLUGIN_ENTRY():
    return crauEmuPlugin()

if CRAUEMU_USE_AS_SCRIPT:
    if __name__ == '__main__':
        crauEmu = crauEmuPlugin()
        crauEmu.init()
        crauEmu.run()


repository: rcx/ida-scripts
file: plugins/HexRaysPyTools.py
description: Collection of IDA Pro/Hex-Rays configs, scripts, and plugins
wanted_name: HexRaysPyTools
comment: Plugin for automatic classes reconstruction
created_at: 2018-08-01 03:12:29+00:00
pushed_at: 2020-03-20 20:45:25+00:00
url:
https://github.com/rcx/ida-scripts/blob/963d7a40330eed1d5e8ead8f4297e5a3555e2456/plugins/HexRaysPyTools.py

content:
import logging
import HexRaysPyTools.Actions as Actions
import HexRaysPyTools.Core.Cache
from HexRaysPyTools.Core.TemporaryStructure import *
import HexRaysPyTools.Forms as Forms
import idaapi
import HexRaysPyTools.Core.NegativeOffsets as NegativeOffsets
import HexRaysPyTools.Core.Helper as Helper
import HexRaysPyTools.Core.Cache as Cache
import HexRaysPyTools.Core.Const as Const
from HexRaysPyTools.Core.SpaghettiCode import SpaghettiVisitor, SwapThenElseVisitor
from HexRaysPyTools.Core.StructXrefs import *

potential_negatives = {}


def hexrays_events_callback(*args):
    global potential_negatives

    hexrays_event = args[0]

    if hexrays_event == idaapi.hxe_populating_popup:
        form, popup, hx_view = args[1:]
        item = hx_view.item  # current ctree_item_t

        if Actions.GuessAllocation.check(hx_view.cfunc, item):
            idaapi.attach_action_to_popup(form, popup, Actions.GuessAllocation.name, None)

        if Actions.RecastItemRight.check(hx_view.cfunc, item):
            idaapi.attach_action_to_popup(form, popup, Actions.RecastItemRight.name, None)

        if Actions.RecastItemLeft.check(hx_view.cfunc, item):
            idaapi.attach_action_to_popup(form, popup, Actions.RecastItemLeft.name, None)

        if Actions.RenameOther.check(hx_view.cfunc, item):
            idaapi.attach_action_to_popup(form, popup, Actions.RenameOther.name, None)

        if Actions.RenameInside.check(hx_view.cfunc, item):
            idaapi.attach_action_to_popup(form, popup, Actions.RenameInside.name, None)

        if Actions.RenameOutside.check(hx_view.cfunc, item):
            idaapi.attach_action_to_popup(form, popup, Actions.RenameOutside.name, None)

        if Actions.RenameUsingAssert.check(hx_view.cfunc, item):
            idaapi.attach_action_to_popup(form, popup, Actions.RenameUsingAssert.name, None)

        if Actions.SwapThenElse.check(hx_view.cfunc, item):
            idaapi.attach_action_to_popup(form, popup, Actions.SwapThenElse.name, None)

        if Actions.ShallowScanVariable.check(hx_view.cfunc, item):
            idaapi.attach_action_to_popup(form, popup, Actions.ShallowScanVariable.name, None)
            idaapi.attach_action_to_popup(form, popup, Actions.DeepScanVariable.name, None)
            idaapi.attach_action_to_popup(form, popup, Actions.RecognizeShape.name, None)

        if Actions.CreateNewField.check(hx_view.cfunc, item):
            idaapi.attach_action_to_popup(form, popup, Actions.CreateNewField.name, None)

        if Actions.FindFieldXrefs.check(item):
            idaapi.attach_action_to_popup(form, popup, Actions.FindFieldXrefs.name, None)

        if Actions.PropagateName.check(hx_view.cfunc, item):
            idaapi.attach_action_to_popup(form, popup, Actions.PropagateName.name, None)

        if item.citype == idaapi.VDI_FUNC:
            # If we clicked on function
            if not hx_view.cfunc.entry_ea == idaapi.BADADDR:  # Probably never happen
                idaapi.attach_action_to_popup(form, popup, Actions.AddRemoveReturn.name, None)
                idaapi.attach_action_to_popup(form, popup, Actions.ConvertToUsercall.name, None)
                if Actions.DeepScanReturn.check(hx_view):
                    idaapi.attach_action_to_popup(form, popup, Actions.DeepScanReturn.name, None)

        elif item.citype == idaapi.VDI_LVAR:
            # If we clicked on argument
            local_variable = hx_view.item.get_lvar()          # idaapi.lvar_t
            if local_variable.is_arg_var:
                idaapi.attach_action_to_popup(form, popup, Actions.RemoveArgument.name, None)

        elif item.citype == idaapi.VDI_EXPR:
            if item.e.op == idaapi.cot_num:
                # number_format = item.e.n.nf                       # idaapi.number_format_t
                # print "(number) flags: {0:#010X}, type_name: {1}, opnum: {2}".format(
                #     number_format.flags,
                #     number_format.type_name,
                #     number_format.opnum
                # )
                idaapi.attach_action_to_popup(form, popup, Actions.GetStructureBySize.name, None)
            elif item.e.op == idaapi.cot_var:
                # Check if we clicked on variable that is a pointer to a structure that is potentially part of
                # containing structure
                if item.e.v.idx in potential_negatives:
                    idaapi.attach_action_to_popup(form, popup, Actions.SelectContainingStructure.name, None)
                if Actions.ResetContainingStructure.check(hx_view.cfunc.get_lvars()[item.e.v.idx]):
                    idaapi.attach_action_to_popup(form, popup, Actions.ResetContainingStructure.name, None)

    elif hexrays_event == idaapi.hxe_double_click:
        hx_view = args[1]
        item = hx_view.item
        if item.citype == idaapi.VDI_EXPR and item.e.op == idaapi.cot_memptr:
            # Look if we double clicked on expression that is member pointer. Then get tinfo_t of  the structure.
            # After that remove pointer and get member name with the same offset
            if item.e.x.op == idaapi.cot_memref and item.e.x.x.op == idaapi.cot_memptr:
                vtable_tinfo = item.e.x.type.get_pointed_object()
                method_offset = item.e.m
                class_tinfo = item.e.x.x.x.type.get_pointed_object()
                vtable_offset = item.e.x.x.m
            elif item.e.x.op == idaapi.cot_memptr:
                vtable_tinfo = item.e.x.type.get_pointed_object()
                method_offset = item.e.m
                class_tinfo = item.e.x.x.type.get_pointed_object()
                vtable_offset = item.e.x.m
            else:
                func_offset = item.e.m
                struct_tinfo = item.e.x.type.get_pointed_object()
                func_ea = Helper.get_virtual_func_address(Helper.get_member_name(struct_tinfo, func_offset))
                if func_ea:
                    idaapi.jumpto(func_ea)
                return 0

            func_name = Helper.get_member_name(vtable_tinfo, method_offset)
            func_ea = Helper.get_virtual_func_address(func_name, class_tinfo, vtable_offset)
            if func_ea:
                idaapi.open_pseudocode(func_ea, 0)
                return 1

    elif hexrays_event == idaapi.hxe_maturity:
        cfunc, level_of_maturity = args[1:]

        if level_of_maturity == idaapi.CMAT_BUILT:
            # print '=' * 40
            # print '=' * 15, "LEVEL", level_of_maturity, '=' * 16
            # print '=' * 40
            # print cfunc

            # First search for CONTAINING_RECORD made by Ida
            visitor = NegativeOffsets.SearchVisitor(cfunc)
            visitor.apply_to(cfunc.body, None)
            negative_lvars = visitor.result

            # Second get saved information from comments
            lvars = cfunc.get_lvars()
            for idx in xrange(len(lvars)):
                result = NegativeOffsets.parse_lvar_comment(lvars[idx])
                if result and result.tinfo.equals_to(lvars[idx].type().get_pointed_object()):
                    negative_lvars[idx] = result

            # Third make an analysis of local variables that a structure pointers and have reference that pass
            # through structure boundaries. This variables will be considered as potential pointers to substructure
            # and will get a menu on right click that helps to select Containing Structure from different libraries

            structure_pointer_variables = {}
            for idx in set(range(len(lvars))) - set(negative_lvars.keys()):
                if lvars[idx].type().is_ptr():
                    pointed_tinfo = lvars[idx].type().get_pointed_object()
                    if pointed_tinfo.is_udt():
                        structure_pointer_variables[idx] = pointed_tinfo

            if structure_pointer_variables:
                visitor = NegativeOffsets.AnalyseVisitor(structure_pointer_variables, potential_negatives)
                visitor.apply_to(cfunc.body, None)

            if negative_lvars:
                visitor = NegativeOffsets.ReplaceVisitor(negative_lvars)
                visitor.apply_to(cfunc.body, None)

        elif level_of_maturity == idaapi.CMAT_TRANS1:

            visitor = SwapThenElseVisitor(cfunc.entry_ea)
            visitor.apply_to(cfunc.body, None)

        elif level_of_maturity == idaapi.CMAT_TRANS2:
            # print '=' * 40
            # print '=' * 15, "LEVEL", level_of_maturity, '=' * 16
            # print '=' * 40
            # print cfunc
            visitor = SpaghettiVisitor()
            visitor.apply_to(cfunc.body, None)

        elif level_of_maturity == idaapi.CMAT_FINAL:
            StructXrefVisitor(cfunc).process()

    return 0


class MyPlugin(idaapi.plugin_t):
    # flags = idaapi.PLUGIN_HIDE
    flags = 0
    comment = "Plugin for automatic classes reconstruction"
    help = "This is help"
    wanted_name = "HexRaysPyTools"
    wanted_hotkey = "Alt-F8"

    @staticmethod
    def init():
        if not idaapi.init_hexrays_plugin():
            print "[ERROR] Failed to initialize Hex-Rays SDK"
            return idaapi.PLUGIN_SKIP

        Cache.temporary_structure = TemporaryStructureModel()
        # Actions.register(Actions.CreateVtable)
        Actions.register(Actions.ShowGraph)
        Actions.register(Actions.ShowClasses)
        Actions.register(Actions.GetStructureBySize)
        Actions.register(Actions.RemoveArgument)
        Actions.register(Actions.AddRemoveReturn)
        Actions.register(Actions.ConvertToUsercall)
        Actions.register(Actions.ShallowScanVariable, Cache.temporary_structure)
        Actions.register(Actions.DeepScanVariable, Cache.temporary_structure)
        Actions.register(Actions.DeepScanReturn, Cache.temporary_structure)
        Actions.register(Actions.DeepScanFunctions, Cache.temporary_structure)
        Actions.register(Actions.RecognizeShape)
        Actions.register(Actions.CreateNewField)
        Actions.register(Actions.SelectContainingStructure, potential_negatives)
        Actions.register(Actions.ResetContainingStructure)
        Actions.register(Actions.RecastItemRight)
        Actions.register(Actions.RecastItemLeft)
        Actions.register(Actions.RenameOther)
        Actions.register(Actions.RenameInside)
        Actions.register(Actions.RenameOutside)
        Actions.register(Actions.RenameUsingAssert)
        Actions.register(Actions.SwapThenElse)
        Actions.register(Actions.FindFieldXrefs)
        Actions.register(Actions.PropagateName)
        Actions.register(Actions.GuessAllocation)

        idaapi.attach_action_to_menu('View/Open subviews/Local types', Actions.ShowClasses.name, idaapi.SETMENU_APP)
        idaapi.install_hexrays_callback(hexrays_events_callback)

        Const.init()
        XrefStorage().open()

        return idaapi.PLUGIN_KEEP

    @staticmethod
    def run(arg):
        tform = idaapi.find_tform("Structure Builder")
        if tform:
            idaapi.switchto_tform(tform, True)
        else:
            Forms.StructureBuilder(Cache.temporary_structure).Show()

    @staticmethod
    def term():
        if Cache.temporary_structure:
            Cache.temporary_structure.clear()
        # Actions.unregister(Actions.CreateVtable)
        Actions.unregister(Actions.ShowGraph)
        Actions.unregister(Actions.ShowClasses)
        Actions.unregister(Actions.GetStructureBySize)
        Actions.unregister(Actions.RemoveArgument)
        Actions.unregister(Actions.AddRemoveReturn)
        Actions.unregister(Actions.ConvertToUsercall)
        Actions.unregister(Actions.ShallowScanVariable)
        Actions.unregister(Actions.DeepScanVariable)
        Actions.unregister(Actions.DeepScanReturn)
        Actions.unregister(Actions.DeepScanFunctions)
        Actions.unregister(Actions.RecognizeShape)
        Actions.unregister(Actions.CreateNewField)
        Actions.unregister(Actions.SelectContainingStructure)
        Actions.unregister(Actions.ResetContainingStructure)
        Actions.unregister(Actions.RecastItemRight)
        Actions.unregister(Actions.RecastItemLeft)
        Actions.unregister(Actions.RenameOther)
        Actions.unregister(Actions.RenameInside)
        Actions.unregister(Actions.RenameOutside)
        Actions.unregister(Actions.RenameUsingAssert)
        Actions.unregister(Actions.SwapThenElse)
        Actions.unregister(Actions.FindFieldXrefs)
        Actions.unregister(Actions.PropagateName)
        Actions.unregister(Actions.GuessAllocation)
        idaapi.term_hexrays_plugin()
        XrefStorage().close()


def PLUGIN_ENTRY():
    idaapi.notify_when(idaapi.NW_OPENIDB, Cache.init_demangled_names)
    idaapi.notify_when(idaapi.NW_OPENIDB, Cache.init_imported_ea)
    idaapi.notify_when(idaapi.NW_OPENIDB, Cache.reset_touched_functions)
    Helper.extend_ida()
    return MyPlugin()


repository: synacktiv/dotNIET
file: dotNIET_plugin.py
wanted_name: .NIET
created_at: 2021-03-01 17:38:29+00:00
pushed_at: 2021-06-30 10:35:50+00:00
url:
https://github.com/synacktiv/dotNIET/blob/8619e50686295b11882224b02d57ea6b221703cc/dotNIET_plugin.py

content:
# ----------------------------------------------------------------------------
# "THE BEER-WARE LICENSE" (Revision 42):
# <jean-christophe.delaunay@synacktiv.com> wrote this file. As long as you 
# retain this notice you can do whatever you want with this stuff. 
# If we meet some day, and you think this stuff is worth it, you can buy me a
# beer in return
# ----------------------------------------------------------------------------

import os, shutil, tempfile, subprocess
from collections import defaultdict

import idaapi, idc, idautils, ida_kernwin, ida_pro
from idaapi import PluginForm
from ida_name import SN_NOCHECK, SN_FORCE, SN_NODUMMY

from PyQt5 import QtCore, QtWidgets
from dotNIET import utils


dotnet_versions_offsets = defaultdict(dict)
dotnet_versions_offsets["1.3"] = {"nbCustomModules" : 0x124, "ImportDescriptors" : 0x120}
dotnet_versions_offsets["1.6"] = {"nbCustomModules" : 0x124, "ImportDescriptors" : 0x120}
dotnet_versions_offsets["1.7"] = {"nbCustomModules" : 0x124, "ImportDescriptors" : 0x120}
dotnet_versions_offsets["2.2"] = {"nbCustomModules" : 0x10C, "ImportDescriptors" : 0x108}

class CustomImportDescriptor:
    def __init__(self, **kwds):
        self.__dict__.update(kwds)


class dotNIET():
    def __init__(self, dotnet_version):
        self.dotnet_version = dotnet_version
        self.imagebase = idaapi.get_imagebase()
        self.module_header_struct = idaapi.get_segm_by_name(".rdata").start_ea
        self.nb_custom_modules = idaapi.get_dword(self.module_header_struct + \
                                 dotnet_versions_offsets[self.dotnet_version]\
                                 ["nbCustomModules"])

    def get_modules_info(self):
        if self.nb_custom_modules == 1:
            idaapi.msg("This binary does not have any custom imports\n")

        import_descriptors_offset = self.module_header_struct + \
                                    idaapi.get_dword(self.module_header_struct 
                                    + dotnet_versions_offsets[self.dotnet_version]\
                                    ["ImportDescriptors"])
        # this array contains structs describing, EAT, IAT and CountIAT of the 
        # modules to be treated for import
        import_descriptors = []
        for i in range(self.nb_custom_modules):
            import_descriptors.append(CustomImportDescriptor(\
            RvaEAT=idaapi.get_dword(import_descriptors_offset + i * 12),
            RvaIAT=idaapi.get_dword(import_descriptors_offset + i * 12 + 4),
            CountIAT=idaapi.get_dword(import_descriptors_offset + i * 12 + 8)))

        # only get SharedLibrary.dll imports. Could there be some more?
        # first entry is the app itself
        assert import_descriptors[0].RvaEAT == import_descriptors[0].RvaIAT \
               and import_descriptors[0].CountIAT == 0

        # First module should not be checked. Now we check that this belongs 
        # to SharedLibrary.dll
        self.eat_addr = self.imagebase + import_descriptors[1].RvaEAT
        # This is the first symbol from the CustomExportSection of SharedLibrary.dll
        assert idc.get_name(self.eat_addr) == "$NEAT$"

        self.ordinals = self.imagebase + import_descriptors[1].RvaIAT
        self.nb_symbols = import_descriptors[1].CountIAT

    def resolve_symbols(self, dll):
        tmpdir  = tempfile.gettempdir().replace('\\','/') + '/'
        # File who will first contain ordinals then symbols
        tmpfile = tmpdir + "dotNIETsymbols.txt"
        tmplog = tmpdir + "dotNIETlogs.txt"
        # Destination path for a copy of SharedLibrary.dll
        tmpdll = tmpdir + os.path.basename(dll)
        # Path to the idat binary, needed to parse a remote idb
        idat = idc.idadir().replace('\\','/') + '/' + 'idat' + \
               ['64' if utils.is_x64(dll) else ''][0]

        if os.getenv("windir") is not None:
            idat += '.exe'
        # script called along with idat in order to parse SharedLibrary.idb
        parsing_script_path = '"' + os.path.dirname(os.path.realpath(__file__)).replace('\\','/')\
                              + '/dotNIET/resolve.py"'

        # we first copy SharedLibrary.dll to tmp to be sure we are in a 
        # writable location
        shutil.copyfile(dll, tmpdll)

        # we have to use temporary files because of the ida headless stuff...
        # pretty dirty
        ordinals = []
        with open(tmpfile, 'w') as f:
            for i in range(self.nb_symbols):
                ordinals.append((idaapi.get_qword(self.ordinals + i * 8) & ~0x8000000000000000)*4)
            f.write(",".join([str(x) for x in ordinals]))

        # be prepared to wait as this will load the binary + pdb
        idaapi.msg("Starting parsing of %d symbols...\n" % self.nb_symbols)
        subprocess.run([idat, "-L" + tmplog, "-c", "-A", "-S" + \
                        parsing_script_path + " " + tmpfile, tmpdll], 
                        creationflags=subprocess.CREATE_NO_WINDOW,
                        check=True)

        # we read back tmpfile which now contains symbols
        symbols = []
        with open(tmpfile, 'r') as f:
            symbolsArray = f.read()
            symbols = symbolsArray.split(',')

        # we first have to undef structure as it is an array and ida will 
        # refuse to set names within it
        idc.del_items(self.ordinals)
        idc.set_cmt(self.ordinals - 8, "Custom imports from SharedLibrary.dll", 0)
        # we then apply the symbols at this very same location (start of .idata)
        for i in range(self.nb_symbols):
            idc.set_name(self.ordinals + i*8, symbols[i], SN_NOCHECK|SN_FORCE|SN_NODUMMY)

        # finally we remove our temp files
        os.remove(tmpfile)
        os.remove(tmpdll)
        # ida generate a db
        os.remove(tmpdll + '.i64')
        # if everything went smoothly, we should not need this anymore
        os.remove(tmplog)


class dotNIETForm_t(PluginForm):
    def cb_btn_run(self):
        if self.dotnet_version_full == "unknown":
            ida_kernwin.warning(".NET Native framework could not be identified.\n"\
                                ".NIET needs it to work properly.")
            return
        # self.dotnet_version_full[:3] is "major.minor"
        if not self.dotnet_version_full[:3] in dotnet_versions_offsets:
            ida_kernwin.warning(".NIET currently does not support %s, please "\
                                "create an issue.")
            return

        instance = dotNIET(self.dotnet_version_full[:3])
        instance.get_modules_info()

        # if "restore" is checked, everything else is greyed out
        if self.cb_restore.checkState() == QtCore.Qt.Checked:
            ida_kernwin.show_wait_box("HIDECANCEL\nClearing symbol names...")
            for i in range(instance.nb_symbols):
                # unset name of imports
                idc.set_name(instance.ordinals + i*8, "")
            idaapi.msg("%d symbols removed!\n" % instance.nb_symbols)
        else :
            if self.dll_input_path.text() == "":
                idaapi.msg("Error: \"SharedLibrary.dll\" path must be selected\n")
                del instance
                return
            # target SharedLibrary.dll .NET framework version is asked to be checked
            if self.cb_verify.checkState() == QtCore.Qt.Checked:
                ida_kernwin.show_wait_box("HIDECANCEL\nVerifying target dll "\
                                          ".NET Native framework version...")
                dll_dotnet_version_full = utils.get_NET_Native_version(self.dll_input_path.text())
                ida_kernwin.hide_wait_box()
                if dll_dotnet_version_full == "unknown" \
                   or dll_dotnet_version_full != self.dotnet_version_full:
                    answer = ida_kernwin.ask_buttons("", "","", 1, "HIDECANCEL\n"\
                                                    "Target dll .NET Native "\
                                                    "framework version is '%s' "\
                                                    "whereas current binary one "\
                                                    "is '%s'.\nProceed anyway?" \
                                                    % (dll_dotnet_version_full,\
                                                    self.dotnet_version_full))
                    # "No" or "cancel/escape"
                    if not answer:
                        return
            # getting target SharedLibrary.dll GUID to verify that the pdb does 
            # exist and is the right one
            ida_kernwin.show_wait_box("HIDECANCEL\nGetting pdb information...")
            if not utils.find_pdb(self.dll_input_path.text()):
                ida_kernwin.hide_wait_box()
                del instance
                return

            # everything is okay, ready to import
            ida_kernwin.replace_wait_box("HIDECANCEL\nImporting symbols...")
            instance.resolve_symbols(self.dll_input_path.text())
            idaapi.msg("%d symbols imported at 0x%x\n" % (instance.nb_symbols, 
                       instance.ordinals))
        ida_kernwin.hide_wait_box()
        del instance

    def cb_btn_browse(self):
        dll = QtWidgets.QFileDialog.getOpenFileName(None, "Select a file...", './',
                                                    filter="*.dll;;All Files (*)")
        if isinstance(dll, tuple):
            self.dll_input_path.setText(str(dll[0]))
        else:
            self.dll_input_path.setText(str(dll))

    def cb_restore_toggle(self, state):
        if state == QtCore.Qt.Checked:
            self.btn_browse.setEnabled(False)
            self.dll_input_path.setEnabled(False)
            self.cb_verify.setEnabled(False)
        else :
            self.btn_browse.setEnabled(True)
            self.dll_input_path.setEnabled(True)
            self.cb_verify.setEnabled(True)

    def disable_plugin_options(self):
        self.btn_browse.setEnabled(False)
        self.btn_run.setEnabled(False)
        if self.cb_restore.isChecked():
            self.cb_restore.toggle()
        self.cb_restore.setEnabled(False)
        if self.cb_verify.isChecked():
            self.cb_verify.toggle()
        self.cb_verify.setEnabled(False)
        self.dll_input_path.setReadOnly(True)

    def OnCreate(self, form):
        # get parent widget
        parent = self.FormToPyQtWidget(form)

        # checkboxes
        self.cb_restore = QtWidgets.QCheckBox('Restore .idata section')
        self.cb_restore.move(20, 20)
        self.cb_restore.stateChanged.connect(self.cb_restore_toggle)

        self.cb_verify = QtWidgets.QCheckBox("Verify target dll .NET Native "\
                                             "framework version")
        self.cb_verify.move(20, 20)
        # default is checked
        self.cb_verify.toggle()

        label_sharedlibrary = QtWidgets.QLabel("Path to target dll "\
                                               "(SharedLibrary.dll):")
        # create input field for SharedLibrary.dll
        self.dll_input_path = QtWidgets.QLineEdit(parent)
        self.dll_input_path.setMaxLength = 256
        self.dll_input_path.setFixedWidth(300)

        # create buttons
        self.btn_run = QtWidgets.QPushButton("Run", parent)
        self.btn_run.setToolTip("Proceed to import or restore.")
        self.btn_run.clicked.connect(self.cb_btn_run)

        self.btn_browse = QtWidgets.QPushButton("Browse", parent)
        self.btn_browse.setToolTip('Browse to "SharedLibrary.dll" location.')
        self.btn_browse.clicked.connect(self.cb_btn_browse)

        # we try to guess .NET Native framework version
        ida_kernwin.show_wait_box("HIDECANCEL\nIdentifying .NET "\
                                  "Native framework version...")
        dotnet_version_full_text = ".NET Native framework version: "
        dotnet_version_full = "unknown"
        if not os.path.exists(idc.get_input_file_path()):
            ida_kernwin.warning("%s could not be found.\n.NIET must identify .NET"\
                                " Native framework version of the original binary "\
                                "in order to work properly."\
                                % idc.get_input_file_path())
        else:
            dotnet_version_full = utils.get_NET_Native_version(idc.get_input_file_path())
            if dotnet_version_full == "unknown":
                ida_kernwin.warning(".NET Native framework could not be identified.\n"\
                                    ".NIET needs it to work properly.")
            # assume pefile raised an error
            elif "Error" in dotnet_version_full:
                ida_kernwin.hide_wait_box()
                ida_kernwin.warning("pefile: %s" % dotnet_version_full)
                self.disable_plugin_options()
                dotnet_version_full = "unsupported"

            self.dotnet_version_full = dotnet_version_full
            dotnet_version_full_text += dotnet_version_full
        ida_kernwin.hide_wait_box()

        label_dotnet_version_full = QtWidgets.QLabel(dotnet_version_full_text)

        # then we check if SharedLibrary.dll is an import
        imported_modules = [idaapi.get_import_module_name(i) for i in range(idaapi.get_import_module_qty())]
        if not "SharedLibrary" in imported_modules:
            ida_kernwin.warning("This binary does not import symbols from "
                                "'SharedLibrary.dll' at runtime.\n.NIET is not"\
                                " required")
            self.disable_plugin_options()

        # create layout
        spacerItem = QtWidgets.QSpacerItem(5, 16)
        layout = QtWidgets.QGridLayout()
        layout.addWidget(label_dotnet_version_full, 0, 0)
        layout.addItem(spacerItem)
        layout.addWidget(label_sharedlibrary, 1, 0)
        layout.addWidget(self.dll_input_path, 2, 0)
        layout.addWidget(self.btn_browse, 2, 1)
        spacerItem = QtWidgets.QSpacerItem(5, 16)
        layout.addItem(spacerItem)
        layout.addWidget(self.cb_restore, 4, 0)
        layout.addWidget(self.btn_run, 5, 0)
        layout.addWidget(self.cb_verify, 5, 1, 1, 2)
        layout.setColumnStretch(4, 1)
        layout.setRowStretch(6, 1)
        parent.setLayout(layout)

    def OnClose(self, form):
        global dotNIETForm
        del dotNIETForm

    def Show(self):
        return PluginForm.Show(self, ".NIET", options=PluginForm.WOPN_PERSIST)


class dotNIETplugin(idaapi.plugin_t):
    flags = idaapi.PLUGIN_KEEP
    comment = ""
    help = ""
    wanted_name = ".NIET"
    wanted_hotkey = "Alt-Shift-N"

    def init(self):
        return idaapi.PLUGIN_KEEP

    def run(self, arg):
        global dotNIETForm
        dotNIETForm = dotNIETForm_t()
        dotNIETForm.Show()

    def term(self):
        pass

def PLUGIN_ENTRY():
    return dotNIETplugin()


repository: thelongestusernameofall/dbgStrCmtor
file: dbgStrCmtor.py
wanted_name: dbgStrCmtor
comment: string commenter and encrypt function recognition
created_at: 2016-06-27 03:02:02+00:00
pushed_at: 2016-10-21 02:47:13+00:00
url:
https://github.com/thelongestusernameofall/dbgStrCmtor/blob/989f32dc3eb59fd05a1a930d7df9cef44a723f13/dbgStrCmtor.py

content:
#!python
#coding=utf-8

# Author : Simon Huang
# Mail   : thelongestusernameofall@gmail.com
# Time   : '2016-06-06 11:56:58'
#

__author__ = 'simon_huang'

__version__    = "0.0.1"
__date__       = "2016-06-06 11:57:22"
__maintainer__ = "simon_huang"
__email__      = "thelongestusernameofall#gmail.com"
__copyright__  = "Copyright 2015, " + __author__
__license__    = "MIT"
__status__     = "Prototype"
__credits__    = [""]
__description__= "Use IDAPython"

from idaapi import *
from idautils import *
from idc import *
import traceback
import os
import sys

dbgStrCmtor=None


#333333333333333333333333333333333333333333333333333333333333333333
TEA_algorithm_name = "<<TEA>>"
TEA_algorithm_SBox = [0x9e3779b9]

SHA256_algorithm_name="<<SHA256>>"
SHA256_algorithm_SBox = [
    0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,
    0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5,
    0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,
    0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174,
    0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,
    0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da,
    0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,
    0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967,
    0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,
    0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85,
    0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,
    0xd192e819,0xd6990624,0xf40e3585,0x106aa070,
    0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,
]

SHA512_algorithm_name="<<SHA512>>"
SHA512_algorithm_SBox=[
    0x428a2f98,0xd728ae22,0x71374491,0x23ef65cd,0xb5c0fbcf,0xec4d3b2f,0xe9b5dba5,0x8189dbbc,
    0x3956c25b,0xf348b538,0x59f111f1,0xb605d019,0x923f82a4,0xaf194f9b,0xab1c5ed5,0xda6d8118,
    0xd807aa98,0xa3030242,0x12835b01,0x45706fbe,0x243185be,0x4ee4b28c,0x550c7dc3,0xd5ffb4e2,
    0x72be5d74,0xf27b896f,0x80deb1fe,0x3b1696b1,0x9bdc06a7,0x25c71235,0xc19bf174,0xcf692694,
    0xe49b69c1,0x9ef14ad2,0xefbe4786,0x384f25e3,0x0fc19dc6,0x8b8cd5b5,0x240ca1cc,0x77ac9c65,
    0x2de92c6f,0x592b0275,0x4a7484aa,0x6ea6e483,0x5cb0a9dc,0xbd41fbd4,0x76f988da,0x831153b5,
    0x983e5152,0xee66dfab,0xa831c66d,0x2db43210,0xb00327c8,0x98fb213f,0xbf597fc7,0xbeef0ee4,
    0xc6e00bf3,0x3da88fc2,0xd5a79147,0x930aa725,0x06ca6351,0xe003826f,0x14292967,0x0a0e6e70,
    0x27b70a85,0x46d22ffc,0x2e1b2138,0x5c26c926,0x4d2c6dfc,0x5ac42aed,0x53380d13,0x9d95b3df,
    0x650a7354,0x8baf63de,0x766a0abb,0x3c77b2a8,0x81c2c92e,0x47edaee6,0x92722c85,0x1482353b,
    0xa2bfe8a1,0x4cf10364,0xa81a664b,0xbc423001,0xc24b8b70,0xd0f89791,0xc76c51a3,0x0654be30,
    0xd192e819,0xd6ef5218,0xd6990624,0x5565a910,0xf40e3585,0x5771202a,0x106aa070,0x32bbd1b8,
    0x19a4c116,0xb8d2d0c8,0x1e376c08,0x5141ab53,0x2748774c,0xdf8eeb99,0x34b0bcb5,0xe19b48a8,
    0x391c0cb3,0xc5c95a63,0x4ed8aa4a,0xe3418acb,0x5b9cca4f,0x7763e373,0x682e6ff3,0xd6b2b8a3,
    0x748f82ee,0x5defb2fc,0x78a5636f,0x43172f60,0x84c87814,0xa1f0ab72,0x8cc70208,0x1a6439ec,
    0x90befffa,0x23631e28,0xa4506ceb,0xde82bde9,0xbef9a3f7,0xb2c67915,0xc67178f2,0xe372532b,
    0xca273ece,0xea26619c,0xd186b8c7,0x21c0c207,0xeada7dd6,0xcde0eb1e,0xf57d4f7f,0xee6ed178,
    0x06f067aa,0x72176fba,0x0a637dc5,0xa2c898a6,0x113f9804,0xbef90dae,0x1b710b35,0x131c471b,
    0x28db77f5,0x23047d84,0x32caab7b,0x40c72493,0x3c9ebe0a,0x15c9bebc,0x431d67c4,0x9c100d4c,
    0x4cc5d4be,0xcb3e42b6,0x597f299c,0xfc657e2a,0x5fcb6fab,0x3ad6faec,0x6c44198c,0x4a475817,
]

CRC32_algorithm_name="<<CRC32>>"
CRC32_algorithm_SBox=[
    0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
    0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
    0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
    0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
    0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
    0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
    0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
    0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
    0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
    0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
    0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
    0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
    0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
    0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
    0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
    0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
    0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
    0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
    0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
    0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
    0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
    0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
    0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
    0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
    0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
    0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
    0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
    0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
    0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
    0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
    0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
    0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
    0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
    0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
    0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
    0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
    0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
    0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
    0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
    0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
    0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
    0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
    0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
]

MD5_algorithm_name="<<MD5>>"
MD5_algorithm_SBox=[
    0X67452301L,0XEFCDAB89L,0X98BADCFEL,0X10325476L
]

SHA1_algorithm_name="<<SHA1>>"
SHA1_algorithm_SBox=[
    0x67452301,0xEFCDAB89,0x98BADCFE,0x10325476,0xC3D2E1F0
]

algorithmNameList = [
    TEA_algorithm_name,SHA256_algorithm_name,SHA512_algorithm_name,SHA1_algorithm_name,CRC32_algorithm_name,MD5_algorithm_name
]
algorithmSBoxList = [
    TEA_algorithm_SBox,SHA256_algorithm_SBox,SHA512_algorithm_SBox,SHA1_algorithm_SBox,CRC32_algorithm_SBox,MD5_algorithm_SBox
]

#jump operations
INST_JUMP = ['BLX','BL','BX','B','CBNZ','CBZ']
#arithmetic operations
INST_ADD = ['ADC','ADD']
INST_SUB = ['RSB','RSC','SBC','SUB']
INST_MUL = ['MUL','MLA'] # MLA multiple add

#Logical operations
INST_LOG = ['ORR','EOR','AND','BIC']

#Compare instructions
INST_CMP = ['CMN','CMP','TEQ','TST']

#interrupt instruction
INST_INT = ['SWI']

#Data move operations
INST_MOV = ['MOV',
            'MRC',#协处理器
            'MRS',#
            'MSR',#
            'MVN',
            'LDC',#协处理器
            'LDM',# rd为地址的内存块到寄存器组 ->
            'LDR',#内存到寄存器
            'MCR',
            'CDP',
            'STC',
            'STM',#
            'STR',
            'SWP'
]

#the 0 opnd is the destination opnd
INST_MOV_TO   = ['MOV','MVN','LDR']#'STM'
#the 0 opnd is the source, and the 1 opnd is destination
INST_MOV_BACK = ['STR']#'LDM'
#the 0 opnd is the source and destination of 1 opnd
INST_MOV_SWAP = ['SWP']

INST_SHIFT = ['LSL', 'LSR', 'ASR', 'ROR', 'RRX']


INST_DICT = {
    'JUMP':INST_JUMP,
    'ADD':INST_ADD,
    'SUB':INST_SUB,
    'MUL':INST_MUL,
    'LOG':INST_LOG,
    'CMP':INST_CMP,
    'INT':INST_INT,
    'MOV':INST_MOV,
    'SHIFT':INST_SHIFT
}

INST_CONDITION = ['EQ',
                  'NE',
                  'VS',
                  'VC',
                  'HI',
                  'LS',
                  'PL',
                  'MI',
                  'CS',
                  'CC',
                  'GE',
                  'GT',
                  'LE',
                  'LT',
                  'AL',
                  'NV',

                  'FA',
                  'FD', # note that FD is not a condition in fact.
                  'EA',
                  'ED',

                  'S'   # not a condition
]

REG_LIST = [] #R0-R15, R15=PC, R14=LR, R13=SP
for i in xrange(0,16):
    REG_LIST.append('R'+str(i))
REG_LIST.append('PC')
REG_LIST.append('LR')
REG_LIST.append('SP')

PrintableCharList = range(32,127)
PrintableCharList.append(0x0d)
PrintableCharList.append(0x0a)

#333333333333333333333333333333333333333333333333333333333333333333

STR_PREFIX = "[STR]:"
FUNC_PREFIX = "[FUNC]:"
PARA_PREFIX = "[PARAMETER]:"
RTN_PREFIX="[RETURN]:"

splitChar = [',','[',']','+','-','(',')','#','!','=']

global symbolDict
strMaxLen=100

def waitAuto():
    """
    wait for the finish of auto analysis
    """
    #print "waiting for auto process done ... "
    autoWait()


def introduction():
    print("-----------------------------------------------------------------")
    print("dbgStrCmtor: \r\n Author: Simon Huang \r\n Email: thelongestusernameofall#gmail.com\r\n")
    print("ShortCut:\r\n\tStart: alt-2\r\n\tStep Into: `-1\r\n\tStep Over:`-2\r\n")
    print("-----------------------------------------------------------------")

def init():
    #print '[init]: ...   '
    global symbolDict
    waitAuto()
    introduction()
    symbolDict = getDataSymbols()


def getDataSymbols():
    #print 'waiting for symbol parsing done ..'
    dataSectionKeywords = ['data']
    symbolDict = {}

    for ea in Segments():
        segName = SegName(ea)
        isDataSeg = False
        for aKeyWord in dataSectionKeywords:
            if aKeyWord in segName:
                isDataSeg = True
        if isDataSeg:
            startEa = SegStart(ea)
            endEa = SegEnd(ea)
            aLocation = startEa
            #for aLocation in xrange(startEa,endEa):
            while aLocation<endEa:
                lName = Name(aLocation)
                if lName and len(lName.strip())>0:
                    symbolDict[lName.strip()] = aLocation
                aLocation+=1
    return symbolDict


def cleanMnem(instMnem):
    """:type instMnem str"""
    instMnem = instMnem.upper()
    for aC in INST_CONDITION:
        """:type aC str"""
        if instMnem.endswith(aC):
            instMnem.replace(aC,"")
    return instMnem


def isANum(aStr):
    """:type aStr str"""
    try:
        aStr = aStr.replace("#","").replace("=","").strip()
        if aStr.lower().startswith("0x"):
            intvalue = int(aStr,16)
            return True
        else:
            intvalue = int(aStr,10)
        return True
    except Exception as e:
        return False


def getANum(aStr):
    """:type aStr str"""
    intValue = -1
    aStr = aStr.replace("#","").replace("=","").strip()
    if aStr.lower().startswith("0x"):
        intValue = int(aStr,16)
    else:
        intValue = int(aStr,10)
    return intValue


def tokenizeOpnd(opndStrValue):
    tokenList = []

    aToken = ''
    for achar in opndStrValue:
        if achar==' ':
            continue
        if achar in splitChar:
            if len(aToken) > 0:
                tokenList.append(aToken)
            tokenList.append(achar)
            aToken = ''
        else:
            aToken+=achar
    if len(aToken) > 0:
        tokenList.append(aToken)
    #print tokenList
    return tokenList


def makeSuffixExp(tokenList):
    suffixExp = []
    operationStack = []
    for aToken in tokenList:
        if aToken in splitChar:
            if aToken in ["(","["]:
                operationStack.append(aToken)
            elif aToken in [',','+','-']:
                while 1:
                    if len(operationStack) == 0:
                        operationStack.append(aToken)
                        break
                    elif operationStack[-1] in [',','+','-']:
                        suffixExp.append(operationStack.pop())
                    else:
                        operationStack.append(aToken)
                        break
            elif aToken==')':
                while 1:
                    if len(operationStack) == 0:
                        break
                    elif operationStack[-1] != '(':
                        suffixExp.append(operationStack.pop())
                    else:
                        operationStack.pop()
                        break
            elif aToken==']':
                while 1:
                    if len(operationStack) == 0:
                        break
                    elif operationStack[-1] != '[':
                        suffixExp.append(operationStack.pop())
                    else:
                        operationStack.pop()
                        break

        else:
            suffixExp.append(aToken)
    while 1:
        if len(operationStack) > 0:
            suffixExp.append(operationStack.pop())
        else:
            break
    #print "suffixExp:",suffixExp
    return suffixExp


def getOpndDataValue(opndStrValue):
    """:type opndStrValue str"""

    tokenList = tokenizeOpnd(opndStrValue)
    suffixExp = makeSuffixExp(tokenList)
    valueStack = []

    odv = 0

    for aToken in suffixExp:
        tokenV = -1
        if aToken not in ["+","-",","]:
            if isANum(aToken):
                tokenV = getANum(aToken)
            elif aToken in REG_LIST:
                tokenV = GetRegValue(aToken)
            elif aToken in symbolDict:
                tokenV = symbolDict[aToken]
            elif aToken.lower().startswith("var_"):
                try:
                    tokenV = int(aToken.lower().replace("var_","-0x"),16)
                except Exception as e:
                    print e.message
                    traceback.print_exc()
                    pass
            elif aToken.lower().startswith("dword_"):
                try:
                    tokenV = int(aToken.lower().replace("dword_","0x"),16)
                except Exception as e:
                    traceback.print_exc()
                    print e.message
                    pass
            else:
                print "UNDEFINED:" + aToken
                pass

            valueStack.append(tokenV)
        elif aToken in ["+",","]:
            oper_2 = valueStack.pop()
            oper_1 = valueStack.pop()
            valueStack.append(oper_1+oper_2)
        elif aToken in ["-"]:
            oper_2 = valueStack.pop()
            oper_1 = 0
            if len(valueStack)>0:
                oper_1 = valueStack.pop()
            valueStack.append(oper_1-oper_2)

    assert len(valueStack)==1
    odv = valueStack.pop()
    odv = odv&0xffffffff
    return odv


def getSourceData(instMnem,instOpndList):
    if not instMnem or not instOpndList or len(instOpndList)==0:
        return None

    srcData = 0
    idx = 1

    if instMnem in INST_ADD:
        if len(instOpndList) == 2:
            srcData = getOpndDataValue(instOpndList[0]) + getOpndDataValue(instOpndList[1])
        elif len(instOpndList) ==3:
            srcData = getOpndDataValue(instOpndList[2]) + getOpndDataValue(instOpndList[1])
    elif instMnem in INST_SUB :
        if len(instOpndList) == 2:
            srcData = getOpndDataValue(instOpndList[0]) - getOpndDataValue(instOpndList[1])
        elif len(instOpndList) ==3:
            srcData = getOpndDataValue(instOpndList[1]) - getOpndDataValue(instOpndList[2])
    elif  instMnem in INST_MUL:
        if len(instOpndList) == 2:
            srcData = getOpndDataValue(instOpndList[0]) *getOpndDataValue(instOpndList[1])
        elif len(instOpndList) ==3:
            srcData = getOpndDataValue(instOpndList[1]) *getOpndDataValue(instOpndList[2])

    elif instMnem in INST_MOV_BACK:
        srcData = getOpndDataValue(instOpndList[0])
    elif instMnem in INST_MOV_TO:
        srcData = getOpndDataValue(instOpndList[1])
    elif instMnem in INST_MOV_SWAP: # only process the later opnd for simple
        srcData = getOpndDataValue(instOpndList[1])

    elif instMnem in INST_JUMP:
        pass #process JMP instruction

    return srcData

def updateSymbolDict(instLocation):
    instName = Name(instLocation)
    global symbolDict
    if instName and len(instName.strip())>0 and instName.strip() not in symbolDict:
        symbolDict[instName] = instLocation


def getAlgorithmName(memLocation):
    #print "getAlgorithmName : ",memLocation
    for i in xrange(0,len(algorithmSBoxList)):
        if memLocation in algorithmSBoxList[i]:
            return algorithmNameList[i]
    return None


def readMemAsStr(memLocation):
    i=0
    strValue=None
    tmpV = Byte(memLocation)

    isAStr = True

    if tmpV and tmpV in PrintableCharList:
        strValue=chr(tmpV)
        for i in xrange(1,strMaxLen):
            tmpV = Byte(memLocation+i)
            if tmpV==0:
                break
            if tmpV not in PrintableCharList:
                isAStr = False
                break

            strValue += chr(tmpV)

    possibleAlgorithmName = getAlgorithmName(memLocation)


    if strValue:
        #print isAStr,strValue
        pass

    if not isAStr:
        strValue=None

    if possibleAlgorithmName:
        if not strValue:
            strValue=possibleAlgorithmName
        else:
            strValue=strValue+"||"+possibleAlgorithmName
    return strValue


def getMemLinkedList(memLocation):
    """:type memLocation int"""
    if not memLocation or memLocation == 0:
        return None

    memList = [memLocation]
    #strList.append(readMemAsStr(memLocation))

    nMemLocation = Dword(memLocation)
    while nMemLocation and nMemLocation!=0xffffffff and nMemLocation not in memList:
        memList.append(nMemLocation)
        nMemLocation=Dword(nMemLocation)

    return memList


def getMemLinkedStrList(memList):
    if not memList:
        return None
    strList = []
    for aMem in memList:
        strList.append(readMemAsStr(aMem))

    return strList


def getOldComment(memLocation):
    oldCmt_rep = GetCommentEx(memLocation,1)
    #print 'oldCmt_rep:',oldCmt_rep
    oldCmt = GetCommentEx(memLocation,0)
    #print 'oldCmt:',oldCmt
    if not oldCmt:
        oldCmt=oldCmt_rep
    else:
        if oldCmt_rep:
            oldCmt = oldCmt + "\r\n"+oldCmt_rep
    return oldCmt


def addComment(memLocation,commentStr):
    #print 'addComment: location:',memLocation,"commentStr:",commentStr
    oldCmt = getOldComment(memLocation)
    #print "oldCtm:",oldCmt
    """:type oldCmt str"""

    cmtStr=""
    if not oldCmt or not oldCmt.strip().startswith(STR_PREFIX):
        if not oldCmt or len(oldCmt.strip())==0:
            cmtStr=STR_PREFIX+commentStr
        else:
            cmtStr=STR_PREFIX+commentStr+"\r\n"+oldCmt

        MakeRptCmt(memLocation,cmtStr)
    else:
        # if oldCmt.strip()!=(STR_PREFIX+commentStr).strip():
        #     cmtStr=oldCmt+"\r\n"+commentStr
        #     print "2.cmtStr:",cmtStr
        #     MakeRptCmt(memLocation,cmtStr)
        oldCmts = oldCmt.split('\r\n')
        """:type oldCmts list of str"""
        isContain=False
        for aOldCmt in oldCmts:
            aOldCmt=aOldCmt.strip()
            if aOldCmt.startswith(STR_PREFIX):
                if aOldCmt==(STR_PREFIX+commentStr).strip():
                    isContain=True
                    break
            elif aOldCmt==commentStr.strip():
                isContain=True
                break
        if not isContain:
            cmtStr=oldCmt.strip()+"\r\n"+commentStr
            MakeRptCmt(memLocation,cmtStr)


def addComments(memList):
    strList = getMemLinkedStrList(memList)
    #print "strList",strList
    i=len(memList)
    cmtStr=None
    while i>0:
        i-=1
        aMem = memList[i]
        aStr = strList[i]
        """:type aStr str"""
        if aStr and len(aStr.strip())>0:
            if not cmtStr or len(cmtStr.strip())==0:
                cmtStr=aStr
            else:
                cmtStr=aStr+"->"+cmtStr
        if cmtStr and len(cmtStr.strip())>0:
            addComment(aMem,cmtStr)
    #print cmtStr
    return cmtStr


def isFuncStart(instLocation):
    funcInnerOffset = GetFuncOffset(instLocation)
    if not funcInnerOffset:
        return False
    if not "+" in funcInnerOffset:
        return True
    elif funcInnerOffset.endswith('+1'):  #Thumb mode, Sometimes jump to functionStart + 1
        return True
    return False


def cmtFuncParameters(instLocatin, inst_mnem):

    # if the function already be commented by this tool, no more comment should be done.
    # function should be more considering its logic than its parameters each time.

    oldCmt =  getOldComment(instLocatin)
    if oldCmt and  FUNC_PREFIX in oldCmt:
        return

    if inst_mnem in INST_JUMP:
        xrefObjList = [ref for ref in XrefsFrom(instLocatin,1)]
        if len(xrefObjList) !=1:
            print("[ERROR]: should be 1, something goes wrong"+"instLocation:"+str(instLocatin))
            return
        targetLocation = xrefObjList[0].to
        if isFuncStart(targetLocation):
            functionCmtStr = FUNC_PREFIX + "-"*20 + "\r\n" + PARA_PREFIX

            for i in xrange(0,3):
                R_value = GetRegValue("R"+str(i))
                R_str = "R"+str(i)+"="+hex(R_value).replace("L","")+"="+str(R_value)
                R_MemList = getMemLinkedList(R_value)
                R_MemStrList = getMemLinkedStrList(R_MemList)
                strAppend = ""
                if R_MemStrList and len(R_MemStrList) > 1:
                    for aMemStr in R_MemStrList:
                        if aMemStr and len(aMemStr.strip())>0:
                            strAppend+=aMemStr + " -> "
                R_str = R_str + " : " + strAppend + "\r\n"
                functionCmtStr += R_str

            #functionCmtStr += "[FUNC]:" + "-"*10
            MakeComm(instLocatin,functionCmtStr) # maybe MakeRptCmt better?



def getPrevInstLocation(instLocation):
    xrefsList = [ref for ref in XrefsTo(instLocation,0)]

    if len(xrefsList) == 1:
        xrefObj = xrefsList[0]
        frmLocation = xrefObj.frm
        return frmLocation

    return None


def isPrevInstJmp(instLocation):
    prevInstLocation = getPrevInstLocation(instLocation)
    if not prevInstLocation:
        return False
    prev_inst_mnem = GetMnem(prevInstLocation)
    prev_inst_mnem = cleanMnem(prev_inst_mnem)
    if prev_inst_mnem in INST_JUMP:
        oldCmt = getOldComment(prevInstLocation)
        if oldCmt and FUNC_PREFIX in oldCmt and RTN_PREFIX not in oldCmt:
            return True
    return False


def cmtFuncReturn(instLocation):
    oldCmt = getOldComment(instLocation)
    if oldCmt and FUNC_PREFIX in oldCmt and RTN_PREFIX not in oldCmt:
        oldCmt += "\r\n" + RTN_PREFIX + "\r\n"
        i=0
        R_value = GetRegValue("R"+str(i))
        R_str = "R"+str(i)+"="+hex(R_value).replace("L","")+"="+str(R_value)
        R_MemList = getMemLinkedList(R_value)
        R_MemStrList = getMemLinkedStrList(R_MemList)
        strAppend = ""
        if R_MemStrList and len(R_MemStrList) > 1:
            for aMemStr in R_MemStrList:
                if aMemStr and len(aMemStr.strip())>0:
                    strAppend+=aMemStr + " -> "
        R_str = R_str + " : " + strAppend
        oldCmt += R_str +"\r\n"
        oldCmt += FUNC_PREFIX + '-'*20

        MakeComm(instLocation,oldCmt)


def analyzeInst(instLocation):
    inst = GetDisasm(instLocation)
    inst_mnem = GetMnem(instLocation)
    inst_mnem = cleanMnem(inst_mnem)

    updateSymbolDict(instLocation)

    #print inst

    opndList = []

    i = 0

    opnd = GetOpnd(instLocation,i)
    while opnd and len(opnd)>0:
        opndList.append(opnd)
        i+=1
        opnd = GetOpnd(instLocation,i)

    #1. function parameters
    if inst_mnem in INST_JUMP:
        cmtFuncParameters(instLocation,inst_mnem)
    #2. function return value
    if isPrevInstJmp(instLocation):
        prevInstLocation = getPrevInstLocation(instLocation)
        cmtFuncReturn(prevInstLocation)
    #3. String pointer comment and Hash function recognition
    srcData = getSourceData(inst_mnem,opndList)
    #print 'srcData:'+ hex(srcData)
    if not srcData or srcData<0x1000:
        return
    memList = getMemLinkedList(srcData)
    if not memList:
        #print "memList: None"
        return
    #print "memList:",[hex(x)[0:-1] for x in memList]
    cmtStr=addComments(memList)
    #print "cmtStr",cmtStr
    if cmtStr:
        addComment(instLocation,cmtStr)


def myStepOver():
    #
    pc_value = GetRegValue("pc")
    # cmt_str = GetCommentEx(pc_value,0)
    # """:type cmt_str str"""
    # analyzed = False
    # if cmt_str:
    #     cmt_str = cmt_str.strip()
    #     if len(cmt_str) > 0:
    #         if cmt_str.startswith(STR_PREFIX):
    #             analyzed = True
    # else:
    #     cmt_str = ""
    # if not analyzed:
    try:
        analyzeInst(pc_value)
    except Exception as e:
        traceback.print_exc()
        print e
    request_step_over()
    run_requests()

def myStepInto():
    pc_value = GetRegValue("pc")
    analyzeInst(pc_value)
    try:
        request_step_into() # causes internal error 40396 on version before 6.8
        run_requests()
    except Exception as e:
        traceback.print_exc()
        print e.message
def process():
    #print '[process]: start'

    # dbgStrCmtor = DbgStrCmtor()
    # dbgStrCmtor.hook()
    idaapi.add_hotkey("`+1",myStepInto)
    idaapi.add_hotkey("`+2",myStepOver)

    #print '[process]: done'


def finish():
    #print "[finish]: ..."
    pass


def main():
    #print '__main__ start'
    init()
    process()
    finish()
    #print '__main__ done'


if __name__=="__main__":
     main()

class DbgStrCmtorPlugin(idaapi.plugin_t):
    flags=idaapi.PLUGIN_KEEP
    comment="string commenter and encrypt function recognition"

    help = "dbgStrCmtor: \r\n Author: Simon Huang \r\n Email: thelongestusernameofall#gmail.com\r\n"
    wanted_name = "dbgStrCmtor"
    wanted_hotkey = "Alt-2"

    def init(self):
        return idaapi.PLUGIN_OK

    def run(self,arg):
        main()
        pass

    def term(self):
        pass


def PLUGIN_ENTRY():
    return DbgStrCmtorPlugin()


