# plugins

repository: N1nEmAn/VxHPub
file: ida_tools/ida_firm_fix.py
description: VxHello for public
wanted_name: FirmwareFix
comment: FirmwareFix
created_at: 2024-07-30 13:02:24+00:00
pushed_at: 2024-07-30 13:29:22+00:00
url:
https://github.com/N1nEmAn/VxHPub/blob/99f038dae5994f4017aacafe02de520476d85689/ida_tools/ida_firm_fix.py

content:
import ida_ida
import ida_kernwin
import ida_search
import ida_funcs
import idautils
import ida_bytes
import ida_offset
import ida_segment
import ida_idaapi

import functools
import typing


class InfoForm(ida_kernwin.Form):
    def __init__(self):
        self.first = True
        F = ida_kernwin.Form
        F.__init__(
            self,
            r"""Segment Info

{FormChangeCb}
<Fix function: {rFunc}>{ChkFunc}>
<## Start of code        :{iStartCode}>
<## End of code          :{iEndCode}>

<Fix string: {rString}>{ChkStr}>
<## Start of string      :{iStartStr}>
<## End of string        :{iEndStr}>

<Fix data reg: {rData}>{ChkData}>
<## Start of data point  :{iStartP}>
<## End of data point    :{iEndP}>
            """, {
                "ChkFunc": F.ChkGroupControl(("rFunc",)),
                "ChkStr": F.ChkGroupControl(("rString",)),
                "ChkData": F.ChkGroupControl(("rData",)),
                "iStartCode": F.NumericInput(tp=F.FT_ADDR),
                "iEndCode": F.NumericInput(tp=F.FT_ADDR),
                "iStartStr": F.NumericInput(tp=F.FT_ADDR),
                "iEndStr": F.NumericInput(tp=F.FT_ADDR),
                "iStartP": F.NumericInput(tp=F.FT_ADDR),
                "iEndP": F.NumericInput(tp=F.FT_ADDR),
                "FormChangeCb": F.FormChangeCb(self.OnFormChange)
            }
        )

    def OnFormChange(self, fid):
        if self.first:
            input_list = [self.iStartCode, self.iEndCode, self.iStartStr, self.iEndStr, self.iStartP, self.iEndP]
            for ctrl in input_list:
                self.EnableField(ctrl, 0)
            self.first = False

        notify_ctr = (self.rFunc, self.rString, self.rData)
        notify_map = dict(map(lambda x: (x.id, x), notify_ctr))

        control_map = {
            self.rFunc: (self.iStartCode, self.iEndCode),
            self.rString: (self.iStartStr, self.iEndStr),
            self.rData: (self.iStartP, self.iEndP),
        }

        if fid in notify_map:
            ctrl = notify_map[fid]
            for i in control_map[ctrl]:
                self.EnableField(i, self.GetControlValue(ctrl))
        return 1

    @staticmethod
    def show_from() -> typing.Dict[str, typing.Tuple]:
        f = InfoForm()
        f.Compile()

        value_map = {
            "func": {"ctrl": f.rFunc, "start": f.iStartCode, "end": f.iEndCode},
            "str": {"ctrl": f.rString, "start": f.iStartStr, "end": f.iEndStr},
            "data": {"ctrl": f.rData, "start": f.iStartP, "end": f.iEndP},
        }

        for key, value in value_map.items():
            value["start"].value = ida_ida.inf_get_min_ea()
            value["end"].value = ida_ida.inf_get_max_ea()

        ok = f.Execute()
        if ok == 1:
            res = {}
            for key, value in value_map.items():
                if value["ctrl"].checked:
                    res[key] = (value["start"].value, value["end"].value)
            return res
        else:
            return {}


class Context(object):
    accept_process = ("ARM", "MIPS", "metapc")

    def __init__(self):
        if ida_ida.idainfo_is_64bit():
            self.bits = 64
        elif ida_ida.idainfo_is_32bit():
            self.bits = 32
        else:
            self.bits = 16

        self.is_be = ida_ida.idainfo_is_be()
        self.proc_name = ida_ida.inf_get_procname()
        if self.proc_name not in self.accept_process:
            print(f"The process {self.proc_name} not support, the result maybe not right")


def search_function(start: int, end: int):
    ea = start
    while ea < end:
        ea1 = ida_search.find_unknown(ea, ida_search.SEARCH_DOWN | ida_search.SEARCH_NEXT)
        ea2 = ida_search.find_not_func(ea, ida_search.SEARCH_DOWN | ida_search.SEARCH_NEXT)
        ea = min(ea1, ea2)
        ida_funcs.add_func(ea)


def search_string(start: int, end: int):
    for s in filter(lambda x: start < x.ea < end, idautils.Strings()):
        ida_bytes.create_strlit(s.ea, s.length, 0)

    # for str in idautils.Strings():
    #     if start < str.ea < end:
    #         ida_bytes.create_strlit(str.ea, str.length, 0)


def search_data(start: int, end: int):
    process_bytes = context.bits // 8

    find_next_data = lambda x: ida_search.find_data(x, ida_search.SEARCH_DOWN | ida_search.SEARCH_NEXT)
    find_next_unknown = lambda x: ida_search.find_unknown(x, ida_search.SEARCH_DOWN | ida_search.SEARCH_NEXT)
    check_data_size = lambda x: ida_bytes.get_item_size(x) == process_bytes
    check_data_align = lambda x: x % process_bytes == 0
    is_unknown = lambda x: ida_bytes.is_unknown(ida_bytes.get_flags(x))
    all_unknown = lambda x: all(map(lambda i: is_unknown(x + i), range(process_bytes)))
    get_data = (lambda x: ida_bytes.get_wide_dword(x)) if process_bytes == 4 else (
        lambda x: ida_bytes.get_qword(x))
    in_mem_range = lambda x: ida_segment.getseg(get_data(x)) is not None

    def ea_iter(find_next):
        ea = start
        while ea < end:
            ea = find_next(ea)
            yield ea

    f = ea_iter(find_next_data)
    f = filter(check_data_size, f)
    f = filter(in_mem_range, f)
    for ea in f:
        ida_offset.op_plain_offset(ea, 0, 0)

    f = ea_iter(find_next_unknown)
    f = filter(check_data_align, f)
    f = filter(all_unknown, f)
    f = filter(in_mem_range, f)
    for ea in f:
        ida_offset.op_plain_offset(ea, 0, 0)


def main_process():
    result = InfoForm.show_from()
    if "func" in result:
        code_start, code_end = result["func"]
        search_function(code_start, code_end)

    if "str" in result:
        str_start, str_end = result["str"]
        search_string(str_start, str_end)

    if "data" in result:
        data_start, data_end = result["data"]
        search_data(data_start, data_end)


class DoFix(ida_kernwin.action_handler_t):
    def __init__(self):
        ida_kernwin.action_handler_t.__init__(self)

    def activate(self, ctx):
        main_process()
        return 1

    def update(self, ctx):
        return ida_kernwin.AST_ENABLE_ALWAYS


context = None


class FirmwareFix_t(ida_idaapi.plugin_t):
    flags = ida_idaapi.PLUGIN_KEEP
    comment = "FirmwareFix"
    help = ""
    wanted_name = "FirmwareFix"
    wanted_hotkey = ""

    def init(self):
        global context
        context = Context()

        self.act_name = "firmware:Fixed"
        if ida_kernwin.register_action(ida_kernwin.action_desc_t(
                self.act_name,
                "fix firmware",
                DoFix()
        )):
            print("add firmware fix plugins")
            ida_kernwin.attach_action_to_menu("Edit/Plugins/", self.act_name, ida_kernwin.SETMENU_APP)
        return ida_idaapi.PLUGIN_KEEP

    def run(self, arg):
        main_process()

    def term(self):
        ida_kernwin.unregister_action(self.act_name)


def PLUGIN_ENTRY():
    return FirmwareFix_t()

# print("big" if context.is_be else "little")
# print(f"bits {context.bits}")
# print(f"process name {context.proc_name}")


repository: geohot/qira
file: ida/python/qira.py
description: QEMU Interactive Runtime Analyser
url: https://github.com/geohot/qira/blob/5f34406410aa492bc491fe0e579dbe103390a432/ida/python/qira.py
wanted_name: QIRA Plugin
comment: QEMU Interactive Runtime Analyser plugin
created_at: 2014-09-05 15:09:40+00:00
pushed_at: 2022-07-02 21:00:16+00:00
content:
import idaapi
import threading
import time

wsserver = None
qira_address = None

# this handles all receiving
msg_queue = []  # python array is threadsafe

def handle_message_queue():
  global msg_queue
  while len(msg_queue) > 0:
    dat = msg_queue[0].split(" ")
    msg_queue = msg_queue[1:]

    if dat[0] == "setaddress" and dat[1] != "undefined":
      try:
        a = idaapi.toEA(0, int(str(dat[1][2:]),16))
        jump_to(a)
      except e:
        idaapi.msg("[QIRA Plugin] Error processing the address\n")

def start_server():
  global wsserver
  wsserver = SimpleWebSocketServer('', 3003, QiraServer)
  if wsserver is not None:
    idaapi.msg("[QIRA Plugin] Starting WS Server\n")
    wsserver.serveforever()

def set_qira_address(la):
  global qira_address
  ea = 0
  if qira_address is not None and qira_address != BADADDR:
    ea = idaapi.toEA(0, qira_address)
    idaapi.del_bpt(ea)

  qira_address = la
  idaapi.add_bpt(qira_address, 0, BPT_SOFT)
  EnableBpt(qira_address, False)

def jump_to(a):
  global qira_address
  if a is not None:
    if (a != qira_address) and (a != BADADDR):
      set_qira_address(a)
      idaapi.jumpto(qira_address, -1, 0)
    else:
      idaapi.jumpto(qira_address, -1, 0)

def ws_send(msg):
  global wsserver
  if (wsserver is not None) and (msg is not None):
    for conn in wsserver.connections.itervalues():
      conn.sendMessage(msg)

def update_address(addr_type, addr):
  if (addr_type is not None) and (addr is not None):
    cmd = "set%s 0x%x" % (addr_type, addr)
    ws_send(cmd)

def update_comment(addr, rpt):
  cmt = idaapi.get_cmt(addr, rpt)
  if cmt is not None:
    ws_send("setcmt 0x%x %s" % (addr, cmt))

class MyIDAViewWrapper(idaapi.IDAViewWrapper):
  def __init__(self, viewName):
    idaapi.IDAViewWrapper.__init__(self, viewName)
    self.old_addr = None
    self.addr = None

  def OnViewCurpos(self):
    self.addr = idaapi.get_screen_ea()
    if (self.old_addr != self.addr):
      if (idaapi.isCode(idaapi.getFlags(self.addr))):
        # don't update the address if it's already the qira address or None
        if (self.addr is not None) and (self.addr != qira_address):
          #idaapi.msg("[QIRA Plugin] Qira Address %x \n" % (self.addr))
          # Instruction Address
          set_qira_address(self.addr)
          update_address("iaddr", self.addr)
      else:
        # Data Address
        update_address("daddr", self.addr)
    self.old_addr = self.addr

class idbhook(idaapi.IDB_Hooks):
  def cmt_changed(self, a, b):
    update_comment(a, b)
    return 0

class idphook(idaapi.IDP_Hooks):
  def renamed(self, ea, new_name, local_name):
    #print ea, new_name
    ws_send("setname 0x%x %s" % (ea, new_name))
    return 0
  
class uihook(idaapi.UI_Hooks):
  def __init__(self):
    idaapi.UI_Hooks.__init__(self)
    self.binds = []
  def preprocess(self, arg):
    #print "preprocess", arg
    return 0
  def current_tform_changed(self, a1, a2):
    #print "tform", idaapi.get_tform_title(a1)
    tm = MyIDAViewWrapper(idaapi.get_tform_title(a1))
    if tm.Bind():
      self.binds.append(tm)
    return 0


class qiraplugin_t(idaapi.plugin_t):
  flags = 0
  comment = "QEMU Interactive Runtime Analyser plugin"
  help = "Visit qira.me for more infos"
  wanted_name = "QIRA Plugin"
  wanted_hotkey = "z"

  def init(self):
    threading.Thread(target=start_server).start()
    idaapi.msg("[QIRA Plugin] Ready to go!\n")
    
    self.w1 = None

    #threading.Thread(target=poll_address).start()

    self.idbhook = idbhook()
    self.idbhook.hook()
    self.idphook = idphook()
    self.idphook.hook()
    self.uihook = uihook()
    self.uihook.hook()

    return idaapi.PLUGIN_KEEP


  def run(self, arg):
    global qira_address
    idaapi.msg("[QIRA Plugin] Syncing with Qira\n")


    # sync names
    for i in range(idaapi.get_nlist_size()):
      ws_send("setname 0x%x %s" % (idaapi.get_nlist_ea(i), idaapi.get_nlist_name(i)))

    # sync comment
    addr = idaapi.get_segm_base(idaapi.get_first_seg())
    while addr != idaapi.BADADDR:
      for rpt in [True, False]:
        update_comment(addr, rpt)
      addr = idaapi.nextaddr(addr)

  def term(self):
    global wsserver
    if wsserver is not None:
      wsserver.close()
    idaapi.msg("[QIRA Plugin] Plugin uninstalled!\n")


def PLUGIN_ENTRY():
  return qiraplugin_t()



###########################################################
#                                                         #
#    SimpleWebSocketServer                                #
#    https://github.com/opiate/SimpleWebSocketServer.git  #
#                                                         #
###########################################################

'''
The MIT License (MIT)

Copyright (c) 2013 Dave P.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
'''

import SocketServer
import hashlib
import base64
import socket
import struct
import ssl
import time
import sys
import errno
import logging
from BaseHTTPServer import BaseHTTPRequestHandler
from StringIO import StringIO
from select import select


class HTTPRequest(BaseHTTPRequestHandler):
  def __init__(self, request_text):
    self.rfile = StringIO(request_text)
    self.raw_requestline = self.rfile.readline()
    self.error_code = self.error_message = None
    self.parse_request()


class WebSocket(object):

  handshakeStr = (
    "HTTP/1.1 101 Switching Protocols\r\n"
    "Upgrade: WebSocket\r\n"
    "Connection: Upgrade\r\n"
    "Sec-WebSocket-Protocol: qira\r\n"
    "Sec-WebSocket-Accept: %(acceptstr)s\r\n\r\n"
  )

  hixiehandshakedStr = (
    "HTTP/1.1 101 WebSocket Protocol Handshake\r\n"
    "Upgrade: WebSocket\r\n"
    "Connection: Upgrade\r\n"
    "Sec-WebSocket-Origin: %(origin)s\r\n"
    "Sec-WebSocket-Protocol: qira\r\n"
    "Sec-WebSocket-Location: %(type)s://%(host)s%(location)s\r\n\r\n"
  )

  GUIDStr = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'

  STREAM = 0x0
  TEXT = 0x1
  BINARY = 0x2
  CLOSE = 0x8
  PING = 0x9
  PONG = 0xA

  HEADERB1 = 1
  HEADERB2 = 3
  LENGTHSHORT = 4
  LENGTHLONG = 5
  MASK = 6
  PAYLOAD = 7

  def __init__(self, server, sock, address):
    self.server = server
    self.client = sock
    self.address = address

    self.handshaked = False
    self.headerbuffer = ''
    self.readdraftkey = False
    self.draftkey = ''
    self.headertoread = 2048
    self.hixie76 = False

    self.fin = 0
    self.data = None
    self.opcode = 0
    self.hasmask = 0
    self.maskarray = None
    self.length = 0
    self.lengtharray = None
    self.index = 0
    self.request = None
    self.usingssl = False

    self.state = self.HEADERB1

    # restrict the size of header and payload for security reasons
    self.maxheader = 65536
    self.maxpayload = 4194304

  def close(self):
    self.client.close()
    self.state = self.HEADERB1
    self.hasmask = False
    self.handshaked = False
    self.readdraftkey = False
    self.hixie76 = False
    self.headertoread = 2048
    self.headerbuffer = ''
    self.data = ''


  def handleMessage(self):
    pass

  def handleConnected(self):
    pass

  def handleClose(self):
    pass

  def handlePacket(self):
    # close
    if self.opcode == self.CLOSE:
      self.sendClose()
      raise Exception("received client close")
    # ping
    elif self.opcode == self.PING:
      pass

    # pong
    elif self.opcode == self.PONG:
      pass

    # data
    elif self.opcode == self.STREAM or self.opcode == self.TEXT or self.opcode == self.BINARY:
      self.handleMessage()


  def handleData(self):

    # do the HTTP header and handshake
    if self.handshaked is False:

      data = self.client.recv(self.headertoread)

      if data:
        # accumulate
        self.headerbuffer += data

        if len(self.headerbuffer) >= self.maxheader:
          raise Exception('header exceeded allowable size')

        # we need to read the entire 8 bytes of after the HTTP header, ensure we do
        if self.readdraftkey is True:
          self.draftkey += self.headerbuffer
          read = self.headertoread - len(self.headerbuffer)

          if read != 0:
            self.headertoread = read
          else:
            # complete hixie76 handshake
            self.handshake_hixie76()

        # indicates end of HTTP header
        elif '\r\n\r\n' in self.headerbuffer:
          self.request = HTTPRequest(self.headerbuffer)
          # hixie handshake
          if self.request.headers.has_key('Sec-WebSocket-Key1'.lower()) and self.request.headers.has_key('Sec-WebSocket-Key2'.lower()):
            # check if we have the key in our buffer
            index = self.headerbuffer.find('\r\n\r\n') + 4
            # determine how much of the 8 byte key we have
            read = len(self.headerbuffer) - index
            # do we have all the 8 bytes we need?
            if read < 8:
              self.headertoread = 8 - read
              self.readdraftkey = True
              if read > 0:
                self.draftkey += self.headerbuffer[index:index+read]

            else:
              # get the key
              self.draftkey += self.headerbuffer[index:index+8]
              # complete hixie handshake
              self.handshake_hixie76()

          # handshake rfc 6455
          elif self.request.headers.has_key('Sec-WebSocket-Key'.lower()):
            key = self.request.headers['Sec-WebSocket-Key'.lower()]
            hStr = self.handshakeStr % { 'acceptstr' :  base64.b64encode(hashlib.sha1(key + self.GUIDStr).digest()) }
            self.sendBuffer(hStr)
            self.handshaked = True
            self.headerbuffer = ''

            try:
              self.handleConnected()
            except:
              pass
          else:
            raise Exception('Sec-WebSocket-Key does not exist')

      # remote connection has been closed
      else:
        raise Exception("remote socket closed")

    # else do normal data
    else:
      data = self.client.recv(2048)
      if data:
        for val in data:
          if self.hixie76 is False:
            self.parseMessage(ord(val))
          else:
            self.parseMessage_hixie76(ord(val))
      else:
        raise Exception("remote socket closed")



  def handshake_hixie76(self):

    k1 = self.request.headers['Sec-WebSocket-Key1'.lower()]
    k2 = self.request.headers['Sec-WebSocket-Key2'.lower()]

    spaces1 = k1.count(" ")
    spaces2 = k2.count(" ")
    num1 = int("".join([c for c in k1 if c.isdigit()])) / spaces1
    num2 = int("".join([c for c in k2 if c.isdigit()])) / spaces2

    key = ''
    key += struct.pack('>I', num1)
    key += struct.pack('>I', num2)
    key += self.draftkey

    typestr = 'ws'
    if self.usingssl is True:
      typestr = 'wss'

    response = self.hixiehandshakedStr % { 'type' : typestr, 'origin' : self.request.headers['Origin'.lower()], 'host' : self.request.headers['Host'.lower()], 'location' : self.request.path }

    self.sendBuffer(response)
    self.sendBuffer(hashlib.md5(key).digest())

    self.handshaked = True
    self.hixie76 = True
    self.headerbuffer = ''

    try:
      self.handleConnected()
    except:
      pass


  def sendClose(self):

    msg = bytearray()
    if self.hixie76 is False:
      msg.append(0x88)
      msg.append(0x00)
      self.sendBuffer(msg)
    else:
      pass

  def sendBuffer(self, buff):
    size = len(buff)
    tosend = size
    index = 0

    while tosend > 0:
      try:
        # i should be able to send a bytearray
        sent = self.client.send(str(buff[index:size]))
        if sent == 0:
          raise RuntimeError("socket connection broken")

        index += sent
        tosend -= sent

      except socket.error as e:
        # if we have full buffers then wait for them to drain and try again
        if e.errno == errno.EAGAIN:
          time.sleep(0.001)
        else:
          raise e


  #if s is a string then websocket TEXT is sent else BINARY
  def sendMessage(self, s):

    if self.hixie76 is False:

      header = bytearray()
      isString = isinstance(s, str)

      if isString is True:
        header.append(0x81)
      else:
        header.append(0x82)

      b2 = 0
      length = len(s)

      if length <= 125:
        b2 |= length
        header.append(b2)

      elif length >= 126 and length <= 65535:
        b2 |= 126
        header.append(b2)
        header.extend(struct.pack("!H", length))

      else:
        b2 |= 127
        header.append(b2)
        header.extend(struct.pack("!Q", length))

      if length > 0:
        self.sendBuffer(header + s)
      else:
        self.sendBuffer(header)
      header = None

    else:
      msg = bytearray()
      msg.append(0)
      if len(s) > 0:
        msg.extend(str(s).encode("UTF8"))
      msg.append(0xFF)

      self.sendBuffer(msg)
      msg = None


  def parseMessage_hixie76(self, byte):

    if self.state == self.HEADERB1:
      if byte == 0:
        self.state = self.PAYLOAD
        self.data = bytearray()

    elif self.state == self.PAYLOAD:
      if byte == 0xFF:
        self.opcode = 1
        self.length = len(self.data)
        try:
          self.handlePacket()
        finally:
          self.data = None
          self.state = self.HEADERB1
      else :
        self.data.append(byte)
        # if length exceeds allowable size then we except and remove the connection
        if len(self.data) >= self.maxpayload:
          raise Exception('payload exceeded allowable size')


  def parseMessage(self, byte):
    # read in the header
    if self.state == self.HEADERB1:
      # fin
      self.fin = (byte & 0x80)
      # get opcode
      self.opcode = (byte & 0x0F)

      self.state = self.HEADERB2

    elif self.state == self.HEADERB2:
      mask = byte & 0x80
      length = byte & 0x7F

      if mask == 128:
        self.hasmask = True
      else:
        self.hasmask = False

      if length <= 125:
        self.length = length

        # if we have a mask we must read it
        if self.hasmask is True:
          self.maskarray = bytearray()
          self.state = self.MASK
        else:
          # if there is no mask and no payload we are done
          if self.length <= 0:
            try:
              self.handlePacket()
            finally:
              self.state = self.HEADERB1
              self.data = None

          # we have no mask and some payload
          else:
            self.index = 0
            self.data = bytearray()
            self.state = self.PAYLOAD

      elif length == 126:
        self.lengtharray = bytearray()
        self.state = self.LENGTHSHORT

      elif length == 127:
        self.lengtharray = bytearray()
        self.state = self.LENGTHLONG


    elif self.state == self.LENGTHSHORT:
      self.lengtharray.append(byte)

      if len(self.lengtharray) > 2:
        raise Exception('short length exceeded allowable size')

      if len(self.lengtharray) == 2:
        self.length = struct.unpack_from('!H', str(self.lengtharray))[0]

        if self.hasmask is True:
          self.maskarray = bytearray()
          self.state = self.MASK
        else:
          # if there is no mask and no payload we are done
          if self.length <= 0:
            try:
              self.handlePacket()
            finally:
              self.state = self.HEADERB1
              self.data = None

          # we have no mask and some payload
          else:
            self.index = 0
            self.data = bytearray()
            self.state = self.PAYLOAD

    elif self.state == self.LENGTHLONG:

      self.lengtharray.append(byte)

      if len(self.lengtharray) > 8:
        raise Exception('long length exceeded allowable size')

      if len(self.lengtharray) == 8:
        self.length = struct.unpack_from('!Q', str(self.lengtharray))[0]

        if self.hasmask is True:
          self.maskarray = bytearray()
          self.state = self.MASK
        else:
          # if there is no mask and no payload we are done
          if self.length <= 0:
            try:
              self.handlePacket()
            finally:
              self.state = self.HEADERB1
              self.data = None

          # we have no mask and some payload
          else:
            self.index = 0
            self.data = bytearray()
            self.state = self.PAYLOAD

    # MASK STATE
    elif self.state == self.MASK:
      self.maskarray.append(byte)

      if len(self.maskarray) > 4:
        raise Exception('mask exceeded allowable size')

      if len(self.maskarray) == 4:
        # if there is no mask and no payload we are done
        if self.length <= 0:
          try:
            self.handlePacket()
          finally:
            self.state = self.HEADERB1
            self.data = None

        # we have no mask and some payload
        else:
          self.index = 0
          self.data = bytearray()
          self.state = self.PAYLOAD

    # PAYLOAD STATE
    elif self.state == self.PAYLOAD:
      if self.hasmask is True:
        self.data.append( byte ^ self.maskarray[self.index % 4] )
      else:
        self.data.append( byte )

      # if length exceeds allowable size then we except and remove the connection
      if len(self.data) >= self.maxpayload:
        raise Exception('payload exceeded allowable size')

      # check if we have processed length bytes; if so we are done
      if (self.index+1) == self.length:
        try:
          self.handlePacket()
        finally:
          self.state = self.HEADERB1
          self.data = None
      else:
        self.index += 1


class SimpleWebSocketServer(object):
  def __init__(self, host, port, websocketclass):
    self.websocketclass = websocketclass
    self.serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    self.serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    self.serversocket.bind((host, port))
    self.serversocket.listen(5)
    self.connections = {}
    self.listeners = [self.serversocket]
    self.forceclose = False


  def decorateSocket(self, sock):
    return sock

  def constructWebSocket(self, sock, address):
    return self.websocketclass(self, sock, address)

  def close(self):
    self.serversocket.close()

    for conn in self.connections.itervalues():
      try:
        conn.handleClose()
      except:
        pass

      conn.close()

    self.forceclose = True


  def serveforever(self):
    while True:
      rList, wList, xList = select(self.listeners, [], self.listeners, 1)

      if self.forceclose:
        break

      for ready in rList:
        if ready == self.serversocket:
          try:
            sock, address = self.serversocket.accept()
            newsock = self.decorateSocket(sock)
            newsock.setblocking(0)
            fileno = newsock.fileno()
            self.listeners.append(fileno)
            self.connections[fileno] = self.constructWebSocket(newsock, address)

          except Exception as n:

            logging.debug(str(address) + ' ' + str(n))

            if sock is not None:
              sock.close()
        else:
          client = self.connections[ready]

          try:
            client.handleData()

          except Exception as n:

            logging.debug(str(client.address) + ' ' + str(n))

            try:
              client.handleClose()
            except:
              pass

            client.close()

            del self.connections[ready]
            self.listeners.remove(ready)

      for failed in xList:
        if failed == self.serversocket:
          self.close()
          raise Exception("server socket failed")
        else:
          client = self.connections[failed]

          try:
            client.handleClose()
          except:
            pass

          client.close()

          del self.connections[failed]
          self.listeners.remove(failed)


class SimpleSSLWebSocketServer(SimpleWebSocketServer):

  def __init__(self, host, port, websocketclass, certfile, keyfile, version = ssl.PROTOCOL_TLSv1):

    SimpleWebSocketServer.__init__(self, host, port, websocketclass)

    self.cerfile = certfile
    self.keyfile = keyfile
    self.version = version

  def close(self):
    super(SimpleSSLWebSocketServer, self).close()

  def decorateSocket(self, sock):
    sslsock = ssl.wrap_socket(sock,
                  server_side=True,
                  certfile=self.cerfile,
                  keyfile=self.keyfile,
                  ssl_version=self.version)
    return sslsock

  def constructWebSocket(self, sock, address):
    ws = self.websocketclass(self, sock, address)
    ws.usingssl = True
    return ws

  def serveforever(self):
    super(SimpleSSLWebSocketServer, self).serveforever()



###################
#                 #
#    QIRA CODE    #
#                 #
###################


class QiraServer(WebSocket):
  def handleMessage(self):
    msg_queue.append(self.data)
    idaapi.execute_sync(handle_message_queue, idaapi.MFF_WRITE)

  def handleConnected(self):
    idaapi.msg("[QIRA Plugin] Client connected\n")

  def handleClose(self):
    idaapi.msg("[QIRA Plugin] WebSocket closed\n")


repository: mandiant/flare-ida
file: plugins/struct_typer_plugin.py
description: IDA Pro utilities from FLARE team
wanted_name: StructTyper
comment: This is a comment
created_at: 2014-07-30 18:42:23+00:00
pushed_at: 2024-10-29 14:38:07+00:00
url:
https://github.com/mandiant/flare-ida/blob/fc667de6db279c6f8ec8bcc2f3eedff348db450f/plugins/struct_typer_plugin.py

content:
#!/usr/bin/env python
# Jay Smith
# jay.smith@fireeye.com
# 
########################################################################
# Copyright 2013 Mandiant
# Copyright 2014 FireEye
# Copyright 2019 FireEye
#
# Mandiant licenses this file to you under the Apache License, Version
# 2.0 (the "License"); you may not use this file except in compliance with the
# License. You may obtain a copy of the License at:
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied. See the License for the specific language governing
# permissions and limitations under the License.
########################################################################
#
# IDA Plugin wrapper for struct typer script.
#
########################################################################


import idc 
import idautils  
import idaapi

idaapi.require('flare')
idaapi.require('flare.struct_typer')

class struct_typer_plugin_t(idaapi.plugin_t):
    flags = idaapi.PLUGIN_UNL
    comment = "This is a comment"

    help = "This is help"
    wanted_name = "StructTyper"
    wanted_hotkey = ""

    def init(self):
        #idaapi.msg("StructTyper init() called!\n")
        return idaapi.PLUGIN_OK

    def run(self, arg):
        #idaapi.msg("StructTyper run() called with %d!\n" % arg)
        flare.struct_typer.main()

    def term(self):
        #idaapi.msg("StructTyper term() called!\n")
        pass

def PLUGIN_ENTRY():
    return struct_typer_plugin_t()


repository: anatolikalysch/VMAttack
file: VMAttack.py
description: VMAttack PlugIn for IDA Pro
wanted_name: VMAttack
comment: This Framework is supposed to help with the analysis of virtualization obfuscated binaries.
created_at: 2016-09-14 14:48:28+00:00
pushed_at: 2017-11-30 01:01:31+00:00
url:
https://github.com/anatolikalysch/VMAttack/blob/67dcce6087163d85bbe7780e3f6e6e9e72e2212a/VMAttack.py

content:
# coding=utf-8
from lib.Logging import get_log

__author__ = 'Anatoli Kalysch'

import ui.SettingsWindow as SettingsWindow
from dynamic.dynamic_deobfuscate import *
from lib.VMRepresentation import *
from static.static_deobfuscate import *
from ui.AboutWindow import AboutWindow
from ui.UIManager import UIManager


class VMAttack_Manager(object):
    def __init__(self):
        self.choice = None
        self._vmr = get_vmr()

        # UI Management
        self.ui_mgr = UIManager()
        self.ui_mgr.get_init_menu()
        self.menu_name = "VMAttack"
        self.menu_extensions = []

    ### EVIRONMENT AND INIT ###
    @property
    def trace(self):
        return self.vmr._trace

    @trace.setter
    def trace(self, value):
        self.vmr._trace = value

    @property
    def vmr(self):
        self.update_vmr()
        return self._vmr

    @property
    def dbg_handl(self):
        return get_dh(self.choice)

    @dbg_handl.setter
    def dbg_handl(self, value):
        self.vmr._dbg_handl = value

    @property
    def vm_operands(self):
        return self.vmr._vm_operands

    @vm_operands.setter
    def vm_operands(self, value):
        self.vmr._vm_operands = value

    @property
    def vm_returns(self):
        return self.vmr._vm_returns

    @vm_returns.setter
    def vm_returns(self, value):
        self.vmr._vm_returns = value

    @property
    def vm_ctx(self):
        return self.vmr._vm_ctx

    @vm_ctx.setter
    def vm_ctx(self, value):
        self.vmr._vm_ctx = value

    def select_debugger(self):
        c = Choose([], "Choose your preferred debugger:", 1)
        c.list = ["Currently selected IDA Debugger", "Bochs Dbg", "Win32 Dbg"]  # TODO , "OllyDbg", "Immunity Dbg"]
        c.width = 33
        # choose() starts counting at 1, not 0
        self.choice = c.choose() - 1
        if self.choice == 1:
            LoadDebugger('Bochs', 0)
        elif self.choice == 2:
            LoadDebugger('Win32', 0)

    def update_vmr(self):
        self._vmr = get_vmr()

    ### UI MANAGEMENT ###
    @staticmethod
    def show_about():
        AboutWindow().exec_()

    @staticmethod
    def show_settings():
        SettingsWindow.Show()

    def show_trace(self):
        self.update_vmr()
        if self._vmr.trace is not None:
            for line in self._vmr.trace:
                print line.to_str_line()

    def remove_colors(self):
        # reset color
        heads = Heads(SegStart(ScreenEA()), SegEnd(ScreenEA()))
        for i in heads:
            SetColor(i, CIC_ITEM, 0xFFFFFF)

    def extend_menu(self):
        """
        Extends the menu.
        """
        try:
            self.revert_menu()
            menu_path = self.menu_name + "/"
            self.ui_mgr.get_init_menu()
            self.ui_mgr.add_menu('&'+self.menu_name)

            # debugger selection - will be added after plugin interaction with ollydbg and immunitydbg will be enabled - as of now no additional value is generated compared to the debugger selection in IDA itself so it is commented out
            # An alternative to the chooser would be to hook IDAs Debugger selection?
            #select_debugger_menu_item = add_menu_item(menu_path, "Select VMAttack Debugger", "", 0, self.select_debugger, None)
            # credits & settings
            settings_menu_item = add_menu_item(menu_path, "Settings", "", 0, self.show_settings, None)
            about_menu_item = add_menu_item(menu_path, "About ...", "", 0, self.show_about, None)
            # instruction trace generation and handling
            remove_colors_menu_item = add_menu_item(menu_path + "Instruction Trace/", "Remove Colors from Graph", "", 0, self.remove_colors, None)
            load_trace_menu_item = add_menu_item(menu_path + "Instruction Trace/", "Load Trace", "", 0, load_trace, None)
            save_trace_menu_item = add_menu_item(menu_path + "Instruction Trace/", "Save Trace", "", 0, save_trace, None)
            gen_trace_menu_item = add_menu_item(menu_path + "Instruction Trace/", "Generate Trace", "", 0, gen_instruction_trace, (self.choice,))
            show_trace_menu_item = add_menu_item(menu_path + "Instruction Trace/", "Show Trace", "", 0, self.show_trace, None)



            ### automation ###
            grading_menu_item = add_menu_item(menu_path + 'Automated Analysis/', "Grading System Analysis", "", 0, grading_automaton, None)
            automaton_menu_item = add_menu_item(menu_path + 'Automated Analysis/', "Run all analysis capabilities", "", 0, self.automaton, None)

            show_opti_menu_item = add_menu_item(menu_path + "Automated Analysis/Semi Automated (dynamic)/", "Dynamic Trace Optimization", "", 0, optimization_analysis, None)
            analyze_addr_trace_menu_item = add_menu_item(menu_path + 'Automated Analysis/Semi Automated (dynamic)/', "Clustering Analysis", "", 0, clustering_analysis, None)
            show_input_output = add_menu_item(menu_path + "Automated Analysis/Semi Automated (dynamic)/", "VM Input<=>Ouput Analysis", "", 0, input_output_analysis, None)

            deobfuscate_from_menu_item = add_menu_item(menu_path + "Automated Analysis/Semi Automated (static)/", "Static deobfuscate", "", 0, static_deobfuscate, None)
            show_abstract_graph_menu_item = add_menu_item(menu_path + "Automated Analysis/Semi Automated (static)/", "Create Abstract VM-Graph", "", 0, static_deobfuscate, (2,))
            ### manual analysis ###
            # vm context related
            static_start_search_menu_item = add_menu_item(menu_path + "Manual Analysis/VM Context/", "Find VM Context (static)", "", 0, static_vmctx, (True,))
            find_vm_values_menu_item = add_menu_item(menu_path + "Manual Analysis/VM Context/", "Find VM Context (dynamic)", "", 0, dynamic_vmctx, (True,))
            # static analysis menu items
            manual_static_menu_item = add_menu_item(menu_path + "Manual Analysis/Static/", "Deobfuscate from ...", "", 0, static_deobfuscate, (0,True))
            # dynamic analysis menu items
            follow_virt_register = add_menu_item(menu_path + "Manual Analysis/Dynamic/", "Follow Virtual Register", "", 0, manual_analysis, (3,))
            find_reg_mapping = add_menu_item(menu_path + "Manual Analysis/Dynamic/", "Find Virtual Reg to Reg mapping", "", 0, manual_analysis, (2,))
            find_vmfunc_input = add_menu_item(menu_path + "Manual Analysis/Dynamic/", "Find VM Function Input Parameter", "", 0, manual_analysis, (1,))
            find_vmfunc_output = add_menu_item(menu_path + "Manual Analysis/Dynamic/", "Find VM Function Output Parameter", "", 0, manual_analysis, (0,))
            analyze_count_menu_item = add_menu_item(menu_path + "Manual Analysis/Dynamic/", "Address Count", "", 0, address_heuristic, None)
            #manual_input_output = add_menu_item(menu_path + "Manual Analysis/Dynamic/", " Run Input<=>Ouput Analysis on Function", "", 0, input_output_analysis, (True,))


            self.menu_extensions.append(deobfuscate_from_menu_item)
            self.menu_extensions.append(settings_menu_item)
            #self.menu_extensions.append(select_debugger_menu_item)
            self.menu_extensions.append(load_trace_menu_item)
            self.menu_extensions.append(save_trace_menu_item)
            self.menu_extensions.append(gen_trace_menu_item)
            self.menu_extensions.append(analyze_count_menu_item)
            self.menu_extensions.append(analyze_addr_trace_menu_item)
            self.menu_extensions.append(static_start_search_menu_item)
            self.menu_extensions.append(find_vm_values_menu_item)
            self.menu_extensions.append(automaton_menu_item)
            self.menu_extensions.append(show_abstract_graph_menu_item)
            self.menu_extensions.append(find_vmfunc_input)
            self.menu_extensions.append(find_vmfunc_output)
            self.menu_extensions.append(manual_static_menu_item)
            self.menu_extensions.append(find_reg_mapping)
            self.menu_extensions.append(follow_virt_register)
            self.menu_extensions.append(show_input_output)
            self.menu_extensions.append(show_trace_menu_item)
            self.menu_extensions.append(about_menu_item)
            self.menu_extensions.append(show_opti_menu_item)
            self.menu_extensions.append(grading_menu_item)
            #self.menu_extensions.append(manual_input_output)
            self.menu_extensions.append(remove_colors_menu_item)


        except Exception, e:
            print "[*] Menu could not be added! Following Error occurred:\n %s" % e.message

    def revert_menu(self):
        for i in self.menu_extensions:
            del_menu_item(i)
        self.ui_mgr.clear()

    def welcome(self):
        msg("\n\
    ..........llllllllllllllllll..llllllll......llllll.......llllll..........\n\
    ..........llllllllllllllllll..lllllllll.....llllll.......llllll..........\n\
    ..........llllll.............lllll.lllll....llllll.......llllll..........\n\
    ..........llllll............,lllll.lllll,...llllll.......llllll..........\n\
    ..........llllll............llllll.llllll...llllll.......llllll..........\n\
    ..........lllllllllllllllllllllll...lllll...llllll.......llllll..........\n\
    ..........lllllllllllllllllllllll...llllll..llllll.......llllll..........\n\
    ..........llllllllllllllllllllll.....lllll..llllll.......llllll..........\n\
    ..........llllll..........lllllllllllllllll.llllll.......llllll..........\n\
    ..........llllll.........llllllllllllllllll.llllll.......llllll..........\n\
    ..........llllll........lllllllllllllllllllllllllll......llllll..........\n\
    ..........llllll........llllll.........lllllllllllllllllllllll...........\n\
    ..........llllll.......lllllll.........lllllll.lllllllllllll.............\n\
    ..........llllll......lllllll...........lllllll.lllllllllll..............\n\
    ............Friedrich-Alexander University Erlangen-Nuremberg............\n\
    ")

    def reset_grade(self, trace):
        for line in trace:
            line.grade = 0

        return trace

    def grade(self, trace, excerpt):
        for line in excerpt:
            trace[trace.index(line)].raise_grade()

        return trace

    # automaton
    def automaton(self):
        trace = prepare_trace()
        self.reset_grade(trace)
        # load current IDA-Debugger
        if self.dbg_handl.dbg.module_name is "NoDbg":
            self.dbg_handl = self.select_debugger()

        # instruction trace
        if trace is None:
            try:
                trace = self.dbg_handl.gen_instruction_trace()
            except:
                self._vmr.trace = prepare_trace()
        # run all trace analysis functions and present the results
        dynamic_vmctx()

        # afterwards if the VM context was found run the static analysis automatically since it depends on the VM context
        try:
            self.update_vmr()
            deobfuscate(self._vmr.code_start, self._vmr.base_addr, self._vmr.code_end, self._vmr.vm_addr)
        except Exception, e:
            try:
                static_vmctx()
                self.update_vmr()
                deobfuscate(self._vmr.code_start, self._vmr.base_addr, self._vmr.code_end, self._vmr.vm_addr)
            except Exception, ex:
                msg("[*] Could not provide static deobfuscation analysis! The following errors occured:\n %s \n %s" % (
                e.message, ex.message))


        # run the dynamic analysis capabilities of the plugin -> each analysis increases a special trace lines grade which will be evaluated at the end of the analysis
        # input / output
        try:
            input_output_analysis()
        except Exception, e:
            print '[*] Exception occured while running Input/Output analysis!\n %s' % e.message
        # clustering
        try:
            clustering_analysis()
        except Exception, e:
            print '[*] Exception occured while running Clustering analysis!\n %s' % e.message
        # optimizations
        try:
            optimization_analysis()
        except Exception, e:
            print '[*] Exception occured while running optimization analysis!\n %s' % e.message
        # grade the trace line
        try:
            grading_automaton()
        except Exception, e:
            print '[*] Exception occured while running grading analysis!\n %s' % e.message


# Virtualization obfuscated interpretation
class VMAttack(plugin_t):
    flags = PLUGIN_PROC
    comment = "This Framework is supposed to help with the analysis of virtualization obfuscated binaries."
    help = "HELP!"
    wanted_name = "VMAttack"
    wanted_hotkey = ""

    def init(self):
        self.vma_mgr = None
        try:
            self.vma_mgr = get_mgr()
            self.vma_mgr.extend_menu()
            #self.vma_mgr.welcome()
            msg('[*] Starting VMAttack plugin...\n')
            get_log().log('[VMA] Starting VMAttack and initiating variables ...\n')
            return PLUGIN_KEEP

        except Exception, e:
            msg("[*] Failed to initialize VMAttack.\n %s\n" % e.message)
            if self.vma_mgr is not None:
                self.vma_mgr.revert_menu()
                del self.vma_mgr
            return PLUGIN_SKIP

    def run(self, arg):
        try:
            self.vma_mgr = get_mgr()
            self.vma_mgr.extend_menu()
            #self.vma_mgr.welcome()
            msg('[*] Reloading VMAttack plugin...\n')
            add_menu_item('Edit/Plugins/', 'Load VMAttack', None, 0, self.vma_mgr.extend_menu, ())
        except Exception,e:
            msg("[*] Failed to initialize VMAttack.\n %s\n" % e.message)
            msg(e.args)

    def term(self):
        if self.vma_mgr is not None:
            get_log().finalize()
            self.vma_mgr.revert_menu()
            del_vmr()
            del self

def PLUGIN_ENTRY():
    return VMAttack()


# Singelton VMA MGR
vma_mgr = None

def get_mgr():
    global vma_mgr
    if vma_mgr is None:
        vma_mgr = VMAttack_Manager()
    return vma_mgr


repository: tmr232/Sark
file: plugins/meaningful.py
description: IDAPython Made Easy
url: https://github.com/tmr232/Sark/blob/950e2c17e520ab637a3cdcd0da312d224b951f13/plugins/meaningful.py
wanted_name: Meaningful
comment: Show meaningful information
created_at: 2015-01-19 09:13:48+00:00
pushed_at: 2025-02-22 10:17:16+00:00
content:
import idaapi
import idc

import sark
from sark.data import get_string
from sark import exceptions
import sark.ui


class NoName(Exception):
    pass


def get_name(ea):
    name = None

    if not sark.Line(ea).has_name:
        raise NoName("No non-trivial name for 0x{:08X}".format(ea))

    try:
        function = sark.Function(ea)
        if function.ea == ea:
            name = function.demangled
    except:
        pass

    if not name:
        name = idc.Name(ea)

    if not name:
        raise NoName("No named for address 0x{:08X}".format(ea))

    return name


def show_meaningful_in_function(function):
    idaapi.msg("\n\nMeaningful References in {!r} : 0x{:08X}\n".format(function.demangled, function.start_ea))
    idaapi.msg("Type    Usage         Address       Object\n")
    idaapi.msg("------------------------------------------\n")

    for xref in function.xrefs_from:
        if xref.type.is_code:
            try:
                name = get_name(xref.to)
            except NoName:
                continue

            idaapi.msg("code    0x{:08X}    0x{:08X}    {}\n".format(xref.frm, xref.to, name))

        else:
            try:
                string = get_string(xref.to)
            except exceptions.SarkNoString:
                continue

            # Trim the string for easier display
            string = string[:100]

            idaapi.msg("data    0x{:08X}    {}    {}\n".format(xref.frm, sark.core.get_name_or_address(xref.to), repr(string)))

    idaapi.msg("\n\n")


def show_current_function_meaningful():
    try:
        function = sark.Function(idc.here())
        show_meaningful_in_function(function)

    except sark.exceptions.SarkNoFunction:
        idaapi.msg("[FunctionStrings] No function at 0x{:08X}.\n".format(idc.here()))


def show_highlighted_function_meaningful():
    line = sark.Line()
    meaningful_displayed = False
    for xref in line.xrefs_from:
        try:
            if xref.type.is_flow:
                continue

            function = sark.Function(xref.to)
            show_meaningful_in_function(function)
            meaningful_displayed = True

        except sark.exceptions.SarkNoFunction:
            pass

    if not meaningful_displayed:
        idaapi.msg("[FunctionStrings] No function referenced by current line: 0x{:08X}.\n".format(idc.here()))


class Meaningful(idaapi.plugin_t):
    flags = 0
    comment = "Show meaningful information"
    help = "Show strings and named xrefs"
    wanted_name = "Meaningful"
    wanted_hotkey = ""

    def init(self):
        self.hotkeys = []
        self.hotkeys.append(idaapi.add_hotkey("Alt+0", show_current_function_meaningful))
        self.hotkeys.append(idaapi.add_hotkey("Ctrl+Alt+0", show_highlighted_function_meaningful))
        return idaapi.PLUGIN_KEEP

    def term(self):
        pass

    def run(self, arg):
        pass


def PLUGIN_ENTRY():
    return Meaningful()


repository: patois/abyss
file: abyss.py
description: abyss - augmentation of Hexrays decompiler output
url: https://github.com/patois/abyss/blob/c88371a9081d0b2e5a49fc5a2b52b43d0668fdfa/abyss.py
comment: Postprocess Hexrays Output
created_at: 2020-05-26 18:29:56+00:00
pushed_at: 2022-10-26 20:03:35+00:00
content:
import ida_kernwin as kw
import ida_hexrays as hr
import ida_diskio, ida_idaapi
import os, sys, configparser

__author__ = "https://github.com/patois"

DEBUG = False

PLUGIN_NAME = "abyss"
ACTION_NAME = "%s:" % PLUGIN_NAME
POPUP_ENTRY = "%s/" % PLUGIN_NAME
FILTER_DIR = "%s_filters" % PLUGIN_NAME
CFG_FILENAME = "%s.cfg" % PLUGIN_NAME

FILTERS = {}

# ----------------------------------------------------------------------------
def dbg_print(s):
    if DEBUG:
        print("%s" % s)

# ----------------------------------------------------------------------------
class abyss_filter_t:
    """new filters should inherit from this class and
    override respective handlers/methods"""
 
    def __init__(self):
        self.set_activated(False)
        return

    def finish_populating_widget_popup_ev(self, widget, popup_handle):
        return

    def refresh_pseudocode_ev(self, vu):
        return 0

    def print_func_ev(self, cfunc, printer):
        return 0

    def func_printed_ev(self, cfunc):
        return 0

    def curpos_ev(self, vu):
        return 0

    def maturity_ev(self, cfunc, new_maturity):
        return 0

    def create_hint_ev(self, vu):
        return 0

    def get_lines_rendering_info_ev(self, out, widget, info):
        return

    def screen_ea_changed_ev(self, ea, prev_ea):
        return

    def is_activated(self):
        return self.activated

    def set_activated(self, active):
        self.activated = active

# ----------------------------------------------------------------------------
def get_cfg_filename():
    """returns full path of abyss config file."""
    return os.path.join(
        ida_diskio.get_user_idadir(),
        "cfg",
        "%s" % CFG_FILENAME)

# ----------------------------------------------------------------------------
def apply_cfg(reload=False, filters={}):
    """loads abyss configuration."""

    cfg_file = get_cfg_filename()
    kw.msg("%s: %sloading %s...\n" % (PLUGIN_NAME,
        "re" if reload else "",
        cfg_file))
    if not os.path.isfile(cfg_file):
        kw.msg("%s: default configuration (%s) does not exist!\n" % (PLUGIN_NAME, cfg_file))
        kw.msg("Creating default configuration\n")
        try:
            with open(cfg_file, "w") as f:
                f.write("[init]\n")
                for name, mod in filters.items():
                    f.write("%s=False\n" % name )
        except:
            kw.msg("failed!\n")
            return False
        return apply_cfg(reload=True)

    config = configparser.ConfigParser()
    config.read(cfg_file)

    # read all sections
    for section in config.sections():
        if section == "init":
            for name, value in config.items(section):
                try:
                    filters[name].set_activated(config[section].getboolean(name))
                    dbg_print("%s -> %s" % (name, value))
                except:
                    pass
    kw.msg("done!\n")
    return True

# ----------------------------------------------------------------------------
def create_cfg_folder():
    cfg_file = get_cfg_filename()
    if not os.path.isfile(cfg_file):
        os.makedirs(os.path.dirname(cfg_file), exist_ok=True)
    return

# ----------------------------------------------------------------------------
def load_filters(reload=False):
    global FILTERS

    create_cfg_folder()
    print("%s: %sloading filters..." % (PLUGIN_NAME, "re" if reload else ""))
    if reload:
        # TODO: properly clean-up and unload filters
        #       implement FILTER_EXIT
        FILTERS = {}
    filterdir = os.path.join(os.path.dirname(__file__), FILTER_DIR)
    if os.path.exists(filterdir):
        for entry in os.listdir(filterdir):
            if entry.lower().endswith(".py") and entry.lower() != "__init__.py":
                mod, ext = os.path.splitext(entry)
                if mod not in FILTERS:
                    try:
                        ida_idaapi.require("%s.%s" % (FILTER_DIR, mod), FILTER_DIR)
                        flt = sys.modules["%s.%s" % (FILTER_DIR, mod)].FILTER_INIT()
                        if flt:
                            print("  loaded: \"%s\"" % (mod))
                            FILTERS[mod] = flt
                    except ModuleNotFoundError:
                        print("  failed: \"%s\"" % (mod))
        apply_cfg(reload, FILTERS)
    return

# ----------------------------------------------------------------------------
class ui_event_t(kw.UI_Hooks):
    def finish_populating_widget_popup(self, widget, popup_handle):
        if kw.get_widget_type(widget) == kw.BWN_PSEUDOCODE:
            class FilterHandler(kw.action_handler_t):
                def __init__(self, name):
                    self.name = name
                    kw.action_handler_t.__init__(self)

                # on-click event handler for pop-up menu
                def activate(self, ctx):
                    # toggle activated state for current filter
                    obj = FILTERS[self.name]
                    obj.set_activated(not obj.is_activated())
                    # refresh current decompiler widget
                    vu = hr.get_widget_vdui(ctx.widget)
                    if vu:
                        vu.refresh_view(not obj.is_activated())
                    return 1

                def update(self, ctx):
                    return kw.AST_ENABLE_FOR_WIDGET

            # dynamically create event handler for each filter
            # and attach to pop-up menu
            for name, obj in FILTERS.items():
                action_desc = kw.action_desc_t(
                    '%s%s' % (ACTION_NAME, name),
                    name,
                    FilterHandler(name),
                    None,
                    None,
                    34 if obj.is_activated() else -1)
                kw.attach_dynamic_action_to_popup(widget, popup_handle, action_desc, POPUP_ENTRY)

            # forward event finish_populating_widget_popup to activated filters
            for name, obj in FILTERS.items():
                if obj.is_activated():
                    obj.finish_populating_widget_popup_ev(widget, popup_handle)
            return
    
    # forward event screen_ea_changed to activated filters
    def screen_ea_changed(self, ea, prev_ea):
        for name, obj in FILTERS.items():
            if obj.is_activated():
                obj.screen_ea_changed_ev(ea, prev_ea)
        return

    # forward event get_lines_rendering_info to activated filters
    def get_lines_rendering_info(self, out, widget, info):
        if kw.get_widget_type(widget) == kw.BWN_PSEUDOCODE:
            for name, obj in FILTERS.items():
                if obj.is_activated():
                    obj.get_lines_rendering_info_ev(out, widget, info)
        return

# ----------------------------------------------------------------------------
"""
via hexrays.hpp:
/// When the possible return value is not specified, your callback
/// must return zero.
"""
class hx_event_t(hr.Hexrays_Hooks):
    def __init__(self):
        hr.Hexrays_Hooks.__init__(self)

    # forward event refresh_pseudocode to activated filters
    def refresh_pseudocode(self, vu):
        for name, obj in FILTERS.items():
            if obj.is_activated():
                # TBD
                obj.refresh_pseudocode_ev(vu)
        return 0

    # forward event print_func to activated filters
    def print_func(self, cfunc, vp):
        """via hexrays.hpp:
        ///< Returns: 1 if text has been generated by the plugin
        ///< It is forbidden to modify ctree at this event.
        """
        custom_text = 0
        for name, obj in FILTERS.items():
            if obj.is_activated():
                custom_text |= obj.print_func_ev(cfunc, vp)
        return custom_text != 0

    # forward event func_printed to activated filters
    def func_printed(self, cfunc):
        ret = 0
        for name, obj in FILTERS.items():
            if obj.is_activated():
                # TBD
                ret |= obj.func_printed_ev(cfunc)
        return 0

    # forward event curpos to activated filters
    def curpos(self, vu):
        ret = 0
        for name, obj in FILTERS.items():
            if obj.is_activated():
                # TBD
                ret |= obj.curpos_ev(vu)
        return 0

    # forward event maturity to activated filters
    def maturity(self, cfunc, new_maturity):
        ret = 0
        for name, obj in FILTERS.items():
            if obj.is_activated():
                # TBD
                ret |= obj.maturity_ev(cfunc, new_maturity)
        return 0

    # forward event create_hint to activated filters
    def create_hint(self, vu):
        lines = ""
        count = 0
        for name, obj in FILTERS.items():
            if obj.is_activated():
                # TBD
                ret = obj.create_hint_ev(vu)
                if ret and isinstance(ret, tuple) and len(ret) == 3:
                    rv, l, n = ret
                    lines += l
                    count += n
        if not count:
            return 0
        return (2, lines, count)

# ----------------------------------------------------------------------------
class abyss_plugin_t(ida_idaapi.plugin_t):
    flags = 0
    comment = "Postprocess Hexrays Output"
    help = comment
    wanted_name = PLUGIN_NAME
    # pressing this hotkey will reload any filter scripts
    # without having to restart IDA (useful during development)
    wanted_hotkey = "Ctrl-Alt-R"

    def init(self):
        if hr.init_hexrays_plugin():
            load_filters()
            self.ui_hooks = ui_event_t()
            self.ui_hooks.hook()
            self.hr_hooks = hx_event_t()
            self.hr_hooks.hook()
            return ida_idaapi.PLUGIN_KEEP
        return ida_idaapi.PLUGIN_SKIP

    def run(self, arg):
        load_filters(reload=True)
        return

    def term(self):
        try:
            self.ui_hooks.unhook()
            self.hr_hooks.unhook()
        except:
            pass
        return

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


repository: tintinweb/ida-batch_decompile
file: ida_batch_decompile.py
wanted_name: Ida Batch Decompile
comment: Batch Decompile
created_at: 2016-09-29 22:57:36+00:00
pushed_at: 2020-05-14 12:36:23+00:00
description:
*Decompile All the Things* - IDA Batch Decompile plugin and script for Hex-Ray's IDA Pro that adds the ability to batch decompile multiple files and their imports with additional annotations (xref, stack var size) to the pseudocode .c file

url:
https://github.com/tintinweb/ida-batch_decompile/blob/6abb6cd02a6b030608ef7915aac70db600822345/ida_batch_decompile.py

content:
#! /usr/bin/env python
# -*- coding: UTF-8 -*-
# Author : <github.com/tintinweb>
"""
IdaBatchDecompile Plugin and Script adds annotation and batch decompilation functionality to IDA Pro

* requires hexrays decompiler plugin

Usage:

* as idascript in ida gui mode: IDA Pro -> File/Script file... -> IdaDecompileBatch ...
* as idascript in ida cmdline mode: ida(w|w64) -B -M -S"<path_to_this_script> \"--option1\" \"--option2\"", "<target>"
 * see --help for options
* as Plugin: follow ida documentation on how to add python plugins

"""
import sys
import json
import glob
import subprocess
import shutil
import os
import tempfile
from optparse import OptionParser

import idaapi
import idautils
from idc import *

if idaapi.IDA_SDK_VERSION >= 700:
    import ida_idaapi
    import ida_kernwin
    from idaapi import *
    from idc import *

import logging

logger = logging.getLogger(__name__)


class IdaLocation(object):
    """ Wrap idautils Function
    """

    def __init__(self, location):
        self.at = location
        # self.name = GetFunctionName(location)
        self.name = GetFuncOffset(location)
        self.start = 0
        self.end = 0
        self.func_offset = 0
        try:
            _func = idaapi.get_func(location)
            self.start = _func.startEA
            self.end = _func.endEA  # ==FindFuncEnd(location)
            self.func_offset = self.start - self.at
        except Exception, e:
            logger.exception(e)
        if not self.name:
            self.indirect = True
        else:
            self.indirect = False

    def __repr__(self, *args, **kwargs):
        return "<Function %r at 0x%x (0x%x::0x%x)>" % (self.name, self.at,
                                                       self.start, self.end)

    def get_xrefs(self):
        return (IdaLocation(x.frm) for x in idautils.XrefsTo(self.at))

    def get_coderefs(self):
        return (IdaLocation(frm) for frm in idautils.CodeRefsTo(self.at, 0))

    def as_dict(self):
        return {'at': self.at, 'name': self.name}

    def decompile(self):
        """ decompile function
        """
        try:
            return idaapi.decompile(self.at)
        except idaapi.DecompilationFailure, e:
            return repr(str(e))
        text = str(idaapi.decompile(self.at)).strip()
        '''
        sprintf:
        Python>for w in idaapi.decompile(0x00001578 ).lvars: print w.name
            s
            format
            result
        '''
        # decompile.arguments
        # for w in idaapi.decompile(0x00001EF0 ).lvars: print w.name
        if not grep:
            return text.split('\n')
        # return all lines 
        return [line.strip() for line in text.split('\n') if grep in line]

    def get_function_args(self):
        # find the stack frame
        stack = GetFrame(self.start)
        stack_size = GetStrucSize(stack)
        # figure out all of the variable names
        # base is either ' s' ... saved register or ' r' ... return address
        base = GetMemberOffset(stack, ' s')
        if base == -1:
            base = GetMemberOffset(stack, ' r')
        if base == -1:
            # no ' s' no ' r' assume zero
            base == 0
        stack_vars = []

        for memberoffset in xrange(stack_size):
            previous = stack_vars[-1] if len(stack_vars) else None
            var_name = GetMemberName(stack, memberoffset)
            if not var_name or (previous and var_name == previous.get("name")):
                # skip that entry, already processed
                continue

            offset = GetMemberOffset(stack, var_name) - base
            size = GetMemberSize(stack, memberoffset)
            if previous:
                diff = offset - previous['offset']
                previous['diff_size'] = diff
            stack_vars.append({'name': var_name,
                               'offset': offset,
                               'offset_text': '[bp%Xh]' % offset if offset < 0 else '[bp+%Xh]' % offset,
                               'size': size,
                               'diff_size': size})
        return stack_size, stack_vars


class IdaHelper(object):
    """ Namespace for ida helper functions
    """

    @staticmethod
    def get_functions():
        return (IdaLocation(f) for f in idautils.Functions())

    @staticmethod
    def get_imports():
        for i in xrange(0, idaapi.get_import_module_qty()):
            name = idaapi.get_import_module_name(i)
            if name:
                yield name

    @staticmethod
    def decompile_full(outfile):
        return idaapi.decompile_many(outfile, None, 0)

    @staticmethod
    def annotate_xrefs():
        stats = {'annotated_functions': 0, 'errors': 0}
        for f in IdaHelper.get_functions():
            try:
                function_comment = GetFunctionCmt(f.start, 0)
                if '**** XREFS ****' in function_comment:
                    logger.debug("[i] skipping function %r, already annotated." % f.name)
                    continue
                xrefs = [x.name for x in f.get_coderefs()]
                comment = []
                if function_comment:
                    comment.append(function_comment)
                comment.append("***** XREFS *****")
                comment.append("* # %d" % len(xrefs))
                comment.append(', '.join(xrefs))
                comment.append("*******************")
                SetFunctionCmt(f.start, '\n'.join(comment), 0)
                stats['annotated_functions'] += 1
            except Exception as e:
                print ("Annotate XRefs: %r"%e)
                stats['errors'] += 1
        print "[+] stats: %r" % stats
        print "[+] Done!"

    @staticmethod
    def annotate_functions_with_local_var_size():
        stats = {'annotated_functions': 0, 'errors': 0}
        for f in IdaHelper.get_functions():
            try:
                function_comment = GetFunctionCmt(f.start, 0)
                if '**** Variables ****' in function_comment:
                    logger.debug("[i] skipping function %r, already annotated." % f.name)
                    continue
                size, stack_vars = f.get_function_args()
                comment = []
                if function_comment:
                    comment.append(function_comment)
                comment.append("**** Variables ****")
                comment.append("* stack size: %s" % size)
                for s in stack_vars:
                    comment.append(json.dumps(s))
                comment.append("*******************")
                SetFunctionCmt(f.start, '\n'.join(comment), 0)
                stats['annotated_functions'] += 1
            except Exception, e:
                print ("Annotate Funcs: %r" % e)
                stats['errors'] += 1
        print "[+] stats: %r" % stats
        print "[+] Done!"


class IdaDecompileBatchController(object):
    def __init__(self):
        self.is_windows = sys.platform.startswith('win')
        self.is_ida64 = GetIdbPath().endswith(".i64")  # hackhackhack - check if we're ida64 or ida32
        logger.debug("[+] is_windows: %r" % self.is_windows)
        logger.debug("[+] is_ida64: %r" % self.is_ida64)
        self.my_path = os.path.abspath(__file__)
        self.temp_path = None
        self._init_target()
        # settings (form)
        # todo: load from configfile if available.
        self.output_path = None
        self.chk_annotate_stackvar_size = False
        self.chk_annotate_xrefs = False
        self.chk_decompile_imports = False
        self.chk_decompile_imports_recursive = False
        self.chk_decompile_alternative = False
        # self.ida_home = idaapi.idadir(".")
        self.ida_home = GetIdaDirectory()
        # wait for ida analysis to finish
        self.wait_for_analysis_to_finish()
        if not idaapi.init_hexrays_plugin():
            logger.warning("forcing hexrays to load...")
            self.load_plugin_decompiler()
        if not idaapi.init_hexrays_plugin():
            raise Exception("hexrays decompiler is not available :(")

    def _init_target(self):
        self.target_path = idc.GetInputFilePath()
        self.target_file = idc.GetInputFile()
        self.target_dir = os.path.split(self.target_path)[0]
        logger.debug("reinitializing target: %r" % self.target_file)

    def init_tempdir(self):
        self.temp_path = self.temp_path or tempfile.mkdtemp(prefix="idbc_")
        logger.debug("[i] using tempdir: %r" % self.temp_path)

    def remove_tempdir(self):
        if not self.temp_path:
            return
        logger.debug("[i] removing tempdir: %r" % self.temp_path)
        shutil.rmtree(self.temp_path)
        self.temp_path = None

    def wait_for_analysis_to_finish(self):
        logger.debug("[+] waiting for analysis to finish...")
        idaapi.autoWait()
        idc.Wait()
        logger.debug("[+] analysis finished.")

    def load_plugin_decompiler(self):
        # load decompiler plugins (32 and 64 bits, just let it fail)
        logger.debug("[+] trying to load decompiler plugins")
        if self.is_ida64:
            # 64bit plugins
            idc.RunPlugin("hexx64", 0)
        else:
            # 32bit plugins
            idc.RunPlugin("hexrays", 0)
            idc.RunPlugin("hexarm", 0)
        logger.debug("[+] decompiler plugins loaded.")

    def run(self):
        files_decompiled = []
        self._init_target()

        if self.chk_decompile_imports:
            self.init_tempdir()
            if self.chk_decompile_imports_recursive:
                pass
            for image_type, image_name, image_path in self.enumerate_import_images():
                try:
                    self.exec_ida_batch_decompile(target = image_path, output = self.output_path,
                                                  annotate_stackvar_size = self.chk_annotate_stackvar_size,
                                                  annotate_xrefs = self.chk_annotate_xrefs,
                                                  imports = self.chk_decompile_imports,
                                                  recursive = self.chk_decompile_imports_recursive,
                                                  experimental_decomile_cgraph = self.chk_decompile_alternative)
                    files_decompiled.append(image_path)
                except subprocess.CalledProcessError, cpe:
                    logger.warning("[!] failed to decompile %r - %r" % (image_path, cpe))

            self.remove_tempdir()

        if self.chk_annotate_stackvar_size:
            self.annotate_stack_variable_size()
        if self.chk_annotate_xrefs:
            self.annotate_xrefs()

        if self.chk_decompile_alternative:
            raise NotImplemented("Not yet implemented")
            pass
        else:
            self.decompile_all(self.output_path)
            files_decompiled.append(self.target_file)

        logger.info("[+] finished decompiling: %r" % files_decompiled)
        logger.info("    output dir: %s"%self.output_path if self.output_path else self.target_dir)
        return files_decompiled

    def annotate_stack_variable_size(self):
        logger.debug("[+] annotating function stack variables")
        IdaHelper.annotate_functions_with_local_var_size()
        logger.debug("[+] done.")

    def annotate_xrefs(self):
        logger.debug("[+] annotating function xrefs")
        IdaHelper.annotate_xrefs()
        logger.debug("[+] done.")

    def file_is_decompilable(self, path):
        with open(path, 'rb') as ftest:
            magic = ftest.read(4)
            if magic == 'MZ\x90\x00':
                return 'pe/dos'
            elif magic == "\x7fELF":
                return 'elf'
        return None

    def enumerate_import_images(self):
        for import_name in IdaHelper.get_imports():
            logger.debug("[i] trying to find image for %r" % import_name)
            for image_path in glob.glob(os.path.join(self.target_dir, import_name) + '*'):
                image_type = self.file_is_decompilable(image_path)
                if image_type:
                    logger.debug("[i] got image %r as %r" % (image_path, image_type))
                    yield image_type, os.path.split(image_path)[1], image_path
                    # I do not think there's any need to check other files with the same name ?!
                    break

    def enumerate_files(self, recursive=False):
        for root, dirs, files in os.walk(self.target_dir):
            for name in files:
                fpath = os.path.join(root, name)
                logger.debug("[+] checking %r" % fpath)
                try:
                    ftype = self.file_is_decompilable(fpath)
                    if ftype:
                        logger.debug("[+] is candidate %r" % [fpath, ftype])
                        yield ftype, name, fpath
                except IOError: pass

    def decompile_all(self, outfile=None):
        outfile = self._get_suggested_output_filename(outfile or self.target_path)
        logger.warning(outfile)
        logger.debug("[+] trying to decompile %r as %r" % (self.target_file,
                                                           os.path.split(outfile)[1]))
        IdaHelper.decompile_full(outfile)
        logger.debug("[+] finished decompiling %r as %r" % (self.target_file,
                                                            os.path.split(outfile)[1]))

    def _get_suggested_output_filename(self, target):
        # /a/b/c/d/e/bin.ext
        # target is a directory
        if os.path.isdir(target):
            fname, fext = os.path.splitext(self.target_file)
            return '%s.c' % os.path.join(target, fname)
        # target is not a directory
        root, fname = os.path.split(target)
        if fname:
            fname, fext = os.path.splitext(fname)  # bin,ext
        else:
            fname, fext = os.path.splitext(self.target_file)

        # obsolete
        # suggested_outpath = '%s.c'%os.path.join(root,fname)
        # if not os.path.exists(suggested_outpath):
        #    return suggested_outpath
        return '%s.c' % os.path.join(root, fname)

    def exec_ida_batch_decompile(self, target, output, annotate_stackvar_size, annotate_xrefs, imports, recursive,
                                 experimental_decomile_cgraph):
        logger.debug("[+] batch decompile %r" % target)
        # todo: pass commandlines,
        # todo parse commandline
        script_args = ['--output=%s' % output]
        if annotate_stackvar_size:
            script_args.append("--annotate-stackvar-size")
        if annotate_xrefs:
            script_args.append("--annotate-xrefs")
        if imports:
            script_args.append("--imports")
        if recursive:
            script_args.append("--recursive")
        if experimental_decomile_cgraph:
            script_args.append("--experimental-decompile-cgraph")

        script_args = ['\\"%s\\"' % a for a in script_args]
        command = "%s %s" % (self.my_path, ' '.join(script_args))
        self._exec_ida_batch(target, command)

    def _exec_ida_batch(self, target, command):
        # build exe path
        if self.is_windows:
            ida_exe = os.path.join(self.ida_home, 'idaw64.exe' if self.is_ida64 else 'idaw.exe')
        else:
            ida_exe = os.path.join(self.ida_home, 'idal64' if self.is_ida64 else 'idal')
        '''
        https://www.hex-rays.com/products/ida/support/idadoc/417.shtml
        -B  ..  Batch mode
        -M  ..  disable mouse
        -c  ..  create new database
        -o  ..  database output path
        -S  ..  execute script
        '''
        #temp_path = os.path.join(self.temp_path, os.path.splitext(os.path.split(target)[1])[0] + '.idb')
        cmd = [ida_exe, '-B', '-M', '-c', '-o"%s"'%self.temp_path if self.temp_path else '', '-S"%s"' % command, '"' + target + '"']
        logger.debug(' '.join(cmd))
        logger.debug('[+] executing: %r' % cmd)
        #return 0
        # TODO: INSECURE!
        return subprocess.check_call(' '.join(cmd), shell=True)


class TestEmbeddedChooserClass(Choose,Choose2):
    """
    A simple chooser to be used as an embedded chooser
    """
    def __init__(self, title, nb = 5, flags=0):
        Choose.__init__(self,
                         title,
                         [["Type", 10], ["Name", 10], ["Path", 30]],
                         flags=flags)
        Choose2.__init__(self,
                         title,
                         [ ["Type", 10], ["Name", 10],  ["Path", 30] ],
                         embedded=True, width=50, height=10, flags=flags)
        self.n = 0
        self.items = []
        self.icon = 5
        self.selcount = 0

        self.selected = []

    def OnClose(self):
        pass

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

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

    def OnRefresh(self, n):
        print "refresh %s"%n

    def OnSelectionChange(self, sel_list):
        self.selected = sel_list

    def getSelected(self):
        for idx in self.selected:
            yield self.items[idx-1]

    def addItem(self, e):
        if e not in self.items:
            self.items.append(e)


class DecompileBatchForm(Form):
    """
    Form to prompt for target file, backup file, and the address
    range to save patched bytes.
    """

    def __init__(self, idbctrl, enumerate_imports=True, enumerate_other=False):
        self.idbctrl = idbctrl
        self.EChooser = TestEmbeddedChooserClass("Batch Decompile", flags=Choose2.CH_MULTI)
        self.propagateItems(enumerate_imports=enumerate_imports, enumerate_other=enumerate_other)
        Form.__init__(self,
                      r"""Ida Batch Decompile ...
{FormChangeCb}
<##Target    :{target}>
<##OutputPath:{outputPath}>
<##Annotate StackVar Size:{chkAnnotateStackVars}>
<##Annotate Func XRefs   :{chkAnnotateXrefs}>
<##Process Imports       :{chkDecompileImports}>
<##Cgraph (experimental) :{chkDecompileAlternative}>{cGroup1}>


<##Scan Target Directory:{btnLoad}> <##Recursive:{chkDecompileImportsRecursive}>{cGroup2}>
<##Decompile!:{btnProcessFiles}>
<Please select items to decompile:{cEChooser}>


""", {
                          'target': Form.FileInput(swidth=50, open=True, value=idbctrl.target_path),
                          'outputPath': Form.DirInput(swidth=50, value=idbctrl.output_path),
                          'cGroup1': Form.ChkGroupControl(("chkAnnotateStackVars", "chkAnnotateXrefs",
                                                           "chkDecompileImports",
                                                           "chkDecompileAlternative")),
                          'cGroup2': Form.ChkGroupControl(("chkDecompileImportsRecursive", )),
                          'FormChangeCb': Form.FormChangeCb(self.OnFormChange),
                          'btnLoad':  Form.ButtonInput(self.OnButtonLoad),
                          'btnProcessFiles': Form.ButtonInput(self.OnButtonProcess),
                          'cEChooser': Form.EmbeddedChooserControl(self.EChooser),
                      })
        self.Compile()

    def propagateItems(self, enumerate_imports=False, enumerate_other=False):
        self.EChooser.addItem([self.idbctrl.file_is_decompilable(self.idbctrl.target_path),
                               os.path.split(self.idbctrl.target_path)[1],
                               self.idbctrl.target_path])

        if enumerate_imports:
            for candidate in self.idbctrl.enumerate_import_images():
                self.EChooser.addItem(list(candidate))
        if enumerate_other:
            for candidate in self.idbctrl.enumerate_files(recursive=self.chkDecompileImportsRecursive.checked):
                self.EChooser.addItem(list(candidate))

    def OnButtonProcess(self, code=0):
        ### process selected files
        if not len(list(self.EChooser.getSelected())):
            logger.warning("[!] Aborting. Please select at least one item from the list!")
            return

        self.idbctrl.target = self.target.value
        outputPath = self.GetControlValue(self.outputPath)
        if outputPath == '' or os.path.exists(outputPath):
            self.idbctrl.output_path = outputPath
        else:
            logger.warning("[!!] output path not valid! %r" % outputPath)
            self.idbctrl.output_path = None

        self.idbctrl.chk_annotate_stackvar_size = self.chkAnnotateStackVars.checked
        self.idbctrl.chk_decompile_imports = self.chkDecompileImports.checked
        self.idbctrl.chk_decompile_imports_recursive = self.chkDecompileImportsRecursive.checked
        self.idbctrl.chk_annotate_xrefs = self.chkAnnotateXrefs.checked
        self.idbctrl.chk_decompile_alternative = self.chkDecompileAlternative.checked
        logger.debug("[+] config updated")

        files_decompiled = []
        decompile_main_binary = False

        self.idbctrl.init_tempdir()
        for _type, name, image_path in self.EChooser.getSelected():
            if image_path is self.idbctrl.target_path:
                decompile_main_binary = True
                continue
            try:
                self.idbctrl.exec_ida_batch_decompile(target=image_path, output=outputPath,
                                              annotate_stackvar_size=self.idbctrl.chk_annotate_stackvar_size,
                                              annotate_xrefs=self.idbctrl.chk_annotate_xrefs,
                                              imports=self.idbctrl.chk_decompile_imports,
                                              recursive=self.idbctrl.chk_decompile_imports_recursive,
                                              experimental_decomile_cgraph=self.idbctrl.chk_decompile_alternative)
                files_decompiled.append(image_path)
            except subprocess.CalledProcessError, cpe:
                logger.warning("[!] failed to decompile %r - %r" % (image_path, cpe))

        self.idbctrl.remove_tempdir()
        ## process current file
        if decompile_main_binary:
            # well, loop here even though we know it can only
            logger.debug("[+] decompiling current file...")
            files_decompiled += self.idbctrl.run()  # decompile main binary
            logger.info("[+] finished decompiling: %r" % files_decompiled)
            logger.info("    output dir: %s" % self.idbctrl.output_path if self.idbctrl.output_path else self.idbctrl.target_dir)

    def OnButtonLoad(self, code=0):
        self.Close(0)
        self.propagateItems(enumerate_other=True, enumerate_imports=True)
        self.Execute()

    def OnFormChange(self, fid):
        # Set initial state
        INIT = -1
        BTN_OK = -2

        if fid == INIT:
            self.EnableField(self.target, False)
            self.EnableField(self.outputPath, True)
            self.EnableField(self.chkDecompileAlternative, False)

        elif fid == BTN_OK:
            # just return
            return True

        # Toggle backup checkbox
        elif fid == self.chkAnnotateStackVars.id:
            self.chkAnnotateStackVars.checked = not self.chkAnnotateStackVars.checked
        elif fid == self.chkDecompileImports.id:
            self.chkDecompileImports.checked = not self.chkDecompileImports.checked
        elif fid == self.chkDecompileImportsRecursive.id:
            self.chkDecompileImportsRecursive.checked = not self.chkDecompileImportsRecursive.checked
        elif fid == self.chkDecompileAlternative.id:
            self.chkDecompileAlternative.checked = not self.chkDecompileAlternative.checked
        elif fid == self.chkAnnotateXrefs.id:
            self.chkAnnotateXrefs.checked = not self.chkAnnotateXrefs.checked

        return False


if idaapi.IDA_SDK_VERSION >= 700:
    class IdaDecompileUiActionHandler(idaapi.action_handler_t):

        def __init__(self, caller):
            idaapi.action_handler_t.__init__(self)
            self.caller = caller

        def activate(self, ctx):
            self.caller.menu_config()
            return 1

        def update(self, ctx):
            return idaapi.AST_ENABLE_ALWAYS

            def update(self, ctx):
                return idaapi.AST_ENABLE_ALWAYS


class IdaDecompileBatchPlugin(idaapi.plugin_t):
    """ IDA Plugin Base"""
    flags = idaapi.PLUGIN_FIX
    comment = "Batch Decompile"
    help = "github.com/tintinweb"
    wanted_name = "Ida Batch Decompile"
    wanted_hotkey = ""
    wanted_menu = "File/Produce file/", "{} ...".format(wanted_name)
    wanted_menu_id = 'tintinweb:batchdecompile'

    def init(self):
        NO_HOTKEY = ""
        SETMENU_INS = 0
        NO_ARGS = tuple()

        logger.debug("[+] %s.init()" % self.__class__.__name__)
        self.menuitems = []

        logger.debug("[+] setting up menus for ida version %s" % idaapi.IDA_SDK_VERSION)

        if idaapi.IDA_SDK_VERSION >= 700:
            # >= 700
            action_desc = idaapi.action_desc_t("tintinweb:batchdecompile:load", self.wanted_name, IdaDecompileUiActionHandler(self))
            idaapi.register_action(action_desc)
            idaapi.attach_action_to_menu(''.join(self.wanted_menu), "tintinweb:batchdecompile:load", idaapi.SETMENU_APP)

        else:
            menu = idaapi.add_menu_item(self.wanted_menu[0],
                                        self.wanted_menu[1],
                                        NO_HOTKEY,
                                        SETMENU_INS,
                                        self.menu_config,
                                        NO_ARGS)

            self.menuitems.append(menu)

        return idaapi.PLUGIN_KEEP

    def run(self, arg=None):
        logger.debug("[+] %s.run()" % self.__class__.__name__)

    def term(self):
        logger.debug("[+] %s.term()" % self.__class__.__name__)
        if idaapi.IDA_SDK_VERSION < 700:
            for menu in self.menuitems:
                idaapi.del_menu_item(menu)

    def menu_config(self):
        logger.debug("[+] %s.menu_config()" % self.__class__.__name__)
        self.idbctrl._init_target() # force target reinit
        DecompileBatchForm(self.idbctrl).Execute()

    def set_ctrl(self, idbctrl):
        logger.debug("[+] %s.set_ctrl(%r)" % (self.__class__.__name__, idbctrl))
        self.idbctrl = idbctrl


def PLUGIN_ENTRY(mode=None):
    """ check execution mode:

        a) as Plugin, return plugin object
        b) as script as part of a batch execution, do not spawn plugin object
     """
    logging.basicConfig(level=logging.DEBUG,
                        format="[%(name)s/%(process)s][%(levelname)-10s] [%(module)s.%(funcName)-14s] %(message)s")
    logger.setLevel(logging.DEBUG)
    # always wait for analysis to finish
    logger.debug("[+] initializing IdaDecompileBatchPlugin")
    # create our controller interface
    idbctrl = IdaDecompileBatchController()
    # parse cmdline
    if mode == '__main__':
        # cmdline mode
        if len(idc.ARGV) > 1:
            # cmdline batch mode
            logger.debug("[+] Mode: commandline")
            parser = OptionParser()
            parser.add_option("-o", "--output", dest="output",
                              help="output path")
            parser.add_option("-S", "--annotate-stackvar-size",
                              action="store_true", default=False,
                              help="Generate stack variable size annotations")
            parser.add_option("-X", "--annotate-xrefs",
                              action="store_true", default=False,
                              help="Generate xref annotations")
            parser.add_option("-I", "--imports",
                              action="store_true", default=False,
                              help="try to decompile files referenced in IAT")
            parser.add_option("-R", "--recursive",
                              action="store_true", default=False,
                              help="Recursive decompile files/imports")
            parser.add_option("-Z", "--experimental-decompile-cgraph",
                              action="store_true", default=False,
                              help="[experimental] decompile funcs referenced in calltree manually")

            options, args = parser.parse_args(idc.ARGV[1:])
            # set options
            idbctrl.output_path = options.output
            idbctrl.chk_annotate_stackvar_size = options.annotate_stackvar_size
            idbctrl.chk_annotate_xrefs = options.annotate_xrefs
            idbctrl.chk_decompile_imports = options.imports
            idbctrl.chk_decompile_imports_recursive = options.recursive
            idbctrl.chk_decompile_alternative = options.experimental_decompile_cgraph
            # set all the idbctrl checkboxes and files
            idbctrl.run()
            idc.Exit(0)
            # return

        logger.debug("[+] Mode: commandline w/o args")
        # PluginMode
        plugin = IdaDecompileBatchPlugin()
        plugin.set_ctrl(idbctrl=idbctrl)
        plugin.init()
        logger.info("[i] %s loaded, see Menu: %s" % (IdaDecompileBatchPlugin.wanted_name,
                                                     IdaDecompileBatchPlugin.wanted_menu))
        #plugin.menu_config()
        return plugin

    else:
        logger.debug("[+] Mode: plugin")
        # PluginMode
        plugin = IdaDecompileBatchPlugin()
        plugin.set_ctrl(idbctrl=idbctrl)
        return plugin


if __name__ == '__main__':
    PLUGIN_ENTRY(mode=__name__)


repository: thalium/symless
file: plugin/symless_plugin.py
wanted_name: Symless
comment: Symless interactive plugin
created_at: 2022-05-05 07:37:43+00:00
pushed_at: 2023-09-25 21:03:21+00:00
url:
https://github.com/thalium/symless/blob/b5356f02df7053c9c7f67026a631a6abd3ac1f34/plugin/symless_plugin.py

content:
import base64
import collections
import importlib
import inspect
import os
import pkgutil
import sys
from typing import Collection

import idaapi

import symless
import symless.plugins as plugins
import symless.utils.utils as utils


class fixedBtn(idaapi.Form.ButtonInput):
    def __init__(self, plugin: "SymlessPlugin"):
        super().__init__(self.reload, "0")
        self.plugin = plugin

    def reload(self, code):
        idaapi.show_wait_box("Reloading Symless..")

        try:
            # terminate all extensions
            self.plugin.term()

            # reload symless code
            reload_plugin()

            # rebind all extensions
            self.plugin.find_extensions()

        except Exception as e:
            import traceback

            utils.g_logger.critical(repr(e) + "\n" + traceback.format_exc())
        finally:
            idaapi.hide_wait_box()

    def get_tag(self):
        return "<Reload:%s%d:%s%s:%s:%s>" % (
            self.tp,
            self.id,
            "+" if self.is_relative_offset else "",
            self.width,
            self.swidth,
            ":" if self.hlp is None else self.hlp,
        )


class SymlessInfoForm(idaapi.Form):
    def __init__(self, plugin: "SymlessPlugin"):
        icon_path = os.path.join(os.path.abspath(symless.__path__[0]), "resources", "bigger_champi.png")
        with open(icon_path, "rb") as file:
            icon_b64 = base64.b64encode(file.read()).decode()

        img_html = "<img src='data:image/png;base64,%s'>" % icon_b64
        info_html = """
            <div style='text-align: center; margin: 6px; font-size: 16px;'>
                <pre style='font-size: 72px;'>Symless</pre>
                <pre style='font-size: 16px; text-align: left;'>%s<br><br>Version: <b>%.1f</b></pre>
            </div>
        """ % (
            symless.PLUGIN_DESC,
            symless.PLUGIN_VERSION,
        )

        super().__init__(
            "BUTTON YES NONE\nBUTTON CANCEL NONE\nSymless plugin\n{img}<|>{info}\n{reload}",
            {
                "img": idaapi.Form.StringLabel(img_html, tp=idaapi.Form.FT_HTML_LABEL, size=None),
                "info": idaapi.Form.StringLabel(info_html, tp=idaapi.Form.FT_HTML_LABEL, size=None),
                "reload": fixedBtn(plugin),
            },
        )


# Symless plugin
class SymlessPlugin(idaapi.plugin_t):
    flags = idaapi.PLUGIN_MOD | idaapi.PLUGIN_PROC  # | idaapi.PLUGIN_HIDE
    comment = "Symless interactive plugin"
    wanted_name = "Symless"
    help = ""  # not used, IDA < 7.6 compatibility
    wanted_hotkey = ""  # not used, IDA < 7.6 compatibility

    # find & initialize all extensions
    def init(self) -> idaapi.plugmod_t:
        self.ext: Collection[plugins.plugin_t] = collections.deque()
        self.find_extensions()
        return idaapi.PLUGIN_KEEP

    # find and load extensions from symless plugins folder
    def find_extensions(self):
        for mod_info in pkgutil.walk_packages(plugins.__path__, prefix="symless.plugins."):
            if mod_info.ispkg:
                continue

            spec = mod_info.module_finder.find_spec(mod_info.name)
            module = importlib.util.module_from_spec(spec)

            # module is already loaded
            if module.__name__ in sys.modules:
                module = sys.modules[module.__name__]

            # load the module
            else:
                sys.modules[module.__name__] = module
                try:
                    spec.loader.exec_module(module)
                except BaseException as e:
                    sys.modules.pop(module.__name__)
                    print(f"Error while loading extension {mod_info.name}: {e}")
                    continue

            # module defines an extension
            if not hasattr(module, "get_plugin"):
                continue

            ext: plugins.plugin_t = module.get_plugin()
            self.ext.append(ext)

    # debug - reload plugin action
    def run(self, args):
        info = SymlessInfoForm(self)
        info.Compile()
        return info.Execute()

    # term all extensions
    def term(self):
        while len(self.ext) > 0:
            ext = self.ext.pop()
            ext.term()


def PLUGIN_ENTRY() -> idaapi.plugin_t:
    return SymlessPlugin()


# reload one module, by first reloading all imports from that module
# to_reload contains all modules to reload
def reload_module(module, to_reload: set):
    if module not in to_reload:
        return

    # remove from set first, avoid infinite recursion if recursive imports
    to_reload.remove(module)

    # reload all imports first
    for _, dep in inspect.getmembers(module, lambda k: inspect.ismodule(k)):
        reload_module(dep, to_reload)

    # reload the module
    utils.g_logger.info(f"Reloading {module.__name__} ..")
    importlib.reload(module)


# reload all symless code
def reload_plugin():
    # list all modules to reload, unordered
    to_reload = set()
    for k, mod in sys.modules.items():
        if k.startswith("symless"):
            to_reload.add(mod)

    for mod in list(to_reload):  # copy to alter
        reload_module(mod, to_reload)


repository: danigargu/IDAtropy
file: IDAtropy.py
url: https://github.com/danigargu/IDAtropy/blob/1517e081a2dbafc6b7dee5a63d4e69bf16a78474/IDAtropy.py
comment: IDAtropy
created_at: 2015-11-25 18:49:58+00:00
pushed_at: 2021-04-16 12:02:41+00:00
description:
IDAtropy is a plugin for Hex-Ray's IDA Pro designed to generate charts of entropy and histograms using the power of idapython and matplotlib.

content:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
#  IDAtropy
#  last update: 2021/04/12
#
#  Daniel Garcia <danigargu [at] gmail.com>
#  @danigargu
# 
#  This program is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software  Foundation, either  version 3 of  the License, or
#  (at your option) any later version.
#

import sys
import math
import zlib
import string
import random
import traceback

import idc
import idautils
import idaapi

from idaapi import Choose

from PyQt5 import QtGui, QtCore, QtWidgets
from PyQt5.QtWidgets import QTextEdit, QTableWidget, QTreeWidget, QCheckBox

from collections import Counter, OrderedDict

try:
  import sip
  import matplotlib
  import matplotlib.pyplot as plt
  import matplotlib.ticker as ticker
  from matplotlib.colors import hsv_to_rgb

  from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
  from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
  from matplotlib.backend_bases import key_press_handler  
except ImportError:
  ERROR_MATPLOTLIB = True


PLUG_NAME = "IDAtropy"

def log(msg):
  idaapi.msg("[%s] %s\n" % (PLUG_NAME, msg))

def histogram(data):
  table = [0]*256
  for i in data:
    table[i] += 1
  return table

def gen_rand_colors(n_colors=20):
  """ https://martin.ankerl.com/2009/12/09/how-to-create-random-colors-programmatically/ """
  colors = []
  golden_ratio_conjugate = 0.618033988749895
  h = random.random()

  for i in range(n_colors):
    h += golden_ratio_conjugate
    h %= 1
    values = hsv_to_rgb([h, 0.3, 0.95])
    res = "#" + ''.join(["%02X" % int(val*256) for val in values])
    colors.append(res)

  return colors

def entropy_scan(data, block_size=256, step_size=1) :
  for block in (data[x:block_size+x] for x in range (0, len(data)-block_size, step_size)):
    yield entropy(block)

def entropy(data):
    """Calculate the entropy of a chunk of data."""

    if len(data) == 0:
        return 0.0

    occurences = Counter(bytearray(data))
    entropy = 0
    for x in occurences.values():
        p_x = float(x) / len(data)
        entropy -= p_x*math.log(p_x, 2)

    return entropy

def calc_compression_ratio(data):
  comp = zlib.compress(data)
  result = (float(len(comp))/len(data))*100
  if result > 100:
    result = 100
  return result

def get_loaded_bytes(start_addr, size, fill_with="\x00"):
  data = ""
  cur_ea = start_addr
  while cur_ea < (start_addr+size):
    if idaapi.is_loaded(cur_ea):
      data += chr(idaapi.get_byte(cur_ea))
    else:
      data += fill_with
    cur_ea += 1
  return bytes(data, 'latin1')

def get_disk_binary():
  data = None
  with open(idc.get_input_file_path(), 'rb') as f:
    data = f.read()
  return data

def load_file_in_segment(filename, seg_name):
  last_seg = idaapi.get_last_seg()
  seg_start = last_seg.end_ea
  data = None

  with open(filename, 'rb') as f:
    data = f.read()

  seg_len = len(data)
  if seg_len % 0x1000 != 0:
    seg_len = seg_len + (0x1000 - (seg_len % 0x1000))

  if idaapi.add_segm(0, seg_start, seg_start+seg_len, seg_name, "DATA"):
    idaapi.put_bytes(seg_start, data)
    return True
  return False

class Config:
  chart_type   = 0
  start_addr   = 0
  end_addr     = 0
  use_disk_binary  = False
  use_debug_memory = False

  chart_types = ('Entropy','Histogram')

  xrefs = {
    "min_entropy": 6.8,
    "block_size": 256
  }

  entropy = {
    "all_segments": False,
    "block_size": 256,
    "step_size": 256,
    "segm_exists": False,
    "segm_addr": None,
    "segm_name": "IDAtropy"
  }

class ChartTypes:
  ENTROPY = 0
  HISTOGRAM = 1

class XrefsEntropy(Choose):
  def __init__(self, title, config):
    super().__init__(
      title, [
        ["Address", 16 | Choose.CHCOL_HEX],
        ["Entropy", 10 | Choose.CHCOL_DEC],
        ["Xrefs", 4 | Choose.CHCOL_HEX],
        ["Is code", 4 | Choose.CHCOL_DEC],
        ["Xref Type", 15 | Choose.CHCOL_PLAIN]
      ]
    )

    self.title  = title
    self.items  = []
    self.icon   = 55 # xref icon
    self.config = config 
    self.PopulateItems()

  def OnClose(self):
    return True

  def OnSelectLine(self, n):
    item = self.items[int(n)]
    idaapi.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):
    pass

  def OnRefresh(self, n):
    return n

  def OnCommand(self, n, cmd_id):
    if cmd_id == self.cmd_exclude_code_xrefs:
      self.exclude_code_xrefs()  
    return n

  def exclude_code_xrefs(self):
    if not len(self.items):
      return False 

    self.items = [i for i in self.items if i[3] != '1']
    idaapi.refresh_choosers()

    return True

  def show(self):
    n_items = len(self.items)
    if n_items > 0:
      b = self.Show()
      if b == 0:
        self.cmd_exclude_code_xrefs = self.AddCommand("Exclude code xrefs")
        return True
    else:
      idaapi.warning("No xrefs found")
    return False

  def PopulateItems(self):
    min_entropy = self.config['min_entropy']
    cur_ea = self.config['start_addr']

    idaapi.show_wait_box("Searching xrefs...")

    while cur_ea < self.config['end_addr']:
      xrefs = list(idautils.XrefsTo(cur_ea))
      if len(xrefs) > 0 and xrefs[0].type != idaapi.fl_F: # discard ordinary flow
        data = idaapi.get_bytes(cur_ea, self.config['block_size'])
        assert len(data) == self.config['block_size']

        ent = entropy(data)
        if ent >= min_entropy:
          self.items.append([
            "%08X" % cur_ea,
            "%.04f" % ent,
            "%d" % len(xrefs),
            "%d" % xrefs[0].iscode,
            "%s" % idautils.XrefTypeName(xrefs[0].type)
          ])
      cur_ea += 1

    idaapi.hide_wait_box()


class Options(QtWidgets.QWidget):
  def __init__(self, parent):
    super().__init__()
    self.parent = parent
    self.config = parent.config
    self.name = "Options"
    self.check_if_segm_exists()
    self.create_gui()

  def check_if_segm_exists(self):
    segm = idaapi.get_segm_by_name(self.config.entropy['segm_name'])
    if segm:
      self.config.entropy['segm_exists'] = True
      self.config.entropy['segm_addr']   = segm.start_ea
    else:
      self.config.entropy['segm_exists'] = False
      self.config.entropy['segm_addr']   = None

  def button_chart_on_click(self):
    try:      
      idaapi.show_wait_box("Making chart...")
      tab_title = self.get_tab_title()

      if self.config.chart_type == ChartTypes.ENTROPY:
        if self.config.use_disk_binary and not self.config.entropy['segm_exists']:

          msg1 = "Do you want to create new segment with the binary content?\n"
          msg2 = "This will allow you to navigate over the file by double-clicking on the chart"
          if idaapi.ask_yn(1, "HIDECANCEL\n" + msg1 + msg2) == 1:
            self.create_segment_with_binary()

        self.parent.tabs.addTab(Entropy(self), tab_title)

      elif self.config.chart_type == ChartTypes.HISTOGRAM:
        self.parent.tabs.addTab(Histogram(self), tab_title)
      
    except Exception as e:
      idaapi.warning("%s" % traceback.format_exc())

    idaapi.hide_wait_box()

  def button_xrefs_on_click(self):
    log("Start address : 0x%08x" % self.config.start_addr)
    log("End address : 0x%08x" % self.config.end_addr)

    xrefs_config = {
      "start_addr": self.config.start_addr,
      "end_addr": self.config.end_addr,
      "block_size": self.config.xrefs['block_size'],
      "min_entropy": float(self.t_min_entropy.text()),
    }    
    choose = XrefsEntropy("%s - XrefsTo" % PLUG_NAME, xrefs_config)
    choose.show()

  def create_segment_with_binary(self):
    segm_name = self.config.entropy['segm_name']
    if load_file_in_segment(idc.get_input_file_path(), segm_name):
      self.config.segm_exists = True
      self.check_if_segm_exists()

  def get_chart_type_str(self):
    return self.config.chart_types[self.config.chart_type]

  def get_tab_title(self):
    title = self.get_chart_type_str() + " - "
    if self.config.use_disk_binary:
      title += "Disk binary"
    elif self.config.entropy['all_segments']:
      title += "All segments"
    else:
      segname = idc.get_segm_name(self.config.start_addr)
      if segname:
        title += "%s " % segname
      title += "[0x%08x - 0x%08x]" % (self.config.start_addr, self.config.end_addr)
    return title

  def get_data(self):
    data = None
    if self.config.use_disk_binary:
      data = get_disk_binary()
    else:
      data_size = self.config.end_addr - self.config.start_addr
      data = idaapi.get_bytes(self.config.start_addr, data_size)
    return data

  def update_progress_bars(self):
    try:
      data = self.get_data()
      ent = entropy(data)
      norm_ent = ent/8*100
      comp_ratio = calc_compression_ratio(data)

      self.pb_entropy.setValue(norm_ent)
      self.pb_entropy.setFormat("%0.2f" % ent)
      self.pb_comp_ratio.setValue(comp_ratio)

    except FileNotFoundError:
      idaapi.warning("Input file doesn't exist: %s" % idc.get_input_file_path())
    except Exception as e:
      idaapi.warning("%s" % traceback.format_exc())


  def update_address(self):
    sender = self.sender()
    value = sender.text()

    if len(value) == 0 or value == "0x":
      return

    try:
      value = int(value, 16)
      if sender is self.t_start_addr:
        self.config.start_addr = value
      elif sender is self.t_end_addr:
        self.config.end_addr = value

      """ update progress bars """

      """
      if self.config.start_addr and self.config.end_addr \
        and self.config.start_addr < self.config.end_addr:

        self.update_progress_bars()
      """

    except ValueError as e:
      idaapi.warning("Invalid value for address")

  def update_entropy_config(self):
    try:
      block_size = int(self.t_block_size.text())
      step_size = int(self.t_step_size.text())

      self.slider_block_s.setValue(block_size)
      self.slider_step_s.setValue(step_size)
      self.slider_step_s.setMaximum(block_size)

      self.config.entropy['block_size'] = block_size
      self.config.entropy['step_size'] = step_size
    except ValueError:
      log("Invalid value")
    
  def create_gui(self):
    lbl_start_address = QtWidgets.QLabel("Start address")
    self.t_start_addr = QtWidgets.QLineEdit()
    self.t_start_addr.setFixedWidth(200)

    lbl_end_address = QtWidgets.QLabel("End address")
    self.t_end_addr = QtWidgets.QLineEdit()
    self.t_end_addr.setFixedWidth(200)
    
    lbl_segment = QtWidgets.QLabel("Segment")
    self.cb_segment = QtWidgets.QComboBox()
    self.cb_segment.setFixedWidth(200)

    lbl_disk_binary = QtWidgets.QLabel("Use disk binary")
    self.cb_disk_bin = QtWidgets.QCheckBox()

    lbl_chart_type = QtWidgets.QLabel("Chart type")
    chart_types_group = self.create_chart_type_group()

    """ chunk size """
    ent_config = self.config.entropy

    lbl_block_size = QtWidgets.QLabel("Block size")
    lbl_block_size.setFixedWidth(60)
    self.t_block_size = QtWidgets.QLineEdit()
    self.t_block_size.setFixedWidth(40)
    self.t_block_size.setText("%d" % ent_config['block_size'])
    self.t_block_size.setToolTip("Size of the data blocks to calculate entropy")

    self.slider_block_s = QtWidgets.QSlider(QtCore.Qt.Horizontal)
    self.slider_block_s.setFixedWidth(70)
    self.slider_block_s.setMinimum(256)
    self.slider_block_s.setMaximum(4096)
    self.slider_block_s.setValue(ent_config['block_size'])
    self.slider_block_s.setSingleStep(8)

    hbox_block_s = QtWidgets.QHBoxLayout()
    hbox_block_s.addWidget(lbl_block_size)
    hbox_block_s.addWidget(self.t_block_size)
    hbox_block_s.addWidget(self.slider_block_s)
    
    """ step size """
    lbl_step_size = QtWidgets.QLabel("Step size")
    lbl_step_size.setFixedWidth(60)
    self.t_step_size = QtWidgets.QLineEdit()
    self.t_step_size.setFixedWidth(40)
    self.t_step_size.setText("%d" % ent_config['step_size'])

    msg1  = "Displacement in bytes between each iteration of entropy.\n"
    msg1 += "The step used must be less than or equal to the block size.\n\n"
    msg1 += "For example, 1 will get a list of entropies for all data offsets.\n"
    msg1 += "Step sizes greater will have less precision and will be normalized\n"
    msg1 += "in the mouse events on the chart."

    self.t_step_size.setToolTip(msg1)

    self.slider_step_s = QtWidgets.QSlider(QtCore.Qt.Horizontal)
    self.slider_step_s.setFixedWidth(70)
    self.slider_step_s.setMinimum(1)
    self.slider_step_s.setMaximum(ent_config['block_size'])
    self.slider_step_s.setValue(ent_config['step_size'])

    hbox_step = QtWidgets.QHBoxLayout()
    hbox_step.addWidget(lbl_step_size)
    hbox_step.addWidget(self.t_step_size)
    hbox_step.addWidget(self.slider_step_s)

    """ All segments """
    lbl_all_segments = QtWidgets.QLabel("All segments")
    self.cb_all_segments = QtWidgets.QCheckBox()

    """ entropy config """
    form_entropy =  QtWidgets.QFormLayout()
    form_entropy.addRow(lbl_all_segments, self.cb_all_segments)
    form_entropy.addRow(hbox_block_s)
    form_entropy.addRow(hbox_step)
    self.groupbox_entropy = QtWidgets.QGroupBox('Entropy chart config')
    self.groupbox_entropy.setLayout(form_entropy)
    self.groupbox_entropy.setFixedWidth(300)

    lbl_min_entropy = QtWidgets.QLabel("Min entropy")
    lbl_block_s_xref = QtWidgets.QLabel("Block size")
    self.t_min_entropy = QtWidgets.QLineEdit()
    self.t_block_s_xrefs = QtWidgets.QLineEdit()
    self.t_min_entropy.setFixedWidth(80)
    self.t_block_s_xrefs.setFixedWidth(80)
    self.t_min_entropy.setText("%.02f" % self.config.xrefs['min_entropy'])
    self.t_block_s_xrefs.setText("%d" % self.config.xrefs['block_size'])

    form_xrefs = QtWidgets.QFormLayout()
    form_xrefs.addRow(lbl_min_entropy, self.t_min_entropy)
    form_xrefs.addRow(lbl_block_s_xref, self.t_block_s_xrefs)

    groupbox_xrefs = QtWidgets.QGroupBox('Xrefs finder')
    groupbox_xrefs.setLayout(form_xrefs)
    groupbox_xrefs.setFixedWidth(300)

    """ progress bars """
    progress_bar_style = """
    QProgressBar {
        border: 1px solid grey;
        border-radius: 2px;
        text-align: center;
    }

    QProgressBar::chunk {
        background-color: #BDE99F;
        width: 4px;
    }"""

    lbl_compress_ratio = QtWidgets.QLabel("Compression ratio") 
    self.pb_comp_ratio = QtWidgets.QProgressBar()
    self.pb_comp_ratio.setStyleSheet(progress_bar_style)
    self.pb_comp_ratio.setFixedWidth(200)

    lbl_entropy = QtWidgets.QLabel("Shannon entropy") 
    self.pb_entropy = QtWidgets.QProgressBar()
    self.pb_entropy.setStyleSheet(progress_bar_style)
    self.pb_entropy.setFixedWidth(200)

    """ buttons """
    self.button_chart = QtWidgets.QPushButton("Draw chart")
    self.button_chart.setFixedWidth(100)

    self.button_xrefs = QtWidgets.QPushButton("Find Xrefs")
    self.button_xrefs.setFixedWidth(100)

    hbox_buttons = QtWidgets.QHBoxLayout()
    hbox_buttons.addWidget(self.button_chart)
    hbox_buttons.addWidget(self.button_xrefs)

    """ main form """
    main_form = QtWidgets.QFormLayout()
    main_form.addRow(lbl_start_address, self.t_start_addr)
    main_form.addRow(lbl_end_address, self.t_end_addr)
    main_form.addRow(lbl_segment, self.cb_segment)
    main_form.addRow(lbl_compress_ratio, self.pb_comp_ratio)
    main_form.addRow(lbl_entropy, self.pb_entropy)
    main_form.addRow(lbl_disk_binary, self.cb_disk_bin)
    main_form.addRow(lbl_chart_type, chart_types_group)
    main_form.addRow(self.groupbox_entropy)
    main_form.addRow(groupbox_xrefs)  
    main_form.addRow(hbox_buttons)
    main_form.setAlignment(QtCore.Qt.AlignLeft)

    """ signals """
    self.cb_segment.currentIndexChanged[int].connect(self.cb_segment_changed)
    self.cb_all_segments.stateChanged.connect(self.cb_changed)
    self.cb_disk_bin.stateChanged.connect(self.cb_changed)
    self.button_chart.clicked.connect(self.button_chart_on_click)
    self.button_xrefs.clicked.connect(self.button_xrefs_on_click)

    for widget in [self.slider_block_s, self.slider_step_s]:
      widget.valueChanged.connect(self.slider_valuechanged)

    for widget in [self.t_start_addr, self.t_end_addr]:
      widget.textChanged.connect(self.update_address)

    for widget in [self.t_block_size, self.t_step_size]:
      widget.textChanged.connect(self.update_entropy_config)

    self.fill_segments()
    self.setLayout(main_form)

  def slider_valuechanged(self):
    sender = self.sender()
    value = int(sender.value())

    if sender is self.slider_step_s:
      self.t_step_size.setText("%d" % value)
      self.config.entropy['step_size']  = value

    elif sender is self.slider_block_s:      
      self.t_block_size.setText("%d" % value)
      self.config.entropy['block_size'] = value

  def cb_changed(self, state):
    sender = self.sender()

    if sender is self.cb_disk_bin:
      checked = (state == QtCore.Qt.Checked)  
      self.config.use_disk_binary = checked
      b_enabled = not checked

      self.update_progress_bars()

      self.t_start_addr.setEnabled(b_enabled)
      self.t_end_addr.setEnabled(b_enabled)
      self.cb_segment.setEnabled(b_enabled)
      self.cb_all_segments.setEnabled(b_enabled)

    elif sender is self.cb_all_segments:
      checked = (state == QtCore.Qt.Checked)
      self.config.entropy['all_segments'] = checked
      self.cb_disk_bin.setEnabled(not checked)

  def fill_segments(self):
    segments = filter(self.segment_filter, idautils.Segments())

    for idx, s_ea in enumerate(segments):
      if idx == 0:
        self.set_address(idc.get_segm_start(s_ea), idc.get_segm_end(s_ea))
      self.cb_segment.addItem(idc.get_segm_name(s_ea), s_ea)

    if not segments:
      self.set_address(MinEA(), MaxEA())
      self.cb_segment.setEnabled(False)

  def create_chart_type_group(self):
    vbox = QtWidgets.QVBoxLayout()
    self.rg_chart_type = QtWidgets.QButtonGroup()
    self.rg_chart_type.setExclusive(True)

    for i, choice in enumerate(self.config.chart_types):
      radio = QtWidgets.QRadioButton(choice)
      self.rg_chart_type.addButton(radio, i)
      if i == self.config.chart_type: 
        radio.setChecked(True)
      vbox.addWidget(radio)

    vbox.addStretch(1)
    self.rg_chart_type.buttonClicked.connect(self.bg_graph_type_changed)
    return vbox

  def bg_graph_type_changed(self, radio):
    selected_id = self.rg_chart_type.checkedId()
    self.config.chart_type = selected_id

    b_enabled = (self.get_chart_type_str() == 'Entropy')
    self.groupbox_entropy.setEnabled(b_enabled)

  def cb_segment_changed(self, value):
    s_ea = self.sender().itemData(value)
    self.set_address(idc.get_segm_start(s_ea), idc.get_segm_end(s_ea))
    self.update_progress_bars()   

  def set_address(self, start_addr, end_addr):
    self.t_start_addr.setText("0x%x" % start_addr)
    self.t_end_addr.setText("0x%x" % end_addr)

  def segment_filter(self, s_ea):
    """ Discard extern segments """
    if idc.get_segm_attr(s_ea, idc.SEGATTR_TYPE) != idc.SEG_XTRN and \
     idc.get_segm_name(s_ea) != self.config.entropy['segm_name']:
      return True
    return False


class Entropy(QtWidgets.QWidget):
  def __init__(self, parent):
    super().__init__()
    self.parent         = parent
    self.config         = parent.config
    self.entropy_cfg    = self.config.entropy
    self.segments       = None
    self.data           = None
    self.data_size      = None
    self.calc_addr_fcn  = None
    self.make_chart()

  def make_chart(self):
    if self.entropy_cfg['all_segments']:
      self.get_segments_memory()
      self.make_segments_chart()
    else:
      self.get_data()
      self.make_normal_chart()

  def segment_filter(self, s_ea):
    """ Discard extern segments """
    if idc.get_segm_attr(s_ea, idc.SEGATTR_TYPE) != idc.SEG_XTRN and \
     idc.get_segm_name(s_ea) != self.config.entropy['segm_name']:
      return True
    return False

  def get_data(self):
    data = None
    if self.config.use_disk_binary:
      data = get_disk_binary()
    else:
      data_size = self.config.end_addr - self.config.start_addr
      data = idaapi.get_bytes(self.config.start_addr, data_size)

    self.data = data
    self.data_size = len(data)

  def format_coord_segments(self, x, y):
    try:
      addr = self.calc_addr_fcn(int(x))
      return "0x%08X - %-20s" % (addr, idc.get_segm_name(addr))
    except:
      return 'bad address'

  def get_segments_memory(self):
    memory = b""
    step_size = self.entropy_cfg['step_size']
    segments  = OrderedDict()

    for ea in filter(self.segment_filter, idautils.Segments()):
      seg_name = idc.get_segm_name(ea)
      segm  = idaapi.get_segm_by_name(seg_name)
      data = idaapi.get_bytes(segm.start_ea, segm.size())
      assert len(data) == segm.size()

      start_offset = len(memory)
      end_offset   = (start_offset+len(data))

      seg_info = {
        'segm': segm,
        'entropy': entropy(data),
        'offsets': [
          start_offset ,
          end_offset
        ],
        'chart_offsets': [
          start_offset // step_size,
          end_offset // step_size
        ]
      }
      segments[seg_name] = seg_info
      memory += data

    self.data = memory
    self.data_size = len(memory)
    self.segments = segments

  def calc_point_addr_segments(self, x):
    addr = None
    for segm_name, segm_info in self.segments.items():
      start, end = segm_info['chart_offsets']
      if start <= x < end:
        norm_x = (x-start) * self.entropy_cfg['step_size']
        addr = segm_info['segm'].start_ea + norm_x
        break
    return addr

  def calc_point_addr_normal(self, x):
    addr = None
    offset = x * self.config.entropy['step_size']
    if self.config.use_disk_binary and self.config.entropy['segm_exists']:
      addr = self.config.entropy['segm_addr'] + offset
    else:
      addr = self.config.start_addr + offset
    return addr

  def segment_changed(self, item):
      row = item.row()
      col = item.column()
      seg_name = item.text()

      if (item.checkState() == QtCore.Qt.Checked):
        start, end = self.segments[seg_name]['chart_offsets']
        aspan = plt.axvspan(start, end, color=self.colors[row % len(self.colors)], alpha=0.6)
        self.spans[seg_name] = aspan      
      else:
        if seg_name in self.spans.keys():
          self.spans[seg_name].remove()
          del self.spans[seg_name]      
      self.canvas.draw()

  def make_segments_chart(self):    
    segment_names = []
    x_axis = []

    self.calc_addr_fcn = self.calc_point_addr_segments

    for segm_name, seg_info in self.segments.items():
      segment_names.append(segm_name)
      x_axis.append(seg_info['chart_offsets'][0])

    x_limit = self.data_size / self.entropy_cfg['step_size']
    self.colors = gen_rand_colors(25)

    self.spans = dict()
    results = list(entropy_scan(self.data, 
      self.config.entropy['block_size'], 
      self.config.entropy['step_size'])
    )

    blocks = len(results)
    min_value, max_value  = min(results), max(results)
    avg_values = sum(results) / len(results)

    #plt.rc('xtick', labelsize=6)
    #plt.rc('ytick', labelsize=6)

    self.fig = plt.figure(facecolor='white')
    ax = plt.subplot(111, facecolor='white')

    ax.set_xlabel("byte range / blocks")
    ax.set_ylabel("Entropy (E)")

    ax2 = ax.twiny()
    ax2.set_xlabel("Segments")
    ax2.set_xlim(0, x_limit)
    ax2.set_xticks(x_axis)

    labels = ax2.set_xticklabels(segment_names, rotation=40)
    ax.axis([0, blocks, 0, 8])

    ax2.format_coord = self.format_coord_segments
    plt.plot(results, color="#2E9AFE")
    plt.tight_layout()
   
    log("Entropy - Start address: 0x%08x" % self.config.start_addr)
    log("Entropy - End address:   0x%08x" % self.config.end_addr)
    log("Entropy - Data size: %d bytes (blocks: %d)" % (len(self.data), blocks))
    info_str = 'Entropy - Min: %.2f | Max:  %.2f | Avg: %.2f' % (min_value, max_value, avg_values)
    log(info_str)

    del self.data

    self.canvas  = FigureCanvas(self.fig)
    self.toolbar = NavigationToolbar(self.canvas, self)

    self.cb_jump_on_click = QtWidgets.QCheckBox("Disable double-click event")    
    self.cb_jump_on_click.stateChanged.connect(self.disable_jump_on_click)

    """ segment table """
    self.segments_table = QtWidgets.QTableView()
    self.segments_table.setMaximumWidth(200)
    self.segments_table.setMaximumHeight(200)
    self.segments_table.verticalHeader().hide()

    model = QtGui.QStandardItemModel()
    model.setHorizontalHeaderLabels(['Segment','Entropy'])
    model.setHeaderData(0, QtCore.Qt.Horizontal, QtCore.Qt.AlignJustify, QtCore.Qt.TextAlignmentRole)
    
    self.segments_table.setSelectionMode(QtWidgets.QTableView.SingleSelection)
    self.segments_table.setSelectionBehavior(QtWidgets.QTableView.SelectRows)
    self.segments_table.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
    self.segments_table.setModel(model)

    for n, seg_name in enumerate(segment_names):
      segment_name = QtGui.QStandardItem(seg_name)
      segment_entropy = QtGui.QStandardItem("%.03f" % self.segments[seg_name]['entropy'])
      model.setItem(n, 0, segment_name)
      model.setItem(n, 1, segment_entropy)
      segment_name.setCheckable(True)

    self.segments_table.horizontalHeader().setStretchLastSection(1);
    self.segments_table.resizeRowsToContents()
    self.segments_table.resizeColumnsToContents()
    self.segments_table.sortByColumn(1, QtCore.Qt.DescendingOrder)
    model.itemChanged.connect(self.segment_changed)
    
    lbl_segments = QtWidgets.QLabel("Select a segment to color it")
    vbox = QtWidgets.QVBoxLayout(self)
    vbox.addWidget(lbl_segments)
    vbox.addWidget(self.segments_table)

    group = QtWidgets.QGroupBox()
    group.setFlat(True)
    group.setStyleSheet("border:0")
    group.setLayout(vbox)    

    grid = QtWidgets.QGridLayout()
    grid.addWidget(self.canvas, 0, 0, 1, 2)
    grid.addWidget(group, 0, 3, QtCore.Qt.AlignCenter)
    grid.addWidget(self.toolbar, 1, 0)
    grid.addWidget(self.cb_jump_on_click, 2, 0)

    self.cid = self.fig.canvas.mpl_connect('button_press_event', self.on_click)

    self.setLayout(grid)
    
  def on_click(self, event):
    if event.dblclick and event.xdata:
      addr = self.calc_addr_fcn(int(event.xdata))
      if addr:
        idaapi.jumpto(addr)
      else:
        idaapi.warning("Unable to calculate the address")

  def format_coord_normal(self, x, y):
    try:
      if self.config.use_disk_binary or self.entropy_cfg['segm_exists']:
        addr = int(x) * self.config.entropy['step_size']
        return "Offset : 0x%08x %-30s" % (addr, "")
      else:
        addr = self.calc_addr_fcn(int(x))
        return "0x%08x - %-30s" % (addr, idc.get_segm_name(addr))
    except:
      pass
    return "bad address"

  def make_normal_chart(self):
    blocks = self.data_size / self.entropy_cfg['block_size']
    self.calc_addr_fcn = self.calc_point_addr_normal

    results = list(entropy_scan(self.data, 
      self.entropy_cfg['block_size'], 
      self.entropy_cfg['step_size'])
    )
    min_value, max_value  = min(results), max(results)
    avg_values = sum(results) / len(results)

    self.fig = plt.figure(facecolor='white')
    ax = plt.subplot(111, facecolor='white')
    ax.axis([0, len(results), 0, 8])
    ax.format_coord = self.format_coord_normal
    plt.plot(results, color="#2E9AFE")    

    log("Entropy - Start address: 0x%08x" % self.config.start_addr)
    log("Entropy - End address:   0x%08x" % self.config.end_addr)
    log("Entropy - Data size: %d bytes (blocks: %d)" % (self.data_size, blocks))
    info_str = 'Entropy - Min: %.2f | Max:  %.2f | Avg: %.2f' % (min_value, max_value, avg_values)
    log(info_str)
    del self.data

    plt.xlabel('Byte range')
    plt.ylabel('Entropy')
    plt.title('Entropy levels')

    self.canvas = FigureCanvas(self.fig)
    self.toolbar = NavigationToolbar(self.canvas, self)
    self.line_edit = QtWidgets.QLineEdit()

    self.cb_jump_on_click = QtWidgets.QCheckBox("Disable double-click event")    
    self.cb_jump_on_click.stateChanged.connect(self.disable_jump_on_click)

    grid = QtWidgets.QGridLayout()
    grid.addWidget(self.canvas, 0, 0)
    grid.addWidget(self.toolbar, 1, 0)

    if not self.config.use_disk_binary or self.entropy_cfg['segm_exists']:
      grid.addWidget(self.cb_jump_on_click, 2, 0)
      self.cid = self.fig.canvas.mpl_connect('button_press_event', self.on_click)

    self.setLayout(grid)

  def disable_jump_on_click(self, state):
    if state == QtCore.Qt.Checked:
      self.fig.canvas.mpl_disconnect(self.cid)
    else:
      self.cid = self.fig.canvas.mpl_connect('button_press_event', self.on_click)


class TableItem(QtWidgets.QTableWidgetItem):  
  class ItemType:
    DEC = 0
    HEX = 1
    FLOAT = 2
    TEXT = 3

  def __init__(self, text, item_type):
    super().__init__(text, QtWidgets.QTableWidgetItem.UserType)
    self.setFlags(QtCore.Qt.ItemIsEnabled)
    self.item_type = item_type
    self.text = text  

  def __lt__(self, other):
    if self.item_type == self.ItemType.DEC:
      return int(self.text) < int(other.text)
    elif self.item_type == self.ItemType.HEX:
      return int(self.text, 16) < int(other.text, 16)
    elif self.item_type == self.ItemType.FLOAT:
      data = float(self.text.replace("%", ""))
      other_data = float(other.text.replace("%", ""))
      return data < other_data
    elif self.item_type == self.ItemType.TEXT:
      return self.text < other.text
    else:
      raise TypeError("The ItemType specified is not supported")

class Histogram(QtWidgets.QWidget):
  def __init__(self, parent):
    super().__init__()
    self.parent   = parent
    self.config   = parent.config
    self.data     = None
    self.data_size = None
    self.get_data()
    self.make_histogram()

  def get_data(self):
    data = None

    if self.config.use_disk_binary:
      data = get_disk_binary()
    else:
      data_size = self.config.end_addr - self.config.start_addr
      data = get_loaded_bytes(self.config.start_addr, data_size, "")

    self.data = data
    self.data_size = len(data)

  def format_coord(self, x, y):    
    try:
      value = int(x)
      return "value: %d | count: %-30s" % (value, self.counter[value])
    except:
      pass
    return "bad value"

  def make_histogram(self):    
    self.counter   = histogram(self.data)
    self.counts    = [round(100*float(byte_count)/self.data_size, 2) for byte_count in self.counter] 
    top_y          = math.ceil(max(self.counts)*10.0)/10.0
    del self.data

    self.create_table()
    fig = plt.figure(facecolor='white')
    ax = plt.subplot(111, facecolor='white')

    control_bytes    = 0
    whitespace_bytes = 0
    null_bytes       = self.counter[0]
    printable_bytes  = sum([self.counter[byte] for byte in range(0x21, 0x7F)])
    high_bytes       = sum([self.counter[byte] for byte in range(0x80, 0x100)])

    for byte in range(1, 0x21):
      if chr(byte) in string.whitespace:
        whitespace_bytes += self.counter[byte]
      else:
        control_bytes += self.counter[byte]

    log("Histogram - Data size: %d bytes" % self.data_size)
    self.log_byte_stats("NULL bytes", null_bytes)
    self.log_byte_stats("Control bytes", control_bytes)
    self.log_byte_stats("Whitespace bytes", whitespace_bytes)
    self.log_byte_stats("Printable bytes", printable_bytes)
    self.log_byte_stats("High bytes", high_bytes)

    plt.axis([0, 256, 0, top_y])
    ax.set_xlim(0, 255)
    ax.set_xticks([0, 64, 128, 192, 255])
    ax.format_coord = self.format_coord

    bar_colors = ['#2040D0','#2E9AFE']
    ax.bar(range(256), self.counts, width=1, edgecolor="black", linewidth=0.4, color=bar_colors*128)

    plt.title("Byte histogram")
    plt.xlabel('Byte range')
    plt.ylabel('Occurance [%]')

    self.canvas  = FigureCanvas(fig)
    self.toolbar = NavigationToolbar(self.canvas, self)

    grid = QtWidgets.QGridLayout()
    grid.addWidget(self.canvas, 0, 0)
    grid.addWidget(self.toolbar, 1, 0)
    grid.addWidget(self.table, 0, 1, 0, 2)
    self.setLayout(grid)

  def log_byte_stats(self, name, n_bytes):
    log("Histogram - %-18s: %6d (%2.02f %%)" % (name, n_bytes, (float(n_bytes)/self.data_size*100)))

  def create_table(self):
    self.table = QtWidgets.QTableWidget()
    self.table.setColumnCount(5)
    self.table.setColumnWidth(0, 5)
    self.table.setColumnWidth(1, 5)
    self.table.setColumnWidth(2, 1)
    self.table.setColumnWidth(3, 5)
    self.table.setColumnWidth(4, 5)
    self.table.setHorizontalHeaderLabels(["Dec", "Hex", "Char", "Count", "Percent"])
    self.table.verticalHeader().hide()
    self.table.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
    
    printable = string.printable[:-6]
    for byte, count in enumerate(self.counter):
      char = ''
      if chr(byte) in printable:
        char = chr(byte)

      dec_item     = TableItem("%d" % byte,  TableItem.ItemType.DEC)
      hex_item     = TableItem("%02X" % byte,TableItem.ItemType.HEX)
      char_item    = TableItem("%s" % char,  TableItem.ItemType.TEXT)
      count_item   = TableItem("%d" % count, TableItem.ItemType.DEC)
      percert_item = TableItem("%s%%" % self.counts[byte], TableItem.ItemType.FLOAT)

      self.table.insertRow(byte)
      self.table.setItem(byte, 0, dec_item)
      self.table.setItem(byte, 1, hex_item)
      self.table.setItem(byte, 2, char_item)
      self.table.setItem(byte, 3, count_item)
      self.table.setItem(byte, 4, percert_item)

    self.table.horizontalHeader().setStretchLastSection(1);
    self.table.resizeRowsToContents()
    self.table.resizeColumnsToContents()
    self.table.setSortingEnabled(True)
    self.table.sortItems(3, QtCore.Qt.DescendingOrder)
    self.table.setSelectionBehavior(QtWidgets.QTableWidget.SelectRows)  



class IDAtropyForm(idaapi.PluginForm):
  def __init__(self):
    super().__init__()
    self.config = Config()

    # disable timeout for scripts
    self.old_timeout = idaapi.set_script_timeout(0)

  def OnCreate(self, form):
    self.parent = self.FormToPyQtWidget(form)
    self.PopulateForm()

  def RemoveTab(self, index):
    pass

  def PopulateForm(self):
    self.tabs = QtWidgets.QTabWidget()
    self.tabs.setMovable(True)
    self.tabs.setTabsClosable(True)
    self.tabs.tabCloseRequested.connect(self.remove_tabs)
    self.tabs.addTab(Options(self), "Options")

    layout = QtWidgets.QVBoxLayout()
    layout.addWidget(self.tabs)
    self.parent.setLayout(layout)

  def remove_tabs(self, index):
    if not isinstance(self.tabs.widget(index), Options):
      self.tabs.removeTab(index)

  def OnClose(self, form):
    idaapi.set_script_timeout(self.old_timeout)
    print("[%s] Form closed." % PLUG_NAME)

class IDAtropy_t(idaapi.plugin_t):
    flags = idaapi.PLUGIN_UNL
    comment = "IDAtropy"
    help = ""
    wanted_name = PLUG_NAME
    wanted_hotkey = "Alt-F10"

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

    def run(self, arg=0):
      if not 'ERROR_MATPLOTLIB' in globals():
        f = IDAtropyForm()
        f.Show(PLUG_NAME)
      else:
        idaapi.warning("%s - The plugin requires matplotlib" % PLUG_NAME)

    def term(self):
        pass

def PLUGIN_ENTRY():    
    return IDAtropy_t()

if __name__ == '__main__':
  log("Plugin loaded")


repository: carlosgprado/JARVIS
file: IDAPlugin/jarvis_launcher.py
description: "Just Another ReVersIng Suite" or whatever other bullshit you can think of
wanted_name: JARVIS Launcher
comment: Simple JARVIS Launcher
created_at: 2015-05-20 09:08:58+00:00
pushed_at: 2023-04-28 06:02:25+00:00
url:
https://github.com/carlosgprado/JARVIS/blob/29ffc6e92d2e251d1a29ded71f52d634482a2a57/IDAPlugin/jarvis_launcher.py

content:
import idaapi


class jarvis_launcher_t(idaapi.plugin_t):
    flags = idaapi.PLUGIN_UNL
    comment = "Simple JARVIS Launcher"

    help = "Convenience Launcher for JARVIS"
    wanted_name = "JARVIS Launcher"
    wanted_hotkey = "Ctrl-J"

    def init(self):
        idaapi.msg("JARVIS launcher initialized\n")
        return idaapi.PLUGIN_OK

    def run(self, arg):
        jarvis_loc = os.path.join('plugins', 'jarvis', 'jarvis.py')
        full_path = idaapi.idadir(jarvis_loc)
        idaapi.load_and_run_plugin(full_path, 0)

    def term(self):
        pass


def PLUGIN_ENTRY():
    return jarvis_launcher_t()


repository: stevemk14ebr/RETools
file: IdaScripts/plugins/easy_nop.py
description: My reversing tools. Some custom, some not.
wanted_name: Easy Nop
comment: Easy nopping tool for you nopping needs
created_at: 2020-05-27 21:10:30+00:00
pushed_at: 2024-01-03 21:54:17+00:00
url:
https://github.com/stevemk14ebr/RETools/blob/9501332bf02688d3a4b31d85a25349671767be98/IdaScripts/plugins/easy_nop.py

content:
## mev: https://www.unknowncheats.me/forum/general-programming-and-reversing/343177-easy-nop-ida-plugin.html
import idaapi

popup_action_names = []
 
class Hooks(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 EasyNopPlugin(idaapi.plugin_t):
    flags = idaapi.PLUGIN_HIDE | idaapi.PLUGIN_PROC
    comment = "Easy nopping tool for you nopping needs"
    help = "Use Shift+N to nop out current line or selection"
    wanted_name = "Easy Nop"
    wanted_hotkey = ""
 
    def init(self):
        idaapi.msg("Easy Nop :: init\n")
        AddToPopup('nopaction:add_action', 'Nop line or selection', NopAction(), 'Shift+N', None)
 
        self.hooks = Hooks()
        self.hooks.hook()
        return idaapi.PLUGIN_KEEP
 
    def run(self):
        idaapi.msg("Easy Nop :: run\n")
 
    def term(self):
        idaapi.msg("Easy Nop :: term\n")
        if self.hooks:
            self.hooks.unhook()
 
        idaapi.unregister_action('nopaction:add_action')
 
class EA_Action(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
 
class NopAction(EA_Action):
    def activate(self, ctx):
        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:
                print('Easy Nop :: Screen EA == idaapi.BADADDR')
                return 0
 
            end = start + idaapi.get_item_size(start)
 
        if start == idaapi.BADADDR:
            print('Easy Nop :: Selection EA == idaapi.BADADDR')
            return 0
 
        if start == end:
            print('Easy Nop :: Nothing to nop')
            return 0
 
        for x in range(start, end):
            # will prob fail on archs that don't have a nop instruction but whatever
            idaapi.assemble(x, 0, x, True, "nop")
 
        for x in range(start + 1, end):
            idaapi.hide_item(x)
 
        # Must do this else it bugs out on 2x 1 byte instructions being nopped
        idaapi.hide_item(start)
        idaapi.unhide_item(start)
 
        # Search for hidden nops and add to count
        while idaapi.get_byte(end) == 0x90 and idaapi.is_hidden_item(end) == True:
            end += 1
 
        count = end - start
 
        if count > 1:
            idaapi.set_cmt(start, "truncated nops (%d)" % (count), False)
 
        print(end)
        print(start)
 
        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('Easy Nop :: Error registering action %s' % (action_name))
 
def PLUGIN_ENTRY(*args, **kwargs):
    return EasyNopPlugin()


repository: JANlittle/IDARustHelper
file: IDARustHelperPlugin.py
description: Small rust binary analysis helper for IDA.
created_at: 2024-01-23 09:38:26+00:00
pushed_at: 2024-07-14 11:03:19+00:00
url:
https://github.com/JANlittle/IDARustHelper/blob/10cc4636df0fe30aef9411b4ef75d3ca263101a6/IDARustHelperPlugin.py

content:
from idaapi import *
import IDARustHelper.Demangler as Demangler
import IDARustHelper.RustType as RustType
import IDARustHelper.StringRecover.rust as StringRecover

# Register the actual plugin
class IDARustHelperHandler(plugin_t):
    PLUGIN_NAME = "IDA Rust Helper"
    PLUGIN_DIRECTORY = "IDARustHelper"
    PLUGIN_DESCRIPTION = "Rust RE Helper"

    flags = PLUGIN_UNL
    comment = PLUGIN_DESCRIPTION
    help = PLUGIN_DESCRIPTION
    wanted_name = PLUGIN_NAME
    wanted_hotkey = ""

    def init(self):
        self.demangler = Demangler.IDARustDemangler(debug=False)
        self.ctx = StringRecover.rust_ctx_t(debug=False)
        return PLUGIN_OK

    def run(self, arg):
        print(f"[*] {self.PLUGIN_NAME} started!")

        self.demangler.run()
        RustType.set_rust_type()
        self.ctx.perform_final_strlit_analysis()

        print(f"[*] {self.PLUGIN_NAME} finished!")
        return 1

    def term(self):
        pass


class IDARustDemanglerHook(ida_kernwin.UI_Hooks):
    """
    this class is only used to install the icon to the corresponding IDA action
    """

    def __init__(self, cb):
        super().__init__()
        self.cb = cb

    def updated_actions(self):
        if self.cb():
            self.unhook()


def install_icon():
    plugin_name = IDARustHelperHandler.PLUGIN_NAME
    action_name = "Edit/Plugins/" + plugin_name
    LOGO_PATH = None

    # if the action is not present wait for our hook action
    if action_name not in ida_kernwin.get_registered_actions():
        return False

    # check if in any of the IDA plugins directory if there is
    # our plugin directory and take the logo from there
    for plugin_path in get_ida_subdirs("plugins"):
        LOGO_PATH = os.path.join(
            plugin_path, f"{IDARustHelperHandler.PLUGIN_DIRECTORY}\\resource\\rust-logo.png")

        # if the file exists use the first one found
        if os.path.isfile(LOGO_PATH):
            break

    if LOGO_PATH is None:
        print("[?] IDA Rust Helper logo not found")
        return True

    # load the logo and apply it to the action
    icon = load_custom_icon(
        LOGO_PATH, format="png")

    ida_kernwin.update_action_icon(action_name, icon)

    return True


def PLUGIN_ENTRY():
    return IDARustHelperHandler()


h = IDARustDemanglerHook(install_icon)
h.hook()


repository: HongThatCong/AFX_MSGMAP
file: AFX_MSGMAP.py
description: IDAPython plugin to parse AFX_MSGMAPs and some internals structures in VC++ MFC application
created_at: 2020-06-28 02:06:23+00:00
pushed_at: 2020-11-12 05:05:43+00:00
url:
https://github.com/HongThatCong/AFX_MSGMAP/blob/b9f07d17f632577067c38a1205a1e6c1bfa66196/AFX_MSGMAP.py

content:
﻿# pylint: disable=C0301,C0103,C0111

# ==============================================================================
#
# AfxMSGMap plugin for IDA
# Copyright (c) 2018 - 2020
# Snow 85703533
# Port to IDA 7x by HTC - VinCSS (a member of Vingroup)
# All rights reserved.
#
# ==============================================================================

from __future__ import print_function

import idaapi
import idc
import ida_segment
import ida_nalt
import ida_moves

# Constants

PLUGIN_NAME = "MFC_Helper"

WM_USER = 0x400
WM_APP = 0x8000

MSG_TABLES = [
    (0x0, "WM_NULL"),
    (0x1, "WM_CREATE"),
    (0x2, "WM_DESTROY"),
    (0x3, "WM_MOVE"),
    (0x4, "WM_SIZEWAIT"),
    (0x5, "WM_SIZE"),
    (0x6, "WM_ACTIVATE"),
    (0x7, "WM_SETFOCUS"),
    (0x8, "WM_KILLFOCUS"),
    (0x9, "WM_SETVISIBLE"),
    (0xA, "WM_ENABLE"),
    (0xB, "WM_SETREDRAW"),
    (0xC, "WM_SETTEXT"),
    (0xD, "WM_GETTEXT"),
    (0xE, "WM_GETTEXTLENGTH"),
    (0xF, "WM_PAINT"),
    (0x10, "WM_CLOSE"),
    (0x11, "WM_QUERYENDSESSION"),
    (0x12, "WM_QUIT"),
    (0x13, "WM_QUERYOPEN"),
    (0x14, "WM_ERASEBKGND"),
    (0x15, "WM_SYSCOLORCHANGE"),
    (0x16, "WM_ENDSESSION"),
    (0x17, "WM_SYSTEMERROR"),
    (0x18, "WM_SHOWWINDOW"),
    (0x19, "WM_CTLCOLOR"),
    (0x1A, "WM_SETTINGCHANGE"),
    (0x1A, "WM_WININICHANGE"),
    (0x1B, "WM_DEVMODECHANGE"),
    (0x1C, "WM_ACTIVATEAPP"),
    (0x1D, "WM_FONTCHANGE"),
    (0x1E, "WM_TIMECHANGE"),
    (0x1F, "WM_CANCELMODE"),
    (0x20, "WM_SETCURSOR"),
    (0x21, "WM_MOUSEACTIVATE"),
    (0x22, "WM_CHILDACTIVATE"),
    (0x23, "WM_QUEUESYNC"),
    (0x24, "WM_GETMINMAXINFO"),
    (0x25, "WM_LOGOFF"),
    (0x26, "WM_PAINTICON"),
    (0x27, "WM_ICONERASEBKGND"),
    (0x28, "WM_NEXTDLGCTL"),
    (0x29, "WM_ALTTABACTIVE"),
    (0x2A, "WM_SPOOLERSTATUS"),
    (0x2B, "WM_DRAWITEM"),
    (0x2C, "WM_MEASUREITEM"),
    (0x2D, "WM_DELETEITEM"),
    (0x2E, "WM_VKEYTOITEM"),
    (0x2F, "WM_CHARTOITEM"),
    (0x30, "WM_SETFONT"),
    (0x31, "WM_GETFONT"),
    (0x32, "WM_SETHOTKEY"),
    (0x33, "WM_GETHOTKEY"),
    (0x34, "WM_FILESYSCHANGE"),
    (0x35, "WM_ISACTIVEICON"),
    (0x36, "WM_QUERYPARKICON"),
    (0x37, "WM_QUERYDRAGICON"),
    (0x38, "WM_WINHELP"),
    (0x39, "WM_COMPAREITEM"),
    (0x3A, "WM_FULLSCREEN"),
    (0x3B, "WM_CLIENTSHUTDOWN"),
    (0x3C, "WM_DDEMLEVENT"),
    (0x3D, "WM_GETOBJECT"),
    (0x3F, "MM_CALCSCROLL"),
    (0x40, "WM_TESTING"),
    (0x41, "WM_COMPACTING"),
    (0x42, "WM_OTHERWINDOWCREATED"),
    (0x43, "WM_OTHERWINDOWDESTROYED"),
    (0x44, "WM_COMMNOTIFY"),
    (0x45, "WM_MEDIASTATUSCHANGE"),
    (0x46, "WM_WINDOWPOSCHANGING"),
    (0x47, "WM_WINDOWPOSCHANGED"),
    (0x48, "WM_POWER"),
    (0x49, "WM_COPYGLOBALDATA"),
    (0x4A, "WM_COPYDATA"),
    (0x4B, "WM_CANCELJOURNAL"),
    (0x4C, "WM_LOGONNOTIFY"),
    (0x4D, "WM_KEYF1"),
    (0x4E, "WM_NOTIFY"),
    (0x4F, "WM_ACCESS_WINDOW"),
    (0x50, "WM_INPUTLANGCHANGEREQUEST"),
    (0x51, "WM_INPUTLANGCHANGE"),
    (0x52, "WM_TCARD"),
    (0x53, "WM_HELP"),
    (0x54, "WM_USERCHANGED"),
    (0x55, "WM_NOTIFYFORMAT"),
    (0x60, "WM_QM_ACTIVATE"),
    (0x61, "WM_HOOK_DO_CALLBACK"),
    (0x62, "WM_SYSCOPYDATA"),
    (0x70, "WM_FINALDESTROY"),
    (0x71, "WM_MEASUREITEM_CLIENTDATA"),
    (0x7B, "WM_CONTEXTMENU"),
    (0x7C, "WM_STYLECHANGING"),
    (0x7D, "WM_STYLECHANGED"),
    (0x7E, "WM_DISPLAYCHANGE"),
    (0x7F, "WM_GETICON"),
    (0x80, "WM_SETICON"),
    (0x81, "WM_NCCREATE"),
    (0x82, "WM_NCDESTROY"),
    (0x83, "WM_NCCALCSIZE"),
    (0x84, "WM_NCHITTEST"),
    (0x85, "WM_NCPAINT"),
    (0x86, "WM_NCACTIVATE"),
    (0x87, "WM_GETDLGCODE"),
    (0x88, "WM_SYNCPAINT"),
    (0x89, "WM_SYNCTASK"),
    (0xA0, "WM_NCMOUSEMOVE"),
    (0xA1, "WM_NCLBUTTONDOWN"),
    (0xA2, "WM_NCLBUTTONUP"),
    (0xA3, "WM_NCLBUTTONDBLCLK"),
    (0xA4, "WM_NCRBUTTONDOWN"),
    (0xA5, "WM_NCRBUTTONUP"),
    (0xA6, "WM_NCRBUTTONDBLCLK"),
    (0xA7, "WM_NCMBUTTONDOWN"),
    (0xA8, "WM_NCMBUTTONUP"),
    (0xA9, "WM_NCMBUTTONDBLCLK"),
    (0xAB, "WM_NCXBUTTONDOWN"),
    (0xAC, "WM_NCXBUTTONUP"),
    (0xAD, "WM_NCXBUTTONDBLCLK"),
    (0xB0, "EM_GETSEL"),
    (0xB1, "EM_SETSEL"),
    (0xB2, "EM_GETRECT"),
    (0xB3, "EM_SETRECT"),
    (0xB4, "EM_SETRECTNP"),
    (0xB5, "EM_SCROLL"),
    (0xB6, "EM_LINESCROLL"),
    (0xB7, "EM_SCROLLCARET"),
    (0xB8, "EM_GETMODIFY"),
    (0xB8, "IE_GETMODIFY"),
    (0xB9, "EM_SETMODIFY"),
    (0xB9, "IE_SETMODIFY"),
    (0xBA, "EM_GETLINECOUNT"),
    (0xBB, "EM_LINEINDEX"),
    (0xBC, "EM_SETHANDLE"),
    (0xBD, "EM_GETHANDLE"),
    (0xBE, "EM_GETTHUMB"),
    (0xC1, "EM_LINELENGTH"),
    (0xC2, "EM_REPLACESEL"),
    (0xC3, "EM_SETFONT"),
    (0xC4, "EM_GETLINE"),
    (0xC5, "EM_LIMITTEXT"),
    (0xC5, "EM_SETLIMITTEXT"),
    (0xC6, "EM_CANUNDO"),
    (0xC6, "IE_CANUNDO"),
    (0xC7, "EM_UNDO"),
    (0xC7, "IE_UNDO"),
    (0xC8, "EM_FMTLINES"),
    (0xC9, "EM_LINEFROMCHAR"),
    (0xCA, "EM_SETWORDBREAK"),
    (0xCB, "EM_SETTABSTOPS"),
    (0xCC, "EM_SETPASSWORDCHAR"),
    (0xCD, "EM_EMPTYUNDOBUFFER"),
    (0xCD, "IE_EMPTYUNDOBUFFER"),
    (0xCE, "EM_GETFIRSTVISIBLELINE"),
    (0xCF, "EM_SETREADONLY"),
    (0xD0, "EM_SETWORDBREAKPROC"),
    (0xD1, "EM_GETWORDBREAKPROC"),
    (0xD2, "EM_GETPASSWORDCHAR"),
    (0xD3, "EM_SETMARGINS"),
    (0xD4, "EM_GETMARGINS"),
    (0xD5, "EM_GETLIMITTEXT"),
    (0xD6, "EM_POSFROMCHAR"),
    (0xD7, "EM_CHARFROMPOS"),
    (0xD8, "EM_SETIMESTATUS"),
    (0xD9, "EM_GETIMESTATUS"),
    (0xE0, "SBM_SETPOS"),
    (0xE1, "SBM_GETPOS"),
    (0xE2, "SBM_SETRANGE"),
    (0xE3, "SBM_GETRANGE"),
    (0xE4, "SBM_ENABLE_ARROWS"),
    (0xE6, "SBM_SETRANGEREDRAW"),
    (0xE9, "SBM_SETSCROLLINFO"),
    (0xEA, "SBM_GETSCROLLINFO"),
    (0xEB, "SBM_GETSCROLLBARINFO"),
    (0xF0, "BM_GETCHECK"),
    (0xF1, "BM_SETCHECK"),
    (0xF2, "BM_GETSTATE"),
    (0xF3, "BM_SETSTATE"),
    (0xF4, "BM_SETSTYLE"),
    (0xF5, "BM_CLICK"),
    (0xF6, "BM_GETIMAGE"),
    (0xF7, "BM_SETIMAGE"),
    (0xF8, "BM_SETDONTCLICK"),
    (0xFE, "WM_INPUT_DEVICE_CHANGE"),
    (0xFF, "WM_INPUT"),
    (0x100, "WM_KEYDOWN"),
    (0x100, "WM_KEYFIRST"),
    (0x101, "WM_KEYUP"),
    (0x102, "WM_CHAR"),
    (0x103, "WM_DEADCHAR"),
    (0x104, "WM_SYSKEYDOWN"),
    (0x105, "WM_SYSKEYUP"),
    (0x106, "WM_SYSCHAR"),
    (0x107, "WM_SYSDEADCHAR"),
    (0x108, "WM_KEYLAST_PRE501"),
    (0x108, "WM_YOMICHAR"),
    (0x109, "WM_KEYLAST_NT501"),
    (0x109, "WM_KEYLAST"),
    (0x109, "WM_UNICHAR"),
    (0x109, "WM_WNT_CONVERTREQUESTEX"),
    (0x10A, "WM_CONVERTREQUEST"),
    (0x10B, "WM_CONVERTRESULT"),
    (0x10C, "WM_IM_INFO"),
    (0x10C, "WM_INTERIM"),
    (0x10D, "WM_IME_STARTCOMPOSITION"),
    (0x10E, "WM_IME_ENDCOMPOSITION"),
    (0x10F, "WM_IME_COMPOSITION"),
    (0x10F, "WM_IME_KEYLAST"),
    (0x110, "WM_INITDIALOG"),
    (0x111, "WM_COMMAND"),
    (0x112, "WM_SYSCOMMAND"),
    (0x113, "WM_TIMER"),
    (0x114, "WM_HSCROLL"),
    (0x115, "WM_VSCROLL"),
    (0x116, "WM_INITMENU"),
    (0x117, "WM_INITMENUPOPUP"),
    (0x118, "WM_SYSTIMER"),
    (0x119, "WM_GESTURE"),
    (0x11A, "WM_GESTURENOTIFY"),
    (0x11F, "WM_MENUSELECT"),
    (0x120, "WM_MENUCHAR"),
    (0x121, "WM_ENTERIDLE"),
    (0x122, "WM_MENURBUTTONUP"),
    (0x123, "WM_MENUDRAG"),
    (0x124, "WM_MENUGETOBJECT"),
    (0x125, "WM_UNINITMENUPOPUP"),
    (0x126, "WM_MENUCOMMAND"),
    (0x127, "WM_KEYBOARDCUES"),
    (0x127, "WM_CHANGEUISTATE"),
    (0x128, "WM_UPDATEUISTATE"),
    (0x129, "WM_QUERYUISTATE"),
    (0x131, "WM_LBTRACKPOINT"),
    (0x132, "WM_CTLCOLORMSGBOX"),
    (0x133, "WM_CTLCOLOREDIT"),
    (0x134, "WM_CTLCOLORLISTBOX"),
    (0x135, "WM_CTLCOLORBTN"),
    (0x136, "WM_CTLCOLORDLG"),
    (0x137, "WM_CTLCOLORSCROLLBAR"),
    (0x138, "WM_CTLCOLORSTATIC"),
    (0x140, "CB_GETEDITSEL"),
    (0x141, "CB_LIMITTEXT"),
    (0x142, "CB_SETEDITSEL"),
    (0x143, "CB_ADDSTRING"),
    (0x144, "CB_DELETESTRING"),
    (0x144, "CBEM_DELETEITEM"),
    (0x145, "CB_DIR"),
    (0x146, "CB_GETCOUNT"),
    (0x147, "CB_GETCURSEL"),
    (0x148, "CB_GETLBTEXT"),
    (0x149, "CB_GETLBTEXTLEN"),
    (0x14A, "CB_INSERTSTRING"),
    (0x14B, "CB_RESETCONTENT"),
    (0x14C, "CB_FINDSTRING"),
    (0x14D, "CB_SELECTSTRING"),
    (0x14E, "CB_SETCURSEL"),
    (0x14F, "CB_SHOWDROPDOWN"),
    (0x150, "CB_GETITEMDATA"),
    (0x151, "CB_SETITEMDATA"),
    (0x152, "CB_GETDROPPEDCONTROLRECT"),
    (0x153, "CB_SETITEMHEIGHT"),
    (0x154, "CB_GETITEMHEIGHT"),
    (0x155, "CB_SETEXTENDEDUI"),
    (0x156, "CB_GETEXTENDEDUI"),
    (0x157, "CB_GETDROPPEDSTATE"),
    (0x158, "CB_FINDSTRINGEXACT"),
    (0x159, "CB_SETLOCALE"),
    (0x15A, "CB_GETLOCALE"),
    (0x15B, "CB_GETTOPINDEX"),
    (0x15C, "CB_SETTOPINDEX"),
    (0x15D, "CB_GETHORIZONTALEXTENT"),
    (0x15E, "CB_SETHORIZONTALEXTENT"),
    (0x15F, "CB_GETDROPPEDWIDTH"),
    (0x160, "CB_SETDROPPEDWIDTH"),
    (0x161, "CB_INITSTORAGE"),
    (0x163, "CB_MSGMAX"),
    (0x163, "CB_MULTIPLEADDSTRING"),
    (0x164, "CB_GETCOMBOBOXINFO"),
    (0x170, "STM_SETICON"),
    (0x171, "STM_GETICON"),
    (0x172, "STM_SETIMAGE"),
    (0x173, "STM_GETIMAGE"),
    (0x174, "STM_MSGMAX"),
    (0x180, "LB_ADDSTRING"),
    (0x181, "LB_INSERTSTRING"),
    (0x182, "LB_DELETESTRING"),
    (0x183, "LB_SELITEMRANGEEX"),
    (0x184, "LB_RESETCONTENT"),
    (0x185, "LB_SETSEL"),
    (0x186, "LB_SETCURSEL"),
    (0x187, "LB_GETSEL"),
    (0x188, "LB_GETCURSEL"),
    (0x189, "LB_GETTEXT"),
    (0x18A, "LB_GETTEXTLEN"),
    (0x18B, "LB_GETCOUNT"),
    (0x18C, "LB_SELECTSTRING"),
    (0x18D, "LB_DIR"),
    (0x18E, "LB_GETTOPINDEX"),
    (0x18F, "LB_FINDSTRING"),
    (0x190, "LB_GETSELCOUNT"),
    (0x191, "LB_GETSELITEMS"),
    (0x192, "LB_SETTABSTOPS"),
    (0x193, "LB_GETHORIZONTALEXTENT"),
    (0x194, "LB_SETHORIZONTALEXTENT"),
    (0x195, "LB_SETCOLUMNWIDTH"),
    (0x196, "LB_ADDFILE"),
    (0x197, "LB_SETTOPINDEX"),
    (0x198, "LB_GETITEMRECT"),
    (0x199, "LB_GETITEMDATA"),
    (0x19A, "LB_SETITEMDATA"),
    (0x19B, "LB_SELITEMRANGE"),
    (0x19C, "LB_SETANCHORINDEX"),
    (0x19D, "LB_GETANCHORINDEX"),
    (0x19E, "LB_SETCARETINDEX"),
    (0x19F, "LB_GETCARETINDEX"),
    (0x1A0, "LB_SETITEMHEIGHT"),
    (0x1A1, "LB_GETITEMHEIGHT"),
    (0x1A2, "LB_FINDSTRINGEXACT"),
    (0x1A3, "LBCB_CARETON"),
    (0x1A4, "LBCB_CARETOFF"),
    (0x1A5, "LB_SETLOCALE"),
    (0x1A6, "LB_GETLOCALE"),
    (0x1A7, "LB_SETCOUNT"),
    (0x1A8, "LB_INITSTORAGE"),
    (0x1A9, "LB_ITEMFROMPOINT"),
    (0x1AA, "LB_INSERTSTRINGUPPER"),
    (0x1AB, "LB_INSERTSTRINGLOWER"),
    (0x1AC, "LB_ADDSTRINGUPPER"),
    (0x1AD, "LB_ADDSTRINGLOWER"),
    (0x1B1, "LB_MULTIPLEADDSTRING"),
    (0x1B1, "LB_MSGMAX"),
    (0x1B2, "LB_GETLISTBOXINFO"),
    (0x1E0, "MN_SETHMENU"),
    (0x1E1, "MN_GETHMENU"),
    (0x1E2, "MN_SIZEWINDOW"),
    (0x1E3, "MN_OPENHIERARCHY"),
    (0x1E4, "MN_CLOSEHIERARCHY"),
    (0x1E5, "MN_SELECTITEM"),
    (0x1E6, "MN_CANCELMENUS"),
    (0x1E7, "MN_SELECTFIRSTVALIDITEM"),
    (0x1EA, "MN_GETPPOPUPMENU"),
    (0x1EB, "MN_FINDMENUWINDOWFROMPOINT"),
    (0x1EC, "MN_SHOWPOPUPWINDOW"),
    (0x1ED, "MN_BUTTONDOWN"),
    (0x1EE, "MN_MOUSEMOVE"),
    (0x1EF, "MN_BUTTONUP"),
    (0x1F0, "MN_SETTIMERTOOPENHIERARCHY"),
    (0x1F1, "MN_DBLCLK"),
    (0x200, "WM_MOUSEFIRST"),
    (0x200, "WM_MOUSEMOVE"),
    (0x201, "WM_LBUTTONDOWN"),
    (0x202, "WM_LBUTTONUP"),
    (0x203, "WM_LBUTTONDBLCLK"),
    (0x204, "WM_RBUTTONDOWN"),
    (0x205, "WM_RBUTTONUP"),
    (0x206, "WM_RBUTTONDBLCLK"),
    (0x207, "WM_MBUTTONDOWN"),
    (0x208, "WM_MBUTTONUP"),
    (0x209, "WM_MBUTTONDBLCLK"),
    (0x20A, "WM_MOUSEWHEEL"),
    (0x20B, "WM_XBUTTONDOWN"),
    (0x20C, "WM_XBUTTONUP"),
    (0x20D, "WM_XBUTTONDBLCLK"),
    (0x20E, "WM_MOUSEHWHEEL"),
    (0x20E, "WM_MOUSELAST"),
    (0x210, "WM_PARENTNOTIFY"),
    (0x211, "WM_ENTERMENULOOP"),
    (0x212, "WM_EXITMENULOOP"),
    (0x213, "WM_NEXTMENU"),
    (0x214, "WM_SIZING"),
    (0x215, "WM_CAPTURECHANGED"),
    (0x216, "WM_MOVING"),
    (0x218, "WM_POWERBROADCAST"),
    (0x219, "WM_DEVICECHANGE"),
    (0x220, "WM_MDICREATE"),
    (0x221, "WM_MDIDESTROY"),
    (0x222, "WM_MDIACTIVATE"),
    (0x223, "WM_MDIRESTORE"),
    (0x224, "WM_MDINEXT"),
    (0x225, "WM_MDIMAXIMIZE"),
    (0x226, "WM_MDITILE"),
    (0x227, "WM_MDICASCADE"),
    (0x228, "WM_MDIICONARRANGE"),
    (0x229, "WM_MDIGETACTIVE"),
    (0x22A, "WM_DROPOBJECT"),
    (0x22B, "WM_QUERYDROPOBJECT"),
    (0x22C, "WM_BEGINDRAG"),
    (0x22D, "WM_DRAGLOOP"),
    (0x22E, "WM_DRAGSELECT"),
    (0x22F, "WM_DRAGMOVE"),
    (0x230, "WM_MDISETMENU"),
    (0x231, "WM_ENTERSIZEMOVE"),
    (0x232, "WM_EXITSIZEMOVE"),
    (0x233, "WM_DROPFILES"),
    (0x234, "WM_MDIREFRESHMENU"),
    (0x238, "WM_POINTERDEVICECHANGE"),
    (0x239, "WM_POINTERDEVICEINRANGE"),
    (0x23A, "WM_POINTERDEVICEOUTOFRANGE"),
    (0x240, "WM_TOUCH"),
    (0x241, "WM_NCPOINTERUPDATE"),
    (0x242, "WM_NCPOINTERDOWN"),
    (0x243, "WM_NCPOINTERUP"),
    (0x245, "WM_POINTERUPDATE"),
    (0x246, "WM_POINTERDOWN"),
    (0x247, "WM_POINTERUP"),
    (0x249, "WM_POINTERENTER"),
    (0x24A, "WM_POINTERLEAVE"),
    (0x24B, "WM_POINTERACTIVATE"),
    (0x24C, "WM_POINTERCAPTURECHANGED"),
    (0x24D, "WM_TOUCHHITTESTING"),
    (0x24E, "WM_POINTERWHEEL"),
    (0x24F, "WM_POINTERHWHEEL"),
    (0x251, "WM_POINTERROUTEDTO"),
    (0x252, "WM_POINTERROUTEDAWAY"),
    (0x253, "WM_POINTERROUTEDRELEASED"),
    (0x280, "WM_HANGEULFIRST"),
    (0x280, "WM_IME_REPORT"),
    (0x280, "WM_KANJIFIRST"),
    (0x281, "WM_IME_SETCONTEXT"),
    (0x282, "WM_IME_NOTIFY"),
    (0x283, "WM_IME_CONTROL"),
    (0x284, "WM_IME_COMPOSITIONFULL"),
    (0x285, "WM_IME_SELECT"),
    (0x286, "WM_IME_CHAR"),
    (0x287, "WM_IME_SYSTEM"),
    (0x288, "WM_IME_REQUEST"),
    (0x290, "WM_IME_KEYDOWN"),
    (0x290, "WM_IMEKEYDOWN"),
    (0x291, "WM_IME_KEYUP"),
    (0x291, "WM_IMEKEYUP"),
    (0x29F, "WM_HANGEULLAST"),
    (0x29F, "WM_KANJILAST"),
    (0x2A0, "WM_NCMOUSEHOVER"),
    (0x2A1, "WM_MOUSEHOVER"),
    (0x2A2, "WM_NCMOUSELEAVE"),
    (0x2A3, "WM_MOUSELEAVE"),
    (0x2AF, "WM_TRACKMOUSEEVENT_LAST"),
    (0x2B1, "WM_WTSSESSION_CHANGE"),
    (0x2C0, "WM_TABLET_FIRST"),
    (0x2C8, "WM_TABLET_ADDED"),
    (0x2C9, "WM_TABLET_DELETED"),
    (0x2CB, "WM_TABLET_FLICK"),
    (0x2CC, "WM_TABLET_QUERYSYSTEMGESTURESTATUS"),
    (0x2DF, "WM_TABLET_LAST"),
    (0x2E0, "WM_DPICHANGED"),
    (0x2E2, "WM_DPICHANGED_BEFOREPARENT"),
    (0x2E3, "WM_DPICHANGED_AFTERPARENT"),
    (0x2E4, "WM_GETDPISCALEDSIZE"),
    (0x300, "WM_CUT"),
    (0x301, "WM_COPY"),
    (0x302, "WM_PASTE"),
    (0x303, "WM_CLEAR"),
    (0x304, "WM_UNDO"),
    (0x305, "WM_RENDERFORMAT"),
    (0x306, "WM_RENDERALLFORMATS"),
    (0x307, "WM_DESTROYCLIPBOARD"),
    (0x308, "WM_DRAWCLIPBOARD"),
    (0x309, "WM_PAINTCLIPBOARD"),
    (0x30A, "WM_VSCROLLCLIPBOARD"),
    (0x30B, "WM_SIZECLIPBOARD"),
    (0x30C, "WM_ASKCBFORMATNAME"),
    (0x30D, "WM_CHANGECBCHAIN"),
    (0x30E, "WM_HSCROLLCLIPBOARD"),
    (0x30F, "WM_QUERYNEWPALETTE"),
    (0x310, "WM_PALETTEISCHANGING"),
    (0x311, "WM_PALETTECHANGED"),
    (0x312, "WM_HOTKEY"),
    (0x313, "WM_SYSMENU"),
    (0x314, "WM_HOOKMSG"),
    (0x315, "WM_EXITPROCESS"),
    (0x316, "WM_WAKETHREAD"),
    (0x317, "WM_PRINT"),
    (0x318, "WM_PRINTCLIENT"),
    (0x319, "WM_APPCOMMAND"),
    (0x31A, "WM_THEMECHANGED"),
    (0x31D, "WM_CLIPBOARDUPDATE"),
    (0x31E, "WM_DWMCOMPOSITIONCHANGED"),
    (0x31F, "WM_DWMNCRENDERINGCHANGED"),
    (0x320, "WM_DWMCOLORIZATIONCOLORCHANGED"),
    (0x321, "WM_DWMWINDOWMAXIMIZEDCHANGE"),
    (0x323, "WM_DWMSENDICONICTHUMBNAIL"),
    (0x326, "WM_DWMSENDICONICLIVEPREVIEWBITMAP"),
    (0x33F, "WM_GETTITLEBARINFOEX"),
    (0x358, "WM_HANDHELDFIRST"),
    (0x35F, "WM_HANDHELDLAST"),

    # HTC - AFX internal messages, see in afxpriv.h file
    (0x360, "WM_QUERYAFXWNDPROC"),  # = WM_AFXFIRST
    (0x361, "WM_SIZEPARENT"),
    (0x362, "WM_SETMESSAGESTRING"),
    (0x363, "WM_IDLEUPDATECMDUI"),
    (0x364, "WM_INITIALUPDATE"),
    (0x365, "WM_COMMANDHELP"),
    (0x366, "WM_HELPHITTEST"),
    (0x367, "WM_EXITHELPMODE"),
    (0x368, "WM_RECALCPARENT"),
    (0x369, "WM_SIZECHILD"),
    (0x36A, "WM_KICKIDLE"),
    (0x36B, "WM_QUERYCENTERWND"),
    (0x36C, "WM_DISABLEMODAL"),
    (0x36D, "WM_FLOATSTATUS"),
    (0x36E, "WM_ACTIVATETOPLEVEL"),
    (0x36F, "WM_AFX_RESERVED_036F"),
    (0x370, "WM_AFX_RESERVED_0370"),
    (0x371, "WM_AFX_RESERVED_0371"),
    (0x372, "WM_AFX_RESERVED_0372"),
    (0x372, "WM_SOCKET_NOTIFY"),
    (0x374, "WM_SOCKET_DEAD"),
    (0x375, "WM_POPMESSAGESTRING"),
    (0x376, "WM_HELPPROMPTADDR"),
    (0x376, "WM_OCC_LOADFROMSTREAM"),
    (0x377, "WM_OCC_LOADFROMSTORAGE"),
    (0x378, "WM_OCC_INITNEW"),
    (0x379, "WM_QUEUE_SENTINEL"),
    (0x37A, "WM_OCC_LOADFROMSTREAM_EX"),
    (0x37B, "WM_OCC_LOADFROMSTORAGE_EX"),
    (0x37C, "WM_MFC_INITCTRL"),
    (0x37D, "WM_AFX_RESERVED_037D"),
    (0x37E, "WM_AFX_RESERVED_037E"),
    (0x37F, "WM_FORWARDMSG"),  # = WM_AFXLAST
    # end of AFX private messages

    (0x380, "WM_PENWINFIRST"),
    (0x381, "WM_RCRESULT"),
    (0x382, "WM_HOOKRCRESULT"),
    (0x383, "WM_GLOBALRCCHANGE"),
    (0x383, "WM_PENMISCINFO"),
    (0x384, "WM_SKB"),
    (0x385, "WM_HEDITCTL"),
    (0x385, "WM_PENCTL"),
    (0x386, "WM_PENMISC"),
    (0x387, "WM_CTLINIT"),
    (0x388, "WM_PENEVENT"),
    (0x38F, "WM_PENWINLAST"),
    (0x390, "WM_COALESCE_FIRST"),
    (0x390, "WM_INTERNAL_COALESCE_FIRST"),
    (0x39F, "WM_COALESCE_LAST"),
    (0x3A0, "WM_MM_RESERVED_FIRST"),
    (0x3B0, "WM_INTERNAL_COALESCE_LAST"),
    (0x3DF, "WM_MM_RESERVED_LAST"),
    (0x3E0, "WM_DDE_FIRST"),
    (0x3E0, "WM_DDE_INITIATE"),
    (0x3E1, "WM_DDE_TERMINATE"),
    (0x3E2, "WM_DDE_ADVISE"),
    (0x3E3, "WM_DDE_UNADVISE"),
    (0x3E4, "WM_DDE_ACK"),
    (0x3E5, "WM_DDE_DATA"),
    (0x3E6, "WM_DDE_REQUEST"),
    (0x3E7, "WM_DDE_POKE"),
    (0x3E8, "WM_DDE_EXECUTE"),
    (0x3E8, "WM_DDE_LAST"),
    (0x3FD, "WM_DBNOTIFICATION"),
    (0x3FE, "WM_NETCONNECT"),
    (0x3FF, "WM_HIBERNATE"),
    (0x400, "DDM_SETFMT"),
    (0x400, "DM_GETDEFID"),
    (0x400, "NIN_SELECT"),
    (0x400, "TBM_GETPOS"),
    (0x400, "WM_CAP_START"),
    (0x400, "WM_PSD_PAGESETUPDLG"),
    (0x400, "WM_USER"),
    (0x401, "CBEM_INSERTITEMA"),
    (0x401, "DDM_DRAW"),
    (0x401, "DM_SETDEFID"),
    (0x401, "HKM_SETHOTKEY"),
    (0x401, "NIN_KEYSELECT"),
    (0x401, "PBM_SETRANGE"),
    (0x401, "RB_INSERTBANDA"),
    (0x401, "SB_SETTEXTA"),
    (0x401, "TB_ENABLEBUTTON"),
    (0x401, "TBM_GETRANGEMIN"),
    (0x401, "TTM_ACTIVATE"),
    (0x401, "WM_CAP_GET_CAPSTREAMPTR"),
    (0x401, "WM_CHOOSEFONT_GETLOGFONT"),
    (0x401, "WM_PSD_FULLPAGERECT"),
    (0x402, "CBEM_SETIMAGELIST"),
    (0x402, "DDM_CLOSE"),
    (0x402, "DM_REPOSITION"),
    (0x402, "HKM_GETHOTKEY"),
    (0x402, "NIN_BALLOONSHOW"),
    (0x402, "PBM_SETPOS"),
    (0x402, "RB_DELETEBAND"),
    (0x402, "SB_GETTEXTA"),
    (0x402, "TB_CHECKBUTTON"),
    (0x402, "TBM_GETRANGEMAX"),
    (0x402, "WM_CAP_SET_CALLBACK_ERRORA"),
    (0x402, "WM_PSD_MINMARGINRECT"),
    (0x403, "CBEM_GETIMAGELIST"),
    (0x403, "DDM_BEGIN"),
    (0x403, "HKM_SETRULES"),
    (0x403, "NIN_BALLOONHIDE"),
    (0x403, "PBM_DELTAPOS"),
    (0x403, "RB_GETBARINFO"),
    (0x403, "SB_GETTEXTLENGTHA"),
    (0x403, "TB_PRESSBUTTON"),
    (0x403, "TBM_GETTIC"),
    (0x403, "TTM_SETDELAYTIME"),
    (0x403, "WM_CAP_SET_CALLBACK_STATUSA"),
    (0x403, "WM_PSD_MARGINRECT"),
    (0x404, "CBEM_GETITEMA"),
    (0x404, "DDM_END"),
    (0x404, "NIN_BALLOONTIMEOUT"),
    (0x404, "PBM_SETSTEP"),
    (0x404, "RB_SETBARINFO"),
    (0x404, "SB_SETPARTS"),
    (0x404, "TB_HIDEBUTTON"),
    (0x404, "TBM_SETTIC"),
    (0x404, "TTM_ADDTOOLA"),
    (0x404, "WM_CAP_SET_CALLBACK_YIELD"),
    (0x404, "WM_PSD_GREEKTEXTRECT"),
    (0x405, "CBEM_SETITEMA"),
    (0x405, "NIN_BALLOONUSERCLICK"),
    (0x405, "PBM_STEPIT"),
    (0x405, "TB_INDETERMINATE"),
    (0x405, "TBM_SETPOS"),
    (0x405, "TTM_DELTOOLA"),
    (0x405, "WM_CAP_SET_CALLBACK_FRAME"),
    (0x405, "WM_PSD_ENVSTAMPRECT"),
    (0x406, "CBEM_GETCOMBOCONTROL"),
    (0x406, "NIN_POPUPOPEN"),
    (0x406, "PBM_SETRANGE32"),
    (0x406, "RB_SETBANDINFOA"),
    (0x406, "SB_GETPARTS"),
    (0x406, "TB_MARKBUTTON"),
    (0x406, "TBM_SETRANGE"),
    (0x406, "TTM_NEWTOOLRECTA"),
    (0x406, "WM_CAP_SET_CALLBACK_VIDEOSTREAM"),
    (0x406, "WM_PSD_YAFULLPAGERECT"),
    (0x407, "CBEM_GETEDITCONTROL"),
    (0x407, "NIN_POPUPCLOSE"),
    (0x407, "PBM_GETRANGE"),
    (0x407, "RB_SETPARENT"),
    (0x407, "SB_GETBORDERS"),
    (0x407, "TBM_SETRANGEMIN"),
    (0x407, "TTM_RELAYEVENT"),
    (0x407, "WM_CAP_SET_CALLBACK_WAVESTREAM"),
    (0x408, "CBEM_SETEXSTYLE"),
    (0x408, "PBM_GETPOS"),
    (0x408, "RB_HITTEST"),
    (0x408, "SB_SETMINHEIGHT"),
    (0x408, "TBM_SETRANGEMAX"),
    (0x408, "TTM_GETTOOLINFOA"),
    (0x408, "WM_CAP_GET_USER_DATA"),
    (0x409, "CBEM_GETEXSTYLE"),
    (0x409, "CBEM_GETEXTENDEDSTYLE"),
    (0x409, "PBM_SETBARCOLOR"),
    (0x409, "RB_GETRECT"),
    (0x409, "SB_SIMPLE"),
    (0x409, "TB_ISBUTTONENABLED"),
    (0x409, "TBM_CLEARTICS"),
    (0x409, "TTM_SETTOOLINFOA"),
    (0x409, "WM_CAP_SET_USER_DATA"),
    (0x40A, "CBEM_HASEDITCHANGED"),
    (0x40A, "PBM_SETMARQUEE"),
    (0x40A, "RB_INSERTBANDW"),
    (0x40A, "SB_GETRECT"),
    (0x40A, "TB_ISBUTTONCHECKED"),
    (0x40A, "TBM_SETSEL"),
    (0x40A, "TTM_HITTESTA"),
    (0x40A, "WIZ_QUERYNUMPAGES"),
    (0x40A, "WM_CAP_DRIVER_CONNECT"),
    (0x40B, "CBEM_INSERTITEMW"),
    (0x40B, "RB_SETBANDINFOW"),
    (0x40B, "SB_SETTEXTW"),
    (0x40B, "TB_ISBUTTONPRESSED"),
    (0x40B, "TBM_SETSELSTART"),
    (0x40B, "TTM_GETTEXTA"),
    (0x40B, "WIZ_NEXT"),
    (0x40B, "WM_CAP_DRIVER_DISCONNECT"),
    (0x40C, "CBEM_SETITEMW"),
    (0x40C, "RB_GETBANDCOUNT"),
    (0x40C, "SB_GETTEXTLENGTHW"),
    (0x40C, "TB_ISBUTTONHIDDEN"),
    (0x40C, "TBM_SETSELEND"),
    (0x40C, "TTM_UPDATETIPTEXTA"),
    (0x40C, "WIZ_PREV"),
    (0x40C, "WM_CAP_DRIVER_GET_NAMEA"),
    (0x40D, "CBEM_GETITEMW"),
    (0x40D, "PBM_GETSTEP"),
    (0x40D, "RB_GETROWCOUNT"),
    (0x40D, "SB_GETTEXTW"),
    (0x40D, "TB_ISBUTTONINDETERMINATE"),
    (0x40D, "TTM_GETTOOLCOUNT"),
    (0x40D, "WM_CAP_DRIVER_GET_VERSIONA"),
    (0x40E, "CBEM_SETEXTENDEDSTYLE"),
    (0x40E, "PBM_GETBKCOLOR"),
    (0x40E, "RB_GETROWHEIGHT"),
    (0x40E, "SB_ISSIMPLE"),
    (0x40E, "TB_ISBUTTONHIGHLIGHTED"),
    (0x40E, "TBM_GETPTICS"),
    (0x40E, "TTM_ENUMTOOLSA"),
    (0x40E, "WM_CAP_DRIVER_GET_CAPS"),
    (0x40F, "PBM_GETBARCOLOR"),
    (0x40F, "SB_SETICON"),
    (0x40F, "TBM_GETTICPOS"),
    (0x40F, "TTM_GETCURRENTTOOLA"),
    (0x410, "PBM_SETSTATE"),
    (0x410, "RB_IDTOINDEX"),
    (0x410, "SB_SETTIPTEXTA"),
    (0x410, "TBM_GETNUMTICS"),
    (0x410, "TTM_WINDOWFROMPOINT"),
    (0x411, "PBM_GETSTATE"),
    (0x411, "RB_GETTOOLTIPS"),
    (0x411, "SB_SETTIPTEXTW"),
    (0x411, "TB_SETSTATE"),
    (0x411, "TBM_GETSELSTART"),
    (0x411, "TTM_TRACKACTIVATE"),
    (0x412, "RB_SETTOOLTIPS"),
    (0x412, "SB_GETTIPTEXTA"),
    (0x412, "TB_GETSTATE"),
    (0x412, "TBM_GETSELEND"),
    (0x412, "TTM_TRACKPOSITION"),
    (0x413, "RB_SETBKCOLOR"),
    (0x413, "SB_GETTIPTEXTW"),
    (0x413, "TB_ADDBITMAP"),
    (0x413, "TBM_CLEARSEL"),
    (0x413, "TTM_SETTIPBKCOLOR"),
    (0x414, "RB_GETBKCOLOR"),
    (0x414, "SB_GETICON"),
    (0x414, "TB_ADDBUTTONSA"),
    (0x414, "TBM_SETTICFREQ"),
    (0x414, "TTM_SETTIPTEXTCOLOR"),
    (0x414, "WM_CAP_FILE_SET_CAPTURE_FILEA"),
    (0x415, "RB_SETTEXTCOLOR"),
    (0x415, "TB_INSERTBUTTONA"),
    (0x415, "TBM_SETPAGESIZE"),
    (0x415, "TTM_GETDELAYTIME"),
    (0x415, "WM_CAP_FILE_GET_CAPTURE_FILEA"),
    (0x416, "RB_GETTEXTCOLOR"),
    (0x416, "TB_DELETEBUTTON"),
    (0x416, "TBM_GETPAGESIZE"),
    (0x416, "TTM_GETTIPBKCOLOR"),
    (0x416, "WM_CAP_FILE_ALLOCATE"),
    (0x417, "RB_SIZETORECT"),
    (0x417, "TB_GETBUTTON"),
    (0x417, "TBM_SETLINESIZE"),
    (0x417, "TTM_GETTIPTEXTCOLOR"),
    (0x417, "WM_CAP_FILE_SAVEASA"),
    (0x418, "RB_BEGINDRAG"),
    (0x418, "TB_BUTTONCOUNT"),
    (0x418, "TBM_GETLINESIZE"),
    (0x418, "TTM_SETMAXTIPWIDTH"),
    (0x418, "WM_CAP_FILE_SET_INFOCHUNK"),
    (0x419, "RB_ENDDRAG"),
    (0x419, "TB_COMMANDTOINDEX"),
    (0x419, "TBM_GETTHUMBRECT"),
    (0x419, "TTM_GETMAXTIPWIDTH"),
    (0x419, "WM_CAP_FILE_SAVEDIBA"),
    (0x41A, "RB_DRAGMOVE"),
    (0x41A, "TB_SAVERESTOREA"),
    (0x41A, "TBM_GETCHANNELRECT"),
    (0x41A, "TTM_SETMARGIN"),
    (0x41B, "RB_GETBARHEIGHT"),
    (0x41B, "TB_CUSTOMIZE"),
    (0x41B, "TBM_SETTHUMBLENGTH"),
    (0x41B, "TTM_GETMARGIN"),
    (0x41C, "RB_GETBANDINFOW"),
    (0x41C, "TB_ADDSTRINGA"),
    (0x41C, "TBM_GETTHUMBLENGTH"),
    (0x41C, "TTM_POP"),
    (0x41D, "RB_GETBANDINFOA"),
    (0x41D, "TB_GETITEMRECT"),
    (0x41D, "TBM_SETTOOLTIPS"),
    (0x41D, "TTM_UPDATE"),
    (0x41E, "RB_MINIMIZEBAND"),
    (0x41E, "TB_BUTTONSTRUCTSIZE"),
    (0x41E, "TBM_GETTOOLTIPS"),
    (0x41E, "TTM_GETBUBBLESIZE"),
    (0x41E, "WM_CAP_EDIT_COPY"),
    (0x41F, "RB_MAXIMIZEBAND"),
    (0x41F, "TB_SETBUTTONSIZE"),
    (0x41F, "TBM_SETTIPSIDE"),
    (0x41F, "TTM_ADJUSTRECT"),
    (0x420, "MSG_FTS_JUMP_HASH"),
    (0x420, "TB_SETBITMAPSIZE"),
    (0x420, "TBM_SETBUDDY"),
    (0x420, "TTM_SETTITLEA"),
    (0x421, "MSG_FTS_JUMP_VA"),
    (0x421, "TB_AUTOSIZE"),
    (0x421, "TBM_GETBUDDY"),
    (0x421, "TTM_SETTITLEW"),
    (0x422, "MSG_FTS_GET_TITLE"),
    (0x422, "RB_GETBANDBORDERS"),
    (0x422, "TBM_SETPOSNOTIFY"),
    (0x423, "MSG_FTS_JUMP_QWORD"),
    (0x423, "RB_SHOWBAND"),
    (0x423, "TB_GETTOOLTIPS"),
    (0x423, "WM_CAP_SET_AUDIOFORMAT"),
    (0x424, "MSG_REINDEX_REQUEST"),
    (0x424, "TB_SETTOOLTIPS"),
    (0x424, "WM_CAP_GET_AUDIOFORMAT"),
    (0x425, "MSG_FTS_WHERE_IS_IT"),
    (0x425, "RB_SETPALETTE"),
    (0x425, "TB_SETPARENT"),
    (0x426, "RB_GETPALETTE"),
    (0x427, "RB_MOVEBAND"),
    (0x427, "TB_SETROWS"),
    (0x428, "RB_GETBANDMARGINS"),
    (0x428, "TB_GETROWS"),
    (0x429, "RB_SETEXTENDEDSTYLE"),
    (0x429, "TB_GETBITMAPFLAGS"),
    (0x429, "WM_CAP_DLG_VIDEOFORMAT"),
    (0x42A, "RB_GETEXTENDEDSTYLE"),
    (0x42A, "TB_SETCMDID"),
    (0x42A, "WM_CAP_DLG_VIDEOSOURCE"),
    (0x42B, "RB_PUSHCHEVRON"),
    (0x42B, "TB_CHANGEBITMAP"),
    (0x42B, "WM_CAP_DLG_VIDEODISPLAY"),
    (0x42C, "RB_SETBANDWIDTH"),
    (0x42C, "TB_GETBITMAP"),
    (0x42C, "WM_CAP_GET_VIDEOFORMAT"),
    (0x42D, "MSG_GET_DEFFONT"),
    (0x42D, "TB_GETBUTTONTEXTA"),
    (0x42D, "WM_CAP_SET_VIDEOFORMAT"),
    (0x42E, "TB_REPLACEBITMAP"),
    (0x42E, "WM_CAP_DLG_VIDEOCOMPRESSION"),
    (0x42F, "TB_SETINDENT"),
    (0x430, "TB_SETIMAGELIST"),
    (0x431, "TB_GETIMAGELIST"),
    (0x432, "EM_CANPASTE"),
    (0x432, "TB_LOADIMAGES"),
    (0x432, "TTM_ADDTOOLW"),
    (0x432, "WM_CAP_SET_PREVIEW"),
    (0x433, "EM_DISPLAYBAND"),
    (0x433, "TB_GETRECT"),
    (0x433, "TTM_DELTOOLW"),
    (0x433, "WM_CAP_SET_OVERLAY"),
    (0x434, "EM_EXGETSEL"),
    (0x434, "TB_SETHOTIMAGELIST"),
    (0x434, "TTM_NEWTOOLRECTW"),
    (0x434, "WM_CAP_SET_PREVIEWRATE"),
    (0x435, "EM_EXLIMITTEXT"),
    (0x435, "TB_GETHOTIMAGELIST"),
    (0x435, "TTM_GETTOOLINFOW"),
    (0x435, "WM_CAP_SET_SCALE"),
    (0x436, "EM_EXLINEFROMCHAR"),
    (0x436, "TB_SETDISABLEDIMAGELIST"),
    (0x436, "TTM_SETTOOLINFOW"),
    (0x436, "WM_CAP_GET_STATUS"),
    (0x437, "EM_EXSETSEL"),
    (0x437, "TB_GETDISABLEDIMAGELIST"),
    (0x437, "TTM_HITTESTW"),
    (0x437, "WM_CAP_SET_SCROLL"),
    (0x438, "EM_FINDTEXT"),
    (0x438, "TB_SETSTYLE"),
    (0x438, "TTM_GETTEXTW"),
    (0x439, "EM_FORMATRANGE"),
    (0x439, "TB_GETSTYLE"),
    (0x439, "TTM_UPDATETIPTEXTW"),
    (0x43A, "EM_GETCHARFORMAT"),
    (0x43A, "TB_GETBUTTONSIZE"),
    (0x43A, "TTM_ENUMTOOLSW"),
    (0x43B, "EM_GETEVENTMASK"),
    (0x43B, "TB_SETBUTTONWIDTH"),
    (0x43B, "TTM_GETCURRENTTOOLW"),
    (0x43C, "EM_GETOLEINTERFACE"),
    (0x43C, "TB_SETMAXTEXTROWS"),
    (0x43C, "WM_CAP_GRAB_FRAME"),
    (0x43D, "EM_GETPARAFORMAT"),
    (0x43D, "TB_GETTEXTROWS"),
    (0x43D, "WM_CAP_GRAB_FRAME_NOSTOP"),
    (0x43E, "EM_GETSELTEXT"),
    (0x43E, "TB_GETOBJECT"),
    (0x43E, "WM_CAP_SEQUENCE"),
    (0x43F, "EM_HIDESELECTION"),
    (0x43F, "TB_GETBUTTONINFOW"),
    (0x43F, "WM_CAP_SEQUENCE_NOFILE"),
    (0x440, "EM_PASTESPECIAL"),
    (0x440, "TB_SETBUTTONINFOW"),
    (0x440, "WM_CAP_SET_SEQUENCE_SETUP"),
    (0x441, "EM_REQUESTRESIZE"),
    (0x441, "TB_GETBUTTONINFOA"),
    (0x441, "WM_CAP_GET_SEQUENCE_SETUP"),
    (0x442, "EM_SELECTIONTYPE"),
    (0x442, "TB_SETBUTTONINFOA"),
    (0x442, "WM_CAP_SET_MCI_DEVICEA"),
    (0x443, "EM_SETBKGNDCOLOR"),
    (0x443, "TB_INSERTBUTTONW"),
    (0x443, "WM_CAP_GET_MCI_DEVICEA"),
    (0x444, "EM_SETCHARFORMAT"),
    (0x444, "TB_ADDBUTTONSW"),
    (0x444, "WM_CAP_STOP"),
    (0x445, "EM_SETEVENTMASK"),
    (0x445, "TB_HITTEST"),
    (0x445, "WM_CAP_ABORT"),
    (0x446, "EM_SETOLECALLBACK"),
    (0x446, "TB_SETDRAWTEXTFLAGS"),
    (0x446, "WM_CAP_SINGLE_FRAME_OPEN"),
    (0x447, "EM_SETPARAFORMAT"),
    (0x447, "TB_GETHOTITEM"),
    (0x447, "WM_CAP_SINGLE_FRAME_CLOSE"),
    (0x448, "EM_SETTARGETDEVICE"),
    (0x448, "TB_SETHOTITEM"),
    (0x448, "WM_CAP_SINGLE_FRAME"),
    (0x449, "EM_STREAMIN"),
    (0x449, "TB_SETANCHORHIGHLIGHT"),
    (0x44A, "EM_STREAMOUT"),
    (0x44A, "TB_GETANCHORHIGHLIGHT"),
    (0x44B, "EM_GETTEXTRANGE"),
    (0x44B, "TB_GETBUTTONTEXTW"),
    (0x44C, "EM_FINDWORDBREAK"),
    (0x44C, "TB_SAVERESTOREW"),
    (0x44D, "EM_SETOPTIONS"),
    (0x44D, "TB_ADDSTRINGW"),
    (0x44E, "EM_GETOPTIONS"),
    (0x44E, "TB_MAPACCELERATORA"),
    (0x44F, "EM_FINDTEXTEX"),
    (0x44F, "TB_GETINSERTMARK"),
    (0x450, "EM_GETWORDBREAKPROCEX"),
    (0x450, "TB_SETINSERTMARK"),
    (0x450, "WM_CAP_PAL_OPENA"),
    (0x451, "EM_SETWORDBREAKPROCEX"),
    (0x451, "TB_INSERTMARKHITTEST"),
    (0x451, "WM_CAP_PAL_SAVEA"),
    (0x452, "EM_SETUNDOLIMIT"),
    (0x452, "TB_MOVEBUTTON"),
    (0x452, "WM_CAP_PAL_PASTE"),
    (0x453, "TB_GETMAXSIZE"),
    (0x453, "WM_CAP_PAL_AUTOCREATE"),
    (0x454, "EM_REDO"),
    (0x454, "TB_SETEXTENDEDSTYLE"),
    (0x454, "WM_CAP_PAL_MANUALCREATE"),
    (0x455, "EM_CANREDO"),
    (0x455, "TB_GETEXTENDEDSTYLE"),
    (0x455, "WM_CAP_SET_CALLBACK_CAPCONTROL"),
    (0x456, "EM_GETUNDONAME"),
    (0x456, "TB_GETPADDING"),
    (0x457, "EM_GETREDONAME"),
    (0x457, "TB_SETPADDING"),
    (0x458, "EM_STOPGROUPTYPING"),
    (0x458, "TB_SETINSERTMARKCOLOR"),
    (0x459, "EM_SETTEXTMODE"),
    (0x459, "TB_GETINSERTMARKCOLOR"),
    (0x45A, "EM_GETTEXTMODE"),
    (0x45A, "TB_MAPACCELERATORW"),
    (0x45B, "EM_AUTOURLDETECT"),
    (0x45B, "TB_GETSTRINGW"),
    (0x45C, "EM_GETAUTOURLDETECT"),
    (0x45C, "TB_GETSTRINGA"),
    (0x45D, "EM_SETPALETTE"),
    (0x45D, "TB_SETBOUNDINGSIZE"),
    (0x45E, "EM_GETTEXTEX"),
    (0x45E, "TB_SETHOTITEM2"),
    (0x45F, "EM_GETTEXTLENGTHEX"),
    (0x45F, "TB_HASACCELERATOR"),
    (0x460, "EM_SHOWSCROLLBAR"),
    (0x460, "TB_SETLISTGAP"),
    (0x461, "EM_SETTEXTEX"),
    (0x462, "TB_GETIMAGELISTCOUNT"),
    (0x463, "TAPI_REPLY"),
    (0x463, "TB_GETIDEALSIZE"),
    (0x464, "ACM_OPENA"),
    (0x464, "BFFM_SETSTATUSTEXTA"),
    (0x464, "CDM_FIRST"),
    (0x464, "CDM_GETSPEC"),
    (0x464, "EM_SETPUNCTUATION"),
    (0x464, "IPM_CLEARADDRESS"),
    (0x464, "MCIWNDM_GETDEVICEID"),
    (0x464, "WM_CAP_UNICODE_START"),
    (0x465, "ACM_PLAY"),
    (0x465, "BFFM_ENABLEOK"),
    (0x465, "CDM_GETFILEPATH"),
    (0x465, "EM_GETPUNCTUATION"),
    (0x465, "IPM_SETADDRESS"),
    (0x465, "MCIWNDM_SENDSTRINGA"),
    (0x465, "PSM_SETCURSEL"),
    (0x465, "TB_GETMETRICS"),
    (0x465, "UDM_SETRANGE"),
    (0x465, "WM_CHOOSEFONT_SETLOGFONT"),
    (0x466, "ACM_STOP"),
    (0x466, "BFFM_SETSELECTIONA"),
    (0x466, "CDM_GETFOLDERPATH"),
    (0x466, "EM_SETWORDWRAPMODE"),
    (0x466, "IPM_GETADDRESS"),
    (0x466, "MCIWNDM_GETPOSITIONA"),
    (0x466, "PSM_REMOVEPAGE"),
    (0x466, "TB_SETMETRICS"),
    (0x466, "UDM_GETRANGE"),
    (0x466, "WM_CAP_SET_CALLBACK_ERRORW"),
    (0x466, "WM_CHOOSEFONT_SETFLAGS"),
    (0x467, "ACM_OPENW"),
    (0x467, "BFFM_SETSELECTIONW"),
    (0x467, "CDM_GETFOLDERIDLIST"),
    (0x467, "EM_GETWORDWRAPMODE"),
    (0x467, "IPM_SETRANGE"),
    (0x467, "MCIWNDM_GETSTART"),
    (0x467, "PSM_ADDPAGE"),
    (0x467, "TB_GETITEMDROPDOWNRECT"),
    (0x467, "UDM_SETPOS"),
    (0x467, "WM_CAP_SET_CALLBACK_STATUSW"),
    (0x468, "ACM_ISPLAYING"),
    (0x468, "BFFM_SETSTATUSTEXTW"),
    (0x468, "CDM_SETCONTROLTEXT"),
    (0x468, "EM_SETIMECOLOR"),
    (0x468, "IPM_SETFOCUS"),
    (0x468, "MCIWNDM_GETLENGTH"),
    (0x468, "PSM_CHANGED"),
    (0x468, "TB_SETPRESSEDIMAGELIST"),
    (0x468, "UDM_GETPOS"),
    (0x469, "BFFM_SETOKTEXT"),
    (0x469, "CDM_HIDECONTROL"),
    (0x469, "EM_GETIMECOLOR"),
    (0x469, "IPM_ISBLANK"),
    (0x469, "MCIWNDM_GETEND"),
    (0x469, "PSM_RESTARTWINDOWS"),
    (0x469, "TB_GETPRESSEDIMAGELIST"),
    (0x469, "UDM_SETBUDDY"),
    (0x46A, "BFFM_SETEXPANDED"),
    (0x46A, "CDM_SETDEFEXT"),
    (0x46A, "EM_SETIMEOPTIONS"),
    (0x46A, "MCIWNDM_GETMODEA"),
    (0x46A, "PSM_REBOOTSYSTEM"),
    (0x46A, "UDM_GETBUDDY"),
    (0x46B, "EM_GETIMEOPTIONS"),
    (0x46B, "MCIWNDM_EJECT"),
    (0x46B, "PSM_CANCELTOCLOSE"),
    (0x46B, "UDM_SETACCEL"),
    (0x46C, "EM_CONVPOSITION"),
    (0x46C, "MCIWNDM_SETZOOM"),
    (0x46C, "PSM_QUERYSIBLINGS"),
    (0x46C, "UDM_GETACCEL"),
    (0x46D, "MCIWNDM_GETZOOM"),
    (0x46D, "PSM_UNCHANGED"),
    (0x46D, "UDM_SETBASE"),
    (0x46E, "MCIWNDM_SETVOLUME"),
    (0x46E, "PSM_APPLY"),
    (0x46E, "UDM_GETBASE"),
    (0x46F, "MCIWNDM_GETVOLUME"),
    (0x46F, "PSM_SETTITLEA"),
    (0x46F, "UDM_SETRANGE32"),
    (0x470, "MCIWNDM_SETSPEED"),
    (0x470, "PSM_SETWIZBUTTONS"),
    (0x470, "UDM_GETRANGE32"),
    (0x470, "WM_CAP_DRIVER_GET_NAMEW"),
    (0x471, "MCIWNDM_GETSPEED"),
    (0x471, "PSM_PRESSBUTTON"),
    (0x471, "UDM_SETPOS32"),
    (0x471, "WM_CAP_DRIVER_GET_VERSIONW"),
    (0x472, "MCIWNDM_SETREPEAT"),
    (0x472, "PSM_SETCURSELID"),
    (0x472, "UDM_GETPOS32"),
    (0x473, "MCIWNDM_GETREPEAT"),
    (0x473, "PSM_SETFINISHTEXTA"),
    (0x474, "PSM_GETTABCONTROL"),
    (0x475, "PSM_ISDIALOGMESSAGE"),
    (0x476, "MCIWNDM_REALIZE"),
    (0x476, "PSM_GETCURRENTPAGEHWND"),
    (0x477, "MCIWNDM_SETTIMEFORMATA"),
    (0x477, "PSM_INSERTPAGE"),
    (0x478, "EM_SETLANGOPTIONS"),
    (0x478, "MCIWNDM_GETTIMEFORMATA"),
    (0x478, "PSM_SETTITLEW"),
    (0x478, "WM_CAP_FILE_SET_CAPTURE_FILEW"),
    (0x479, "EM_GETLANGOPTIONS"),
    (0x479, "MCIWNDM_VALIDATEMEDIA"),
    (0x479, "PSM_SETFINISHTEXTW"),
    (0x479, "WM_CAP_FILE_GET_CAPTURE_FILEW"),
    (0x47A, "EM_GETIMECOMPMODE"),
    (0x47A, "MCIWNDM_PLAYFROM"),
    (0x47B, "EM_FINDTEXTW"),
    (0x47B, "MCIWNDM_PLAYTO"),
    (0x47B, "WM_CAP_FILE_SAVEASW"),
    (0x47C, "EM_FINDTEXTEXW"),
    (0x47C, "MCIWNDM_GETFILENAMEA"),
    (0x47D, "EM_RECONVERSION"),
    (0x47D, "MCIWNDM_GETDEVICEA"),
    (0x47D, "PSM_SETHEADERTITLEA"),
    (0x47D, "WM_CAP_FILE_SAVEDIBW"),
    (0x47E, "EM_SETIMEMODEBIAS"),
    (0x47E, "MCIWNDM_GETPALETTE"),
    (0x47E, "PSM_SETHEADERTITLEW"),
    (0x47F, "EM_GETIMEMODEBIAS"),
    (0x47F, "MCIWNDM_SETPALETTE"),
    (0x47F, "PSM_SETHEADERSUBTITLEA"),
    (0x480, "MCIWNDM_GETERRORA"),
    (0x480, "PSM_SETHEADERSUBTITLEW"),
    (0x481, "MCIWNDM_SETTIMERS"),
    (0x481, "PSM_HWNDTOINDEX"),
    (0x482, "MCIWNDM_SETACTIVETIMER"),
    (0x482, "PSM_INDEXTOHWND"),
    (0x483, "MCIWNDM_SETINACTIVETIMER"),
    (0x483, "PSM_PAGETOINDEX"),
    (0x484, "MCIWNDM_GETACTIVETIMER"),
    (0x484, "PSM_INDEXTOPAGE"),
    (0x485, "DL_BEGINDRAG"),
    (0x485, "MCIWNDM_GETINACTIVETIMER"),
    (0x485, "PSM_IDTOINDEX"),
    (0x486, "DL_DRAGGING"),
    (0x486, "MCIWNDM_NEWA"),
    (0x486, "PSM_INDEXTOID"),
    (0x487, "DL_DROPPED"),
    (0x487, "MCIWNDM_CHANGESTYLES"),
    (0x487, "PSM_GETRESULT"),
    (0x488, "DL_CANCELDRAG"),
    (0x488, "MCIWNDM_GETSTYLES"),
    (0x488, "PSM_RECALCPAGESIZES"),
    (0x489, "MCIWNDM_GETALIAS"),
    (0x489, "PSM_SETNEXTTEXTW"),
    (0x48A, "MCIWNDM_RETURNSTRINGA"),
    (0x48A, "PSM_SHOWWIZBUTTONS"),
    (0x48B, "MCIWNDM_PLAYREVERSE"),
    (0x48B, "PSM_ENABLEWIZBUTTONS"),
    (0x48C, "MCIWNDM_GET_SOURCE"),
    (0x48C, "PSM_SETBUTTONTEXTW"),
    (0x48D, "MCIWNDM_PUT_SOURCE"),
    (0x48E, "MCIWNDM_GET_DEST"),
    (0x48F, "MCIWNDM_PUT_DEST"),
    (0x490, "MCIWNDM_CAN_PLAY"),
    (0x491, "MCIWNDM_CAN_WINDOW"),
    (0x492, "MCIWNDM_CAN_RECORD"),
    (0x493, "MCIWNDM_CAN_SAVE"),
    (0x494, "MCIWNDM_CAN_EJECT"),
    (0x495, "MCIWNDM_CAN_CONFIG"),
    (0x496, "IE_GETINK"),
    (0x496, "IE_MSGFIRST"),
    (0x496, "MCIWNDM_PALETTEKICK"),
    (0x497, "IE_SETINK"),
    (0x497, "MCIWNDM_OPENINTERFACE"),
    (0x498, "IE_GETPENTIP"),
    (0x498, "MCIWNDM_SETOWNER"),
    (0x499, "IE_SETPENTIP"),
    (0x499, "MCIWNDM_OPENA"),
    (0x49A, "IE_GETERASERTIP"),
    (0x49B, "IE_SETERASERTIP"),
    (0x49C, "IE_GETBKGND"),
    (0x49D, "IE_SETBKGND"),
    (0x49E, "IE_GETGRIDORIGIN"),
    (0x49F, "IE_SETGRIDORIGIN"),
    (0x4A0, "IE_GETGRIDPEN"),
    (0x4A1, "IE_SETGRIDPEN"),
    (0x4A2, "IE_GETGRIDSIZE"),
    (0x4A3, "IE_SETGRIDSIZE"),
    (0x4A4, "IE_GETMODE"),
    (0x4A5, "IE_SETMODE"),
    (0x4A6, "IE_GETINKRECT"),
    (0x4A6, "WM_CAP_SET_MCI_DEVICEW"),
    (0x4A7, "WM_CAP_GET_MCI_DEVICEW"),
    (0x4B4, "WM_CAP_PAL_OPENW"),
    (0x4B5, "WM_CAP_END"),
    (0x4B5, "WM_CAP_PAL_SAVEW"),
    (0x4B5, "WM_CAP_UNICODE_END"),
    (0x4B8, "IE_GETAPPDATA"),
    (0x4B9, "IE_SETAPPDATA"),
    (0x4BA, "IE_GETDRAWOPTS"),
    (0x4BB, "IE_SETDRAWOPTS"),
    (0x4BC, "IE_GETFORMAT"),
    (0x4BD, "IE_SETFORMAT"),
    (0x4BE, "IE_GETINKINPUT"),
    (0x4BF, "IE_SETINKINPUT"),
    (0x4C0, "IE_GETNOTIFY"),
    (0x4C1, "IE_SETNOTIFY"),
    (0x4C2, "IE_GETRECOG"),
    (0x4C3, "IE_SETRECOG"),
    (0x4C4, "IE_GETSECURITY"),
    (0x4C5, "IE_SETSECURITY"),
    (0x4C6, "IE_GETSEL"),
    (0x4C7, "IE_SETSEL"),
    (0x4C8, "CDM_LAST"),
    (0x4C8, "EM_SETBIDIOPTIONS"),
    (0x4C8, "IE_DOCOMMAND"),
    (0x4C8, "MCIWNDM_NOTIFYMODE"),
    (0x4C9, "EM_GETBIDIOPTIONS"),
    (0x4C9, "IE_GETCOMMAND"),
    (0x4C9, "MCIWNDM_NOTIFYPOS"),
    (0x4C9, "MCIWNDM_SENDSTRINGW"),
    (0x4CA, "EM_SETTYPOGRAPHYOPTIONS"),
    (0x4CA, "IE_GETCOUNT"),
    (0x4CA, "MCIWNDM_GETPOSITIONW"),
    (0x4CA, "MCIWNDM_NOTIFYSIZE"),
    (0x4CB, "EM_GETTYPOGRAPHYOPTIONS"),
    (0x4CB, "IE_GETGESTURE"),
    (0x4CB, "MCIWNDM_NOTIFYMEDIA"),
    (0x4CC, "EM_SETEDITSTYLE"),
    (0x4CC, "IE_GETMENU"),
    (0x4CD, "EM_GETEDITSTYLE"),
    (0x4CD, "IE_GETPAINTDC"),
    (0x4CD, "MCIWNDM_NOTIFYERROR"),
    (0x4CE, "IE_GETPDEVENT"),
    (0x4CE, "MCIWNDM_GETMODEW"),
    (0x4CF, "IE_GETSELCOUNT"),
    (0x4D0, "IE_GETSELITEMS"),
    (0x4D1, "IE_GETSTYLE"),
    (0x4DB, "MCIWNDM_SETTIMEFORMATW"),
    (0x4DC, "EM_OUTLINE"),
    (0x4DC, "MCIWNDM_GETTIMEFORMATW"),
    (0x4DD, "EM_GETSCROLLPOS"),
    (0x4DE, "EM_SETSCROLLPOS"),
    (0x4DF, "EM_SETFONTSIZE"),
    (0x4E0, "EM_GETZOOM"),
    (0x4E0, "MCIWNDM_GETFILENAMEW"),
    (0x4E1, "EM_SETZOOM"),
    (0x4E1, "MCIWNDM_GETDEVICEW"),
    (0x4E2, "EM_GETVIEWKIND"),
    (0x4E3, "EM_SETVIEWKIND"),
    (0x4E4, "EM_GETPAGE"),
    (0x4E4, "MCIWNDM_GETERRORW"),
    (0x4E5, "EM_SETPAGE"),
    (0x4E6, "EM_GETHYPHENATEINFO"),
    (0x4E7, "EM_SETHYPHENATEINFO"),
    (0x4E8, "EM_INSERTTABLE"),
    (0x4E9, "EM_GETAUTOCORRECTPROC"),
    (0x4EA, "EM_SETAUTOCORRECTPROC"),
    (0x4EA, "MCIWNDM_NEWW"),
    (0x4EB, "EM_GETPAGEROTATE"),
    (0x4EC, "EM_SETPAGEROTATE"),
    (0x4ED, "EM_GETCTFMODEBIAS"),
    (0x4EE, "EM_SETCTFMODEBIAS"),
    (0x4EE, "MCIWNDM_RETURNSTRINGW"),
    (0x4F0, "EM_GETCTFOPENSTATUS"),
    (0x4F1, "EM_SETCTFOPENSTATUS"),
    (0x4F2, "EM_GETIMECOMPTEXT"),
    (0x4F3, "EM_ISIME"),
    (0x4F4, "EM_GETIMEPROPERTY"),
    (0x4FC, "MCIWNDM_OPENW"),
    (0x4FF, "EM_CALLAUTOCORRECTPROC"),
    (0x509, "EM_GETTABLEPARMS"),
    (0x50D, "EM_GETQUERYRTFOBJ"),
    (0x50E, "EM_SETQUERYRTFOBJ"),
    (0x513, "EM_SETEDITSTYLEEX"),
    (0x514, "EM_GETEDITSTYLEEX"),
    (0x522, "EM_GETSTORYTYPE"),
    (0x523, "EM_SETSTORYTYPE"),
    (0x531, "EM_GETELLIPSISMODE"),
    (0x532, "EM_SETELLIPSISMODE"),
    (0x533, "EM_SETTABLEPARMS"),
    (0x536, "EM_GETTOUCHOPTIONS"),
    (0x537, "EM_SETTOUCHOPTIONS"),
    (0x53A, "EM_INSERTIMAGE"),
    (0x540, "EM_SETUIANAME"),
    (0x542, "EM_GETELLIPSISSTATE"),
    (0x600, "FM_GETFOCUS"),
    (0x601, "FM_GETDRIVEINFOA"),
    (0x602, "FM_GETSELCOUNT"),
    (0x603, "FM_GETSELCOUNTLFN"),
    (0x604, "FM_GETFILESELA"),
    (0x605, "FM_GETFILESELLFNA"),
    (0x606, "FM_REFRESH_WINDOWS"),
    (0x607, "FM_RELOAD_EXTENSIONS"),
    (0x611, "FM_GETDRIVEINFOW"),
    (0x614, "FM_GETFILESELW"),
    (0x615, "FM_GETFILESELLFNW"),
    (0x659, "WLX_WM_SAS"),
    (0x7E8, "SM_GETSELCOUNT"),
    (0x7E8, "UM_GETSELCOUNT"),
    (0x7E8, "WM_CPL_LAUNCH"),
    (0x7E9, "SM_GETSERVERSELA"),
    (0x7E9, "UM_GETUSERSELA"),
    (0x7E9, "WM_CPL_LAUNCHED"),
    (0x7EA, "SM_GETSERVERSELW"),
    (0x7EA, "UM_GETUSERSELW"),
    (0x7EB, "SM_GETCURFOCUSA"),
    (0x7EB, "UM_GETGROUPSELA"),
    (0x7EC, "SM_GETCURFOCUSW"),
    (0x7EC, "UM_GETGROUPSELW"),
    (0x7ED, "SM_GETOPTIONS"),
    (0x7ED, "UM_GETCURFOCUSA"),
    (0x7EE, "UM_GETCURFOCUSW"),
    (0x7EF, "UM_GETOPTIONS"),
    (0x7F0, "UM_GETOPTIONS2"),
    (0x84D, "WM_ADSPROP_NOTIFY_PAGEINIT"),
    (0x84E, "WM_ADSPROP_NOTIFY_PAGEHWND"),
    (0x84F, "WM_ADSPROP_NOTIFY_CHANGE"),
    (0x850, "WM_ADSPROP_NOTIFY_APPLY"),
    (0x851, "WM_ADSPROP_NOTIFY_SETFOCUS"),
    (0x852, "WM_ADSPROP_NOTIFY_FOREGROUND"),
    (0x853, "WM_ADSPROP_NOTIFY_EXIT"),
    (0x856, "WM_ADSPROP_NOTIFY_ERROR"),
    (0x1000, "LVM_FIRST"),
    (0x1000, "LVM_GETBKCOLOR"),
    (0x1001, "LVM_SETBKCOLOR"),
    (0x1002, "LVM_GETIMAGELIST"),
    (0x1003, "LVM_SETIMAGELIST"),
    (0x1004, "LVM_GETITEMCOUNT"),
    (0x1005, "LVM_GETITEMA"),
    (0x1006, "LVM_SETITEMA"),
    (0x1007, "LVM_INSERTITEMA"),
    (0x1008, "LVM_DELETEITEM"),
    (0x1009, "LVM_DELETEALLITEMS"),
    (0x100A, "LVM_GETCALLBACKMASK"),
    (0x100B, "LVM_SETCALLBACKMASK"),
    (0x100C, "LVM_GETNEXTITEM"),
    (0x100D, "LVM_FINDITEMA"),
    (0x100E, "LVM_GETITEMRECT"),
    (0x100F, "LVM_SETITEMPOSITION"),
    (0x1010, "LVM_GETITEMPOSITION"),
    (0x1011, "LVM_GETSTRINGWIDTHA"),
    (0x1012, "LVM_HITTEST"),
    (0x1013, "LVM_ENSUREVISIBLE"),
    (0x1014, "LVM_SCROLL"),
    (0x1015, "LVM_REDRAWITEMS"),
    (0x1016, "LVM_ARRANGE"),
    (0x1017, "LVM_EDITLABELA"),
    (0x1018, "LVM_GETEDITCONTROL"),
    (0x1019, "LVM_GETCOLUMNA"),
    (0x101A, "LVM_SETCOLUMNA"),
    (0x101B, "LVM_INSERTCOLUMNA"),
    (0x101C, "LVM_DELETECOLUMN"),
    (0x101D, "LVM_GETCOLUMNWIDTH"),
    (0x101E, "LVM_SETCOLUMNWIDTH"),
    (0x101F, "LVM_GETHEADER"),
    (0x1021, "LVM_CREATEDRAGIMAGE"),
    (0x1022, "LVM_GETVIEWRECT"),
    (0x1023, "LVM_GETTEXTCOLOR"),
    (0x1024, "LVM_SETTEXTCOLOR"),
    (0x1025, "LVM_GETTEXTBKCOLOR"),
    (0x1026, "LVM_SETTEXTBKCOLOR"),
    (0x1027, "LVM_GETTOPINDEX"),
    (0x1028, "LVM_GETCOUNTPERPAGE"),
    (0x1029, "LVM_GETORIGIN"),
    (0x102A, "LVM_UPDATE"),
    (0x102B, "LVM_SETITEMSTATE"),
    (0x102C, "LVM_GETITEMSTATE"),
    (0x102D, "LVM_GETITEMTEXTA"),
    (0x102E, "LVM_SETITEMTEXTA"),
    (0x102F, "LVM_SETITEMCOUNT"),
    (0x1030, "LVM_SORTITEMS"),
    (0x1031, "LVM_SETITEMPOSITION32"),
    (0x1032, "LVM_GETSELECTEDCOUNT"),
    (0x1033, "LVM_GETITEMSPACING"),
    (0x1034, "LVM_GETISEARCHSTRINGA"),
    (0x1035, "LVM_SETICONSPACING"),
    (0x1036, "LVM_SETEXTENDEDLISTVIEWSTYLE"),
    (0x1037, "LVM_GETEXTENDEDLISTVIEWSTYLE"),
    (0x1038, "LVM_GETSUBITEMRECT"),
    (0x1039, "LVM_SUBITEMHITTEST"),
    (0x103A, "LVM_SETCOLUMNORDERARRAY"),
    (0x103B, "LVM_GETCOLUMNORDERARRAY"),
    (0x103C, "LVM_SETHOTITEM"),
    (0x103D, "LVM_GETHOTITEM"),
    (0x103E, "LVM_SETHOTCURSOR"),
    (0x103F, "LVM_GETHOTCURSOR"),
    (0x1040, "LVM_APPROXIMATEVIEWRECT"),
    (0x1041, "LVM_SETWORKAREAS"),
    (0x1042, "LVM_GETSELECTIONMARK"),
    (0x1043, "LVM_SETSELECTIONMARK"),
    (0x1044, "LVM_SETBKIMAGEA"),
    (0x1045, "LVM_GETBKIMAGEA"),
    (0x1046, "LVM_GETWORKAREAS"),
    (0x1047, "LVM_SETHOVERTIME"),
    (0x1048, "LVM_GETHOVERTIME"),
    (0x1049, "LVM_GETNUMBEROFWORKAREAS"),
    (0x104A, "LVM_SETTOOLTIPS"),
    (0x104B, "LVM_GETITEMW"),
    (0x104C, "LVM_SETITEMW"),
    (0x104D, "LVM_INSERTITEMW"),
    (0x104E, "LVM_GETTOOLTIPS"),
    (0x1051, "LVM_SORTITEMSEX"),
    (0x1053, "LVM_FINDITEMW"),
    (0x1057, "LVM_GETSTRINGWIDTHW"),
    (0x105C, "LVM_GETGROUPSTATE"),
    (0x105D, "LVM_GETFOCUSEDGROUP"),
    (0x105F, "LVM_GETCOLUMNW"),
    (0x1060, "LVM_SETCOLUMNW"),
    (0x1061, "LVM_INSERTCOLUMNW"),
    (0x1062, "LVM_GETGROUPRECT"),
    (0x1073, "LVM_GETITEMTEXTW"),
    (0x1074, "LVM_SETITEMTEXTW"),
    (0x1075, "LVM_GETISEARCHSTRINGW"),
    (0x1076, "LVM_EDITLABELW"),
    (0x108A, "LVM_SETBKIMAGEW"),
    (0x108B, "LVM_GETBKIMAGEW"),
    (0x108C, "LVM_SETSELECTEDCOLUMN"),
    (0x108D, "LVM_SETTILEWIDTH"),
    (0x108E, "LVM_SETVIEW"),
    (0x108F, "LVM_GETVIEW"),
    (0x1091, "LVM_INSERTGROUP"),
    (0x1093, "LVM_SETGROUPINFO"),
    (0x1095, "LVM_GETGROUPINFO"),
    (0x1096, "LVM_REMOVEGROUP"),
    (0x1097, "LVM_MOVEGROUP"),
    (0x1098, "LVM_GETGROUPCOUNT"),
    (0x1099, "LVM_GETGROUPINFOBYINDEX"),
    (0x109A, "LVM_MOVEITEMTOGROUP"),
    (0x109B, "LVM_SETGROUPMETRICS"),
    (0x109C, "LVM_GETGROUPMETRICS"),
    (0x109D, "LVM_ENABLEGROUPVIEW"),
    (0x109E, "LVM_SORTGROUPS"),
    (0x109F, "LVM_INSERTGROUPSORTED"),
    (0x10A0, "LVM_REMOVEALLGROUPS"),
    (0x10A1, "LVM_HASGROUP"),
    (0x10A2, "LVM_SETTILEVIEWINFO"),
    (0x10A3, "LVM_GETTILEVIEWINFO"),
    (0x10A4, "LVM_SETTILEINFO"),
    (0x10A5, "LVM_GETTILEINFO"),
    (0x10A6, "LVM_SETINSERTMARK"),
    (0x10A7, "LVM_GETINSERTMARK"),
    (0x10A8, "LVM_INSERTMARKHITTEST"),
    (0x10A9, "LVM_GETINSERTMARKRECT"),
    (0x10AA, "LVM_SETINSERTMARKCOLOR"),
    (0x10AB, "LVM_GETINSERTMARKCOLOR"),
    (0x10AD, "LVM_SETINFOTIP"),
    (0x10AE, "LVM_GETSELECTEDCOLUMN"),
    (0x10AF, "LVM_ISGROUPVIEWENABLED"),
    (0x10B0, "LVM_GETOUTLINECOLOR"),
    (0x10B1, "LVM_SETOUTLINECOLOR"),
    (0x10B3, "LVM_CANCELEDITLABEL"),
    (0x10B4, "LVM_MAPINDEXTOID"),
    (0x10B5, "LVM_MAPIDTOINDEX"),
    (0x10B6, "LVM_ISITEMVISIBLE"),
    (0x10CC, "LVM_GETEMPTYTEXT"),
    (0x10CD, "LVM_GETFOOTERRECT"),
    (0x10CE, "LVM_GETFOOTERINFO"),
    (0x10CF, "LVM_GETFOOTERITEMRECT"),
    (0x10D0, "LVM_GETFOOTERITEM"),
    (0x10D1, "LVM_GETITEMINDEXRECT"),
    (0x10D2, "LVM_SETITEMINDEXSTATE"),
    (0x10D3, "LVM_GETNEXTITEMINDEX"),
    (0x11EF, "WM_DLGBORDER"),
    (0x11F0, "WM_DLGSUBCLASS"),
    (0x1701, "CB_SETMINVISIBLE"),
    (0x1702, "CB_GETMINVISIBLE"),
    (0x1703, "CB_SETCUEBANNER"),
    (0x1704, "CB_GETCUEBANNER"),
    (0x2000, "OCM__BASE"),
    (0x2001, "PBM_SETBKCOLOR"),
    (0x2001, "SB_SETBKCOLOR"),
    (0x2002, "RB_SETCOLORSCHEME"),
    (0x2002, "TB_SETCOLORSCHEME"),
    (0x2003, "RB_GETCOLORSCHEME"),
    (0x2003, "TB_GETCOLORSCHEME"),
    (0x2004, "RB_GETDROPTARGET"),
    (0x2005, "CBEM_SETUNICODEFORMAT"),
    (0x2005, "LVM_SETUNICODEFORMAT"),
    (0x2005, "RB_SETUNICODEFORMAT"),
    (0x2005, "SB_SETUNICODEFORMAT"),
    (0x2005, "TB_SETUNICODEFORMAT"),
    (0x2005, "TBM_SETUNICODEFORMAT"),
    (0x2005, "UDM_SETUNICODEFORMAT"),
    (0x2006, "CBEM_GETUNICODEFORMAT"),
    (0x2006, "LVM_GETUNICODEFORMAT"),
    (0x2006, "RB_GETUNICODEFORMAT"),
    (0x2006, "SB_GETUNICODEFORMAT"),
    (0x2006, "TB_GETUNICODEFORMAT"),
    (0x2006, "TBM_GETUNICODEFORMAT"),
    (0x2006, "UDM_GETUNICODEFORMAT"),
    (0x0209, "WM_MOUSELAST_PRE_4"),
    (0x020A, "WM_MOUSELAST_4"),
    (0x200B, "RB_SETWINDOWTHEME"),
    (0x200B, "TB_SETWINDOWTHEME"),
    (0x020D, "WM_MOUSELAST_5"),
    (0x2019, "OCM_CTLCOLOR"),
    (0x202B, "OCM_DRAWITEM"),
    (0x202C, "OCM_MEASUREITEM"),
    (0x202D, "OCM_DELETEITEM"),
    (0x202E, "OCM_VKEYTOITEM"),
    (0x202F, "OCM_CHARTOITEM"),
    (0x2039, "OCM_COMPAREITEM"),
    (0x204E, "OCM_NOTIFY"),
    (0x2111, "OCM_COMMAND"),
    (0x2114, "OCM_HSCROLL"),
    (0x2115, "OCM_VSCROLL"),
    (0x2132, "OCM_CTLCOLORMSGBOX"),
    (0x2133, "OCM_CTLCOLOREDIT"),
    (0x2134, "OCM_CTLCOLORLISTBOX"),
    (0x2135, "OCM_CTLCOLORBTN"),
    (0x2136, "OCM_CTLCOLORDLG"),
    (0x2137, "OCM_CTLCOLORSCROLLBAR"),
    (0x2138, "OCM_CTLCOLORSTATIC"),
    (0x2210, "OCM_PARENTNOTIFY"),
    (0x8000, "WM_APP"),
    (0xC002, "STDOLEVERB"),  # afxole.h
    (0xCCCD, "WM_RASDIALEVENT"),
    (0xBC00, "WM_REFLECT_BASE"),
    (0xC000, "WM_DRAGLIST"),
]

# afxmsg_.h
# Naming scheme:
# <signature> -> AfxSig_<ReturnType>_<WPARAMType>_<LPARAMType>
# <ReturnType> -> b (BOOL)
#                 h (HANDLE)
#                 v (void)
#                 i (int)
#                 l (LRESULT)
# <WPARAMType> ->
# Naming scheme:
# b - BOOL
# D - CDC*
# W - CWnd*
# w - UINT
# h - handle
# i - int
# s - LPTSTR
# v - void
# l - LPARAM
# M - CMenu*
# p - CPoint
# POS - WINDOWPOS*
# CALC - NCCALCSIZE_PARAMS*
# NMHDR - NMHDR*
# HELPINFO - HELPINFO*
# SIZING - LPRECT
# cmdui - CCmdUI*
# CDS - COPYDATASTRUCT*
# s - short
# by - byte

AfxSig = [
    ("AfxSig_end", "Marks end of message map"), # = 0
    ("AfxSig_b_D_v", "BOOL (AFX_MSG_CALL CCmdTarget::*pfn_b_D)(CDC*);"),
    ("AfxSig_b_b_v", "BOOL (AFX_MSG_CALL CCmdTarget::*pfn_b_b)(BOOL);"),
    ("AfxSig_b_u_v", "BOOL (AFX_MSG_CALL CCmdTarget::*pfn_b_u)(UINT);"),
    ("AfxSig_b_h_v", "BOOL (AFX_MSG_CALL CCmdTarget::*pfn_b_h)(HANDLE);"),
    ("AfxSig_b_W_uu", "BOOL (AFX_MSG_CALL CCmdTarget::*pfn_b_W_u_u)(CWnd*, UINT, UINT));"),
    ("AfxSig_b_W_COPYDATASTRUCT", "BOOL (AFX_MSG_CALL CCmdTarget::*pfn_b_W_COPYDATASTRUCT)(CWnd*, COPYDATASTRUCT*);"),
    ("AfxSig_b_v_HELPINFO", "BOOL (AFX_MSG_CALL CCmdTarget::*pfn_b_HELPINFO)(LPHELPINFO);"),
    ("AfxSig_CTLCOLOR", "HBRUSH (AFX_MSG_CALL CCmdTarget::*pfn_B_D_W_u)(CDC*, CWnd*, UINT);"),
    ("AfxSig_CTLCOLOR_REFLECT", "HBRUSH (AFX_MSG_CALL CCmdTarget::*pfn_B_D_u)(CDC*, UINT);"),
    ("AfxSig_i_u_W_u", "int (AFX_MSG_CALL CCmdTarget::*pfn_i_u_W_u)(UINT, CWnd*, UINT);"),
    ("AfxSig_i_uu_v", "int (AFX_MSG_CALL CCmdTarget::*pfn_i_u_u)(UINT, UINT);"),
    ("AfxSig_i_W_uu", "int (AFX_MSG_CALL CCmdTarget::*pfn_i_W_u_u)(CWnd*, UINT, UINT);"),
    ("AfxSig_i_v_s", "int (AFX_MSG_CALL CWnd::*pfn_i_s)(LPTSTR);"),
    ("AfxSig_l_w_l", "LRESULT (AFX_MSG_CALL CWnd::*pfn_l_w_l)(WPARAM, LPARAM);"),
    ("AfxSig_l_uu_M", "LRESULT (AFX_MSG_CALL CWnd::*pfn_l_u_u_M)(UINT, UINT, CMenu*);"),
    ("AfxSig_v_b_h", "void (AFX_MSG_CALL CWnd::*pfn_v_b_h)(BOOL, HANDLE);"),
    ("AfxSig_v_h_v", "void (AFX_MSG_CALL CWnd::*pfn_v_h)(HANDLE);"),
    ("AfxSig_v_h_h", "void (AFX_MSG_CALL CWnd::*pfn_v_h_h)(HANDLE,HANDLE);"),
    ("AfxSig_v_v_v", "void (AFX_MSG_CALL CWnd::*pfn_v_v)();"),
    ("AfxSig_v_u_v", "void (AFX_MSG_CALL CWnd::*pfn_i_u)(UINT);"),
    ("AfxSig_v_u_u", "void (AFX_MSG_CALL CWnd::*pfn_v_u)(UINT);"),
    ("AfxSig_v_uu_v", "void (AFX_MSG_CALL CWnd::*pfn_v_u_u)(UINT, UINT);"),
    ("AfxSig_v_v_ii", "void (AFX_MSG_CALL CWnd::*pfn_v_i_i)(int, int);"),
    ("AfxSig_v_u_uu", "void (AFX_MSG_CALL CWnd::*pfn_v_u_u_u)(UINT, UINT, UINT);"),
    ("AfxSig_v_u_ii", "void (AFX_MSG_CALL CWnd::*pfn_v_u_i_i)(UINT, int, int);"),
    ("AfxSig_v_u_W", "void (AFX_MSG_CALL CWnd::*pfn_v_u_W)(UINT, CWnd*);"),
    ("AfxSig_i_u_v", "int (AFX_MSG_CALL CWnd::*pfn_i_u)(UINT);"),
    ("AfxSig_u_u_v", "UINT (AFX_MSG_CALL CWnd::*pfn_u_u)(UINT);"),
    ("AfxSig_b_v_v", "BOOL (AFX_MSG_CALL CWnd::*pfn_b_v)();"),
    ("AfxSig_v_w_l", "void (AFX_MSG_CALL CWnd::*pfn_v_w_l)(WPARAM, LPARAM);"),
    ("AfxSig_MDIACTIVATE", "void (AFX_MSG_CALL CWnd::*pfn_v_b_W_W)(BOOL, CWnd*, CWnd*);"),
    ("AfxSig_v_D_v", "void (AFX_MSG_CALL CWnd::*pfn_v_D)(CDC*);"),
    ("AfxSig_v_M_v", "void (AFX_MSG_CALL CWnd::*pfn_v_M)(CMenu*);"),
    ("AfxSig_v_M_ub", "void (AFX_MSG_CALL CWnd::*pfn_v_M_u_b)(CMenu*, UINT, BOOL);"),
    ("AfxSig_v_W_v", "void (AFX_MSG_CALL CWnd::*pfn_v_W)(CWnd*);"),
    ("AfxSig_v_v_W", "void (AFX_MSG_CALL CWnd::*pfn_v_W)(CWnd*);"),
    ("AfxSig_v_W_uu", "void (AFX_MSG_CALL CWnd::*pfn_v_W_u_u)(CWnd*, UINT, UINT);"),
    ("AfxSig_v_W_p", "void (AFX_MSG_CALL CWnd::*pfn_v_W_p)(CWnd*, CPoint);"),
    ("AfxSig_v_W_h", "void (AFX_MSG_CALL CWnd::*pfn_v_W_h)(CWnd*, HANDLE);"),
    ("AfxSig_C_v_v", "HCURSOR (AFX_MSG_CALL CWnd::*pfn_C_v)();"),
    ("AfxSig_ACTIVATE", "	void (AFX_MSG_CALL CWnd::*pfn_v_u_W_b)(UINT, CWnd*, BOOL);"),
    ("AfxSig_SCROLL", "void (AFX_MSG_CALL CWnd::*pfn_v_u_u_W)(UINT, UINT, CWnd*);"),
    ("AfxSig_SCROLL_REFLECT", "void (AFX_MSG_CALL CWnd::*pfn_v_u_u)(UINT, UINT);"),
    ("AfxSig_v_v_s", "void (AFX_MSG_CALL CWnd::*pfn_v_s)(LPTSTR);"),
    ("AfxSig_v_u_cs", "void (AFX_MSG_CALL CWnd::*pfn_v_u_cs)(UINT, LPCTSTR);"),
    ("AfxSig_OWNERDRAW", "void (AFX_MSG_CALL CWnd::*pfn_v_i_s)(int, LPTSTR);"),
    ("AfxSig_i_i_s", "int (AFX_MSG_CALL CWnd::*pfn_i_i_s)(int, LPTSTR)"),
    ("AfxSig_u_v_p", "UINT (AFX_MSG_CALL CWnd::*pfn_u_p)(CPoint);"),
    ("AfxSig_u_v_v", "UINT (AFX_MSG_CALL CWnd::*pfn_u_v)();"),
    ("AfxSig_v_b_NCCALCSIZEPARAMS", "void (AFX_MSG_CALL CWnd::*pfn_v_b_NCCALCSIZEPARAMS)(BOOL, NCCALCSIZE_PARAMS*);"),
    ("AfxSig_v_v_WINDOWPOS", "void (AFX_MSG_CALL CWnd::*pfn_v_v_WINDOWPOS)(WINDOWPOS*);"),
    ("AfxSig_v_uu_M", "void (AFX_MSG_CALL CWnd::*pfn_v_u_u_M)(UINT, UINT, HMENU);"),
    ("AfxSig_v_u_p", "void (AFX_MSG_CALL CWnd::*pfn_v_u_p)(UINT, CPoint);"),
    ("AfxSig_SIZING", "void (AFX_MSG_CALL CWnd::*pfn_v_u_pr)(UINT, LPRECT);"),
    ("AfxSig_MOUSEWHEEL", "BOOL (AFX_MSG_CALL CWnd::*pfn_b_u_s_p)(UINT, short, CPoint);"),
    ("AfxSig_MOUSEHWHEEL", "void (AFX_MSG_CALL CWnd::*pfn_MOUSEHWHEEL)(UINT, short, CPoint);"),
    ("AfxSigCmd_v", "void (AFX_MSG_CALL CCmdTarget::*pfnCmd_v_v)();"),
    ("AfxSigCmd_b", "BOOL (AFX_MSG_CALL CCmdTarget::*pfnCmd_b_v)();"),
    ("AfxSigCmd_RANGE", "void (AFX_MSG_CALL CCmdTarget::*pfnCmd_v_u)(UINT);"),
    ("AfxSigCmd_EX", "BOOL (AFX_MSG_CALL CCmdTarget::*pfnCmd_b_u)(UINT);"),
    ("AfxSigNotify_v", "void (AFX_MSG_CALL CCmdTarget::*pfnNotify_v_NMHDR_pl)(NMHDR*, LRESULT*);"),
    ("AfxSigNotify_b", "BOOL (AFX_MSG_CALL CCmdTarget::*pfnNotify_b_NMHDR_pl)(NMHDR*, LRESULT*);"),
    ("AfxSigNotify_RANGE", "void (AFX_MSG_CALL CCmdTarget::*pfnNotify_v_u_NMHDR_pl)(UINT, NMHDR*, LRESULT*);"),
    ("AfxSigNotify_EX", "BOOL (AFX_MSG_CALL CCmdTarget::*pfnNotify_b_u_NMHDR_pl)(UINT, NMHDR*, LRESULT*);"),
    ("AfxSigCmdUI", "void (AFX_MSG_CALL CCmdTarget::*pfnCmdUI_v_C)(CCmdUI*);"),
    ("AfxSigCmdUI_RANGE", "void (AFX_MSG_CALL CCmdTarget::*pfnCmdUI_v_C_u)(CCmdUI*, UINT);"),
    ("AfxSigCmd_v_pv", "void (AFX_MSG_CALL CCmdTarget::*pfnCmd_v_pv)(void*)"),
    ("AfxSigCmd_b_pv", "BOOL (AFX_MSG_CALL CCmdTarget::*pfnCmd_b_pv)(void*)"),
    ("AfxSig_l", "LRESULT (AFX_MSG_CALL CWnd::*pfn_l_v)();"),
    ("AfxSig_l_p", "LRESULT (AFX_MSG_CALL CWnd::*pfn_l_p)(CPoint);"),
    ("AfxSig_u_W_u", "UINT (AFX_MSG_CALL CWnd::*pfn_u_W_u)(CWnd*, UINT);"),
    ("AfxSig_v_u_M", "void (AFX_MSG_CALL CWnd::*pfn_v_u_M)(UINT, CMenu*);"),
    ("AfxSig_u_u_M", "UINT (AFX_MSG_CALL CWnd::*pfn_u_u_M)(UINT, CMenu*);"),
    ("AfxSig_u_v_MENUGETOBJECTINFO", "UINT (AFX_MSG_CALL CWnd::*pfn_u_v_MENUGETOBJECTINFO)(MENUGETOBJECTINFO*);"),
    ("AfxSig_v_M_u", "void (AFX_MSG_CALL CWnd::*pfn_v_M_u)(CMenu*, UINT);"),
    ("AfxSig_v_u_LPMDINEXTMENU", "void (AFX_MSG_CALL CWnd::*pfn_v_u_LPMDINEXTMENU)(UINT, LPMDINEXTMENU);"),
    ("AfxSig_APPCOMMAND", "void (AFX_MSG_CALL CWnd::*pfn_APPCOMMAND)(CWnd*, UINT, UINT, UINT);"),
    ("AfxSig_RAWINPUT", "BOOL (AFX_MSG_CALL CWnd::*pfn_RAWINPUT)(UINT, HRAWINPUT);"),
    ("AfxSig_u_u_u", "UINT (AFX_MSG_CALL CWnd::*pfn_u_u_u)(UINT, UINT);"),
    ("AfxSig_MOUSE_XBUTTON", "void (AFX_MSG_CALL CWnd::*pfn_MOUSE_XBUTTON)(UINT, UINT, CPoint);"),
    ("AfxSig_MOUSE_NCXBUTTON", "void (AFX_MSG_CALL CWnd::*pfn_MOUSE_NCXBUTTON)(short, UINT, CPoint);"),
    ("AfxSig_INPUTLANGCHANGE", "void (AFX_MSG_CALL CWnd::*pfn_INPUTLANGCHANGE)(BYTE, UINT);"),
    ("AfxSig_v_u_hkl", "BOOL (AFX_MSG_CALL CWnd::*pfn_v_u_h)(UINT, HANDLE);"),
    ("AfxSig_INPUTDEVICECHANGE", "void (AFX_MSG_CALL CWnd::*pfn_INPUTDEVICECHANGE)(unsigned short);"),
]

IS64 = idc.__EA64__

# AFX_MSGMAPs names
S_MSGMAP = "AFX_MSGMAP"
S_MSGMAP_ENTRY = "AFX_MSGMAP_ENTRY"

# AFX_OLECMDMAPs
S_OLECMDMAP = "AFX_OLECMDMAP"
S_OLECMDMAP_ENTRY = "AFX_OLECMDMAP_ENTRY"

# AFX_CONNECTIONMAPs
S_CONNECTIONMAP = "AFX_CONNECTIONMAP"
S_CONNECTIONMAP_ENTRY = "AFX_CONNECTIONMAP_ENTRY"

# AFX_DISPMAPs
S_DISPMAP = "AFX_DISPMAP"
S_DISPMAP_ENTRY = "AFX_DISPMAP_ENTRY"

# AFX_EVENTSINKMAPs
S_EVENTSINKMAP = "AFX_EVENTSINKMAP"
S_EVENTSINKMAP_ENTRY = "AFX_EVENTSINKMAP_ENTRY"

# AFX_INTERFACEMAPs
S_INTERFACEMAP = "AFX_INTERFACEMAP"
S_INTERFACEMAP_ENTRY = "AFX_INTERFACEMAP_ENTRY"

# enum names
S_WM_MESSAGES = "WM_MESSAGES"
S_CN_ENUM = "CN_ENUM"   # CCmdTarget control notification code
S_AFXSIG = "AfxSig"
S_DISPMAP_FLAGS = "AFX_DISPMAP_FLAGS"

# CRuntimeClass names
S_CRuntimeClass = "CRuntimeClass"
S_CRuntimeClass_v7 = "CRuntimeClass_v7"
# public: static struct CRuntimeClass const 'CClassName'::class'CClassName'
S_CRTC_Name = "?class%s@%s@@2UCRuntimeClass@@B"
# public: virtual struct CRuntimeClass * 'CClassName'::GetRuntimeClass(void)const
S_CRTC_GetRTC = "?GetRuntimeClass@%s@@UEBAPEAUCRuntimeClass@@XZ"
# protected: static struct CRuntimeClass * 'CClassName'::_GetBaseClass(void)
S_CRTC_GetBaseClass = "?_GetBaseClass@%s@@KAPEAUCRuntimeClass@@XZ"
# public: static class CObject * 'CClassName'::CreateObject(void)
S_CRTC_CreateObject = "?CreateObject@%s@@SAPEAVCObject@@XZ"

MENU_PATH = "Search/%s/" % PLUGIN_NAME
POPUP_PATH = "%s/" % PLUGIN_NAME

# Utility functions

class Utils(object):
    @staticmethod
    def is_MFC_binary():
        """
        MFC file is an PE file, use VC++ compiler and have "CObject" string

        Return:
            -3: Not in PC 32bit or 64bit mode
            -2: Not a COFF, PE, OMF, ZIP, OMF LIB, AR file type
            -1: Not use VC++ compiler
             0: Not used MFC
             1: Used MFC
        """
        if idaapi.ph_get_id() != idaapi.PLFM_386:
            return -3

        filetype = idc.get_inf_attr(idc.INF_FILETYPE)
        if filetype not in (idc.FT_COFF, idc.FT_PE, idc.FT_OMF, idc.FT_ZIP, idc.FT_OMFLIB, idc.FT_AR):
            # Not a PE, COFF hay LIB binary
            return -2

        compiler = idaapi.get_inf_structure().cc.id
        if compiler & idc.COMP_MASK != idc.COMP_MS:
            # Not a Visual C++ binary
            return -1

        if idc.find_binary(0, idc.SEARCH_CASE | idc.SEARCH_DOWN, '"CObject"') == idc.BADADDR:
            return 0

        return 1

    @staticmethod
    def set_bookmark(addr, description):
        """ Add bookmark with description at addr """
        for slot in range(ida_moves.MAX_MARK_SLOT):
            ea = idc.get_bookmark(slot)
            if ea == idc.BADADDR:
                idc.put_bookmark(addr, 0, 0, 0, slot, description)
                return

    @staticmethod
    def force_add_struct(name):
        sid = idc.get_struc_id(name)
        if sid != idc.BADADDR:
            # struct already existed
            sptr = idaapi.get_struc(sid)
            if sptr:
                idaapi.del_struc_members(sptr, 0, idc.BADADDR)
        else:
            sid = idc.add_struc(idc.BADADDR, name, 0)
        return sid

    @staticmethod
    def force_add_enum(name, flags):
        eid = idc.get_enum(name)
        if eid == idc.BADADDR:
            eid = idc.add_enum(idc.BADADDR, name, flags)
        return eid

    @staticmethod
    def is_mem_zero(ea, size):
        if ea == idc.BADADDR:
            return -1
        buf = idc.get_bytes(ea, size)
        if buf is None:
            return -1
        for b in buf:
            if b != 0:
                return 0
        return 1

class AFXMSGMAPSearchResultChooser(idaapi.Choose):
    def __init__(self, title, items, flags=0, width=None, height=None, embedded=False):
        idaapi.Choose.__init__(self,
                               title,
                               [["Index", idaapi.Choose.CHCOL_PLAIN|6],
                                ["Address", idaapi.Choose.CHCOL_HEX|20],
                                ["Name", idaapi.Choose.CHCOL_HEX|40],
                                ["Entry Num", idaapi.Choose.CHCOL_HEX|10],],
                               flags=flags,
                               width=width,
                               height=height,
                               embedded=embedded)
        self.items = items
        self.selcount = 0
        self.n = len(items)

    def OnClose(self):
        return

    def OnSelectLine(self, n):
        self.selcount += 1
        idc.jumpto(self.items[n][1])

    def OnGetLine(self, n):
        res = self.items[n]
        res = [str(res[0]), idc.atoa(res[1]), res[2], str(res[3])]
        return res

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

    def show(self):
        return self.Show() >= 0


class AFXStructs(object):
    def __init__(self):
        self.min_ea = idc.get_inf_attr(idc.INF_MIN_EA)
        self.max_ea = idc.get_inf_attr(idc.INF_MAX_EA)

        self.msg_enum = 0
        self.isDll = (idc.get_inf_attr(idc.INF_LFLAGS) & idc.LFLG_IS_DLL) != 0
        self.ptrSize = 8 if IS64 else 4

        self.MSGStructSize = 32 if IS64 else 32

    @staticmethod
    def mt_rva():
        ri = ida_nalt.refinfo_t()
        if IS64:
            ri.flags = idc.REF_OFF64
        else:
            ri.flags = idc.REF_OFF32
        ri.target = idc.BADADDR
        mt = ida_nalt.opinfo_t()
        mt.ri = ri
        return mt

    @staticmethod
    def mt_ascii():
        ri = ida_nalt.refinfo_t()
        ri.flags = ida_nalt.STRTYPE_C
        ri.target = idc.BADADDR
        mt = ida_nalt.opinfo_t()
        mt.ri = ri
        return mt

    def add_AFX_structs(self):
        ff_size_t = idc.FF_DATA | (idc.FF_QWORD if IS64 else idc.FF_DWORD)
        rt = idc.REF_OFF64 if IS64 else idc.REF_OFF32
        ptrFlag = idc.FF_0OFF | ff_size_t

        # Add CObject and CCmdTarget root struct
        #
        sid = Utils.force_add_struct("CObject")
        idc.add_struc_member(sid, "vfptr", 0, ptrFlag, -1, self.ptrSize, reftype=rt)

        sid = Utils.force_add_struct("CCmdTarget")
        idc.add_struc_member(sid, "baseclass", 0, idc.FF_DATA | idc.FF_STRUCT,
                             idc.get_struc_id("CObject"), self.ptrSize)  # sizeof(CObject) = sizeof(CObject::vfptr)

        # Add xxx_xxxMAP_ENTRY before xxx_xxxMAP for SetType

        # AFX_MSGMAPs
        sid = Utils.force_add_struct(S_MSGMAP_ENTRY)
        idc.add_struc_member(sid, "nMessage", 0, idc.FF_DATA | idc.FF_DWORD | idc.FF_0ENUM,
                             idc.get_enum(S_WM_MESSAGES), 4)
        idc.add_struc_member(sid, "nCode", -1, idc.FF_DATA | idc.FF_DWORD, -1, 4)
        idc.add_struc_member(sid, "nID", -1, idc.FF_DATA | idc.FF_DWORD, -1, 4)
        idc.add_struc_member(sid, "nLastID", -1, idc.FF_DATA | idc.FF_DWORD, -1, 4)
        idc.add_struc_member(sid, "nSig", -1, ff_size_t | idc.FF_0ENUM,
                             idc.get_enum(S_AFXSIG), self.ptrSize)
        idc.add_struc_member(sid, "pfn", -1, ptrFlag, -1, self.ptrSize, reftype=rt)
        idc.SetType(idc.get_member_id(sid, 4 * 4 + self.ptrSize), "void (__thiscall *)(CCmdTarget *this)")   # pfn type

        sid = Utils.force_add_struct(S_MSGMAP)
        idc.add_struc_member(sid, "pfnGetBaseMap" if self.isDll else "pBaseMap", 0, ptrFlag, -1, self.ptrSize, reftype=rt)
        idc.add_struc_member(sid, "lpEntries", -1, ptrFlag, -1, self.ptrSize, reftype=rt)
        idc.SetType(idc.get_member_id(sid, 0x0), "const AFX_MSGMAP *(__stdcall *)()" if self.isDll else "const AFX_MSGMAP *")
        idc.SetType(idc.get_member_id(sid, self.ptrSize), "const AFX_MSGMAP_ENTRY *")

        # AFX_OLECMDMAPs
        sid = Utils.force_add_struct(S_OLECMDMAP_ENTRY)
        idc.add_struc_member(sid, "pguid", 0, ptrFlag, -1, self.ptrSize, reftype=rt)
        idc.add_struc_member(sid, "cmdID", -1, idc.FF_DATA | idc.FF_DWORD, -1, 4)
        idc.add_struc_member(sid, "nID", -1, idc.FF_DATA | idc.FF_DWORD, -1, 4)
        idc.SetType(idc.get_member_id(sid, 0), "const GUID *")

        sid = Utils.force_add_struct(S_OLECMDMAP)
        idc.add_struc_member(sid, "pfnGetBaseMap" if self.isDll else "pBaseMap", 0, ptrFlag, -1, self.ptrSize, reftype=rt)
        idc.add_struc_member(sid, "lpEntries", -1, ptrFlag, -1, self.ptrSize, reftype=rt)
        idc.SetType(idc.get_member_id(sid, 0x0), "const AFX_OLECMDMAP *(__stdcall *)()" if self.isDll else "const AFX_OLECMDMAP *")
        idc.SetType(idc.get_member_id(sid, self.ptrSize), "const AFX_OLECMDMAP_ENTRY *")

        # AFX_CONNECTIONMAPs
        sid = Utils.force_add_struct(S_CONNECTIONMAP_ENTRY)
        idc.add_struc_member(sid, "piid", 0, ptrFlag, -1, self.ptrSize, reftype=rt)
        idc.add_struc_member(sid, "nOffset", -1, ff_size_t, -1, self.ptrSize)

        sid = Utils.force_add_struct(S_CONNECTIONMAP)
        idc.add_struc_member(sid, "pfnGetBaseMap" if self.isDll else "pBaseMap", 0, ptrFlag, -1, self.ptrSize, reftype=rt)
        idc.add_struc_member(sid, "lpEntries", -1, ptrFlag, -1, self.ptrSize, reftype=rt)
        idc.SetType(idc.get_member_id(sid, 0x0), "const AFX_CONNECTIONMAP *(__stdcall *)()" if self.isDll else "const AFX_CONNECTIONMAP *")
        idc.SetType(idc.get_member_id(sid, self.ptrSize), "const AFX_CONNECTIONMAP_ENTRY *")

        # AFX_DISPMAPs
        sid = Utils.force_add_struct(S_DISPMAP_ENTRY)
        # because padding, all field ofs and size align on ptrSize
        idc.add_struc_member(sid, "lpszName", 0, ptrFlag, -1, self.ptrSize, reftype=rt)
        idc.add_struc_member(sid, "lDispID", 1 * self.ptrSize, ff_size_t, -1, self.ptrSize)
        idc.add_struc_member(sid, "lpszParams", 2 * self.ptrSize, ptrFlag, -1, self.ptrSize, reftype=rt)
        idc.add_struc_member(sid, "vt", 3 * self.ptrSize, ff_size_t, -1, self.ptrSize)    # padding on x64
        idc.add_struc_member(sid, "pfn", 4 * self.ptrSize, ptrFlag, -1, self.ptrSize, reftype=rt)
        idc.add_struc_member(sid, "pfnSet", -1, ptrFlag, -1, self.ptrSize, reftype=rt)
        idc.add_struc_member(sid, "nPropOffset", -1, ff_size_t, -1, self.ptrSize)
        idc.add_struc_member(sid, "flags", -1, ff_size_t | idc.FF_0ENUM,
                             idc.get_enum(S_DISPMAP_FLAGS), self.ptrSize)   # padding on x64
        idc.SetType(idc.get_member_id(sid, 2 * 4 + 2 * self.ptrSize), "void (__thiscall (CCmdTarget *this)")    # pfn type
        idc.SetType(idc.get_member_id(sid, 2 * 4 + 3 * self.ptrSize), "void (__thiscall (CCmdTarget *this)")    # pfnSet type

        sid = Utils.force_add_struct(S_DISPMAP)
        idc.add_struc_member(sid, "pfnGetBaseMap" if self.isDll else "pBaseMap", 0, ptrFlag, -1, self.ptrSize, reftype=rt)
        idc.add_struc_member(sid, "lpEntries", -1, ptrFlag, -1, self.ptrSize, reftype=rt)
        idc.add_struc_member(sid, "lpEntryCount", -1, ptrFlag, -1, self.ptrSize, reftype=rt)
        idc.add_struc_member(sid, "lpStockPropMask", -1, ptrFlag, -1, self.ptrSize, reftype=rt)
        idc.SetType(idc.get_member_id(sid, 0x0), "const AFX_DISPMAP *(__stdcall *)()" if self.isDll else "const AFX_DISPMAP *")
        idc.SetType(idc.get_member_id(sid, self.ptrSize), "const AFX_DISPMAP_ENTRY *")
        idc.SetType(idc.get_member_id(sid, 2 * self.ptrSize), "UINT *")
        idc.SetType(idc.get_member_id(sid, 3 * self.ptrSize), "DWORD *")

        # AFX_EVENTSINKMAPs
        sid = Utils.force_add_struct(S_EVENTSINKMAP_ENTRY)
        sub_sid = idc.get_struc_id(S_DISPMAP_ENTRY)
        idc.add_struc_member(sid, "dispEntry", 0, idc.FF_DATA | idc.FF_STRUCT, sub_sid, idc.get_struc_size(sub_sid))
        idc.add_struc_member(sid, "nCtrlIDFirst", -1, idc.FF_DATA | idc.FF_DWORD, -1, 4)
        idc.add_struc_member(sid, "nCtrlIDLast", -1, idc.FF_DATA | idc.FF_DWORD, -1, 4)

        sid = Utils.force_add_struct(S_EVENTSINKMAP)
        idc.add_struc_member(sid, "pfnGetBaseMap" if self.isDll else "pBaseMap", 0, ptrFlag, -1, self.ptrSize, reftype=rt)
        idc.add_struc_member(sid, "lpEntries", -1, ptrFlag, -1, self.ptrSize, reftype=rt)
        idc.add_struc_member(sid, "lpEntryCount", -1, ptrFlag, -1, self.ptrSize, reftype=rt)
        idc.SetType(idc.get_member_id(sid, 0x0), "const AFX_EVENTSINKMAP *(__stdcall *)()" if self.isDll else "const AFX_EVENTSINKMAP *")
        idc.SetType(idc.get_member_id(sid, self.ptrSize), "const AFX_EVENTSINKMAP_ENTRY *")
        idc.SetType(idc.get_member_id(sid, 2 * self.ptrSize), "UINT *")

        # AFX_INTERFACEMAPs
        sid = Utils.force_add_struct(S_INTERFACEMAP_ENTRY)
        idc.add_struc_member(sid, "piid", 0, ptrFlag, -1, self.ptrSize, reftype=rt)
        idc.add_struc_member(sid, "nOffset", -1, ff_size_t, -1, self.ptrSize)

        sid = Utils.force_add_struct(S_INTERFACEMAP)
        idc.add_struc_member(sid, "pfnGetBaseMap" if self.isDll else "pBaseMap", 0, ptrFlag, -1, self.ptrSize, reftype=rt)
        idc.add_struc_member(sid, "lpEntries", -1, ptrFlag, -1, self.ptrSize, reftype=rt)
        idc.SetType(idc.get_member_id(sid, 0x0), "const AFX_INTERFACEMAP *(__stdcall *)()" if self.isDll else "const AFX_INTERFACEMAP *")
        idc.SetType(idc.get_member_id(sid, self.ptrSize), "const AFX_INTERFACEMAP_ENTRY *")

        # CRuntimeClass
        sid = Utils.force_add_struct(S_CRuntimeClass)
        idc.add_struc_member(sid, "m_lpszClassName", 0, ptrFlag, -1, self.ptrSize, reftype=rt)
        idc.add_struc_member(sid, "m_nObjectSize", -1, idc.FF_DATA | idc.FF_DWORD, -1, 4)
        idc.add_struc_member(sid, "m_wSchema", -1, idc.FF_DATA | idc.FF_DWORD, -1, 4)
        idc.add_struc_member(sid, "m_pfnCreateObject", -1, ptrFlag, -1, self.ptrSize, reftype=rt)
        idc.add_struc_member(sid, "m_pfnGetBaseClass" if self.isDll else "m_pBaseClass",
                             -1, ptrFlag, -1, self.ptrSize, reftype=rt)
        idc.add_struc_member(sid, "m_pNextClass", -1, ptrFlag, -1, self.ptrSize, reftype=rt)

        idc.SetType(idc.get_member_id(sid, self.ptrSize + 8), "CObject *(__stdcall *)()")   # m_pfnCreateObject
        idc.SetType(idc.get_member_id(sid, 2 * self.ptrSize + 8),    # m_pfnGetBaseClass or m_pBaseClass
                    "CRuntimeClass *(__stdcall *)()" if self.isDll else "CRuntimeClass *")
        idc.SetType(idc.get_member_id(sid, 3 * self.ptrSize + 8), "CRuntimeClass *") # m_pNextClass

        # CRuntimeClass_v7
        # HTC - add m_pClassInit field, it is the last field in CRuntimeClass in MFC >= v7
        sid = Utils.force_add_struct(S_CRuntimeClass_v7)
        sub_sid = idc.get_struc_id(S_CRuntimeClass)
        idc.add_struc_member(sid, "CRuntimeClass", 0, idc.FF_DATA | idc.FF_STRUCT, sub_sid, idc.get_struc_size(sub_sid))
        idc.add_struc_member(sid, "m_pClassInit", -1, ptrFlag, -1, self.ptrSize, reftype=rt)

        # Some internal CStrings class in ATL/MFC
        sid = Utils.force_add_struct("ATL::IAtlStringMgr")
        idc.add_struc_member(sid, "vfptr", 0, ptrFlag, -1, self.ptrSize, reftype=rt)

        sid = Utils.force_add_struct("ATL::CStringData")
        idc.add_struc_member(sid, "pStringMgr", 0, ptrFlag, -1, self.ptrSize, reftype=rt)
        idc.add_struc_member(sid, "nDataLength", -1, idc.FF_DATA | idc.FF_DWORD, -1, 4)
        idc.add_struc_member(sid, "nAllocLength", -1, idc.FF_DATA | idc.FF_DWORD, -1, 4)
        idc.add_struc_member(sid, "nRefs", -1, idc.FF_DATA | idc.FF_DWORD, -1, 4)
        idc.SetType(idc.get_member_id(sid, 0), "ATL::IAtlStringMgr *") # m_pNextClass

        sid = Utils.force_add_struct("ATL::CNilStringData")
        sub_sid = idc.get_struc_id("ATL::CStringData")
        idc.add_struc_member(sid, "baseclass", 0, idc.FF_DATA | idc.FF_STRUCT, sub_sid, idc.get_struc_size(sub_sid))
        idc.add_struc_member(sid, "achNil", -1, idc.FF_DATA | idc.FF_WORD, -1, 4)

        sid = Utils.force_add_struct("CAfxStringMgr")
        sub_sid = idc.get_struc_id("ATL::IAtlStringMgr")
        idc.add_struc_member(sid, "baseclass", 0, idc.FF_DATA | idc.FF_STRUCT, sub_sid, idc.get_struc_size(sub_sid))
        sub_sid = idc.get_struc_id("ATL::CNilStringData")
        idc.add_struc_member(sid, "m_nil", -1, idc.FF_DATA | idc.FF_STRUCT, sub_sid, idc.get_struc_size(sub_sid))

        sid = Utils.force_add_struct("ATL::CSimpleString")
        idc.add_struc_member(sid, "m_pszData", 0, ptrFlag, -1, self.ptrSize, reftype=rt)

        sid = Utils.force_add_struct("ATL::CString")
        sub_sid = idc.get_struc_id("ATL::CSimpleString")
        idc.add_struc_member(sid, "baseclass", 0, idc.FF_DATA | idc.FF_STRUCT, sub_sid, idc.get_struc_size(sub_sid))

        return True

    def add_WM_MESSAGES_enum(self):
        eid = Utils.force_add_enum(S_WM_MESSAGES, idaapi.hex_flag())
        for msg in MSG_TABLES:
            msg_id, msg_name = msg
            idc.add_enum_member(eid, msg_name, msg_id, idc.BADADDR)

    def add_AFX_enums(self):
        eid = Utils.force_add_enum(S_CN_ENUM, idc.FF_0NUMH | idaapi.FF_SIGN)
        idc.add_enum_member(eid, "CN_COMMAND", 0x0, -1)
        idc.add_enum_member(eid, "CN_OLE_UNREGISTER", 0xFFFFFFFC, -1)
        idc.add_enum_member(eid, "CN_OLECOMMAND", 0xFFFFFFFD, -1)
        idc.add_enum_member(eid, "CN_EVENT", 0xFFFFFFFE, -1)
        idc.add_enum_member(eid, "CN_UPDATE_COMMAND_UI", 0xFFFFFFFF, -1)

        eid = Utils.force_add_enum(S_AFXSIG, idc.FF_0NUMH)
        for i in range(len(AfxSig)):
            idc.add_enum_member(eid, AfxSig[i][0], i, -1)
            idc.set_enum_member_cmt(idc.get_enum_member(eid, i, 0, -1), AfxSig[i][1], 1)

        eid = Utils.force_add_enum(S_DISPMAP_FLAGS, idc.FF_0NUMH)
        idc.add_enum_member(eid, "afxDispCustom", 0x0, -1)
        idc.add_enum_member(eid, "afxDispStock", 0x1, -1)

    @staticmethod
    def GetMsgName(mid):
        ret = []
        for msg_id, msg_name in MSG_TABLES:
            if msg_id == mid:
                ret.append(msg_name)
        if not ret:
            ret.append("WM_UNKNOWN_0x%X" % mid)
        return ret

    @staticmethod
    def get_DWORD_PTR(addr, offset=0):
        return idaapi.get_qword(addr + offset * 8) if IS64 else idc.get_wide_dword(addr + offset * 4)

    @staticmethod
    def get_pfn(addr):
        return idaapi.get_qword(addr + 24) if IS64 else idc.get_wide_dword(addr + 20)

    @staticmethod
    def MakeOffset(addr):
        if IS64:
            idc.create_data(addr, idc.FF_0OFF | idc.FF_REF | idc.FF_QWORD, 8, idc.BADADDR)
        else:
            idc.create_data(addr, idc.FF_0OFF | idc.FF_REF | idc.FF_DWORD, 4, idc.BADADDR)

    def Check_MSGMAP(self, addr, seg_start_ea, seg_end_ea):
        pBaseMap = self.get_DWORD_PTR(addr, 0)
        if not seg_start_ea < pBaseMap < seg_end_ea:
            return 0

        lpEntries = self.get_DWORD_PTR(addr, 1)
        if not seg_start_ea < lpEntries < seg_end_ea:
            return 0

        if self.Check_MSG_ENTRY(lpEntries) == 0:
            return 0

        if idc.get_wide_dword(lpEntries + 0) == 0 and \
            (idc.get_wide_dword(lpEntries + 4) != 0 or
             idc.get_wide_dword(lpEntries + 8) != 0 or
             idc.get_wide_dword(lpEntries + 12) != 0 or
             self.get_DWORD_PTR(lpEntries + 16) != 0 or
             self.get_pfn(lpEntries) != 0):
            return 0

        if idaapi.get_name(addr) == "":
            if idaapi.get_name(pBaseMap) == "":
                return 0
            return -1

        if idaapi.get_name(pBaseMap)[0:18] == "?GetThisMessageMap":
            return 1

        while lpEntries < self.max_ea:
            if  idc.get_wide_dword(lpEntries + 0) == 0 and \
                idc.get_wide_dword(lpEntries + 4) == 0 and \
                idc.get_wide_dword(lpEntries + 8) == 0 and \
                idc.get_wide_dword(lpEntries + 12) == 0 and \
                self.get_DWORD_PTR(lpEntries + 16) == 0 and \
                self.get_pfn(lpEntries) == 0:
                return 1

            if self.Check_MSG_ENTRY(lpEntries) == 0:
                return 0

            msgfun_addr = self.get_pfn(lpEntries)
            if not self.min_ea < msgfun_addr < self.max_ea:
                return 0

            lpEntries = lpEntries + self.MSGStructSize

        return 0

    def Check_MSG_ENTRY(self, entry):
        if entry == idc.BADADDR:
            return 0
        # Check nMessage
        if idc.get_wide_dword(entry) > 0xFFFF:
            return 0
        # Check nID
        if idc.get_wide_dword(entry + 8) > 0xFFFF:
            return 0
        # Check nLastID
        if idc.get_wide_dword(entry + 12) > 0xFFFF:
            return 0
        # Check nSig
        nSig = self.get_DWORD_PTR(entry + 16)
        if nSig > len(AfxSig) + 20:  # + 20 for future MFC new AfxSig
            if not self.min_ea < nSig < self.max_ea:  # point message
                return 0

        return 1

    def Make_MSG_ENTRY(self, addr):
        msgmapSize = 0
        pBaseMap = self.get_DWORD_PTR(addr, 0)
        lpEntries = self.get_DWORD_PTR(addr, 1)

        idc.create_struct(addr, -1, S_MSGMAP)
        if idc.get_name(addr) in ("off_%lX" % addr, ""):
            idc.set_name(addr, "msgEntries_%lX" % (addr))

        pEntry = lpEntries
        while idc.get_wide_dword(pEntry) != 0:
            idc.del_items(pEntry, idc.DELIT_SIMPLE, self.MSGStructSize)
            if idc.create_struct(pEntry, self.MSGStructSize, S_MSGMAP_ENTRY) == 0:
                print("Create %s failed at %X" % (S_MSGMAP_ENTRY, pEntry))
                return 0

            msgNames = self.GetMsgName(idc.get_wide_dword(pEntry + 0))
            for msgName in msgNames:
                str_funcmt = "MSG function: " + msgName + "\n"
            str_funcmt += "    nMessage: " + ("0x%X" % idc.get_wide_dword(pEntry + 0)) + "\n"
            str_funcmt += "       nCode: " + str(idc.get_wide_dword(pEntry + 4)) + "\n"
            str_funcmt += "         nID: " + str(idc.get_wide_dword(pEntry + 8)) + " - " + str(idc.get_wide_dword(pEntry + 12))

            func_startEa = self.get_pfn(pEntry)
            pfn = idaapi.get_func(func_startEa)
            if pfn is None:
                idc.del_items(func_startEa, idc.DELIT_SIMPLE)
                idaapi.add_func(func_startEa)
                pfn = idaapi.get_func(func_startEa)

            idaapi.set_func_cmt(pfn, str_funcmt, 0)
            oldname = idaapi.get_func_name(func_startEa)
            if oldname == "sub_%lX" % (func_startEa):
                newname = ""
                if idc.get_wide_dword(pEntry + 8) == idc.get_wide_dword(pEntry + 12):
                    if idc.get_wide_dword(pEntry + 8) != 0:
                        newname = "On_%s_%X_%u" % (msgName, func_startEa, idc.get_wide_dword(pEntry + 8))
                    else:
                        newname = "On_%s_%X" % (msgName, func_startEa)
                else:
                    newname = "On_%s_%X_%u_to_%u" % (msgName, func_startEa, idc.get_wide_dword(pEntry + 8), idc.get_wide_dword(pEntry + 12))

                idc.set_name(func_startEa, newname)

            pEntry = pEntry + self.MSGStructSize

        # AFX_MSG_END
        idc.del_items(pEntry, idc.DELIT_SIMPLE, self.MSGStructSize)
        idc.create_struct(pEntry, self.MSGStructSize, S_MSGMAP_ENTRY)
        msgmapSize = pEntry - lpEntries + self.MSGStructSize
        return msgmapSize

    # Search All AFX_MSGMAP
    def Search_MSGMAP(self):
        try:
            idaapi.show_wait_box("Search for AFX_MSGMAP...")

            totalCount = 0
            parseCount = 0
            values = list()

            # Scan AFX_MSGMAP in all .text and .rdata segment
            snum = ida_segment.get_segm_qty()
            for i in range(snum):
                seg_start = 0
                seg_end = 0

                s = ida_segment.getnseg(i)
                segname = ida_segment.get_segm_name(s).lower()

                if segname in (".text", ".rdata"):
                    seg_start = s.start_ea
                    seg_end = s.end_ea
                else:
                    continue

                if seg_start == 0 or seg_end == 0 or seg_start == idc.BADADDR or seg_end == idc.BADADDR:
                    continue

                addr = seg_start
                while addr < seg_end:
                    ret = self.Check_MSGMAP(addr, seg_start, seg_end)
                    MSGMAPSize = 0
                    if ret > 0:
                        totalCount += 1
                        strfind = "Find AFX_MSGMAP at 0x%X" % (addr)
                        idaapi.replace_wait_box(strfind)
                        print(strfind)

                        if idc.Name(addr) == "off_%lX" % (addr):
                            parseCount += 1

                        MSGMAPSize = self.Make_MSG_ENTRY(addr)

                        value = [
                            totalCount-1,
                            addr,
                            idc.Name(addr),
                            (MSGMAPSize - self.MSGStructSize) / self.MSGStructSize
                        ]
                        values.append(value)

                    addr += MSGMAPSize + self.ptrSize
        finally:
            idaapi.hide_wait_box()

        if values:
            c = AFXMSGMAPSearchResultChooser("Search AFX_MSGMAPs results", values)
            c.show()
        print("===== Search complete, total %lu, new resolution %lu=====\n" % (totalCount, parseCount))

    def Search_CRuntimeClass(self):
        # TODO: viet cho xong
        pass

    def Make_CRuntimeClass(self):
        # TODO: viet cho xong
        pass

class MenuContextHandler(idaapi.action_handler_t):
    @classmethod
    def get_name(cls):
        return "MFCParser:" + cls.__name__

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

    @classmethod
    def register(cls, plugin, label):
        cls.plugin = plugin
        cls.label = label
        instance = cls()
        return idaapi.register_action(idaapi.action_desc_t(
            cls.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(cls):
        """Unregister the action.
        After unregistering the class cannot be used.
        """
        idaapi.unregister_action(cls.get_name())

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

    @classmethod
    def update(cls, ctx):
        return idaapi.AST_ENABLE_FOR_FORM if ctx.form_type == idaapi.BWN_DISASM else idaapi.AST_DISABLE_FOR_FORM

# Context menu for make MSGMAP
class Make_MSGMAP_MCH(MenuContextHandler):
    def activate(self, ctx):
        self.plugin.make_msgmap()
        return 1

# Context menu for Search MSGMAP
class Search_MSGMAP_MCH(MenuContextHandler):
    def activate(self, ctx):
        self.plugin.search_msgmap()
        return 1

# Context menu for make CRuntimeClass
class Make_CRuntimeClass_MCH(MenuContextHandler):
    def activate(self, ctx):
        self.plugin.make_CRuntimeClass()
        return 1

# Context menu for search CRuntimeClasses
class Search_CRuntimeClass_MCH(MenuContextHandler):
    def activate(self, ctx):
        self.plugin.search_CRuntimeClass()
        return 1

# hooks for popup menu
class Hooks(idaapi.UI_Hooks):
    # IDA >= 700 right click widget popup
    def finish_populating_widget_popup(self, widget, popup):
        if idaapi.get_widget_type(widget) == idaapi.BWN_DISASM:
            idaapi.attach_action_to_popup(widget, popup, Make_MSGMAP_MCH.get_name(), POPUP_PATH)
            idaapi.attach_action_to_popup(widget, popup, Search_MSGMAP_MCH.get_name(), POPUP_PATH)
            idaapi.attach_action_to_popup(widget, popup, Make_CRuntimeClass_MCH.get_name(), POPUP_PATH)
            idaapi.attach_action_to_popup(widget, popup, Search_CRuntimeClass_MCH.get_name(), POPUP_PATH)


class MFCHelperPlugin_t(idaapi.plugin_t):
    flags = 0
    comment = PLUGIN_NAME
    help = ""
    wanted_name = PLUGIN_NAME
    wanted_hotkey = ""

    def __init__(self):
        self.afxStructs = AFXStructs()
        self.hooks = None

    def init(self):
        ret = Utils.is_MFC_binary()

        if ret >= -1:
            # Alwarys add WM_MESSAGE
            self.afxStructs.add_WM_MESSAGES_enum()

        if ret != 1:
            print("[%s] Not used MFC. Plugin skip" % self.wanted_name)
            return idaapi.PLUGIN_SKIP

        self.afxStructs.add_AFX_enums()  # should call before add AFX_xxx structs
        self.afxStructs.add_AFX_structs()

        # register popup menu handlers
        Make_MSGMAP_MCH.register(self, "Make as AFX_MSGMAP")
        Search_MSGMAP_MCH.register(self, "Search AFX_MSGMAPs")
        Make_CRuntimeClass_MCH.register(self, "Make as CRuntimeClass")
        Search_CRuntimeClass_MCH.register(self, "Search CRuntimeClasses")

        idaapi.attach_action_to_menu(MENU_PATH, Make_MSGMAP_MCH.get_name(), idaapi.SETMENU_APP)
        idaapi.attach_action_to_menu(MENU_PATH, Search_MSGMAP_MCH.get_name(), idaapi.SETMENU_APP)
        idaapi.attach_action_to_menu(MENU_PATH, Make_CRuntimeClass_MCH.get_name(), idaapi.SETMENU_APP)
        idaapi.attach_action_to_menu(MENU_PATH, Search_CRuntimeClass_MCH.get_name(), idaapi.SETMENU_APP)

        # setup popup menu
        self.hooks = Hooks()
        self.hooks.hook()

        addon = idaapi.addon_info_t()
        addon.id = "bangchucaibang.htc@gmail.com"
        addon.name = PLUGIN_NAME
        addon.producer = "Snow & HTC"
        addon.url = ""
        addon.version = "7.x"
        idaapi.register_addon(addon)

        print("[%s] plugin installed - Written by snow<85703533> & HTC" % self.wanted_name)

        return idaapi.PLUGIN_KEEP

    def run(self, _arg):
        return

    def term(self):
        if self.hooks is not None:
            self.hooks.unhook()
            self.hooks = None

        idaapi.detach_action_from_menu(MENU_PATH, Search_CRuntimeClass_MCH.get_name())
        idaapi.detach_action_from_menu(MENU_PATH, Make_CRuntimeClass_MCH.get_name())
        idaapi.detach_action_from_menu(MENU_PATH, Search_MSGMAP_MCH.get_name())
        idaapi.detach_action_from_menu(MENU_PATH, Make_MSGMAP_MCH.get_name())

        Search_CRuntimeClass_MCH.unregister()
        Make_CRuntimeClass_MCH.unregister()
        Search_MSGMAP_MCH.unregister()
        Make_MSGMAP_MCH.unregister()

        print("[%s] plugin terminated." % self.wanted_name)

    # make AFX_MSGMAP at current screen ea
    def make_msgmap(self):
        ea = idc.get_screen_ea()
        seg_start = idc.get_segm_start(ea)
        seg_end = idc.get_segm_end(ea)
        if self.afxStructs.Check_MSGMAP(ea, seg_start, seg_end) > 0:
            self.afxStructs.Make_MSG_ENTRY(ea)
        else:
            print("This is not a AFX_MSGMAP\n")

    # search all AFX_MSGMAP in a user selected section
    def search_msgmap(self):
        self.afxStructs.Search_MSGMAP()

    # make CRuntimeClass and recusive to all parent and next CRuntimeClass
    def make_CRuntimeClass(self):
        self.afxStructs.Make_CRuntimeClass()

    def search_CRuntimeClass(self):
        self.afxStructs.Search_CRuntimeClass()


def PLUGIN_ENTRY():
    return MFCHelperPlugin_t()


repository: mostobriv/daily_tools
file: scripts/ida/ida_buddy.py
wanted_name: IDABuddy
comment: IDABuddy plugin
created_at: 2019-10-24 12:35:50+00:00
pushed_at: 2022-03-23 09:01:24+00:00
url:
https://github.com/mostobriv/daily_tools/blob/042f6710a4ba2a89acbc446d0e5048f79b5ea7a5/scripts/ida/ida_buddy.py

content:
import ida_idaapi
import ida_kernwin
import ida_hexrays


# Define callbacks which are the actions I actually
# want to perform


# Define the action_handler_t object that fires the
# callback function when each action is activated



# Return values for update (from the SDK):
# AST_ENABLE_ALWAYS     // enable action and do not call action_handler_t::update() anymore
# AST_ENABLE_FOR_IDB    // enable action for the current idb. Call action_handler_t::update() when a database is opened/closed
# AST_ENABLE_FOR_FORM   // enable action for the current form. Call action_handler_t::update() when a form gets/loses focus
# AST_ENABLE            // enable action - call action_handler_t::update() when anything changes
# AST_DISABLE_ALWAYS    // disable action and do not call action_handler_t::action() anymore
# AST_DISABLE_FOR_IDB   // analog of ::AST_ENABLE_FOR_IDB
# AST_DISABLE_FOR_FORM  // analog of ::AST_ENABLE_FOR_FORM
# AST_DISABLE           // analog of ::AST_ENABLE

DEBUG = True
def debug(msg):
    global DEBUG
    if DEBUG:
        print('[*] DEBUG: %s' % msg)

class Action(ida_kernwin.action_handler_t):
    hotkey = None
    description = "Just an abstract class"

    def __init__(self):
        ida_kernwin.action_handler_t.__init__(self)

    # @property
    # def name():
    #     return "IDABuddy" + type(self).__name__

    def activate(self, ctx):
        raise NotImplementedError

    def update(self, ctx):
        raise NotImplementedError


class MakeItConst(Action):
    hotkey = 'Shift+C'
    description = 'Make it const'
    name = 'IDABuddy:MakeItConst'

    def __init__(self):
        super(MakeItConst, self).__init__()

    def activate(self, ctx):
        hx_view = ida_hexrays.get_widget_vdui(ctx.widget)
        # if self.check(ctx.cfunc,

    def update(self, ctx):
        debug('ctx.widget_type == %d' % ctx.widget_type)
        if ctx.widget_type == ida_kernwin.BWN_PSEUDOCODE:

            return ida_kernwin.AST_ENABLE_FOR_FORM
        # ida_kernwin.detach_action_from_popup(ctx.widget, self.name)
        return ida_kernwin.AST_DISABLE_FOR_FORM

    @staticmethod
    def check(cfunc, ctree_item):
        lvar = ctree_item.get_lvar()
        if lvar is not None:
            tinfo = lvar.type()
            # tinfo.clr_const()
            debug(tinfo.is_ptr())


        if ctree_item.citype != ida_kernwin.VDI_EXPR:
            return False


        return False



class BuddyHooks(ida_hexrays.Hexrays_Hooks):
    def _shorten(self, cfunc):
        raw = str(cfunc)
        if len(raw) > 20:
            raw = raw[0:20] + "[...snipped...]"
        return raw

    def _format_lvar(self, v):
        parts = []
        if v:
            if v.name:
                parts.append("name=%s" % v.name)
            if v.cmt:
                parts.append("cmt=%s" % v.cmt)
            parts.append("width=%s" % v.width)
            parts.append("defblk=%s" % v.defblk)
            parts.append("divisor=%s" % v.divisor)
        return "{%s}" % ", ".join(parts)

    def _log(self, msg):
        print("### %s" % msg)
        return 0


    def create_hint(self, vu):
        return self._log("create_hint: vu=%s: " % vu)

    def text_ready(self, vu):
        return self._log("text_ready: vu=%s" % vu)

    def populating_popup(self, widget, popup, vu):
        print '[*] DEBUG: vu.item.citype = %d' % vu.item.citype
        if vu.item.citype != ida_hexrays.VDI_EXPR:
            return 0

        lvar = vu.item.get_lvar()

        ida_kernwin.attach_action_to_popup(widget, popup, MakeItConst.name, None)
        return self._log("populating_popup: widget=%s, popup=%s, vu=%s" % (widget, popup, vu))


class Action(ida_kernwin.action_handler_t):

    def __init__(self):
        super(Action, self).__init__(self)
            
    def activate(self, ctx):
        raise NotImplementedError

    def check(self, hx_view):
        raise NotImplementedError

    def update(self, ctx):
        return ida_kernwin.AST_ENABLE_ALWAYS


def hx_actions_dispatcher(*args):
    hexrays_event = args[0]
    print hexrays_event


# Define a method to register all the actions when
# the plugin is initialized


# Define the plugin class itself which is returned by
# the PLUGIN_ENTRY method that scriptable plugins use
# to be recognized within IDA
def register(action, *args):
    ida_kernwin.register_action(
        ida_kernwin.action_desc_t(
            action.name,
            action.description,
            action(*args),
            action.hotkey
        )
    )


def unregister(action):
    ida_kernwin.unregister_action(action.name)


class IDABuddy(ida_idaapi.plugin_t):

    # Use the HIDE flag to avoid the entry in 
    # Edit/Plugins since this plugin's run() 
    # method has no functionality...it's all 
    # in the actions.

    flags = ida_idaapi.PLUGIN_HIDE
    comment = 'IDABuddy plugin'
    help = 'No help - this is just a IDABuddy'
    wanted_name = 'IDABuddy'
    wanted_hotkey = ''

    def init(self):
        print('IDABuddy init')

        if not ida_hexrays.init_hexrays_plugin():
            db_error('Failed to initialize Hex-Rays SDK')
            return ida_idaapi.PLUGIN_SKIP
        
        # actions registration block
        register(MakeItConst)

        self.hx_hook = BuddyHooks()
        self.hx_hook.hook()

        return ida_idaapi.PLUGIN_KEEP

    def run(self, arg):
        pass

    def term(self):
        self.hx_hook.unhook()
        unregister(MakeItConst)
        ida_hexrays.term_hexrays_plugin()

# The PLUGIN_ENTRY method is what IDA calls when
# scriptable plugins are loaded. It needs to
# return a plugin of type ida_idaapi.plugin_t

def PLUGIN_ENTRY():
    pass
'''
    try:
        return IDABuddy()
    
    except Exception, err:
        import traceback
        db_error('%s\n%s' % str((err), traceback.format_exc()))
        raise
'''


repository: darx0r/Stingray
file: src/Stingray.py
description: IDAPython plugin for finding function strings recursively
url: https://github.com/darx0r/Stingray/blob/6ac08721635c961cf2b977d5b8ddc13776e861e7/src/Stingray.py
created_at: 2015-10-05 14:34:20+00:00
pushed_at: 2022-05-17 12:55:59+00:00
content:
import collections
import idautils
import logging
import idaapi
import ida_bytes
import idc
import os


if idaapi.IDA_SDK_VERSION > 700:
   # based on based on https://www.hex-rays.com/products/ida/support/ida74_idapython_no_bc695_porting_guide.shtml
    get_flags = ida_bytes.get_full_flags
else:
    get_flags = idc.GetFlags


logging.basicConfig()
logger = logging.getLogger("Stringray")


class ConfigStingray( idaapi.action_handler_t ):

    PLUGIN_NAME         = "Stingray"
    PLUGIN_COMMENT      = "find strings in current function recursively"
    PLUGIN_HELP         = "www.github.com/darx0r/Stingray"
    PLUGIN_HOTKEY       = "Shift-S"
    CONFIG_FILE_PATH    = os.path.join( idc.GetIdaDirectory(), 
                                        "cfg/Stingray.cfg" )

    CHOOSER_TITLE           = "Stingray - Function Strings"
    CHOOSER_COLUMN_NAMES    = [ "Xref", "Address",  "Type", "String"    ]
    CHOOSER_COLUMN_SIZES    = [ 18,     8,          5,      80          ]
    CHOOSER_COLUMNS         = [ list(c) for c in 
                                zip(CHOOSER_COLUMN_NAMES, CHOOSER_COLUMN_SIZES) ]
    CHOOSER_ROW             = collections.namedtuple(   "ResultRow", 
                                                        CHOOSER_COLUMN_NAMES )

    PLUGIN_TEST                = False
    SEARCH_RECURSION_MAXLVL    = 0
    
    ACTION_NAME = "Stringray:ConfigStingrayAction"

    # Icon in PNG format
    PLUGIN_ICON_PNG =    (
        "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A\x00\x00\x00\x0D\x49\x48\x44\x52"
        "\x00\x00\x00\x10\x00\x00\x00\x10\x08\x04\x00\x00\x00\xB5\xFA\x37"
        "\xEA\x00\x00\x00\x02\x62\x4B\x47\x44\x00\xFF\x87\x8F\xCC\xBF\x00"
        "\x00\x00\x09\x70\x48\x59\x73\x00\x00\x00\x48\x00\x00\x00\x48\x00"
        "\x46\xC9\x6B\x3E\x00\x00\x00\xC4\x49\x44\x41\x54\x28\xCF\xBD\x91"
        "\x3D\x0B\x81\x61\x14\x86\xAF\xE7\xF5\xCA\x77\xCA\x20\x0B\x61\xA0"
        "\x58\x8C\x06\x65\xF4\x11\x99\x8C\xDE\x92\x18\x64\xF6\x13\xFC\x03"
        "\x25\x59\xEC\x7E\x80\x32\xDB\x2C\x36\x61\x92\x85\x59\x49\xD4\xE3"
        "\xF1\x35\x48\x8C\xCE\x5D\x67\x39\xD7\xE9\xDC\xE7\x1C\xF8\x4B\xB8"
        "\xF1\xFD\x06\x8A\xD4\xBE\xD6\x4C\xB7\xD4\x61\x46\xE2\xA3\x64\xC6"
        "\x4B\x8C\x00\x08\x86\x48\xE6\xD4\x49\x13\xA5\x80\x41\x86\xBC\x52"
        "\x8E\x2C\x71\x2C\x37\xB6\xAD\x00\xC9\x85\x03\x5B\x8E\x0C\x08\x12"
        "\x56\xAE\xEC\xAA\xF5\x1E\x42\x33\xF4\x93\x90\xBC\xD4\x47\x7B\x9F"
        "\x55\xA2\x2B\xF6\x56\xA9\xBD\x80\x0D\xA9\x77\xC0\x85\x8D\x8A\x98"
        "\x30\x62\xC5\xF9\x8E\x2C\x69\x12\x79\x8E\x70\x3C\x20\x8F\x92\x4E"
        "\x88\xAA\x32\x3C\x65\xCD\x8E\x05\x63\x7A\xB4\x28\x7F\xEE\xED\xC6"
        "\xAF\x96\x2E\xA8\xDB\x34\x48\xE2\xFC\xC3\xA3\xAE\x2A\xB7\x2E\x12"
        "\xA0\x06\x96\xA7\x00\x00\x00\x25\x74\x45\x58\x74\x64\x61\x74\x65"
        "\x3A\x63\x72\x65\x61\x74\x65\x00\x32\x30\x31\x35\x2D\x31\x30\x2D"
        "\x30\x31\x54\x31\x31\x3A\x35\x34\x3A\x33\x38\x2D\x30\x35\x3A\x30"
        "\x30\x9E\x3D\x6C\xB8\x00\x00\x00\x25\x74\x45\x58\x74\x64\x61\x74"
        "\x65\x3A\x6D\x6F\x64\x69\x66\x79\x00\x32\x30\x31\x35\x2D\x31\x30"
        "\x2D\x30\x31\x54\x31\x31\x3A\x35\x34\x3A\x33\x38\x2D\x30\x35\x3A"
        "\x30\x30\xEF\x60\xD4\x04\x00\x00\x00\x00\x49\x45\x4E\x44\xAE\x42"
        "\x60\x82"        )


    @staticmethod
    def init():

        NO_HOTKEY = ""
        SETMENU_INS = 0
        NO_ARGS = tuple()

        config_action_text = "{} Config".format(ConfigStingray.PLUGIN_NAME)
        idaapi.register_action(idaapi.action_desc_t(ConfigStingray.ACTION_NAME, config_action_text, ConfigStingray()))
        idaapi.attach_action_to_menu("Options/", ConfigStingray.ACTION_NAME, idaapi.SETMENU_APP)
        ConfigStingray.load()


    @staticmethod
    def destory():

        idaapi.unregister_action(ConfigStingray.ACTION_NAME)
        
        try:
            ConfigStingray.save()
        except IOError:
            logger.warning("Failed to write config file")


    @staticmethod
    def load():

        try:
            maxlvl = int( open(ConfigStingray.CONFIG_FILE_PATH,"rb").read() )
            ConfigStingray.SEARCH_RECURSION_MAXLVL = maxlvl
        except:
            pass


    @staticmethod
    def save():

        config_data = str(ConfigStingray.SEARCH_RECURSION_MAXLVL)
        open(ConfigStingray.CONFIG_FILE_PATH,"wb").write(config_data)


    @staticmethod
    def stingray_config():

        input = idc.AskLong(    ConfigStingray.SEARCH_RECURSION_MAXLVL, 
                                "Please enter string search max. depth:"
                                "\n( 0 - non-recursive mode )"            )

        if input >= 0:
            ConfigStingray.SEARCH_RECURSION_MAXLVL = input

    def activate(self, ctx):
        self.stingray_config()

    def update(self, ctx):
        return idaapi.AST_ENABLE_ALWAYS

# ------------------------------------------------------------------------------


class StringParsingException( Exception ):
    pass


class String( object ):

    ASCSTR = [  "C",
                "Pascal",
                "LEN2",
                "Unicode",
                "LEN4",
                "ULEN2",    
                "ULEN4"    ]


    def __init__( self, xref, addr ):

        type = idc.GetStringType(addr)
        if type < 0 or type >= len(String.ASCSTR):
            raise StringParsingException()

        CALC_MAX_LEN = -1
        string = str( idc.GetString(addr, CALC_MAX_LEN, type) )

        self.xref = xref
        self.addr = addr
        self.type = type
        self.string = string


    def get_row( self ):

        xref = "{}:{:08X}".format(idc.GetFunctionName(self.xref), self.xref)
        addr = "{:08X}".format(self.addr)
        type = String.ASCSTR[self.type]
        string = self.string
        # IDA Chooser doesn't like tuples ... row should be a list
        return list( ConfigStingray.CHOOSER_ROW(xref, addr, type, string) )


def find_function_strings( func_ea ):

    end_ea = idc.FindFuncEnd(func_ea)
    if end_ea == idaapi.BADADDR: return

    strings = []
    for line in idautils.Heads(func_ea, end_ea):
        refs = idautils.DataRefsFrom(line)
        for ref in refs:
            try:
                strings.append( String(line, ref) )
            except StringParsingException:
                continue

    return strings


def find_function_callees( func_ea, maxlvl ):

    callees = []
    visited = set()
    pending = set( (func_ea,) )
    lvl = 0

    while len(pending) > 0:
        func_ea = pending.pop()
        visited.add(func_ea)

        func_name = idc.GetFunctionName(func_ea)
        if not func_name: continue
        callees.append(func_ea)

        func_end = idc.FindFuncEnd(func_ea)
        if func_end == idaapi.BADADDR: continue

        lvl +=1
        if lvl >= maxlvl: continue

        all_refs = set()
        for line in idautils.Heads(func_ea, func_end):

            if not ida_bytes.isCode(get_flags(line)): continue

            ALL_XREFS = 0
            refs = idautils.CodeRefsFrom(line, ALL_XREFS)
            refs = set( filter( lambda x: not (x >= func_ea and x <= func_end), 
                                refs) )
            all_refs |= refs

        all_refs -= visited
        pending |= all_refs

    return callees


class StringFinder( object ):

    def __init__( self ):
        pass


    def get_current_function_strings( self ):

        addr_in_func = idc.ScreenEA()
        curr_func = idc.GetFunctionName(addr_in_func)

        funcs = [ addr_in_func ]
        if ConfigStingray.SEARCH_RECURSION_MAXLVL > 0:
            funcs = find_function_callees(  addr_in_func, 
                                            ConfigStingray.SEARCH_RECURSION_MAXLVL  )

        total_strs = []
        for func in funcs:
            strs = find_function_strings(func)
            total_strs += [ s.get_row() for s in strs ]

        return total_strs


# ------------------------------------------------------------------------------


class PluginChooser( idaapi.Choose2 ):

    def __init__( self, title, columns, items, icon, embedded=False ):

        idaapi.Choose2.__init__(self, title, columns, 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
        self.Refresh()


    def OnClose( self ):
        pass


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


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


    def OnSelectLine( self, n ):

        row = ConfigStingray.CHOOSER_ROW( *self.items[n] )
        xref = row.Xref.split(':')[-1]
        idc.Jump( int(xref, 16) )


# ------------------------------------------------------------------------------    


class StingrayPlugin( idaapi.plugin_t ):

    flags           = 0
    comment         = ConfigStingray.PLUGIN_COMMENT
    help            = ConfigStingray.PLUGIN_HELP
    wanted_name     = ConfigStingray.PLUGIN_NAME
    wanted_hotkey   = ConfigStingray.PLUGIN_HOTKEY

    def __init__(self, *args, **kwargs):
        super(StingrayPlugin, self).__init__(*args, **kwargs)
        self._chooser = None


    def init( self ):

        self.icon_id = idaapi.load_custom_icon( data = ConfigStingray.PLUGIN_ICON_PNG, 
                                                format = "png"    )
        if self.icon_id == 0:
            raise RuntimeError("Failed to load icon data!")

        self.finder = StringFinder()

        ConfigStingray.init()

        return idaapi.PLUGIN_KEEP


    def run( self, arg=0 ):

        try:
            rows = self.finder.get_current_function_strings()
            if self._chooser is None:
                self._chooser = PluginChooser(  ConfigStingray.CHOOSER_TITLE, 
                                                ConfigStingray.CHOOSER_COLUMNS, 
                                                rows, 
                                                self.icon_id    )
            else:
                self._chooser.SetItems(rows)
            self._chooser.Show()
        except Exception as e:
            logger.warning("exception", exc_info=True)
        return


    def term( self ):

        ConfigStingray.destory()

        if self.icon_id != 0:
            idaapi.free_custom_icon(self.icon_id)


# ------------------------------------------------------------------------------    


def PLUGIN_ENTRY():
    return StingrayPlugin()


# ------------------------------------------------------------------------------


if ConfigStingray.PLUGIN_TEST:
    print "{} - test".format(ConfigStingray.PLUGIN_NAME)
    p = StingrayPlugin()
    p.init()
    p.run()
    p.term()


repository: GuillaumeOrlando/IDAGraphity
file: plugin/IDAGraphity.py
description: IDAGraphity: An Interactive Binary Data Visualization Plugin for IDA Pro
comment: IDAGraphity - Interactive Function Graph Builder
created_at: 2022-05-05 18:48:24+00:00
pushed_at: 2022-05-05 18:48:55+00:00
url:
https://github.com/GuillaumeOrlando/IDAGraphity/blob/11e52d50a3ca66e6c3bb48da467dc6d8bfa8e66f/plugin/IDAGraphity.py

content:
# IdaGraphity - Generate a graph view of a given the binary
# Guillaume Orlando - 01/05/2022

# Global Plugin Management Variables
PLUGIN_NAME = "IDAGraphity"
PLUGIN_DIR = "C:\\Tools\\IDA 7.7\\IDA 7.7\\plugins\\IDAGraphity"
PLUGIN_HOTKEY = 'Alt+²'
VERSION = '0.0.1'

import idc
import idautils
import idaapi
from ida_kernwin import Form

import os
import sys
import time
import json
import webbrowser
from os import path

# Check if the IDA / Python versions are compatible with our plugin
major, minor = map(int, idaapi.get_kernel_version().split("."))
assert (major > 6),"ERROR: IDAGraphity plugin requires IDA v7+"
assert (sys.version_info >= (3, 5)), "ERROR: IDAGraphity plugin requires Python 3.5"

# Class that handle the modal form when accesing the configuration menu in the Edit -> Plugin -> IDAGraphity
class IdaGraphity_Form(Form):
    def __init__(self):
        self.invert = False
        Form.__init__(self, r"""STARTITEM {id:rNormal}
BUTTON YES* Generate
BUTTON CANCEL Cancel
IDAGraphity
            IDAGraphity: An interactive binary visualisation plugin.
            {FormChangeCb}
            <#Hint2#:{iColor1}>Regular functions base color 
            <#Hint2#:{iColor2}>Library functions base color 
            <#Hint2#:{iColor3}>External Symbols base color  
            <#Hint2#:{iColor4}>Background Color             

            <##Reset Default Colors:{iButton3}>

            <##Node Visibility##Show Library Functions:{rShowLib}>
            <Show External API Functions:{rShowApi}>
            <Display Nodes Names:{rDisplayName}>
            <Remove Orphan Nodes:{rRemoveOrphan}>
            <Display Data on Hoover:{rHoover}>
            <Nodes interactions (drag & move):{rDrag}>{cGroup1}>

            <#Select a file to open#FLARE capa integration:{iFileOpen}>
            <#Hint1#Include functions starting by  :{iStr1}>
            <#Hint1#Blacklist functions starting by:{iStr2}>

            A browser will be open to display the result after the graph generation.""", {
            'iColor1': Form.ColorInput(),
            'iColor2': Form.ColorInput(),
            'iColor3': Form.ColorInput(),
            'iColor4': Form.ColorInput(),
            'iStr1': Form.StringInput(swidth=20),
            'iStr2': Form.StringInput(swidth=20),
            'iButton3': Form.ButtonInput(self.OnResetColor),
            'iFileOpen': Form.FileInput(swidth=20, open=True),
            'cGroup1': Form.ChkGroupControl(("rShowLib", "rShowApi", "rNormal", "rRemoveOrphan", "rDrag", "rDisplayName", "rHoover")),
            'FormChangeCb': Form.FormChangeCb(self.OnFormChange),
        })

    def OnResetColor(self, code=0):
        print("Color Reset")
        self.SetControlValue(self.iColor1, 0xe4c3aa)
        self.SetControlValue(self.iColor2, 0xc9e5d0)
        self.SetControlValue(self.iColor3, 0xdcd1ea)
        self.SetControlValue(self.iColor4, 0xffffff)

    def OnFormChange(self, fid):
        return 1

# Deal with the weird way IDA output color code.
def reverse_hex_color_code(val):
    value = bytes.fromhex(hex(val)[2:])
    value = value[::-1]
    return int('0x' + bytes.hex(value), 16)

# Construct and open the modal form of the plugin configuration option
def IdaGraphity_Configuration_Plugin():
    global f
    use_capa = False
    f = IdaGraphity_Form()
    f.Compile()
    f.iColor1.value = 0xe4c3aa
    f.iColor2.value = 0xc9e5d0
    f.iColor3.value = 0xdcd1ea
    f.iColor4.value = 0xffffff
    f.rShowLib.checked = True
    f.rShowApi.checked = True
    f.rDisplayName.checked = True
    f.rHoover.checked = True
    f.rRemoveOrphan.checked = False
    f.rDrag.checked = False
    f.iFileOpen.value = "capa_plugin_result.json"
    f.iStr1.value = ""
    f.iStr2.value = ""
    ok = f.Execute()

    if ok == 1:
        if (path.exists(f.iFileOpen.value)):
            use_capa = f.iFileOpen.value
        generate_and_render( 
            reverse_hex_color_code(f.iColor1.value), 
            reverse_hex_color_code(f.iColor2.value), 
            reverse_hex_color_code(f.iColor3.value), 
            reverse_hex_color_code(f.iColor4.value),
            f.rShowLib.checked,
            f.rShowApi.checked,
            f.rRemoveOrphan.checked,
            f.rHoover.checked,
            f.rDrag.checked,
            f.rDisplayName.checked,
            use_capa,
            f.iStr1.value,
            f.iStr2.value
        )
    else:
        print("Cancel")

    f.Free()

# Register the plugin into IDA
class IDAGraphity_Plugin_t(idaapi.plugin_t):
    comment = "IDAGraphity - Interactive Function Graph Builder"
    help = ""
    wanted_name = PLUGIN_NAME
    wanted_hotkey = ''
    flags = idaapi.PLUGIN_KEEP
    terminated = False

    def init(self):
        print('IDaGraphity Plugin Loaded')
        #load_settings()
        return idaapi.PLUGIN_KEEP

    def run(self, arg):
        entry()

    def term(self):
        if self.terminated:
            return
        self.terminated = True

# Plugin related data initialisation for the first run
def init_first_run():
    if path.isdir(PLUGIN_DIR):
        pass
    else:
        print('IDAGraphity plugin: No PLUGIN_DIR found, initializing ...')
        os.mkdir(PLUGIN_DIR)
    return

# Get a string value by reference from the given address.
def get_string(address):
    type = idc.get_str_type(address)

    if type != None:
        str_value = idc.get_strlit_contents(address, -1, type)
        return str_value

    return None

# Get a list of callee function from a given frame.
def get_callee_functions(address, end):
    api_count = 0
    content = []

    while address != end:
        if address == idaapi.BADADDR:
            break

        ins = idc.print_insn_mnem(address)

        if ins == "call":
            operand = idc.get_operand_value(address, 0)

            if operand > 10:
                # Not a call to a register
                f_name = idc.get_func_name(operand)

                if f_name:
                    content.append([ hex(address), str(f_name) ])
                    api_count += 1
        address = idc.next_head(address)

    return content, api_count

# Get the calling convention type of the given function.
def get_calling_convention(f_address):
    f_def = idc.get_type(f_address)

    if f_def == None:
        return ""

    try:
        call_conv = f_def.split('__')[1].split('(')[0]
    except:
        call_conv = ""

    return str(call_conv)

# Get the number of instructions in the given frame.
def get_function_content(start, end):
    size = 0
    str_count = 0
    address = start
    content = []
    while address != end:

        if address == idaapi.BADADDR:
            break
        # Search for strings
        refs = idautils.DataRefsFrom(address)

        for ref in refs:
            str_value = get_string(ref)

            if str_value != None:
                content.append([ hex(ref), str(str_value) ])
                str_count += 1
        size += 1
        address = idc.next_head(address)

    return size, str_count, content

# Wrapper for the node object creation.
def generate_function_node(f_name, f_address, dispaly_lib, dispaly_api):
    func_type = "regular"
    size, str_number, content = get_function_content(f_address, idc.find_func_end(f_address))
    call_conv = get_calling_convention(f_address)
    api_content, api_count = get_callee_functions(f_address, idc.find_func_end(f_address))
    content += api_content

    node = { 
	   "functiontype" : "",
	   "calltype" : str(call_conv),
       "capa_rule" : "",
	   "id" : str(f_name),
	   "size" : int(size),
	   "apicount" : int(api_count),
	   "is_ep" : 0,
	   "stringcount" : int(str_number),
	   "content" : content
    }

    return node, api_content

# Complete the links object with proper target function id from the complete node object.
def gen_link_nodes(br_links, node_array):
    rm_array = []

    for link in br_links:
        target = link['target']
        node_index = next((index for (index, d) in enumerate(node_array) if d['id'] == target ), None)
        link['target'] = node_index

        if node_index == None:
            rm_array.append(link)

    for broken in rm_array:
        br_links.remove(broken)

    return br_links

# Generate a graph object with node list and incomplete links.
def generate_graph(dispaly_lib, dispaly_api, blacklist=[], prefix="", ban=""):
    links = []
    nodes = []
    node_index = -1
    use_whitelist = False
    use_banlist = False
    if len(prefix):
        use_whitelist = True
    if len(ban):
        use_banlist = True
    for function_ea in idautils.Functions():
        f_name = idc.get_func_name(function_ea)

        if use_whitelist:
            if str(prefix) not in str(f_name):
                continue

        if use_banlist:
            if str(ban) in str(f_name):
                continue

        if f_name not in blacklist:
            f_node, link_dest = generate_function_node(f_name, function_ea, dispaly_lib, dispaly_api)
            func = idaapi.get_func(function_ea)

            if func.flags & idaapi.FUNC_LIB:
                f_node['functiontype'] = "library_function"
                if not dispaly_lib:
                    continue

            elif func.flags & idaapi.FUNC_THUNK:
                f_node['functiontype'] = "external_api"
                if not dispaly_api:
                    continue

            node_index += 1
            nodes.append(f_node)
            if len(link_dest) > 0:
                for dest_addr, dest_link in link_dest:
                    links.append({
                        'pos': f_node['id'], 
                        'source': node_index, 
                        'target': str(dest_link) 
                    })

    return nodes, links

# Save the constructed json object on disk.
def save_js_data(fname, dump):
    with open(fname, 'w') as f:
        f.write(dump)    
    return

# Open the web browser to start the rendering.
def init_server():
    webbrowser.open('file:///%s/index.html' % PLUGIN_DIR)
    return

# Return a list of orphan nodes from the (still imcomplete) graph object.
def remove_orphan_nodes(links, nodes):
    orphan_nodes = []

    for node in nodes:
        n_id = node['id']
        is_orphan_content = True

        for node_target in nodes:
            node_content = node_target['content']
            if len(node_content):
                if any(n_id in sub for sub in node_target['content']):
                    is_orphan_content = False
                    break

        is_orphan_link = True
        if any(sub['pos'] == n_id for sub in links):
            is_orphan_link = False

        if is_orphan_link and is_orphan_content:
            orphan_nodes.append(n_id)

    return orphan_nodes

# Populate the nodes dataset with the FLARE's capa rule engine
def add_capa_rules(use_capa, nodes):
    if not use_capa:
        return nodes

    capa_r = []
    with open(use_capa, 'r') as f:
        data = json.load(f)

    r = data['rules']
    for rule_name in r:
        details = data['rules'][rule_name]['matches']
        meta = data['rules'][rule_name]['meta']

        rule = ''
        if len(meta['att&ck']):
            rule = meta['att&ck'][0]['technique'].title()
        elif len(meta['mbc']):
            rule = meta['mbc'][0]['behavior'].title()
        else:
            if meta['name'] != 'contain loop':
                rule = meta['name'].title()

        for addr in details:
            if len(rule):
                f_name = idc.get_func_name(int(addr, 10))
                capa_r.append({'func': f_name, 'rule': rule})

    for entry in capa_r:
        target_function = entry['func']
        target_rule = entry['rule']

        for node_entry in nodes:
            if node_entry['id'] == target_function:
                node_entry['capa_rule'] += str(target_rule) + ";"

    return nodes

# Wrapper for the graph generation. Custom colors are passed as arguments fromthe modal.
def generate_and_render(
        func1_color, 
        func2_color, 
        func3_color, 
        background_color, 
        dispaly_lib=True, 
        display_api=True, 
        remove_orphan=False,
        hoover=True,
        DragNodes=False,
        display_name=True,
        use_capa=False,
        prefix="",
        ban=""
    ):
    start_time = time.time()

    config = {
        "color1": func1_color,
        "color2": func2_color,
        "color3": func3_color,
        "background": background_color,
        "drag" : DragNodes,
        "node_name" : display_name,
        "capa" : bool(use_capa),
        "hoover" : hoover
    }

    fname = PLUGIN_DIR + '\\d3.json'
    print('-----------------------------------------------------------------------------------------')
    print('IDAGraphity plugin: FLARE capa plugin binding: %s' % str(use_capa))
    nodes, br_links = generate_graph(dispaly_lib, display_api, [], prefix, ban)
    print('IDAGraphity plugin: %d Nodes created' % len(nodes))

    if remove_orphan:
        orphan_nodes = remove_orphan_nodes(br_links, nodes)
        nodes, br_links = generate_graph(dispaly_lib, display_api, orphan_nodes, prefix, ban)

    links = gen_link_nodes(br_links, nodes)
    print('IDAGraphity plugin: %d Links created' % len(links))
    try:
        nodes = add_capa_rules(use_capa, nodes)
    except:
        print('IDAGraphity plugin: Invalid capa json file provided, unpredictable graph result ...')
    js_dump = json.dumps({
        'config': config,
        'links' : links,
        'nodes' : nodes
    })

    save_js_data(fname, js_dump)
    print('IDAGraphity plugin: Object saved under "%s\\%s"' % (PLUGIN_DIR, fname))
    print("IDAGraphity plugin: Work done in %s seconds" % (time.time() - start_time))
    init_server()

    return

# Plugin entrypoint.
def entry():
    IdaGraphity_Configuration_Plugin()
    return

# Plugin auto entrypoint
def PLUGIN_ENTRY():
    return IDAGraphity_Plugin_t()


repository: 0xKira/api_palette
file: api_palette.py
description: A code-searching/completion tool, for IDA APIs
url: https://github.com/0xKira/api_palette/blob/542ccc7ab9b675b005c5cf1f0a9172995e8a48db/api_palette.py
wanted_name: api palette
comment: Sublime-like api palette for IDA
created_at: 2020-05-25 11:40:40+00:00
pushed_at: 2025-01-22 10:27:01+00:00
content:
# Copyright (c) 2016-2017
# Milan Bohacek <milan.bohacek+apipalette@gmail.com>
# All rights reserved.
#
# ==============================================================================
#
# This file is part of API Palette.
#
# API Palette is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program 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
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# ==============================================================================

import idaapi
import idautils
import idc
from idaapi import PluginForm
from PyQt5 import QtCore, QtGui, QtWidgets
import PyQt5
import re
import inspect

last_api = ""
last_api_search = ""

# timing = 0

# --------------------------------------------------------------------------


def list_api():
    api_list = []
    for module in [idaapi, idautils, idc]:
        for i in inspect.getmembers(module, inspect.isfunction):
            # api_name api_content module_name
            api_list.append((i[0], i[1], module.__name__))
    return sorted(api_list, key=lambda x: x[0])


# --------------------------------------------------------------------------


class MyEdit(QtWidgets.QLineEdit):
    def keyPressEvent(self, event):
        if event.key() in [QtCore.Qt.Key_Down, QtCore.Qt.Key_Up, QtCore.Qt.Key_PageDown, QtCore.Qt.Key_PageUp]:
            QtWidgets.QApplication.sendEvent(self.lst, event)

        QtWidgets.QLineEdit.keyPressEvent(self, event)


# --------------------------------------------------------------------------


class MyApiList(QtWidgets.QListView):
    def keyPressEvent(self, event):
        super(MyApiList, self).keyPressEvent(event)

    def moveCursor(self, cursorAction, modifiers):
        idx = self.currentIndex()
        row = idx.row()
        cnt = idx.model().rowCount()

        if cursorAction in [QtWidgets.QAbstractItemView.MoveUp, QtWidgets.QAbstractItemView.MovePrevious]:
            if row == 0:
                cursorAction = QtWidgets.QAbstractItemView.MoveEnd

        if cursorAction in [QtWidgets.QAbstractItemView.MoveDown, QtWidgets.QAbstractItemView.MoveNext]:
            if row + 1 == cnt:
                cursorAction = QtWidgets.QAbstractItemView.MoveHome

        return super(MyApiList, self).moveCursor(cursorAction, modifiers)


# --------------------------------------------------------------------------


class api_delegate(QtWidgets.QStyledItemDelegate):
    def __init__(self, parent):
        self.cached_size = None
        super(api_delegate, self).__init__(parent)
        self.template_str = "<table cellspacing=0 width=750 cellpadding=0><tr><td width = 20%>{}</td><td width = 60% >{}</td><td width = 10% >{}</td></tr></table>"
        # self.template_str = "<table cellspacing=0 width=750 cellpadding=0><tr><td width = 30% >{}</td><td width = 30% >{}</td><td width=30%>{}</td><td align=right width=100%>{}</td></tr></table>"

    def paint(self, painter, option, index):
        model = index.model()
        row = index.row()
        action = model.data(model.index(row, 0, QtCore.QModelIndex()))
        api = model.data(model.index(row, 1, QtCore.QModelIndex()))
        module = model.data(model.index(row, 2, QtCore.QModelIndex()))

        doc = QtGui.QTextDocument()

        global ApiForm

        if len(ApiForm.regex_pattern) > 1:
            try:
                action = ApiForm.regex.sub(r'<b>\1</b>', action)
            except:
                pass
            try:
                api = ApiForm.regex.sub(r'<b>\1</b>', api)
            except:
                pass
        if api:
            api = api.lstrip("\t \n\r").split("\n")[0]

        document = self.template_str.format(action, api, module)
        doc.setHtml(document)

        painter.save()
        option.widget.style().drawControl(QtWidgets.QStyle.CE_ItemViewItem, option, painter)

        painter.translate(option.rect.left(), option.rect.top())
        clip = QtCore.QRectF(0, 0, option.rect.width(), option.rect.height())
        doc.drawContents(painter, clip)
        painter.restore()

    def sizeHint(self, option, index):
        if self.cached_size is None:
            self.initStyleOption(option, index)
            doc = QtGui.QTextDocument()
            document = self.template_str.format("action", "api", "description")
            doc.setHtml(document)
            doc.setTextWidth(option.rect.width())
            self.cached_size = QtCore.QSize(int(doc.idealWidth()), int(doc.size().height()))
        return self.cached_size


# --------------------------------------------------------------------------


class ApiFilter(QtCore.QSortFilterProxyModel):
    def filterAcceptsRow__(self, sourceRow, sourceParent):
        # t1 = time.clock()
        r = self.filterAcceptsRow_(sourceRow, sourceParent)
        # t2 = time.clock()
        # global timing
        # timing += t2-t1
        # return r

    def filterAcceptsRow(self, sourceRow, sourceParent):
        regex = self.filterRegExp()
        if len(regex.pattern()) == 0:
            return True

        m = self.sourceModel()

        for i in range(2):  # search api name and doc
            if regex.indexIn(m.data(m.index(sourceRow, i, sourceParent))) != -1:
                return True
        return False


# --------------------------------------------------------------------------


class ApiPaletteForm_t(QtWidgets.QDialog):
    def mousePressEvent(self, event):
        event.ignore()
        event.accept()
        if not self.rect().contains(event.pos()):
            close()

    def select(self, row):
        idx = self.proxyModel.index(row, 0, QtCore.QModelIndex())
        self.lst.setCurrentIndex(idx)

    def on_text_changed(self):
        filter = self.filter.text()
        self.regex = re.compile("(%s)" % (re.escape(filter)), flags=re.IGNORECASE)
        self.regex_pattern = filter
        self.proxyModel.setFilterRegExp(
            QtCore.QRegExp(
                filter, QtCore.Qt.CaseInsensitive, QtCore.QRegExp.FixedString
            )
        )

        # self.lst.currentIndex()
        self.select(0)
        self.lst.viewport().update()

    def on_enter(self):
        self.report_action(self.lst.currentIndex())

    def report_action(self, index):
        if not index.isValid():
            return
        self.setResult(1)
        m = index.model()
        row = index.row()
        self.action_name = "%s.%s" % (m.data(m.index(row, 2)), m.data(m.index(row, 0)))
        global last_api_search
        last_api_search = self.filter.text()
        self.done(1)

    def focusOutEvent(self, event):
        pass

    # def event(self, event):
    #    return super(ApiPaletteForm_t, self).event(event)
    def __init__(self, parent=None, flags=None):
        """
        Called when the plugin form is created
        """
        # Get parent widget
        # self.parent = idaapi.PluginForm.FormToPyQtWidget(form)
        # super(ApiPaletteForm_t, self).__init__( parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint  )
        # super(ApiPaletteForm_t, self).__init__( parent, QtCore.Qt.Popup )
        # super(ApiPaletteForm_t, self).__init__( parent, QtCore.Qt.Window | QtCore.Qt.FramelessWindowHint )
        super(ApiPaletteForm_t, self).__init__(parent, QtCore.Qt.Window | QtCore.Qt.FramelessWindowHint)

        self.setFocusPolicy(QtCore.Qt.ClickFocus)

        self.setWindowTitle("API Palette")
        self.resize(800, 500)

        # Create tree control
        self.lst = MyApiList()
        self.actions = list_api()
        self.action_name = None

        self.model = QtGui.QStandardItemModel(len(self.actions), 3)

        self.proxyModel = ApiFilter()
        self.proxyModel.setDynamicSortFilter(True)

        self.model.setHeaderData(0, QtCore.Qt.Horizontal, "api")
        self.model.setHeaderData(1, QtCore.Qt.Horizontal, "doc")
        self.model.setHeaderData(2, QtCore.Qt.Horizontal, "module")

        for row, i in enumerate(self.actions):
            self.model.setData(self.model.index(row, 0, QtCore.QModelIndex()), i[0])  # api name
            # first line of the doc or function definition
            if i[1].__doc__:
                doc = i[1].__doc__.lstrip().split('\n', 1)[0]
            else:
                # XXX: add try-except if we add more modules
                doc = inspect.getsource(i[1]).partition('\n')[0]
            self.model.setData(self.model.index(row, 1, QtCore.QModelIndex()), doc)
            self.model.setData(self.model.index(row, 2, QtCore.QModelIndex()), i[2])  # module name

        self.proxyModel.setSourceModel(self.model)
        self.lst.setModel(self.proxyModel)

        global last_api_search

        self.filter = MyEdit(last_api_search)
        self.regex = re.compile("(%s)" % re.escape(last_api_search), flags=re.IGNORECASE)
        self.regex_pattern = last_api_search

        self.proxyModel.setFilterRegExp(
            QtCore.QRegExp(self.regex_pattern, QtCore.Qt.CaseInsensitive, QtCore.QRegExp.FixedString))

        self.filter.setMaximumHeight(30)
        self.filter.textChanged.connect(self.on_text_changed)
        self.filter.returnPressed.connect(self.on_enter)

        self.lst.clicked.connect(self.on_clicked)
        # self.lst.activated.connect(self.on_activated)

        self.lst.setSelectionMode(1)  # QtSingleSelection

        self.lst.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
        self.lst.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
        self.lst.setItemDelegate(api_delegate(self.lst))

        # self.lst.setSectionResizeMode( QtWidgets.QHeaderView.Fixed )

        self.filter.lst = self.lst
        self.lst.filter = self.filter
        self.filter.setStyleSheet('border: 0px solid black; border-bottom:0px;')
        self.lst.setStyleSheet('QListView{border: 0px solid black; background-color: #F0F0F0;}; ')

        # self.completer = QtWidgets.QCompleter(self.model)
        # self.completer.setCompletionMode(QtWidgets.QCompleter.InlineCompletion)
        # self.filter.setCompleter(self.completer)

        # Create layout
        layout = QtWidgets.QVBoxLayout()
        layout.setSpacing(0)
        layout.addWidget(self.filter)
        layout.addWidget(self.lst)

        # Populate PluginForm
        self.setLayout(layout)
        self.filter.setFocus()
        self.filter.selectAll()

        found = False
        if len(last_api) > 0:
            for row in range(self.proxyModel.rowCount()):
                idx = self.proxyModel.index(row, 0)
                if self.proxyModel.data(idx) == last_api:
                    self.lst.setCurrentIndex(idx)
                    found = True
                    break
        if not found:
            self.lst.setCurrentIndex(self.proxyModel.index(0, 0))

    def on_clicked(self, item):
        self.report_action(item)

    def on_activated(self, item):
        self.report_action(item)


# --------------------------------------------------------------------------
def AskForAPI():
    global ApiForm
    main_window = [
        x
        for x in QtWidgets.QApplication.topLevelWidgets()
        if isinstance(x, QtWidgets.QMainWindow)
    ][0]
    ApiForm = ApiPaletteForm_t(main_window)
    ApiForm.setModal(True)
    idaapi.disable_script_timeout()

    # ApiForm.setStyleSheet("background:transparent;");
    ApiForm.setAttribute(QtCore.Qt.WA_DeleteOnClose, True)
    # ApiForm.setAttribute(QtCore.Qt.WA_TranslucentBackground, True);

    result = None

    if ApiForm.exec_() == 1:
        global last_api
        last_api = ApiForm.action_name
        result = last_api

    del ApiForm
    return result


# --------------------------------------------------------------------------


# https://stackoverflow.com/questions/35762856/how-can-i-move-the-keyboard-cursor-focus-to-a-qlineedit/43383330#43383330
# QPoint pos(line_edit->width()-5, 5);
# QMouseEvent e(QEvent::MouseButtonPress, pos, Qt::LeftButton, Qt::LeftButton, 0);
# qApp->sendEvent(line_edit, &e);
# QMouseEvent f(QEvent::MouseButtonRelease, pos, Qt::LeftButton, Qt::LeftButton, 0);
# qApp->sendEvent(line_edit, &f);
def set_focus_on_qplaintextedit(control):
    r = control.cursorRect()
    pos = QtCore.QPoint(r.left(), r.top())
    # pos  = control.mapToGlobal(pos)
    ev = QtGui.QMouseEvent(
        PyQt5.QtGui.QMouseEvent.MouseButtonPress,
        pos,
        QtCore.Qt.LeftButton,
        QtCore.Qt.LeftButton,
        QtCore.Qt.NoModifier,
    )
    ev2 = QtGui.QMouseEvent(
        PyQt5.QtGui.QMouseEvent.MouseButtonRelease,
        pos,
        QtCore.Qt.LeftButton,
        QtCore.Qt.LeftButton,
        QtCore.Qt.NoModifier,
    )
    control.mousePressEvent(ev)
    control.mouseReleaseEvent(ev2)
    control.activateWindow()


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

    def activate(self, ctx):
        global control
        control = QtWidgets.QApplication.focusWidget()
        action = AskForAPI()

        if action:
            r = repr(control)
            if "QPlainTextEdit" in r:
                control.insertPlainText(action + "(")
                set_focus_on_qplaintextedit(control)

            elif "QLineEdit" in r:
                control.insert(action + "(")
                control.setFocus()
            else:
                CLI_append(action)
                # control.insert(action + "(")
        return 1

    def update(self, ctx):
        return idaapi.AST_ENABLE_ALWAYS


api_palette_action_desc = idaapi.action_desc_t(
    "mb:api_palette",
    "API Palette",
    api_palette_ah(),
    "Shift+P",
    "Opens Sublime-like api palette.",
    -1,
)


def api_register_actions():
    idaapi.register_action(api_palette_action_desc)


def api_unregister_actions():
    idaapi.unregister_action(api_palette_action_desc.name)


def CLI_append(text):
    main_window = [
        x
        for x in QtWidgets.QApplication.topLevelWidgets()
        if isinstance(x, QtWidgets.QMainWindow)
    ][0]
    # Locate Output window and insert text
    # Here we rely on only one QGroupBox in IDA interface, and it's in the Output window.
    output = main_window.findChild(QtWidgets.QGroupBox)
    ed = output.findChild(QtWidgets.QLineEdit)
    ed.insert(text + "(")
    ed.setFocus()


class APIPalettePlugin(idaapi.plugin_t):
    flags = idaapi.PLUGIN_FIX | idaapi.PLUGIN_HIDE
    comment = "Sublime-like api palette for IDA"
    help = "Sublime-like api palette"
    wanted_name = "api palette"
    wanted_hotkey = ""

    def init(self):
        addon = idaapi.addon_info_t()
        addon.id = "milan.bohacek.api_palette"
        addon.name = "API Palette"
        addon.producer = "Milan Bohacek"
        addon.url = "milan.bohacek+apipalette@gmail.com"
        addon.version = "7.00"
        idaapi.register_addon(addon)
        api_register_actions()

        return idaapi.PLUGIN_KEEP

    def term(self):
        api_unregister_actions()
        pass

    def run(self, arg):
        pass


def PLUGIN_ENTRY():
    return APIPalettePlugin()


repository: bsse-scf/pyMINFLUX
file: pyminflux/plugin/_plugin.py
description: Reader, analyzer, and viewer of MINFLUX raw data.
created_at: 2023-05-02 15:38:30+00:00
pushed_at: 2025-05-13 08:19:46+00:00
url:
https://github.com/bsse-scf/pyMINFLUX/blob/7a4a7e4af41a13841b741291e968f5b3a1b72cc8/pyminflux/plugin/_plugin.py

content:
#  Copyright (c) 2022 - 2025 D-BSSE, ETH Zurich.
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.

from abc import ABC

from pyminflux.processor import MinFluxProcessor


def plugin_entry(func):
    """Decorator to label function 'func' as the Plugin entry point.

    Parameters
    ----------

    func : callable
        Function to be decorated.

    Returns
    -------
    funct: callable
        Decorated function.
    """
    func.is_plugin_entry = True
    return func


class Plugin(ABC):
    def __init__(self):
        """Constructor."""
        self.name = "Unnamed Plugin"
        self.description = "No description provided"
        self.version = "0.0"
        self.author = "Unknown"
        self.license = "Unknown"
        self.email = "Unknown"
        self.url = "Unknown"

    def get_info(self):
        """Return the plugin information (as read from metadata.json)."""
        return {
            "name": self.name,
            "description": self.description,
            "version": self.version,
            "author": self.author,
            "license": self.license,
            "email": self.email,
            "url": self.url,
        }

    def execute(self, processor: MinFluxProcessor, *args, **kwargs):
        """Execute the plugin.

        Parameters
        ----------

        processor: MinFluxProcessor
            Reference to the MinFluxProcessor instance. It can be None if no data has been loaded.

        Returns
        -------

        out: Object
            Output of the plugin.
        """
        for attr_name in dir(self):
            attr = getattr(self, attr_name)
            if callable(attr) and hasattr(attr, "is_plugin_entry"):
                """Return the function that has been labeled as entry point for the plugin."""
                return attr(processor, *args, **kwargs)
        raise NotImplementedError("No plugin entry point found in the plugin.")


repository: xorpd/fcatalog_client
file: fcatalog_plugin.py
description: fcatalog idapython client
wanted_name: fcatalog_client
created_at: 2015-09-10 15:26:36+00:00
pushed_at: 2016-08-19 15:09:58+00:00
url:
https://github.com/xorpd/fcatalog_client/blob/d5e727a008438a0b0a4feecaa92f80d0dbdcdcd3/fcatalog_plugin.py

content:
import logging
import os
import idaapi
from idaapi import Form

import idautils
import idc
from fcatalog_client.ida_client import FCatalogClient,clean_idb,MAX_SIM_GRADE

# Set up logging:
LOG_FILE_NAME = 'fcatalog_plugin.log'
cur_dir = os.path.dirname(os.path.abspath(__file__))
log_file_path = os.path.join(cur_dir,LOG_FILE_NAME)
logging.basicConfig(filename=log_file_path,level=logging.INFO)


# Client configuration:
class ClientConfig(object):
    def __init__(self):
        self.db_name = None
        self.remote_host = None
        self.remote_port = None
        self.exclude_pattern = None


##########################################################################

# Configuration stashing:

def save_sstring(s):
    """
    Save a short string inside the idb.
    """
    min_segment_addr = min(list(idautils.Segments()))
    # Keep the string as a regular comment on the first instruction:
    idc.MakeComm(min_segment_addr,s)


def load_sstring():
    """
    Load a short string from the idb.
    """
    min_segment_addr = min(list(idautils.Segments()))
    return idc.GetCommentEx(min_segment_addr,0)

def save_config(client_config):
    """
    Save configuration (client_config instance) to IDB.
    """
    config_str = "%%%"
    config_str += client_config.remote_host
    config_str += ":"
    config_str += str(client_config.remote_port)
    config_str += ":"
    config_str += client_config.db_name
    config_str += ":"
    if client_config.exclude_pattern is not None:
        config_str += client_config.exclude_pattern

    save_sstring(config_str)

def load_config():
    """
    Load configuration (client_config instance) to IDB.
    """
    config_str = load_sstring()
    if (config_str is None) or (not config_str.startswith('%%%')):
        # Return empty configuration:
        return None

    # Skip the percents prefix:
    config_str = config_str[3:]

    try:
        remote_host,remote_port_str,db_name,exclude_pattern = config_str.split(':')
    except ValueError:
        # Abort if could not unpack 4 values
        return None

    remote_port = int(remote_port_str)

    # Create a client config instance and fill it with the loaded
    # configuration:
    client_config = ClientConfig()
    client_config.remote_host = remote_host
    client_config.remote_port = remote_port
    client_config.db_name = db_name
    if len(exclude_pattern) == 0:
        client_config.exclude_pattern = None

    return client_config



##########################################################################


class ConfForm(Form):
    def __init__(self):
        self.invert = False
        Form.__init__(self, r"""STARTITEM {id:host}
FCatalog Client Configuration

<#Host:{host}>
<#Port:{port}>
<#Database Name:{db_name}>
<#Exclude Pattern:{exclude_pattern}>
""", {
        'host': Form.StringInput(tp=Form.FT_TYPE),
        'port': Form.StringInput(tp=Form.FT_TYPE),
        'db_name': Form.StringInput(tp=Form.FT_TYPE),
        'exclude_pattern': Form.StringInput(tp=Form.FT_TYPE),
    })


def get_similarity_cut():
    """
    Get similarity cut value from the user.
    """
    # The default similarity cut grade is just above half:
    default_sim_cut = (MAX_SIM_GRADE // 2) + 1
    # We have to make sure that default_sim_cut is not more than
    # MAX_SIM_GRADE:
    default_sim_cut = min([default_sim_cut,MAX_SIM_GRADE])

    # Keep going until we get a valid sim_cut from the user, or the user picks
    # cancel.
    while True:
        sim_cut = idaapi.asklong(default_sim_cut,\
                "Please choose a similarity grade cut (1 - {}): ".\
                format(MAX_SIM_GRADE))
        if sim_cut is None:
            # If the user has aborted, we return None:
            return None
        if (1 <= sim_cut <= MAX_SIM_GRADE):
            break

    return sim_cut


class FCatalogPlugin(idaapi.plugin_t):
    flags = 0
    comment = ''
    help = 'The Functions Catalog client'
    wanted_name = 'fcatalog_client'
    wanted_hotkey = ''

    def init(self):
        """
        Initialize plugin:
        """
        self._client_config = load_config()
        self._fcc = None
        if self._client_config is not None:
            self._fcc = FCatalogClient(\
                    (self._client_config.remote_host,\
                    self._client_config.remote_port),\
                    self._client_config.db_name,\
                    self._client_config.exclude_pattern)

        # Make sure that self._client config is built, even if it doesn't have
        # any fields inside:
        if self._client_config is None:
            self._client_config = ClientConfig()

        # Set up menus:
        ui_path = "Edit/"
        self.menu_contexts = []
        self.menu_contexts.append(idaapi.add_menu_item(ui_path,
                                "FCatalog: Configure",
                                "",
                                0,
                                self._show_conf_form,
                                (None,)))

        self.menu_contexts.append(idaapi.add_menu_item(ui_path,
                                "FCatalog: Commit Functions",
                                "",
                                0,
                                self._commit_funcs,
                                (None,)))
        self.menu_contexts.append(idaapi.add_menu_item(ui_path,
                                "FCatalog: Find Similars",
                                "",
                                0,
                                self._find_similars,
                                (None,)))
        self.menu_contexts.append(idaapi.add_menu_item(ui_path,
                                "FCatalog: Clean IDB",
                                "",
                                0,
                                self._clean_idb,
                                (None,)))

        return idaapi.PLUGIN_KEEP

    def run(self,arg):
        pass

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


    def _commit_funcs(self,arg):
        """
        This function handles the event of clicking on "commit funcs" from the
        menu.
        """
        if self._fcc is None:
            print('Please configure FCatalog')
            return
        self._fcc.commit_funcs()

    def _find_similars(self,arg):
        """
        This function handles the event of clicking on "find similars" from the
        menu.
        """
        if self._fcc is None:
            print('Please configure FCatalog')
            return
        # Get the similarity cut from the user:
        similarity_cut = get_similarity_cut()

        # If the user has clicked cancel, we abort:
        if similarity_cut is None:
            print('Aborting find_similars.')
            return

        self._fcc.find_similars(similarity_cut)


    def _clean_idb(self,arg):
        """
        Clean the idb from fcatalog names or comments.
        """
        clean_idb()


    def _show_conf_form(self,arg):
        """
        Show the configuration form and update configuration values according
        to user choices.
        """
        # Create form
        cf = ConfForm()

        # Compile (in order to populate the controls)
        cf.Compile()

        # Populate form fields with current configuration values:
        if self._client_config.remote_host is not None:
            cf.host.value = self._client_config.remote_host
        if self._client_config.remote_port is not None:
            cf.port.value = str(self._client_config.remote_port)
        if self._client_config.db_name is not None:
            cf.db_name.value = self._client_config.db_name
        if self._client_config.exclude_pattern is not None:
            cf.exclude_pattern.value = self._client_config.exclude_pattern

        # Execute the form
        res = cf.Execute()
        if res == 1:
            # User pressed OK:

            is_conf_good = True

            # Extract host:
            host = cf.host.value
            if len(host) == 0:
                host = None
                is_conf_good = False
            self._client_config.remote_host = host

            # Extract port:
            try:
                port = int(cf.port.value)
            except ValueError:
                port = None
                is_conf_good = False
            self._client_config.remote_port = port

            # Extract db name:
            db_name = cf.db_name.value
            if len(db_name) == 0:
                db_name = None
                is_conf_good = False
            self._client_config.db_name = db_name

            # Extract exclude_pattern
            exclude_pattern = cf.exclude_pattern.value
            if len(exclude_pattern) == 0:
                exclude_pattern = None
            self._client_config.exclude_pattern = exclude_pattern

            if is_conf_good:
                save_config(self._client_config)
                self._fcc = FCatalogClient(\
                        (self._client_config.remote_host,\
                        self._client_config.remote_port),\
                        self._client_config.db_name,\
                        self._client_config.exclude_pattern)
                print('Configuration successful.')
            else:
                print('Invalid configuration.')
                self._fcc = None


        # Dispose the form
        cf.Free()


def PLUGIN_ENTRY():
    return FCatalogPlugin()


repository: harlamism/IdaClu
file: idaclu.py
url: https://github.com/harlamism/IdaClu/blob/e7a290965b581d267975443c9ecf403afc5a50c3/idaclu.py
comment: Function Clusterization Tool
created_at: 2023-09-25 07:39:19+00:00
pushed_at: 2024-11-17 14:02:43+00:00
description:
IdaClu is a version agnostic IDA Pro plugin for grouping similar functions. Pick an existing grouping algorithm or create your own.

content:
#!/usr/bin/python
###############################################################
# Copyright (c) 2023
# Sergejs 'HRLM' Harlamovs <harlamism<at>gmail<dot>com>
# Licensed under the MIT License. All rights reserved.
###############################################################

import collections
import json
import os
import sys

lib_qt = None
try:
    from PyQt5 import QtCore, QtGui, QtWidgets
    lib_qt = "pyqt5"
except ImportError:
    try:
        from PySide import QtCore, QtGui
        from PySide import QtGui as QtWidgets
        lib_qt = "pyside"
    except ImportError:
        pass


# make sub-plugins discoverable
SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
for i, path in enumerate(sys.path):
    if "IDA" in path and os.path.basename(path) == 'plugins':
        sys.path.insert(i, SCRIPT_DIR)
        break


is_ida = True
try:  # almost version-agnostic imports
    import idc
    import idaapi
    from idaapi import plugin_t, PluginForm
    from idaclu import ida_shims
except ImportError:
    is_ida = False

    # standalone-run caps
    class plugin_t:
        pass
    class PluginForm:
        pass

from idaclu.qt_shims import (
    QCoreApplication,
    QIcon,
    QMessageBox,
    QTranslator
)
from idaclu import idaclu_gui
from idaclu.assets import resource



class ScriptEnv():
    def __init__(self, is_ida, lib_qt):
        # generic environment
        self.is_ida = is_ida
        self.lib_qt = lib_qt
        self.run_mode = 'script'
        self.dir_script = SCRIPT_DIR
        self.ver_py = sys.version_info[0]
        # ida-specific environment
        self.detectEnv()

    def __repr__(self):
        return self.get_dump(is_banner=True)

    def dump(self, is_banner=False):
        d_text = self.get_dump(is_banner=is_banner)
        if self.is_ida:
            ida_shims.msg(d_text)
        else:
            print(d_text)

    def get_dump(self, is_banner=False):
        env_repr = []
        if is_banner:
            env_repr.append(self.get_banner())

        env_repr.append('ENVIRONMENT\n')
        for prop in collections.OrderedDict(sorted(self.__dict__.items())):
            value = getattr(self, prop)
            if isinstance(value, list):
                for i, v in enumerate(value):
                    dots = '.' * (80 - (len(prop) + len(str(v))) + 4)
                    prop = " " * len(prop) if i > 0 else prop
                    env_repr.append("{} {} {}".format(prop, dots, v))
            else:
                dots = '.' * (80 - (len(prop) + len(str(value))) + 4)
                env_repr.append("{} {} {}".format(prop, dots, value))
        env_repr.append('\n')
        return "\n".join(env_repr)

    def get_banner(self):
        banner = "                                      \n" \
               + "     ____    __      ________         \n" \
               + "    /  _/___/ /___ _/ ____/ /_  __    \n" \
               + "    / // __  / __ `/ /   / / / / /    \n" \
               + "  _/ // /_/ / /_/ / /___/ / /_/ /     \n" \
               + " /___/\__,_/\__,_/\____/_/\__,_/      \n" \
               + "         by Sergejs Harlamovs         \n" \
               + "                                      \n"
        return banner

    def get_script_mode(self):  # in case of certainty of IDA environment
        mode = 'script'
        if idaapi.IDA_SDK_VERSION >= 720:
            if __name__ == "__main__":
                mode = "script"
            elif __name__.startswith('__plugins__'):
                # __plugins__<plugin_script_name>, ex.: __plugins__idaclu
                mode = "plugin"
        else:
            # maybe even more preferrable way of determining
            # if the plugin was run "as a plugin" or "as a script" -
            # location in one of default plugin directories
            plugin_dirs = ida_shims.get_ida_subdirs('plugins')
            if SCRIPT_DIR in plugin_dirs:
                mode = 'plugin'
            else:
                mode = 'script'
        return mode

    def get_plugin_ort(self):
        plg_dst = None
        plg_scope = 'None'
        g_plg_path = os.path.join(self.dir_plugin[0], 'idaclu')
        l_plg_path = os.path.join(self.dir_plugin[1], 'idaclu')
        if os.path.isdir(g_plg_path):
            plg_dst = self.dir_plugin[0]
            plg_scope = 'global'
        elif os.path.isdir(l_plg_path):
            plg_dst = self.dir_plugin[1]
            plg_scope = 'local'
        plg_src = os.path.dirname(os.path.realpath(os.path.join(plg_dst, 'idaclu')))
        plg_type = 'copy' if plg_dst == plg_src else 'link'
        return (plg_dst, plg_src, plg_scope, plg_type)

    def detectEnv(self):
        if self.is_ida:
            self.ver_sdk = idaapi.IDA_SDK_VERSION
            self.ida_sample = ida_shims.get_input_file_path()
            ##
            self.dir_plugin = ida_shims.get_ida_subdirs("plugins")
            # self.dir_script
            self.feat_bookmarks = self.ver_sdk >= 760
            self.feat_cpp_oop = self.ver_sdk >= 720
            # C++ class hierarchy and virtual function recognition
            self.feat_folders = self.ver_sdk >= 750
            self.feat_golang = self.ver_sdk >= 760
            self.feat_ida6 = self.ver_sdk < 740
            # Point at which IDA v6.95 compatibility APIs was off
            self.feat_ioi64 = self.ver_sdk >= 820
            # "IdaOnIda64" - decompiling of 32-bit files in IDA64
            self.feat_lumina = self.ver_sdk >= 720
            self.feat_microcode = self.ver_sdk >= 710
            self.feat_microcode_new = self.ver_sdk >= 720
            # Microcode feature was introduced in v7.1, improved in v7.2
            self.feat_python3 = self.ver_sdk >= 740
            self.feat_undo = self.ver_sdk >= 730
            self.ida_arch = "x64" if idc.__EA64__ else "x86"
            self.ida_exe = sys.executable
            self.ida_kernel = idaapi.get_kernel_version()
            self.ida_module = os.path.basename(self.ida_sample) if self.ida_sample else None
            self.idb_path = ida_shims.get_idb_path()
            self.is_dbg = idaapi.is_debugger_on()
            # self.is_ida
            self.lib_qt = self.lib_qt = "pyside" if self.ver_sdk < 690 else "pyqt5"
            self.platform = sys.platform
            plg_dst, plg_src, plg_scope, plg_type = self.get_plugin_ort()
            self.plg_dst = plg_dst
            self.plg_src = plg_src
            self.plg_scope = plg_scope
            self.plg_type = plg_type
            self.run_mode = self.get_script_mode()
            self.ver_hexrays = idaapi.get_hexrays_version() if idaapi.init_hexrays_plugin() else None
            # self.ver_py

def common_init():
    env_desc = ScriptEnv(is_ida, lib_qt)
    return env_desc


__AUTHOR__ = "Sergejs 'HRLM' Harlamovs"

PLUGIN_NAME = "IdaClu"
PLUGIN_HOTKEY = 'Ctrl+Alt+O'
PLUGIN_VERSION = '1.1'
PLUGIN_TITLE = '{0} v{1}'.format(PLUGIN_NAME, PLUGIN_VERSION)
PLUGIN_URL = "https://github.com/harlamism/IdaClu"
PLUGIN_INFO = 'For usage see: <a href="{0}">{0}</a>'.format(PLUGIN_URL)

class IdaCluForm(PluginForm):
    def __init__(self, env_desc):
        super(IdaCluForm, self).__init__()
        self.env_desc = env_desc
        self.icon = QIcon(':/idaclu/icon_64.png')
        self.qss = os.path.join(SCRIPT_DIR, 'idaclu', 'assets', 'style.qss')

    def OnCreate(self, form):
        self.env_desc.dump(True)

        app = QCoreApplication
        translator = QTranslator()
        translator.load('idaclu/assets/i18n/tr_cn', os.path.dirname(__file__))
        app.installTranslator(translator)

        if self.env_desc.lib_qt == 'pyqt5':
            self.parent = self.FormToPyQtWidget(form)
        elif self.env_desc.lib_qt == 'pyside':
            self.parent = self.FormToPySideWidget(form)
        self.parent.setWindowTitle(PLUGIN_TITLE)
        self.parent.setWindowIcon(self.icon)
        self.dialog = idaclu_gui.IdaCluDialog(self.env_desc)
        # environment footprint is passed deeper
        self.dialog.setStyleSheet(open(self.qss).read())
        layout = QtWidgets.QVBoxLayout()
        layout.addWidget(self.dialog)
        layout.setSpacing(0)
        layout.setContentsMargins(0, 0, 0, 0)
        self.parent.setLayout(layout)

    def OnClose(self, form):
        pass


def open_form(env_desc):
    if env_desc.is_ida:
        f = IdaCluForm(env_desc)
        f.Show('IdaClu')

def PLUGIN_ENTRY():
    return IdaCluPlugin()

class IdaCluPlugin(plugin_t):
    flags = idaapi.PLUGIN_UNL
    comment = "Function Clusterization Tool"
    help = "Edit->Plugin->IdaClu or {}.".format(PLUGIN_HOTKEY)
    wanted_name = PLUGIN_NAME
    wanted_hotkey = PLUGIN_HOTKEY

    def init(self):
        super(IdaCluPlugin, self).__init__()
        self.icon_id = 0
        ida_shims.msg("%s %s loaded\n" % (self.wanted_name, PLUGIN_VERSION))
        return idaapi.PLUGIN_KEEP

    def run(self, arg):
        env_desc = common_init()
        open_form(env_desc)
        return

    def term(self):
        pass


def main(env_desc):
    env_desc.dump(True)
    if env_desc.is_ida:
        open_form(env_desc)
    else:
        app = QtWidgets.QApplication(sys.argv) if not is_ida else None
        if app:
            sys.exit(app.exec_())

if __name__ == "__main__":
    env_desc = common_init()
    if env_desc.run_mode == 'script':
        if not ida_shims.get_input_file_path():
            QMessageBox.information(None, "File Missing", "Please load a file in IDA first, then run script again.")
        else:
            main(env_desc)

    if not is_ida:
        main(env_desc)


repository: CKCat/IDAPlugInContest
file: 2016/PlugIn/command_palette.py
description: IDAPlugIn Contest 备份
wanted_name: command palette
comment: Sublime-like command palette for IDA
created_at: 2021-09-23 15:11:52+00:00
pushed_at: 2024-01-29 08:05:30+00:00
url:
https://github.com/CKCat/IDAPlugInContest/blob/7ee5e83dee340fdbce81e86d310287f0a34d7250/2016/PlugIn/command_palette.py

content:
# Copyright (c) 2016
# Milan Bohacek <milan.bohacek+commandpalette@gmail.com>
# All rights reserved.
# 
# ==============================================================================
# 
# This file is part of Command Palette.
# 
# Command Palette is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# 
# This program 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
# General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
# 
# ==============================================================================


import idaapi
import idautils
from idaapi import PluginForm
from PyQt5 import QtCore, QtGui, QtWidgets
import re
import time

last_command = ""
last_search = ""
#timing = 0

# --------------------------------------------------------------------------

def active_actions():
    actions = idaapi.get_registered_actions()
    enabled_actions = []
    for action_name in actions:
        (a, b) = idaapi.get_action_state(action_name)
        if b <= idaapi.AST_ENABLE:
            enabled_actions.append(action_name)
    return sorted(enabled_actions)

# --------------------------------------------------------------------------

class MyEdit(QtWidgets.QLineEdit):

    def keyPressEvent(self, event):
        if event.key() in  [QtCore.Qt.Key_Down, QtCore.Qt.Key_Up, QtCore.Qt.Key_PageDown, QtCore.Qt.Key_PageUp]:
            QtWidgets.QApplication.sendEvent(self.lst, event)

        QtWidgets.QLineEdit.keyPressEvent(self, event)
# --------------------------------------------------------------------------


class MyList(QtWidgets.QListView):

    def keyPressEvent(self, event):
        super(MyList, self).keyPressEvent(event)

    def moveCursor(self,  cursorAction, modifiers):
        #print "move {}".format(repr(cursorAction))
        idx = self.currentIndex()
        row = idx.row()
        cnt = idx.model().rowCount()
        

        if cursorAction in [ QtWidgets.QAbstractItemView.MoveUp, QtWidgets.QAbstractItemView.MovePrevious]:
            if row == 0:
                cursorAction = QtWidgets.QAbstractItemView.MoveEnd
            

        if cursorAction in [ QtWidgets.QAbstractItemView.MoveDown, QtWidgets.QAbstractItemView.MoveNext ]:
            if row+1 == cnt:
                cursorAction = QtWidgets.QAbstractItemView.MoveHome

        return super(MyList, self).moveCursor(cursorAction, modifiers)

# --------------------------------------------------------------------------

        
class HTMLDelegate(QtWidgets.QStyledItemDelegate):
    def __init__(self, parent):
        self.cached_size = None
        super(HTMLDelegate, self).__init__(parent)

    def paint(self, painter, option, index):
        model = index.model()
        row = index.row()
        action = model.data(model.index(row, 0, QtCore.QModelIndex()))
        command = model.data(model.index(row, 1, QtCore.QModelIndex()))
        shortcut = model.data(model.index(row, 2, QtCore.QModelIndex()))
        description = model.data(model.index(row, 3, QtCore.QModelIndex()))

        doc = QtGui.QTextDocument();

        global ImpExpForm

        if len(ImpExpForm.regex_pattern) > 1:
            action = ImpExpForm.regex.sub( r'<b>\1</b>', action )
            command = ImpExpForm.regex.sub( r'<b>\1</b>', command )
            description = ImpExpForm.regex.sub( r'<b>\1</b>', description )
            shortcut = ImpExpForm.regex.sub( r'<b>\1</b>', shortcut )

        document = "<table cellspacing=0 width=750 cellpadding=0><tr><td width = 30% >{}</td><td width = 30% >{}</td><td width=30%>{}</td><td align=right width=100%>{}</td></tr></table>".format(action, command, description, shortcut)


        doc.setHtml( document );

        painter.save()
        option.widget.style().drawControl(QtWidgets.QStyle.CE_ItemViewItem, option, painter);

        
        painter.translate(option.rect.left(), option.rect.top());
        clip = QtCore.QRectF(0, 0, option.rect.width(), option.rect.height());        
        doc.drawContents(painter, clip);
        painter.restore()

    def sizeHint(self, option, index):
        if self.cached_size is None:
            self.initStyleOption(option, index);
            doc = QtGui.QTextDocument();
            document = "<table cellspacing=0 width=750 cellpadding=0><tr><td width = 30% >{}</td><td width = 30% >{}</td><td width=30%>{}</td><td align=right width=100%>{}</td></tr></table>".format("action", "command", "description", "ctrl-alt-delete")
            doc.setHtml(document)
            doc.setTextWidth(option.rect.width());
            self.cached_size = QtCore.QSize(doc.idealWidth(), doc.size().height());
        return self.cached_size



# --------------------------------------------------------------------------

class MyFilter(QtCore.QSortFilterProxyModel):

    def filterAcceptsRow__(self, sourceRow, sourceParent):
        t1 = time.clock()
        r = self.filterAcceptsRow_( sourceRow, sourceParent)
        t2 = time.clock()
        #global timing
        #timing += t2-t1
        #return r

    def filterAcceptsRow(self, sourceRow, sourceParent):
        regex = self.filterRegExp()
        if len( regex.pattern() ) == 0:
            return True

        m = self.sourceModel()
        ind = lambda i: m.index(sourceRow, i, sourceParent)
        st = lambda x: (regex.indexIn(m.data(x)) != -1)
        test = lambda x: st(ind(x))

        for i in xrange(4):
            if test( i ):
                return True
        return False
# --------------------------------------------------------------------------


class ActionPaletteForm_t(QtWidgets.QDialog):

    def mousePressEvent(self, event):
        #print "mouse: ", event
        event.ignore();
        event.accept();
        if not self.rect().contains(event.pos()):
            close();



    def select(self, row):
        idx = self.proxyModel.index( row, 0,  QtCore.QModelIndex() );
        #m = self.lst.selectionModel();
        self.lst.setCurrentIndex( idx )
        #print self.lst.selectedIndexes()
        #print "data: ", idx.data()
        #print "xa", self.lst.currentIndex().data(),"ya"

    def on_text_changed(self):
        filter = self.filter.text()
        self.regex = re.compile("(%s)"%(re.escape(filter)), flags = re.IGNORECASE)
        self.regex_pattern = filter
        self.proxyModel.setFilterRegExp( QtCore.QRegExp(filter, QtCore.Qt.CaseInsensitive, QtCore.QRegExp.FixedString) )

        #self.lst.currentIndex()
        self.select(0)
        #print dir(self.proxyModel)
        self.lst.viewport().update()
        #print filter

    def on_enter(self):
        self.report_action(self.lst.currentIndex())
        


    def report_action(self, index):
        if not index.isValid():
            return
        self.setResult(1)
        self.action_name = str(index.data())
        global last_search
        last_search = self.filter.text()
        
        self.done(1)


    def focusOutEvent(self, event):
        #print "focusout"
        pass

    #def event(self, event):
    #    #print event, event.type()
    #    return super(ActionPaletteForm_t, self).event(event)


    def __init__(self, parent = None, flags = None):
        """
        Called when the plugin form is created
        """
        # Get parent widget
        #self.parent = idaapi.PluginForm.FormToPyQtWidget(form)
        #super(ActionPaletteForm_t, self).__init__( parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint  )
        #super(ActionPaletteForm_t, self).__init__( parent, QtCore.Qt.Popup )
        #super(ActionPaletteForm_t, self).__init__( parent, QtCore.Qt.Window | QtCore.Qt.FramelessWindowHint )
        super(ActionPaletteForm_t, self).__init__( parent, QtCore.Qt.Window | QtCore.Qt.FramelessWindowHint )
        

        self.setFocusPolicy( QtCore.Qt.ClickFocus )

        
        self.setWindowTitle("Command Palette")
        self.resize(800, 500)

        # Create tree control        
        self.lst = MyList()        
        self.actions = active_actions()
        self.action_name = None

        self.model = QtGui.QStandardItemModel(len(self.actions), 4)

        self.proxyModel = MyFilter()
        self.proxyModel.setDynamicSortFilter(True)

        self.model.setHeaderData(0, QtCore.Qt.Horizontal, "Action")
        self.model.setHeaderData(1, QtCore.Qt.Horizontal, "Command")
        self.model.setHeaderData(2, QtCore.Qt.Horizontal, "Shortcut")
        self.model.setHeaderData(3, QtCore.Qt.Horizontal, "Description")


        
        for row, i in enumerate( self.actions ):
            #self.lst.addItem(i)             
             label = idaapi.get_action_label(i).replace("~", "").replace("&","")
             shortcut = idaapi.get_action_shortcut(i)
             tooltip = idaapi.get_action_tooltip(i)

             self.model.setData(self.model.index(row, 0, QtCore.QModelIndex()), i)
             self.model.setData(self.model.index(row, 1, QtCore.QModelIndex()), label)
             self.model.setData(self.model.index(row, 2, QtCore.QModelIndex()), shortcut)
             self.model.setData(self.model.index(row, 3, QtCore.QModelIndex()), tooltip)
             row += 1

        self.proxyModel.setSourceModel(self.model)
        self.lst.setModel(self.proxyModel)

        global last_search

        self.filter = MyEdit(last_search)
        self.regex = re.compile("(%s)"%re.escape(last_search), flags = re.IGNORECASE)
        self.regex_pattern = last_search

        self.proxyModel.setFilterRegExp( QtCore.QRegExp(self.regex_pattern, QtCore.Qt.CaseInsensitive, QtCore.QRegExp.FixedString) )

        self.filter.setMaximumHeight( 30 )
        self.filter.textChanged.connect( self.on_text_changed )
        self.filter.returnPressed.connect( self.on_enter )
        
                
        self.lst.clicked.connect(self.on_clicked)        
        #self.lst.activated.connect(self.on_activated)

        self.lst.setSelectionMode(1)#QtSingleSelection

        self.lst.setEditTriggers( QtWidgets.QAbstractItemView.NoEditTriggers )
        self.lst.setSelectionBehavior( QtWidgets.QAbstractItemView.SelectRows )        
        self.lst.setItemDelegate( HTMLDelegate(self.lst) )
        
        #self.lst.setSectionResizeMode( QtWidgets.QHeaderView.Fixed )

        self.filter.lst = self.lst
        self.lst.filter = self.filter


        self.filter.setStyleSheet('border: 0px solid black; border-bottom:0px;');
        self.lst.setStyleSheet('QListView{border: 0px solid black; background-color: #F0F0F0;}; ');

        #self.completer = QtWidgets.QCompleter(self.model)
        #self.completer.setCompletionMode(QtWidgets.QCompleter.InlineCompletion)
        #self.filter.setCompleter(self.completer)



        # Create layout
        layout = QtWidgets.QVBoxLayout()
        layout.setSpacing(0)
        layout.addWidget( self.filter )        
        layout.addWidget( self.lst )

        # Populate PluginForm
        self.setLayout(layout)
        self.filter.setFocus()
        self.filter.selectAll()        

        found = False
        if len(last_command)>0:
            for row in xrange(self.proxyModel.rowCount()):
                idx = self.proxyModel.index(row, 0)
                if self.proxyModel.data(idx) == last_command:
                    self.lst.setCurrentIndex( idx )
                    found = True
                    break
        if not found:
            self.lst.setCurrentIndex( self.proxyModel.index(0, 0) )
        
    def on_clicked(self, item):
        #print "clicked"
        self.report_action(item)        


    def on_activated(self, item):
        #print "activate"
        self.report_action(item)
    


# --------------------------------------------------------------------------
def AskForAction():
    global ImpExpForm
    #todo change [x for x in QtWidgets.QApplication.topLevelWidgets() if repr(x).find('QMainWindow') != -1][0] into something non-crazy
    parent = [x for x in QtWidgets.QApplication.topLevelWidgets() if repr(x).find('QMainWindow') != -1][0]
    ImpExpForm = ActionPaletteForm_t( parent )
    ImpExpForm.setModal(True)   
    idaapi.disable_script_timeout()

    #ImpExpForm.setStyleSheet("background:transparent;");
    ImpExpForm.setAttribute(QtCore.Qt.WA_DeleteOnClose, True);
    #ImpExpForm.setAttribute(QtCore.Qt.WA_TranslucentBackground, True);

    result = None
    

    #print "result value:", repr( ImpExpForm.res )
    #print "result action:", repr( ImpExpForm.action_name )
    
    if ImpExpForm.exec_() == 1:
        global last_command
        last_command = ImpExpForm.action_name

        result = last_command

    del ImpExpForm
    return result

# --------------------------------------------------------------------------


class command_palette_ah(idaapi.action_handler_t):
    def __init__(self):
        idaapi.action_handler_t.__init__(self)
    
    def activate(self, ctx):
        action = AskForAction()

        if action:
            idaapi.process_ui_action( action )
        return 1
    
    def update(self, ctx):
        return idaapi.AST_ENABLE_ALWAYS

command_palette_action_desc = idaapi.action_desc_t("mb:command_palette", "Command Palette", command_palette_ah(), "Shift+W", "Opens Sublime-like command palette.", -1)

class repeat_action_ah_t(idaapi.action_handler_t):
    def activate(self, ctx):
        global last_command
        return idaapi.process_ui_action( last_command )

    def update(self, ctx):
        return idaapi.AST_ENABLE_ALWAYS

repeat_action_action_desc = idaapi.action_desc_t( "mb:repeat_last_palette_command", "Repeat last palette command", repeat_action_ah_t(), "W", "Repeat last command issued with the Command Palette.", -1 )
    

def register_actions():
    idaapi.register_action(command_palette_action_desc)
    idaapi.register_action(repeat_action_action_desc)

def unregister_actions():
    idaapi.unregister_action( command_palette_action_desc.name );
    idaapi.unregister_action( repeat_action_action_desc.name );


class CommandPalettePlugin(idaapi.plugin_t):
    flags = idaapi.PLUGIN_FIX | idaapi.PLUGIN_HIDE
    comment = "Sublime-like command palette for IDA"
    help = "Sublime-like command palette"
    wanted_name = "command palette"
    wanted_hotkey = ""

    def init(self):
        addon = idaapi.addon_info_t();
        addon.id = "milan.bohacek.command_palette";
        addon.name = "Command Palette";
        addon.producer = "Milan Bohacek";
        addon.url = "milan.bohacek+commandpalette@gmail.com";
        addon.version = "6.95";
        idaapi.register_addon( addon );
        register_actions()

        return idaapi.PLUGIN_KEEP

    def term(self):
        unregister_actions()
        pass

    def run(self, arg):
        pass


def PLUGIN_ENTRY():
    return CommandPalettePlugin()

#if __name__ == "__main__":
#    print "got action:", CommandPalettePlugin()


repository: idapython/src
file: Scripts/AsmViewer.py
description: IDAPython project for Hex-Ray's IDA Pro
url: https://github.com/idapython/src/blob/af978e79e68af701c45bab401afce5145aa4b80b/Scripts/AsmViewer.py
wanted_name: ASM file viewer
comment: ASM viewer
created_at: 2015-03-14 20:09:27+00:00
pushed_at: 2025-03-13 15:18:11+00:00
content:
from __future__ import print_function
# -----------------------------------------------------------------------
# This is an example illustrating how to use customview in Python
# The sample will allow you to open an assembly file and display it in color
# (c) Hex-Rays
#

import os

import ida_idaapi
import ida_kernwin
import ida_lines
import idautils

# ----------------------------------------------------------------------
class asm_colorizer_t(object):
    def is_id(self, ch):
        return ch == '_' or ch.isalpha() or '0' <= ch <= '9'

    def get_identifier(self, line, x, e):
        i = x
        is_digit = line[i].isdigit()
        while i < e:
            ch = line[i]
            if not self.is_id(ch):
                if ch != '.' or not is_digit:
                    break
            i += 1
        return (i, line[x:i])

    def get_quoted_string(self, line, x, e):
        quote = line[x]
        i = x + 1
        while i < e:
            ch = line[i]
            if ch == '\\' and line[i+1] == quote:
                i += 1
            elif ch == quote:
                i += 1 # also take the quote
                break
            i += 1
        return (i, line[x:i])

    def colorize(self, lines):
        for line in lines:
            line = line.rstrip()
            if not line:
                self.add_line()
                continue
            x = 0
            e = len(line)
            s = ""
            while x < e:
                ch = line[x]
                # String?
                if ch == '"' or ch == "'":
                    x, w = self.get_quoted_string(line, x, e)
                    s += self.as_string(w)
                # Tab?
                elif ch == '\t':
                    s += ' ' * 4
                    x += 1
                # Comment?
                elif ch == ';':
                    s += self.as_comment(line[x:])
                    # Done with this line
                    break
                elif ch == '.' and x + 1 < e:
                    x, w = self.get_identifier(line, x + 1, e)
                    s += self.as_directive(ch + w)
                # Identifiers?
                elif self.is_id(ch):
                    x, w = self.get_identifier(line, x, e)
                    # Number?
                    if ch.isdigit():
                        s += self.as_num(w)
                    # Other identifier
                    else:
                        s += self.as_id(w)
                # Output as is
                else:
                    s += ch
                    x += 1
            self.add_line(s)


class base_asmview_ah_t(ida_kernwin.action_handler_t):
    def __init__(self, obj):
        ida_kernwin.action_handler_t.__init__(self)
        self.obj = obj

    def update(self, ctx):
        if self.obj.view and self.obj.view.GetWidget() == ctx.widget:
            return ida_kernwin.AST_ENABLE_FOR_WIDGET
        else:
            return ida_kernwin.AST_DISABLE_FOR_WIDGET


class refresh_ah_t(base_asmview_ah_t):
    def activate(self, ctx):
        self.obj.view.reload_file()
        print("Reloaded")


class close_ah_t(base_asmview_ah_t):
    def activate(self, ctx):
        self.obj.view.Close()
        print("Closed")


# -----------------------------------------------------------------------
class asmview_t(ida_kernwin.simplecustviewer_t, asm_colorizer_t):
    def Create(self, fn):
        # Create the customview
        if not ida_kernwin.simplecustviewer_t.Create(
                self,
                "Viewing file - %s" % os.path.basename(fn)):
            return False

        self.instruction_list = idautils.GetInstructionList()
        self.instruction_list.extend(["ret"])
        self.register_list    = idautils.GetRegisterList()
        self.register_list.extend(["eax", "ebx", "ecx", "edx", "edi", "esi", "ebp", "esp"])

        self.fn = fn
        if not self.reload_file():
            return False

        return True

    def reload_file(self):
        if not self.colorize_file(self.fn):
            self.Close()
            return False
        return True

    def colorize_file(self, fn):
        try:
            f = open(fn, "r")
            lines = f.readlines()
            f.close()
            self.ClearLines()
            self.colorize(lines)
            return True
        except:
            return False

    def add_line(self, s=None):
        if not s:
            s = ""
        self.AddLine(s)

    def as_comment(self, s):
        return ida_lines.COLSTR(s, ida_lines.SCOLOR_RPTCMT)

    def as_id(self, s):
        t = s.lower()
        if t in self.register_list:
            return ida_lines.COLSTR(s, ida_lines.SCOLOR_REG)
        elif t in self.instruction_list:
            return ida_lines.COLSTR(s, ida_lines.SCOLOR_INSN)
        else:
            return s

    def as_string(self, s):
        return ida_lines.COLSTR(s, ida_lines.SCOLOR_STRING)

    def as_num(self, s):
        return ida_lines.COLSTR(s, ida_lines.SCOLOR_NUMBER)

    def as_directive(self, s):
        return ida_lines.COLSTR(s, ida_lines.SCOLOR_KEYWORD)

    def OnKeydown(self, vkey, shift):
        """
        User pressed a key
        @param vkey: Virtual key code
        @param shift: Shift flag
        @return Boolean. True if you handled the event
        """
        # ESCAPE
        if vkey == 27:
            self.Close()
        elif vkey == ord('H'):
            lineno = self.GetLineNo()
            if lineno is not None:
                line, fg, bg = self.GetLine(lineno)
                if line and line[0] != ida_lines.SCOLOR_INV:
                    s = ida_lines.SCOLOR_INV + line + ida_lines.SCOLOR_INV
                    self.EditLine(lineno, s, fg, bg)
                    self.Refresh()
        elif vkey == ord('C'):
            self.ClearLines()
            self.Refresh()
        elif vkey == ord('S'):
            print("Selection (x1, y1, x2, y2) = ", self.GetSelection())
        elif vkey == ord('I'):
            print("Position (line, x, y) = ", self.GetPos(mouse = 0))
        else:
            return False
        return True

# -----------------------------------------------------------------------
ACTNAME_REFRESH = "asmview_t::refresh"
ACTNAME_CLOSE = "asmview_t::close"

class asmviewplg(ida_idaapi.plugin_t):
    flags = ida_idaapi.PLUGIN_KEEP
    comment = "ASM viewer"
    help = "This is help"
    wanted_name = "ASM file viewer"
    wanted_hotkey = "Alt-F8"
    def __init__(self):
        self.view = None

    def init(self):
        # Register actions
        ida_kernwin.register_action(
            ida_kernwin.action_desc_t(
                ACTNAME_REFRESH, "Refresh", refresh_ah_t(self)))
        ida_kernwin.register_action(
            ida_kernwin.action_desc_t(
                ACTNAME_CLOSE, "Close", close_ah_t(self)))
        return ida_idaapi.PLUGIN_KEEP

    def run(self, arg):
        if self.view:
            self.Close()
        fn = ida_kernwin.ask_file(0, "*.asm", "Select ASM file to view")
        if not fn:
            return
        self.view = asmview_t()
        if not self.view.Create(fn):
            return
        self.view.Show()
        widget = self.view.GetWidget()

        # Attach actions to this widget's popup menu
        ida_kernwin.attach_action_to_popup(widget, None, ACTNAME_REFRESH)
        ida_kernwin.attach_action_to_popup(widget, None, ACTNAME_CLOSE)

    def term(self):
        if self.view:
            self.view.Close()

def PLUGIN_ENTRY():
    return asmviewplg()


repository: gmh5225/IDA-MapSymbolParser
file: IDA-MapSymbolParser.py
description: IDA Map File Symbol Renamer
wanted_name: Parse MAP file
comment: Parse MAP file
created_at: 2024-03-15 09:34:16+00:00
pushed_at: 2025-04-01 12:13:26+00:00
url:
https://github.com/gmh5225/IDA-MapSymbolParser/blob/3eee1a52fa897bc322ba9a8198c7944df919a72c/IDA-MapSymbolParser.py

content:
import idaapi
import idautils
import idc
import re

def extract_names_and_rva_base(file_path):
    # Regular expression pattern to match lines with the desired format
    pattern = r"\s(\d+):([a-fA-F0-9]+)\s+(\S+)\s+([a-fA-F0-9]+)\s+(.+)"
    results = []

    # Open the file and read line by line
    with open(file_path, 'r', encoding='utf-8') as file:
        for line in file:
            match = re.search(pattern, line)
            if match:
                # Extract name and Rva+Base
                section_number, offset, name, rva_base, _ = match.groups()
                # Convert Rva+Base to an integer in hexadecimal
                results.append((name, int(rva_base, 16)))  
    return results

def rename_symbols_from_map():
    # Use idaapi.ask_file to show a file selection dialog for choosing a .map file
    map_file_path = idaapi.ask_file(0, "*.map", "Please select a .map file to parse")
    if map_file_path:
        extracted_data = extract_names_and_rva_base(map_file_path)
        for name, rva_base in extracted_data:
            # Use idaapi.set_name to rename the corresponding address, ensuring the address is valid
            if idc.get_segm_name(rva_base) is not None:
                idaapi.set_name(rva_base, name, idaapi.SN_NOWARN)
        print(f"[ParseMap] Processed {len(extracted_data)} symbols.")
    else:
        print("[ParseMap] No .map file selected.")

class ParseMapPlugin(idaapi.plugin_t):
    flags = 0
    comment = 'Parse MAP file'
    help = 'Parse MAP file'
    wanted_name = 'Parse MAP file'
    wanted_hotkey = ''

    def init(self):
        return idaapi.PLUGIN_OK

    def run(self, arg):
        rename_symbols_from_map()

    def term(self):
        pass

def PLUGIN_ENTRY():
    try:
        return ParseMapPlugin()

    except Exception as err:
        import traceback
        print('[ParseMap] Error: %s\n%s' % str((err), traceback.format_exc()))
        raise

# run as script
def main():
    rename_symbols_from_map()

if __name__ == '__main__':
    main()


repository: laughfool/IDA6.1PluginforQIRA
file: q2i.py
description: IDA 6.1 Plug-in(IDA Python) for QIRA
wanted_name: Qira to IDA sync Plugin
created_at: 2014-11-28 13:23:35+00:00
pushed_at: 2014-11-28 13:53:39+00:00
url:
https://github.com/laughfool/IDA6.1PluginforQIRA/blob/e45581d0bb8493532cfd940f1afa380ab3483303/q2i.py

content:
import idaapi
import socket


def jump_to(qira_address):
    #idaapi.jumpto(qira_address, -1, 0)
    idaapi.jumpto(qira_address)

def parse_msg(msg):
  dat = msg.split(" ")
  if dat[0] == "setaddress" and dat[1] != "undefined":
    try:
      a = idaapi.toEA(0, int(str(dat[1][2:]),16))
      #idaapi.msg("[q2i Plugin] parse_msg : %s\n" % a)
      jump_to(a)
    except:
      idaapi.msg("[q2i Plugin] Error processing the address\n")

def s_send(msg):
  idaapi.msg("[q2i]s_send start\n")
  s = socket.socket()
  #host = socket.gethostname()
  host = "127.0.0.1"
  port = 3001
  s.connect((host, port))
  #idaapi.msg("[q2i Plugin]send msg : " + msg + "\n")
  s.send(msg)
  result = s.recv(1024)
  #idaapi.msg("[q2i Plugin]recv msg " + result + "\n" )
  parse_msg(result)
  s.close


def get_address():
  cmd = "getaddress"
  s_send(cmd)
  
class qiraplugin_t(idaapi.plugin_t):
  flags = 0
  comment = ""
  help = ""
  wanted_name = "Qira to IDA sync Plugin"
  wanted_hotkey = "Alt-c"

  def init(self):
    self.old_addr = None
    self.addr = None
    idaapi.msg("[q2i Plugin] init!\n")
    return idaapi.PLUGIN_KEEP


  def run(self, arg):
    idaapi.msg("[q2i Plugin] Syncing ...\n")
    get_address()
    

  def term(self):
    idaapi.msg("[q2i Plugin] Plugin uninstalled!\n")


def PLUGIN_ENTRY():
  return qiraplugin_t()


repository: MlsDmitry/better-rtti-parser
file: rtti_parse.py
description: IDA script to parse RTTI information in executable.
wanted_name: Better RTTI Parser
comment: Parse RTTI information from executable
created_at: 2022-01-02 23:55:55+00:00
pushed_at: 2023-03-10 15:40:15+00:00
url:
https://github.com/MlsDmitry/better-rtti-parser/blob/df3b3989b4f49548eae840da9d54125d475196ac/rtti_parse.py

content:
from rtti_parser_core.vtable import TypeInfoVtable
from rtti_parser_core.elf import read_elf_sym_crossplatform
from rtti_parser_core.common import search, demangle
from rtti_parser_core.rtti import BasicClass, SiClass, VmiClass
import time
import logging

import idc
import idautils
import ida_name
import ida_ida
import ida_bytes
import idaapi
import ida_segment


idaapi.require('rtti_parser_core.binary_stream')
idaapi.require('rtti_parser_core.vtable')
idaapi.require('rtti_parser_core.consts')
idaapi.require('rtti_parser_core.elf')
idaapi.require('rtti_parser_core.common')
idaapi.require('rtti_parser_core.rtti')


logger = logging.getLogger(__name__)


class TiClassKind:
    CLASS_TYPE = '__class_type_info'
    # CLASS_TYPE = '_ZTVN10__cxxabiv117__class_type_infoE'
    # SI_CLASS_TYPE = '_ZTVN10__cxxabiv120__si_class_type_infoE'
    SI_CLASS_TYPE = '__si_class_type_infoE'
    # VMI_CLASS_TYPE = '_ZTVN10__cxxabiv121__vmi_class_type_infoE'
    VMI_CLASS_TYPE = '__vmi_class_type_infoE'


"""
These are symbols, that used to find typeinfos and vtables
"""
symbol_table = {
    TiClassKind.CLASS_TYPE: BasicClass,
    TiClassKind.SI_CLASS_TYPE: SiClass,
    TiClassKind.VMI_CLASS_TYPE: VmiClass
}

typeinfo_counter = 0
vtable_counter = 0
func_counter = 0


def process_class_info(symbol_name, ea):
    global typeinfo_counter, vtable_counter, func_counter

    for typeinfo_ea in idautils.XrefsTo(ea, 0):
        if typeinfo_ea.frm == idc.BADADDR:
            continue

        classtype = symbol_table[symbol_name](typeinfo_ea.frm)

        # skip this one, because name hasn't been read.
        if not classtype.read_name():
            logger.error(
                f'Failed to read name of typeinfo. mangled is: {classtype.type_name} at {hex(typeinfo_ea.frm)}'
            )
            continue
        # will get rid of global variables later
        typeinfo_counter += 1

        classtype.read_typeinfo()

        logger.info(
            f'Found typeinfo for {classtype.dn_name} at {hex(typeinfo_ea.frm)}')

        # read vtable
        if not classtype.read_vtable():
            logger.error(
                f'Failed to find vtable for {classtype.dn_name}'
            )
            continue

        vtable_counter += 1
        func_counter += len(classtype.vtable.entries)

        # create struct for vtable
        if classtype.create_vtable_struct():
            # retype functions
            classtype.retype_vtable_functions()
        else:
            logger.error(
                f'vtable struct for {classtype.dn_name} not created !')


def process():
    start_time = time.time()
    for symbol_name in symbol_table:
        addr_ea = search(symbol_name)
        # get start of the string
        addr_ea = ida_bytes.get_item_head(addr_ea)
        
        logger.info(f'Found {symbol_name} at {hex(addr_ea)}')

        # get only firest xref
        elf_sym_struct_ea = next(idautils.XrefsTo(addr_ea, 0), None)
        if not elf_sym_struct_ea:
            logger.error(
                f'No Code refs found for {symbol_name}'
            )
            continue

        # parse Elf<64/32>_Sym struct
        elf_sym_s = read_elf_sym_crossplatform(
            elf_sym_struct_ea.frm)

        if not elf_sym_s or elf_sym_s.st_value == idc.BADADDR:
            logger.error(
                f'No st_value in Elf Sym struct. ea: {hex(elf_sym_struct_ea.frm)}. elf_sym struct: {elf_sym_s}')
            continue

        logger.info(f'elf_sym_s address is: {hex(elf_sym_s.st_value)}')

        typeinfo_vtable = TypeInfoVtable(
            symbol_name, demangle(symbol_name), elf_sym_s.st_value)

        typeinfo_vtable.read()
        # using typeinfo offset to search for other typeinfos
        process_class_info(symbol_name, typeinfo_vtable.typeinfo_offset_ea)

    logger.info(f'Completed in {round(time.time() - start_time, 2)}s')
    logger.info(f'Total new classes: {typeinfo_counter}\n\
Total vtables: {vtable_counter}\n\
Total reanmed funcitons {func_counter}')

def main():
    process()


if __name__ == '__main__':
    # breakpoint()
    main()


class BetterRTTIParserPlugin(idaapi.plugin_t):
    flags = 0
    comment = 'Parse RTTI information from executable'
    help = 'Parse RTTI information from executable'
    wanted_name = 'Better RTTI Parser'
    wanted_hotkey = ''

    def init(self):
        return idaapi.PLUGIN_OK

    def run(self, arg):
        process()

    def term(self):
        pass

def PLUGIN_ENTRY():
    try:
        return BetterRTTIParserPlugin()

    except Exception as err:
        import traceback
        print('rtti_parse.py Error: %s\n%s' % str((err), traceback.format_exc()))
        raise


repository: matthewrhannon/IDAPython-Automation-Scripts
file: setup_RPyC_Server.py
created_at: 2024-09-24 12:53:57+00:00
pushed_at: 2024-09-24 13:32:30+00:00
url:
https://github.com/matthewrhannon/IDAPython-Automation-Scripts/blob/cad9d3a5bb5a92ac65736b674a7166348a7ebcc6/setup_RPyC_Server.py

content:
"""rpyc IDA server"""

from __future__ import print_function

from rpyc.utils.server import OneShotServer
from rpyc.core import SlaveService
import concurrent

def serve_threaded(hostname="localhost", port=4455):
    """This will run a rpyc server in IDA, so a custom script client will be
    able to access IDA api.
    WARNING: IDA will be locked until the client script terminates.
    """

    print('Running server')
    server = OneShotServer(SlaveService, hostname=hostname,
                           port=port, reuse_addr=True, ipv6=False,
                           authenticator=None,
                           auto_register=False)
    server.logger.quiet = False

    return server.start()

def main():
    print("Start thread running")
    pool = concurrent.futures.ThreadPoolExecutor(max_workers=1)
    pool.submit(serve_threaded)
    pool.shutdown(wait=True)
    print("Main thread continuing to run")

def worker():
    print("Worker thread running")

def PLUGIN_ENTRY_SORDA():
    try:
        serve_threaded()
    except:
        print("WARNING: setup_RPyc_Server script - NO serve_threaded from PLUGIN_ENTRY_SORDA!")
        pass

if __name__ == "__main__":

    try:
       main()
    except:
       print("WARNING: setup_RPyc_Server script - no main()")
       pass

    try:
       serve_threaded()
    except:
       print("WARNING: setup_RPyc_Server script - no serve_threaded()")
       pass

    try:
       import pydevd
       pydevd.settrace(host='localhost', port=51234, stdoutToServer=True, stderrToServer=True)
    except:
       print("exception caught and not handled!")
       pass

try:
   pool = concurrent.futures.ThreadPoolExecutor(max_workers=2)
 
   pool.submit(worker)
   pool.submit(worker)
 
   pool.shutdown(wait=True)
except:
   print("WARNING: setup_RPyc_Server script - starting code threw exception!")
   pass

print("Main thread continuing to run")


repository: cellebrite-labs/PacXplorer
file: pacxplorer.py
description: IDA plugin to find code cross references to virtual functions using PAC codes
wanted_name: PacXplorer
comment: find xrefs for vtable methods using PAC codes
created_at: 2020-09-14 14:19:55+00:00
pushed_at: 2022-03-16 08:48:51+00:00
url:
https://github.com/cellebrite-labs/PacXplorer/blob/7c785e2c04e1091437241f0b154b64bd4b78b72b/pacxplorer.py

content:
# PacXplorer - IDA plugin to find code cross references to virtual functions using PAC codes
#    Copyright (C) 2019, 2020 Cellebrite DI LTD

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

# This program 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 General Public License for more details.

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

from __future__ import print_function
import mmap
import idaapi
import idc
import idautils
from netnode import Netnode
from idaapi import Choose
from collections import namedtuple
from functools import wraps, partial
import os
import struct
import sys

PYTHON3 = sys.version_info.major == 3
if PYTHON3:
    import pickle

    def iterkeys(dct):
        return iter(dct.keys())

    def iteritems(dct):
        return iter(dct.items())
else:
    import cPickle as pickle

    iterkeys = dict.iterkeys
    iteritems = dict.iteritems

NETNODE = "$ pacxplorer"

DEBUG_VTABLE_SIZES = False
FORCE_ORIG_BYTES_FROM_INPUT_FILE = False
DEBUG_PATCHED_BYTES = False
DEBUG_MULTIPLE_INHERITANCE = False


class PickleNetNode(Netnode):
    @staticmethod
    def _encode(data):
        return pickle.dumps(data)

    @staticmethod
    def _decode(data):
        return pickle.loads(data)

    @staticmethod
    def cached(cache_key):
        """Decorator for returning cached values from self.cache[cache_key] for method with no arguments"""
        def decorator(method):
            @wraps(method)
            def wrapper(self):
                if hasattr(self, 'log'):
                    log = self.log.info
                else:
                    log = print

                cached_val = self.cache.get(cache_key)
                if cached_val is not None:
                    log("Using cached value for %s" % cache_key)
                    return cached_val
                log("No cached value for %s, generating..." % cache_key)
                result = method(self)
                self.cache[cache_key] = result
                log("Value for %s generated" % cache_key)
                return result
            return wrapper
        return decorator


VtableXrefTuple = namedtuple('VtableXrefTuple', ['xref_to', 'vtable_addr', 'vtable_entry_addr', 'offset', 'pac'])


class VtableAnalyzer(object):
    class PatchedBytesIDB(object):
        def __enter__(self):
            return self

        def __exit__(self, exc_type, exc_val, exc_tb):
            pass

        def get_original_qword(self, ea):
            return idaapi.get_original_qword(ea)

    class PatchedBytesFile(object):
        def __init__(self, filename):
            self.filename = filename
            self.mapping = None

        def __enter__(self):
            assert self.mapping is None
            with open(self.filename, 'rb') as f:
                # the mmap object dup()s the file descriptor
                self.mapping = mmap.mmap(f.fileno(), 0, mmap.MAP_PRIVATE, mmap.PROT_READ)

            return self

        def __exit__(self, exc_type, exc_val, exc_tb):
            self.mapping.close()
            self.mapping = None

        def get_original_qword(self, ea):
            assert self.mapping is not None

            file_offset = idaapi.get_fileregion_offset(ea)
            try:
                if file_offset == -1:
                    raise IndexError()
                return struct.unpack_from("<Q", self.mapping, file_offset)[0]
            except IndexError:
                if DEBUG_PATCHED_BYTES:
                    print("pacxplorer: get_original_qword(0x%x) - bad file offset" % ea)
                return None

    def __init__(self, cache):
        self.cache = cache  # cache for analyzed data
        if DEBUG_VTABLE_SIZES:
            self.vtable_struct_sizes = None  # {symbol_name: size} for all vtables in idb
        self.vtable_eas = None  # {ea: symbol_name} for all vtables in the idb
        self.funcs_by_code_dict = None  # {}
        self.codes_by_func_dict = None  # {}

        self.use_patched_bytes = self._patched_bytes_in_idb()
        self.file_to_open = None

    def analyze(self):
        if not self.use_patched_bytes and self.file_to_open is None:
            raise RuntimeError("no patched bytes in IDB and backing file not set")

        if DEBUG_VTABLE_SIZES:
            self.vtable_struct_sizes = self.collect_all_vtable_struct_sizes()

        self.vtable_eas = self.collect_all_vtable_eas()
        self.funcs_by_code_dict, self.codes_by_func_dict = self.generate_pac_xrefs()
        print('VtableAnalyzer analysis done')

    def can_xref_from_ea(self, ea):
        if ea in self.codes_by_func_dict:
            return ea
        else:
            refs = list(idautils.DataRefsFrom(idc.get_item_head(ea)))
            if len(refs) != 1:
                return None
            else:
                func_addr = refs[0]
                return func_addr if func_addr in self.codes_by_func_dict else None

    def func_from_pac_tuple(self, pac_tuple):
        return self.funcs_by_code_dict.get(pac_tuple, [])

    def codes_from_func_addr(self, ea):
        codes_dict = self.codes_by_func_dict.get(ea)
        return list(iterkeys(codes_dict)) if codes_dict else None

    # Taken from ida_kernelcache.symbol, to remove the dependency
    @staticmethod
    def _mangle_name(scopes):
        symbol = ''
        if len(scopes) > 1:
            symbol += 'N'
        for name in scopes:
            if len(name) == 0:
                return None
            symbol += '{}{}'.format(len(name), name)
        if len(scopes) > 1:
            symbol += 'E'
        return symbol

    # Taken from ida_kernelcache.symbol, to remove the dependency
    @classmethod
    def vtable_symbol_for_class(cls, classname):
        """Get the mangled symbol name for the vtable for the given class name.

        Arguments:
            classname: The name of the C++ class.

        Returns:
            The symbol name, or None if the classname is invalid.
        """
        name = cls._mangle_name(classname.split('::'))
        if not name:
            return None
        return '__ZTV' + name

    VTABLE_SUFFIX = '::vtable'
    SUFFIX_LEN = len(VTABLE_SUFFIX)

    @PickleNetNode.cached('vtable_struct_sizes')
    def collect_all_vtable_struct_sizes(self):
        """
        Generate a disctionary of <symbol name>:<size> for all the vtables, based on defined structs.
        Iterates all structs with names that end with ::vtable (generated by ida_kernelcache).
        """
        sizes = {self.vtable_symbol_for_class(name[:-self.SUFFIX_LEN]): idaapi.get_struc_size(struct_id)
                 for _, struct_id, name in idautils.Structs() if name.endswith(self.VTABLE_SUFFIX)}
        return sizes

    @staticmethod
    def iter_name_ea(names):
        for name in names:
            yield name, idc.get_name_ea_simple(name)

    @PickleNetNode.cached('vtable_eas2')
    def collect_all_vtable_eas(self):
        """Generate a dictionary of <ea>:<symbol name> for all the vtables whose sizes we know"""
        vtables = {}
        for ea, name in idautils.Names():
            if ea == idc.BADADDR or name is None:
                continue
            if name.startswith('__ZTV') or name.startswith('_ZTV'):
                demangled = idaapi.demangle_name(name, idaapi.inf_get_short_demnames())
                if not demangled or not demangled.startswith("`vtable for'"):
                    print("pacxplorer: warning, name %s, demangled %s, does not seem to be a vtable" %
                          (name, repr(demangled)))
                    continue
                vtables[ea] = name

        if DEBUG_VTABLE_SIZES:
            printed = False
            for name, ea in self.iter_name_ea(self.vtable_struct_sizes):
                if ea == idc.BADADDR:
                    continue
                if ea not in vtables:
                    print("pacxplorer: warning, vtable %s @ 0x%x is only found using the legacy method" %
                          (name, ea))
                    printed = True
            if not printed:
                print("all legacy vtables were accounted for using new method")

        return vtables

    # Based on https://github.com/Synacktiv-contrib/kernelcache-laundering/blob/master/ios12_kernel_cache_helper.py
    @staticmethod
    def get_pac(decorated_addr):
        """Return MOVK pac code from decorated pointer"""
        if decorated_addr & 0x4000000000000000 != 0:
            return None
        if decorated_addr & 0x8000000000000000 == 0:
            return None
        return (decorated_addr >> 32) & 0xFFFF

    @staticmethod
    def _patched_bytes_in_idb():
        if FORCE_ORIG_BYTES_FROM_INPUT_FILE:
            return False

        # For now, we just check if tehere are ANY patched bytes in the IDB,
        # and assume that patches correspond to PAC codes being present in the IDB.
        # This of course doesn't always hold, but in practice, others uses for patches are rare.
        have_patches = bool(idaapi.visit_patched_bytes(0, idc.BADADDR, lambda *x: 1))
        return have_patches

    @PickleNetNode.cached('vtable_pac_xrefs')
    def generate_pac_xrefs(self):
        """
        The main functionality of the vtable analysis module:
        Generates two way xrefs:
            a. from pac code to method, remembering all the different vtables that reach this method
            b. from method to pac code(s), remembering all the different vtables,
               and supporting the unlikely case of various pac code for the same method

        Returns:
            funcs_by_code_dict: dict of <pac code>:<VtableXrefTuple(xref'ed method)>
            codes_by_func_dict: dict of <method ea>:<dict of <pac code>:<VtableXrefTuple(xref'ed method)> >

        """
        funcs_by_code_dict = {}
        codes_by_func_dict = {}

        _patched_bytes_obj = self.PatchedBytesIDB() if self.use_patched_bytes else \
            self.PatchedBytesFile(self.file_to_open)

        with _patched_bytes_obj as patched_bytes:
            """
            In case of multiple inheritance, the vtable contains more concatenated vtables in a special way.
            We need to parse the concatenated vtables of the base classes as well as the main one.
            
            vtable layout with multiple inheritance can look like this:
            <offset to this>
            <rtti>
            vmethod 1
            ...
            vmethod n
            
            <offset to this> --> this is the vtable of one of the base classes
            <rtti>  --> same rtti as before
            vmethod n+1
            ...
            vmethod m
            
            <offset of this> --> another base class
            < etc >
            """

            for ea, vtable_symbol in iteritems(self.vtable_eas):

                first_rtti_ptr = idaapi.get_qword(ea + 8)
                # first section of the vtable - main vtable.
                # mostly, this is the only part there is.
                # If there are concatenated vtables due to multiple inheritance, iterate over them.
                while True:
                    ea += 16  # skip 'this offset' and rtti in vtable
                    # offset in the current vtable
                    offset = 0
                    # now iterate over vmethods
                    while True:
                        orig_qword = patched_bytes.get_original_qword(ea + offset)
                        patched_qword = idaapi.get_qword(ea + offset)
                        if DEBUG_PATCHED_BYTES and orig_qword is None:
                            print("orig_qword is None")
                            print("ea 0x%x, offset 0x%x" % (ea, offset))
                            print("symbol: %s" % vtable_symbol)

                        # end of the vtable is detected by encountering not a tagged pointer
                        # this is okay even if there are severl conjoined vtables back-to-back,
                        # due to the first non-ptr element in the vtable
                        if orig_qword == patched_qword or orig_qword is None:
                            if DEBUG_VTABLE_SIZES:
                                size = self.vtable_struct_sizes.get(vtable_symbol)
                                if size is not None and offset != size:
                                    print("%s @ 0x%x: non-matching sizes: old 0x%x, new 0x%x" %
                                          (vtable_symbol, ea+size, size, offset))
                            break

                        # this is expected to always succeed
                        pac = self.get_pac(orig_qword)
                        if pac is not None:
                            xref_to = idaapi.get_qword(ea + offset)

                            # VtableXrefTuple = namedtuple('VtableXrefTuple', ['xref_to' , 'vtable_addr', 'vtable_entry_addr', 'offset', 'pac'])
                            xref_tuple = VtableXrefTuple(xref_to, ea-16, ea + offset, offset, pac)
                            refs = funcs_by_code_dict.setdefault((offset, pac), [])
                            refs.append(xref_tuple)

                            codes_dict = codes_by_func_dict.setdefault(xref_to, {})
                            refs = codes_dict.setdefault((offset, pac), [])
                            refs.append(xref_tuple)

                        offset += 8

                    # if we haven't parsed anything in the inner loop, no need to process this whole vtable any more
                    if offset == 0:
                        break
                    # in the context of the concatenated vtables loop, skip past the vtable we have just finished
                    ea += offset
                    rtti_ptr = idaapi.get_qword(ea + 8)
                    # we know there is an concatenated vtable only by encountering the same rtti ptr as the main one.
                    # normal case: not concatenated vtable, so break after one iteration
                    if first_rtti_ptr == 0 or rtti_ptr != first_rtti_ptr:
                        break
                    else:
                        if DEBUG_MULTIPLE_INHERITANCE:
                            print("parsing concatenated vtable at 0x%x, symbol %s " % (ea, vtable_symbol))

            return funcs_by_code_dict, codes_by_func_dict


MovkCodeTuple = namedtuple('MovkCodeTuple', ['pac_tuple', 'movk_addr', 'trace'])


class MovkAnalyzer(object):
    GOOD_MOVK_COMMENT = 'This MOVK has PAC xrefs'
    BAD_STATICVTBL_MOVK_COMMENT = 'This MOVK has **NO** PAC xrefs (static vtable)'
    BAD_ERROR_MOVK_COMMENT = 'This MOVK has **NO** PAC xrefs (analysis error, please report!)'
    BAD_INDIRECT_REG_VAL = 'Could not find register value for indirect op (analysis error, please report!)'

    def __init__(self, cache):
        self.cache = cache  # cache for analyzed data
        self.codes_by_movk_dict = None
        self.movk_by_code_dict = None

    def analyze(self):
        self.codes_by_movk_dict, self.movk_by_code_dict = self.analyze_all_funcs()
        print('MOVK analysis done')

    def can_xref_from_ea(self, ea):
        code = self.codes_by_movk_dict.get(ea)
        return ea if (code and code.pac_tuple) else None

    def pac_tuple_from_ea(self, ea):
        # MovkCodeTuple = namedtuple('MovkCodeTuple', ['pac_tuple', 'movk_addr', 'trace'])
        movk_code_tuple = self.codes_by_movk_dict.get(ea)
        if movk_code_tuple is None:
            return None
        return movk_code_tuple.pac_tuple

    def movks_from_pac_codes(self, pac_codes):
        movks = []
        for code in pac_codes:
            for movk in self.movk_by_code_dict.get(code, []):
                movks.append((movk, code))

        return movks

    @classmethod
    def edit_comment(cls, comment, wanted, delete):
        if comment is None:
            comment = ""
        if not delete and wanted not in comment:
            comment = comment.rstrip()
            if comment:
                comment += '\n'
            comment += wanted
        elif delete and wanted in comment:
            parts = comment.split(wanted)
            comment = '\n'.join((x.rstrip() for x in parts))
        return comment

    @classmethod
    def add_comment(cls, ea, wanted):
        comment = idc.get_cmt(ea, True)
        new_comment = cls.edit_comment(comment, wanted, delete=False)
        idc.set_cmt(ea, new_comment, True)

    @classmethod
    def delete_comment(cls, ea, wanted):
        comment = idc.get_cmt(ea, True)
        new_comment = cls.edit_comment(comment, wanted, delete=True)
        idc.set_cmt(ea, new_comment, True)

    @staticmethod
    def does_modify_reg(insn, reg):
        for i in range(6):
            op = insn.ops[i]
            if op.reg != reg:
                continue
            if insn.get_canon_feature() & (0x4 << i):  # CF_CHG1, CF_CHG2, ...
                return True
        return False

    @staticmethod
    def does_use_reg(insn, reg):
        for i in range(6):
            op = insn.ops[i]
            if op.reg != reg:
                continue
            if insn.get_canon_feature() & (0x100 << i):  # CF_USE1, CF_USE2, ...
                return True
        return False

    @staticmethod
    def does_use_any_reg(insn):
        for i in range(6):
            op = insn.ops[i]
            if op.reg == 0:
                continue
            if insn.get_canon_feature() & (0x100 << i):  # CF_USE1, CF_USE2, ...
                return True
        return False

    @classmethod
    def analyze_movk(cls, addr):
        trace = []
        seen_depac = False
        offset = 0

        cur_func = idaapi.get_func(addr)
        if not cur_func:
            return (None, None)
        cur_func_start = cur_func.start_ea

        insn = idautils.DecodeInstruction(addr)
        mnem = insn.get_canon_mnem()
        if mnem != 'MOVK' or insn.Op2.specval != 48:
            return (None, None)
        ctx_reg = insn.Op1.reg
        movk_code = insn.Op2.value

        line = idc.GetDisasm(insn.ea)
        trace.append(line)

        curr = addr
        visited = set()
        while True:
            refs = [x for x in idautils.CodeRefsTo(curr, True) if idaapi.get_func(x) and idaapi.get_func(x).start_ea == cur_func_start]
            if not refs:
                trace.append('ERROR NO REFS')
                break

            curr = refs[0]
            if curr in visited:
                trace.append('ERROR EXHAUSTED')
                break

            visited.add(curr)

            insn = idautils.DecodeInstruction(curr)
            mnem = insn.get_canon_mnem()
            line = idc.GetDisasm(insn.ea)
            line = line.split(';')[0]  # line comments get into the dissasembly
            line_parts = [x for x in line.split(' ') if x]

            trace.append(line)

            if line_parts[0:1] == ['LDRAA'] and line_parts[-1:] and line_parts[-1:][0].endswith('!') and \
                    insn.Op2.reg == ctx_reg:
                offset = insn.Op2.addr
                seen_depac = True
                break
            if line_parts and line_parts[0] in ['AUTDZA', 'AUTDA'] and insn.Op1.reg == ctx_reg:
                seen_depac = True
                break
            if cls.does_modify_reg(insn, ctx_reg):
                if not cls.does_use_any_reg(insn):
                    trace.append('ERROR STATIC VTABLE')
                    cls.add_comment(addr, cls.BAD_STATICVTBL_MOVK_COMMENT)
                    break
                elif mnem == 'MOV':
                    ctx_reg = insn.Op2.reg
                elif mnem == 'ADD':
                    ctx_reg = insn.Op2.reg
                    if insn.Op3.type == idaapi.o_idpspec0:
                        indirect_reg = insn.Op3.reg
                        sliding_ea = insn.ea
                        while sliding_ea >= cur_func_start:
                            prev_insn, _ = idautils.DecodePrecedingInstruction(sliding_ea)
                            
                            if prev_insn is None:
                                trace.append('ERROR FINDING VALUE FOR REG IN INDIRECT OP')
                                cls.add_comment(sliding_ea, cls.BAD_INDIRECT_REG_VAL)
                                break
                            elif prev_insn.get_canon_mnem() == "MOV" and prev_insn.Op1.reg == indirect_reg:
                                offset += prev_insn.Op2.value
                                break

                            sliding_ea = prev_insn.ea
                    else:
                        offset += insn.Op3.value
                else:
                    trace.append('ERROR BAD MODIFY')
                    #cls.add_comment(addr, cls.BAD_ERROR_MOVK_COMMENT)
                    break
            elif cls.does_use_reg(insn, ctx_reg) and line_parts[-1:] and line_parts[-1:][0].endswith('!'):
                if mnem == 'LDR' and insn.Op2.reg == ctx_reg:
                    offset += insn.Op2.addr
                else:
                    trace.append('ERROR BAD MODIFY')
                    #cls.add_comment(addr, cls.BAD_ERROR_MOVK_COMMENT)
                    break

        trace = trace[::-1]
        pac_tuple = (offset, movk_code) if seen_depac else None

        if pac_tuple is not None:
            cls.add_comment(addr, cls.GOOD_MOVK_COMMENT)

        return (pac_tuple, trace)

    def _analyze_func(self, func_addr, codes_by_movk_dict, movk_by_code_dict):
        for addr in idautils.FuncItems(func_addr):
            pac_tuple, trace = self.analyze_movk(addr)
            if not (pac_tuple is None and trace is None):
                codes_by_movk_dict[addr] = MovkCodeTuple(pac_tuple, addr, trace)
                if pac_tuple is not None:
                    movks = movk_by_code_dict.setdefault(pac_tuple, [])
                    movks.append(addr)

    @PickleNetNode.cached('movk_analysis')
    def analyze_all_funcs(self):
        codes_by_movk_dict = {}
        movk_by_code_dict = {}
        for func in idautils.Functions():
            self._analyze_func(func, codes_by_movk_dict, movk_by_code_dict)

        return codes_by_movk_dict, movk_by_code_dict


class _Choose(Choose):
    # Fix Choose.UI_Hooks_Trampoline to work with modal dialogs
    class UI_Hooks_Trampoline(Choose.UI_Hooks_Trampoline):
        def populating_widget_popup(self, form, popup_handle):
            chooser = self.v()
            if hasattr(chooser, "OnPopup") and \
                    callable(getattr(chooser, "OnPopup")):
                chooser.OnPopup(form, popup_handle)

    class chooser_handler_t(idaapi.action_handler_t):
        def __init__(self, handler):
            idaapi.action_handler_t.__init__(self)
            self.handler = handler

        def activate(self, ctx):
            self.handler()
            return 1

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

    def __init__(self, title, items, columns):
        Choose.__init__(
            self,
            title,
            columns,
            flags=Choose.CH_RESTORE)

        self.items = items

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

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

    def show(self):
        selected = self.Show(modal=True)
        if selected < 0:
            return None
        return self.items[selected]


class MovkXrefChooser(_Choose):
    unique_functions = True

    def __init__(self, title, items):
        _Choose.__init__(
            self,
            title,
            items,
            [ ["Address", 20 | Choose.CHCOL_HEX], ["Method", 40 | Choose.CHCOL_PLAIN],
              ["Class", 30 | Choose.CHCOL_PLAIN] ])

        self.all_items = items
        self.calculate_unique()

    def OnPopup(self, form, popup_handle):
        idaapi.attach_action_to_popup(form, popup_handle, "-", None, idaapi.SETMENU_FIRST)

        desc = idaapi.action_desc_t(
            "choose:unique",
            "PAC: toggle unique function names",
            self.chooser_handler_t(self.toggle_unique))
        idaapi.attach_dynamic_action_to_popup(form, popup_handle, desc, None, idaapi.SETMENU_FIRST)

    def OnRefresh(self, n):
        return (Choose.ALL_CHANGED, 0)

    def calculate_unique(self):
        if MovkXrefChooser.unique_functions:
            unique_dict = {}
            for addr, func, classname in self.all_items:
                if classname != unique_dict.setdefault((addr, func), classname):
                    unique_dict[(addr, func)] = '<multiple classes>'
            self.items = list(sorted( ([addr, funcname, classname] for (addr, funcname), classname in
                                       iteritems(unique_dict)), key=lambda x: x[0]))
        else:
            self.items = self.all_items

    def toggle_unique(self):
        MovkXrefChooser.unique_functions = not MovkXrefChooser.unique_functions
        self.calculate_unique()
        self.Refresh()

    @staticmethod
    def demangle(ea):
        name = idc.get_name(ea, idc.GN_LONG)
        demangled = (idaapi.demangle_name(str(name), idc.get_inf_attr(idc.INF_SHORT_DEMNAMES)) or '').\
            replace("`vtable for'", "")
        return demangled or name


class FuncXrefChooser(_Choose):
    def __init__(self, title, items):
        _Choose.__init__(
            self,
            title,
            items,
            [ ["Address", 30 | Choose.CHCOL_PLAIN], ["Address (Hex)", 20 | Choose.CHCOL_HEX],
              ["PAC Code", 20 | Choose.CHCOL_PLAIN] ])


class PacxplorerPlugin(idaapi.plugin_t, idaapi.UI_Hooks):
    plugin_initialized = False
    flags = idaapi.PLUGIN_MOD  | idaapi.PLUGIN_HIDE
    comment = "find xrefs for vtable methods using PAC codes"
    help = ""
    wanted_name = "PacXplorer"
    wanted_hotkey = ""

    class MenuBase(idaapi.action_handler_t):
        label = None
        shortcut = None
        tooltip = None
        icon = -1

        def __init__(self, plugin):
            self.plugin = plugin
            self.name = self.plugin.wanted_name + ':' + self.__class__.__name__
            self.register()

        def register(self):
            return idaapi.register_action(idaapi.action_desc_t(
                self.name,  # Name. Acts as an ID. Must be unique.
                self.label,  # Label. That's what users see.
                self,  # Handler. Called when activated, and for updating
                self.shortcut,  # shortcut,
                self.tooltip,  # tooltip
                self.icon  # icon
            ))

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

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

        def update(self, ctx):
            return idaapi.AST_ENABLE_ALWAYS

        def path(self):
            return "Edit/Plugins/" + self.plugin.wanted_name + "/" + self.label

        def get_name(self):
            return self.name

    class AnalyzeMenu(MenuBase):
        label = 'Analyze IDB...'

        def activate(self, ctx):
            self.plugin.analyze()
            return 1

    class JumpXrefMenu(MenuBase):
        label = 'Jump to PAC XREFs...'
        shortcut = 'Meta-X'
        icon = 151

        def activate(self, ctx):
            self.plugin.choose_window_here()
            return 1

        def update(self, ctx):
            return idaapi.AST_ENABLE if self.plugin.can_find_xrefs_here() else idaapi.AST_DISABLE

    def __init__(self):
        self.analysis_done = False
        self.vtable_analyzer = None
        self.movk_analyzer = None
        self.ui_hook = False
        self.analyze_menu = None
        self.jump_xref_menu = None

    def init(self):
        """plugin_t init() function"""
        super(PacxplorerPlugin, self).__init__()

        typename = idaapi.get_file_type_name().lower()
        if 'arm64e' not in typename:
            print('%s: IDB deemed unsuitable (not an ARM64e binary). Skipping...' % self.wanted_name)
            return idaapi.PLUGIN_SKIP

        if not PacxplorerPlugin.plugin_initialized:
            self.analyze_menu = self.AnalyzeMenu(self)
            self.jump_xref_menu = self.JumpXrefMenu(self)

            self.ui_hook = True
            self.hook()
            print('%s: IDB deemed suitable. Initializing...' % self.wanted_name)

        return idaapi.PLUGIN_KEEP

    def run(self, arg=0):
        """plugin_t run() implementation"""
        return

    def term(self):
        """plugin_t term() implementation"""
        if self.ui_hook:
            self.unhook()
            self.ui_hook = False
        return

    def ready_to_run(self):
        """UI_Hooks function.
        Attaches actions to plugin in main menu.
        """

        idaapi.attach_action_to_menu(self.analyze_menu.path(), self.analyze_menu.get_name(), idaapi.SETMENU_APP)
        idaapi.attach_action_to_menu(self.jump_xref_menu.path(), self.jump_xref_menu.get_name(), idaapi.SETMENU_APP)
        PacxplorerPlugin.plugin_initialized = True
        self.analyze(only_cached=True)

    def finish_populating_widget_popup(self, widget, popup_handle):
        """UI_Hooks function
        Attaches the Find Xref action to the dissasembly right click menu.
        """
        if not self.analysis_done:
            return
        if idaapi.get_widget_type(widget) == idaapi.BWN_DISASM and self.can_find_xrefs_here():
            idaapi.attach_action_to_popup(widget, popup_handle, "-", None, idaapi.SETMENU_FIRST)
            idaapi.attach_action_to_popup(widget, popup_handle, self.jump_xref_menu.get_name(), None,
                                          idaapi.SETMENU_FIRST)

    def analyze(self, only_cached=False):
        cache = PickleNetNode(NETNODE)
        # cache.kill()

        if only_cached:
            if not cache.get('analysis_done', False):
                return
            print('%s: this IDB had been previously analyzed, loading from cache' % self.wanted_name)
        elif self.analysis_done:
            answer = idc.ask_yn(idaapi.ASKBTN_NO, "HIDECANCEL\nRe-analyze the IDB?")
            if answer != idaapi.ASKBTN_YES:
                return
            cache.kill()
            self.analysis_done = False

        should_hide_wait = False
        if not cache.get('analysis_done', False):
            should_hide_wait = True
            idaapi.show_wait_box("HIDECANCEL\n%s analyzing..." % self.wanted_name)

        # not catching exceptions, just want to call the finally block to hide the wait_box
        try:
            self.vtable_analyzer = VtableAnalyzer(cache)
            self.movk_analyzer = MovkAnalyzer(cache)

            if not self.vtable_analyzer.use_patched_bytes:
                file_to_open = self.choose_file_for_patches()
                if file_to_open is None:
                    print("%s: user cancelled" % self.wanted_name)
                    return
                self.vtable_analyzer.file_to_open = file_to_open

            self.vtable_analyzer.analyze()
            if len(self.vtable_analyzer.funcs_by_code_dict) == 0:
                idaapi.warning(('%s\nUnable to find vtables and pac codes.\n' 
                               'If this is a KernelCache:\n'
                                'make sure ida_kernelcache is run on this idb') % self.wanted_name)
                return

            self.movk_analyzer.analyze()
            if len(self.movk_analyzer.movk_by_code_dict) == 0:
                idaapi.warning('%s\nUnable to find movk pac codes.\nThis is weird...' % self.wanted_name)

            cache['analysis_done'] = True
            self.analysis_done = True
        finally:
            if should_hide_wait:
                idaapi.hide_wait_box()
            if not self.analysis_done:
                cache.kill()

    def choose_xref_from_movk(self, ea):
        pac_tuple = self.movk_analyzer.pac_tuple_from_ea(ea)
        if not pac_tuple:
            return None

        raw_candidates = self.vtable_analyzer.func_from_pac_tuple(pac_tuple)
        # VtableXrefTuple = namedtuple('VtableXrefTuple', ['xref_to' , 'vtable_addr', 'vtable_entry_addr', 'offset', 'pac'])
        candidates = [ ["0x%016x" % x.xref_to, MovkXrefChooser.demangle(x.xref_to),
                        MovkXrefChooser.demangle(x.vtable_addr)] for x in raw_candidates ]
        title = "PAC xrefs from 0x%016X" % ea
        chooser = MovkXrefChooser(title, candidates)
        chosen = chooser.show()
        if chosen is None:
            return None
        return int(chosen[0], 16)

    def choose_xref_to_func(self, ea):
        pac_codes = self.vtable_analyzer.codes_from_func_addr(ea)
        if not pac_codes:
            return None

        movks = self.movk_analyzer.movks_from_pac_codes(pac_codes)
        candidates = [[idc.get_func_off_str(addr), "0x%016x" % addr, "(%d, 0x%4x)" % (code[0], code[1])]
                      for addr, code in movks]

        title = 'PAC xrefs to 0x%016X' % ea
        chooser = FuncXrefChooser(title, candidates)
        chosen = chooser.show()

        if chosen is None:
            return None
        return int(chosen[1], 16)

    def pick_choose_func_for_ea(self, ea):
        ref_addr = self.movk_analyzer.can_xref_from_ea(ea)
        if ref_addr:
            return partial(self.choose_xref_from_movk, ref_addr)

        ref_addr = self.vtable_analyzer.can_xref_from_ea(ea)
        if ref_addr:
            return partial(self.choose_xref_to_func, ref_addr)

        return None

    def choose_by_ea(self, ea):
        choose_func = self.pick_choose_func_for_ea(ea)
        if choose_func is None:
            return

        addr = choose_func()
        if addr is not None:
            idc.jumpto(addr)

    def choose_window_here(self):
        if not self.analysis_done:
            return
        self.choose_by_ea(idc.here())

    def can_find_xrefs_here(self):
        if not self.analysis_done:
            return False
        return self.pick_choose_func_for_ea(idc.here()) is not None

    def choose_file_for_patches(self):
        file_path = idaapi.get_input_file_path()
        if os.path.exists(file_path):
            return file_path
        else:
            idaapi.info('%s\nNo patches are present in the IDB.\n'
                        'Please locate the input binary, to load PAC codes from' %
                        self.wanted_name)
            name = idaapi.ask_file(False, '*', 'locate the input binary')
            return name or None


def PLUGIN_ENTRY():
    return PacxplorerPlugin()


repository: 0cyn/iBoot-IDA
file: plugin/src/ibootplugin/sysregs.py
wanted_name: Aarch64 SysReg
comment: Aarch64 Apple SysReg extension
created_at: 2020-08-06 22:24:28+00:00
pushed_at: 2022-03-02 08:21:19+00:00
description:
32/64 bit SecureROM/iBoot loader for IDA Pro. Also supports loading and decrypting encrypted .im4ps within IDA.

url:
https://github.com/0cyn/iBoot-IDA/blob/eb5ac4754d105dbc36f9aa7f610250c2239f58d6/plugin/src/ibootplugin/sysregs.py

content:
'''
Copyright (c) 2022 ntrung03
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at https://mozilla.org/MPL/2.0/.
Based on https://gist.github.com/bazad/42054285391c6e0dcd0ede4b5f969ad2 by Brandon Azad
Based on https://gist.github.com/dougallj/7a75a3be1ec69ca550e7c36dc75e0d6f by Dougall J
'''

import idaapi
import ida_hexrays
import os
import json

from .sysregs_dat import *

ITYPE_MRS = idaapi.CUSTOM_INSN_ITYPE + 13
ITYPE_MSR = ITYPE_MRS + 1
ITYPE_SYS = ITYPE_MSR + 1

# AArch64 PSTATE accesses (op0 = 0b00, CRn = 0b0100).
PSTATE_ACCESS = {
    0b011 : 'UAO',
    0b100 : 'PAN',
    0b101 : 'SPSel',
    0b110 : 'DAIFSet',
    0b111 : 'DAIFClr',
}


def pstate_insn(op2):
    pstate = PSTATE_ACCESS.get(op2)
    if pstate:
        return pstate
    else:
        return ('#{:b}'.format(op2))

def sysreg_insn(op0, op1, CRn, CRm, op2):
    sr = 'S{}_{}_c{}_c{}_{}'.format(op0, op1, CRn, CRm, op2)
    #print(sr)
    reg = SYSTEM_REGISTERS.get(sr)
    if reg:
        name = reg[0]
        description = reg[1]
        return name
    else:
        return sr

def process_msr(insn):
    assert(insn & 0xFFC00000 == 0xD5000000)
    L   = (insn >> 21) &  0x1
    op0 = (insn >> 19) &  0x3
    op1 = (insn >> 16) &  0x7
    CRn = (insn >> 12) &  0xf
    CRm = (insn >>  8) &  0xf
    op2 = (insn >>  5) &  0x7
    Rt  = (insn >>  0) & 0x1f
    if L == 0b0 and op0 == 0b00 and CRn == 0b0100 and Rt == 0b11111:
        return pstate_insn(op2)
    elif op0 != 0b00:
        return sysreg_insn(op0, op1, CRn, CRm, op2)
    else:
        return '{:08x}'.format(insn)


class Aarch64SysRegHook(idaapi.IDP_Hooks):
    CUSTOM_INSTRUCTIONS = {idaapi.ARM_mrs, idaapi.ARM_msr, idaapi.ARM_sys}
    INDENT = 16

    def ev_out_operand(self, outctx, op):
        if outctx.insn.itype in self.CUSTOM_INSTRUCTIONS:
            insn = outctx.insn
            #print("ev_out_operand")
            if op.n < 2:
                if outctx.insn.itype == idaapi.ARM_mrs:
                    if op.n == 1:
                        outctx.out_colored_register_line(sysreg_insn(3, insn.ops[1].value, insn.ops[2].reg, insn.ops[3].reg, insn.ops[4].value))
                    else:
                        return 0
                elif outctx.insn.itype == idaapi.ARM_msr:
                    if op.n == 0:
                        outctx.out_colored_register_line(process_msr(idaapi.get_dword(outctx.insn.ea)))
                    else:
                        if insn.ops[2].type == idaapi.o_void:
                            return 0
                        else:
                            insn.itype = ITYPE_MSR
                            outctx.out_one_operand(4)
                            insn.itype = idaapi.ARM_msr
                elif outctx.insn.itype == idaapi.ARM_sys:
                    if op.n == 0:
                        outctx.out_colored_register_line(sysreg_insn(1, insn.ops[0].value, insn.ops[1].reg, insn.ops[2].reg, insn.ops[3].value).split()[1].strip())
                    else:
                        insn.itype = ITYPE_SYS
                        outctx.out_one_operand(4)
                        insn.itype = idaapi.ARM_sys
                '''
                for i in range(0, 6):
                    op = outctx.insn.ops[i]
                    print(F'Op{op.n}: type: {op.type} reg: {op.reg} value: {op.value}\n')
                '''
            return 1
        return 0
    def ev_out_mnem(self, outctx):
        if outctx.insn.itype == idaapi.ARM_sys:
            #print("ev_out_insn")
            insn = outctx.insn
            outctx.out_custom_mnem(sysreg_insn(1, insn.ops[0].value, insn.ops[1].reg, insn.ops[2].reg, insn.ops[3].value).split()[0].strip(), self.INDENT)
            return 1
        return 0

class Aarch64SysRegPlugin(idaapi.plugin_t):
    flags = idaapi.PLUGIN_PROC | idaapi.PLUGIN_HIDE
    comment = "Aarch64 Apple SysReg extension"
    wanted_hotkey = ""
    help = "Runs transparently"
    wanted_name = "Aarch64 SysReg"
    hook = None
    enabled = 1

    def init(self):
        if idaapi.ph_get_id() != idaapi.PLFM_ARM or idaapi.BADADDR <= 0xFFFFFFFF:
            return idaapi.PLUGIN_SKIP
        if not ida_hexrays.init_hexrays_plugin():
            print(("[-] {0} : no decompiler available, skipping".format(self.wanted_name)))
            return idaapi.PLUGIN_SKIP
        print("%s init"%self.comment)

        d = json.loads(APPLE_REGS_JSON)

        for r in d:
            sr = 'S{}_{}_c{}_c{}_{}'.format(*r['enc'])
            SYSTEM_REGISTERS[sr] = (r['name'], r['fullname'])
        self.hook = Aarch64SysRegHook()
        self.hook.hook()
        return idaapi.PLUGIN_KEEP

    def run():
        pass

    def term(self):
        if self.hook is not None:
            self.hook.unhook()
        print("%s unloaded"%self.comment)

def PLUGIN_ENTRY():
    return Aarch64SysRegPlugin()


repository: binaryai/plugins
file: binaryai.py
description: Ghidra/IDA Pro plugins to load similarity result from binaryai.net
url: https://github.com/binaryai/plugins/blob/fd1c6f59b54979cd5308cf7dea6d63181a89ee36/binaryai.py
wanted_name: BinaryAI
comment: BinaryAI plugin for IDA Pro
created_at: 2023-01-11 08:32:56+00:00
pushed_at: 2023-04-11 01:53:17+00:00
content:
# Load analysis result from binaryai.net,
# BinaryAI is a binary file security analysis platform developed by Tencent Security Keen Lab.
# Copyright 2021-2023 Tencent. All Rights Reserved.

#@author Tencent Security KeenLab
#@category Binary

from __future__ import unicode_literals
import platform
import os
import json
import re
import io

ENV = None
if platform.python_implementation() == "Jython":
    # import ghidra dependency
    import ghidra
    from ghidra.util import Msg
    from javax.swing import JFrame, JTextArea, WindowConstants, JPanel, JScrollPane, JTextPane
    from java.awt import BorderLayout, Dimension, Insets
    from urlparse import urlparse

    ENV = "GHIDRA"
else:
    import ida_kernwin
    import idaapi
    import ida_nalt
    try:
        from pygments.lexers import CppLexer
        from pygments.token import Token
    except Exception:
        # Missing library is managed at the plugin entry
        pass
    if platform.python_version().startswith("2"):
        from urlparse import urlparse
    elif platform.python_version().startswith("3"):
        from urllib.parse import urlparse
    ENV = "IDA"


try:
    from ghidra.ghidra_builtins import *
except:
    pass

IMPORT_ERROR_TITLE = "Import Error"
FORMAT_PATTERN = r"^(https?)://((?:www.)?(?:{}))/([^/]+?)/([^/]+?)(?:.git)?/?$"
repoUrlPolicy = [
    {'pathSeparator': 'tree', 'regexp': FORMAT_PATTERN.format('github.com')},
    {'pathSeparator': '-/tree', 'regexp': FORMAT_PATTERN.format('salsa.debian.org|gitlab.gnome.org|gitlab.com')},
    {'pathSeparator': 'src', 'regexp': FORMAT_PATTERN.format('bitbucket.org')},
]


class Utils:

    @staticmethod
    def is_ida_version_supported():
        if idaapi.IDA_SDK_VERSION > 730:
            return True
        print("IDA version should be at least 7.3")
        return False

    @staticmethod
    def get_source_location(repoUrl, version):
        for policy in repoUrlPolicy:
            matched = re.match(policy['regexp'], repoUrl)
            if not matched or len(matched.groups()) != 4:
                continue
            return "{0}://{1}/{2}/{3}/{pathSep}/{version}".format(
                matched.group(1),
                matched.group(2),
                matched.group(3),
                matched.group(4),
                pathSep=policy['pathSeparator'],
                version=version
            )
        return None

    @staticmethod
    def should_ends_with_l_anchor(host):
        return host in ['github.com', 'gitlab.com', 'gitlab.gnome.org', 'salsa.debian.org']

    @staticmethod
    def should_ends_with_line_anchor(host):
        return host in ['bitbucket.org']

    @staticmethod
    def get_source_location_with_line(repoUrl, version, filepath, line):
        url = urlparse(repoUrl)
        code_location = Utils.get_source_location(repoUrl, version)
        raw_path_with_line = "{}#L{}".format(filepath, line)
        if not code_location:
            return raw_path_with_line
        if Utils.should_ends_with_l_anchor(url.hostname):
            code_location_with_line = "{}/{}#L{}".format(code_location, filepath, line)
        elif Utils.should_ends_with_line_anchor(url.hostname):
            code_location_with_line = "{}/{}#line-{}".format(code_location, filepath, line)
        else:
            code_location_with_line = raw_path_with_line
        return code_location_with_line


# ------------------------------------------------------------
#   GHIDRA Script
# ------------------------------------------------------------

class GhidraScript:

    def __init__(self):
        self.frame = None
        self.functions = {}
        self.base_addr = None
        self.current_function = None

    def setup_ui(self):
        self.frame = JFrame("BinaryAI")

        self.panel = JPanel()
        self.panel.setLayout(BorderLayout(0, 0))
        self.panel.setPreferredSize(Dimension(600, 600))

        self.scrollPane = JScrollPane()
        self.panel.add(self.scrollPane, BorderLayout.CENTER)
        self.codePane = JTextPane()
        self.scrollPane.setViewportView(self.codePane)
        self.infoPane = JTextPane()
        self.infoPane.setPreferredSize(Dimension(600, 120))
        self.infoPane.setMargin(Insets(0, 10, 10, 10))
        self.infoPane.setText("Click on function to see result.")
        self.panel.add(self.infoPane, BorderLayout.NORTH)

        self.frame.add(self.panel)
        self.frame.pack()

        self.frame.setLocationRelativeTo(None)
        self.frame.setAlwaysOnTop(True)
        self.frame.setVisible(True)

    def load_json(self):
        fp = askFile("Please choose the json file downloaded from binaryai.net", "Confirm")
        fp = str(fp)
        if not os.path.exists(fp):
            Msg.showError(self, None, IMPORT_ERROR_TITLE, "{} does not exists.".format(fp))
            return False
        with io.open(fp, "r", encoding="utf-8") as f:
            data = json.load(f)
        sha256 = currentProgram.getExecutableSHA256()
        if 'file_sha256' not in data or data['file_sha256'] != sha256:
            Msg.showError(self, None, IMPORT_ERROR_TITLE, "json file does not match the binary.")
            return False
        if 'base_addr' not in data:
            Msg.showError(self, None, IMPORT_ERROR_TITLE, "Could not found base address in json file.")
            return False

        self.base_addr = data['base_addr']
        currentProgram.setImageBase(toAddr(self.base_addr), True)
        if 'functions' not in data:
            Msg.showError(self, None, IMPORT_ERROR_TITLE, "Could not found functions in json file.")
        for addr, item in data['functions'].iteritems():
            self.functions[int(addr)] = item
        return True

    def update_info(self, func, addr):
        url = Utils.get_source_location_with_line(func['entry_url'], func['slot_name'], func['filepath'],
                                                  func['line_number'])
        comment = "Function Entry: {}\n".format(addr)
        comment += "Function Name: {}\n".format(func['function_name'])
        comment += "Score: {}\n".format(func['score'])
        comment += "Git URL: {}\n".format(func['entry_url'])
        comment += "Version: {}\n".format(func['slot_name'])
        comment += "Source file path: {}\n".format(url)
        self.infoPane.setText(comment)

    def update_code(self, func):
        self.codePane.setText(func['code'])
        self.codePane.setCaretPosition(0);

    def register(self):
        code_viewer = state.getTool().getService(ghidra.app.services.CodeViewerService)

        class MyListener(ghidra.app.util.viewer.listingpanel.AddressSetDisplayListener):
            def __init__(self, script):
                self.script = script

            def visibleAddressesChanged(self, visibleAddresses):

                if visibleAddresses is None or visibleAddresses.minAddress is None:
                    return

                function = getFunctionContaining(visibleAddresses.minAddress)
                if function and function.getEntryPoint().getOffset() in self.script.functions:
                    func = self.script.functions[function.getEntryPoint().getOffset()]
                    self.script.update_code(func)
                    self.script.update_info(func, function.getEntryPoint())
                else:
                    self.script.infoPane.setText("Not Found.")
                    self.script.codePane.setText("")

        if not state.getEnvironmentVar("BINARYAI_INITED"):
            code_viewer.addListingDisplayListener(MyListener(self))
            state.addEnvironmentVar("BINARYAI_INITED", True)

    def add_bookmarks(self):
        idx = 0
        total = len(self.functions)
        monitor.initialize(total)
        for addr, func in self.functions.iteritems():
            monitor.checkCanceled()
            # print("===== {:#x} {} {}".format(addr, func['function_name'], func['score']))
            createBookmark(toAddr(addr), "BinaryAI", "{}".format(func['function_name']))
            monitor.incrementProgress(1)
            monitor.setMessage("Working on {}/{}".format(idx, total))
            idx += 1

        Msg.showInfo(self, None, "Success!", "Annotate {} functions.".format(len(self.functions)))

    def run(self):
        if not self.load_json():
            return
        self.setup_ui()
        self.add_bookmarks()
        self.register()
        self.frame.show()

if __name__ == "__main__":
    if ENV == "GHIDRA":
        script = GhidraScript()
        script.run()
        exit(0)

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

class Viewer(object):

    class InfoViewerUI(idaapi.simplecustviewer_t):
        def __init__(self, title):
            idaapi.simplecustviewer_t.__init__(self)
            self.title = title
            self.text = ""
            self.Create(title)

        def _repaint(self):
            self.ClearLines()
            for line in self.text.splitlines():
                self.AddLine(line)
            self.Refresh()

        def update(self, text):
            self.text = text
            self._repaint()

        def dock(self):
            idaapi.set_dock_pos(self.title, "Output window", idaapi.DP_RIGHT)

    class SourceCodeViewerUI(idaapi.simplecustviewer_t):
        def __init__(self, title):
            idaapi.simplecustviewer_t.__init__(self)
            self.title = title
            self.Create(title)
            self.code = None
            idaapi.set_code_viewer_is_source(idaapi.create_code_viewer(self.GetWidget(), 0x4))

        def color_line(self, code):
            """
            """
            lexer = CppLexer()
            tokens = list(lexer.get_tokens(code))
            new_line = ""
            for t in tokens:
                ttype = t[0]
                ttext = str(t[1])
                if ttype == Token.Text:
                    new_line += idaapi.COLSTR(ttext, idaapi.SCOLOR_INSN)

                elif ttype == Token.Text.Whitespace:
                    new_line += idaapi.COLSTR(ttext, idaapi.SCOLOR_INSN)

                elif ttype == Token.Error:
                    new_line += idaapi.COLSTR(ttext, idaapi.SCOLOR_ERROR)

                elif ttype == Token.Other:
                    new_line += idaapi.COLSTR(ttext, idaapi.SCOLOR_DSTR)

                elif ttype == Token.Keyword:
                    new_line += idaapi.COLSTR(ttext, idaapi.SCOLOR_KEYWORD)

                elif ttype == Token.Keyword.Type:
                    new_line += idaapi.COLSTR(ttext, idaapi.SCOLOR_LOCNAME)

                elif ttype == Token.Name:
                    new_line += idaapi.COLSTR(ttext, idaapi.SCOLOR_DEFAULT)

                elif ttype == Token.Name.Class:
                    new_line += idaapi.COLSTR(ttext, idaapi.SCOLOR_DNAME)

                elif ttype == Token.Name.Constant:
                    new_line += idaapi.COLSTR(ttext, idaapi.SCOLOR_DSTR)

                elif ttype == Token.Name.Builtin:
                    new_line += idaapi.COLSTR(ttext, idaapi.SCOLOR_DEFAULT)

                elif ttype == Token.Literal:
                    new_line += idaapi.COLSTR(ttext, idaapi.SCOLOR_LOCNAME)

                elif ttype in Token.Literal.String:
                    new_line += idaapi.COLSTR(ttext, idaapi.SCOLOR_STRING)

                elif ttype in Token.Literal.Number:
                    new_line += idaapi.COLSTR(ttext, idaapi.SCOLOR_DNUM)

                elif ttype == Token.Operator:
                    new_line += idaapi.COLSTR(ttext, idaapi.SCOLOR_ALTOP)

                elif ttype == Token.Punctuation:
                    new_line += idaapi.COLSTR(ttext, idaapi.SCOLOR_SYMBOL)

                elif ttype in Token.Comment:
                    new_line += idaapi.COLSTR(ttext, idaapi.SCOLOR_REGCMT)

                elif ttype == Token.Generic:
                    new_line += idaapi.COLSTR(ttext, idaapi.SCOLOR_CREFTAIL)

                else:
                    new_line += idaapi.COLSTR(ttext, idaapi.SCOLOR_CREFTAIL)
            return new_line

        def update(self, code):
            self.code = code
            self._repaint()

        def _repaint(self):
            self.ClearLines()
            for line in self.code.splitlines():
                line = self.color_line(line)
                self.AddLine(line)
            self.Refresh()

        def dock(self, widget):
            title = idaapi.get_widget_title(widget)
            idaapi.set_dock_pos(self.title, title, idaapi.DP_RIGHT)

    def __init__(self):
        self.code_view = None # Viewer.SourceCodeViewerUI
        self.info_view = None # Viewer.InfoViewerUI
        self.visible = False

    def reset(self):
        if self.code_view is not None:
            ida_kernwin.close_widget(self.code_view.GetWidget(), 0)
        if self.info_view is not None:
            ida_kernwin.close_widget(self.info_view.GetWidget(), 0)
        self.code_view = None
        self.info_view = None
        self.visible = False

    def is_visible(self):
        return self.code_view and self.code_view.GetWidget()

    def update(self, func):
        if func is None:
            self.reset()
            return
        else:
            code = func['code']
            url = Utils.get_source_location_with_line(func['entry_url'], func['slot_name'], func['filepath'],
                                                      func['line_number'])
            info = "Function Name:    {}\n".format(idaapi.COLSTR(func['function_name'], idaapi.SCOLOR_LIBNAME))
            info += "Score:            {}\n".format(idaapi.COLSTR(str(func['score']), idaapi.SCOLOR_LIBNAME))
            info += "Git URL:          {}\n".format(idaapi.COLSTR(func['entry_url'], idaapi.SCOLOR_LIBNAME))
            info += "Version:          {}\n".format(idaapi.COLSTR(func['slot_name'], idaapi.SCOLOR_LIBNAME))
            info += "Source file path: {}\n".format(idaapi.COLSTR(url, idaapi.SCOLOR_LIBNAME))
        first_time = False
        if not self.is_visible():
            self.code_view = Viewer.SourceCodeViewerUI("BinaryAI Matched Source")
            self.info_view = Viewer.InfoViewerUI("BinaryAI Function metadata")
            first_time = True
        widget = idaapi.get_current_widget()
        self.code_view.update(code)
        self.info_view.update(info)
        if first_time:
            self.code_view.Show()
            self.info_view.Show()
            self.code_view.dock(widget)
            self.info_view.dock()


class UIHooks(idaapi.UI_Hooks):

    HIGHLIGHT_COLOR = 0x95DE64

    def __init__(self, plugin):
        ida_kernwin.UI_Hooks.__init__(self)
        self.plugin = plugin
        self.is_function_window_hooked = False
        self.current_func = None

    def get_chooser_item_attrs(self, chooser, n, attrs):
        func = idaapi.getn_func(n)
        if self.plugin.function_dict and func.start_ea in self.plugin.function_dict:
            attrs.color = self.HIGHLIGHT_COLOR

    def updating_actions(self, ctx):
        title = None
        if idaapi.find_widget("Functions"): # >= version 7.7
            title = "Functions"
        elif idaapi.find_widget("Functions window"):
            title = "Functions window"
        if not self.is_function_window_hooked and title:
            self.is_function_window_hooked = idaapi.enable_chooser_item_attrs(title, True)

    def screen_ea_changed(self, ea, prev_ea):
        if self.plugin.viewer is None:
            return
        func = idaapi.get_func(ea)
        if func and func.start_ea in self.plugin.function_dict:
            if self.current_func != func:
                f = self.plugin.function_dict[func.start_ea]
                self.plugin.viewer.update(f)
                self.current_func = func
        else:
            self.plugin.viewer.reset()

class IDAPlugin(idaapi.plugin_t):
    comment = "BinaryAI plugin for IDA Pro"
    help = "BinaryAI plugin shortcut key is Ctrl-Shift-B"
    wanted_name = "BinaryAI"
    wanted_hotkey = "Ctrl-Shift-B"
    # flags = idaapi.PLUGIN_UNL
    flags = idaapi.PLUGIN_KEEP

    def init(self):
        print("#" * 60)
        print("https://github.com/binaryai/plugins")
        print("BinaryAI plugin shortcut key is Ctrl-Shift-B")
        self.viewer = None
        self.function_dict = None
        self.ui_hooks = UIHooks(self)
        self.ui_hooks.hook()
        return idaapi.PLUGIN_KEEP

    def term(self):
        self.ui_hooks.unhook()
        return

    def to_ida_addr(self, addr):
        return (addr - self.base_addr) + self.ida_base_addr

    def load_json(self):
        fp = ida_kernwin.ask_file(False, "*.json", "file download from binaryai.net")
        if not fp:
            return False
        if not os.path.exists(fp):
            ida_kernwin.warning("file does not exists.")
            return False
        with io.open(fp, "r", encoding="utf-8") as f:
            data = json.load(f)

        if 'base_addr' not in data or 'functions' not in data:
            ida_kernwin.warning("json format error.")
            return False

        sha256 = ida_nalt.retrieve_input_file_sha256().hex()
        if 'file_sha256' not in data or data['file_sha256'] != sha256:
            ida_kernwin.warning("json file does not match the binary.")
            return False

        self.base_addr = data['base_addr']
        self.ida_base_addr = idaapi.get_imagebase()
        self.function_dict = {self.to_ida_addr(int(addr)): func for addr, func in data['functions'].items()}
        ida_kernwin.info("Matches {} functions.".format(len(self.function_dict)))
        return True

    def run(self, arg):
        if not self.load_json():
            return
        if self.viewer is None:
            self.viewer = Viewer()
        else:
            self.viewer.reset()
        self.ui_hooks.is_function_window_hooked = False


def PLUGIN_ENTRY():
    if not Utils.is_ida_version_supported():
        return idaapi.PLUGIN_SKIP
    try:
        import pygments
    except Exception:
        print("BinaryAI: pygments library is missing")
        print("pip install pygments")
        return idaapi.PLUGIN_SKIP
    return IDAPlugin()


repository: hoangcuongflp/Stuff
file: ida-master/plugins/rizzo/rizzo.py
description: because I've orderliness obsession :(
wanted_name: Rizzo
comment: Function signature
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/rizzo/rizzo.py

content:
##########################################################################
# An IDAPython plugin that generates "fuzzy" function signatures that can
# be shared and applied amongst different IDBs.
#
# There are multiple sets of signatures that are generated:
#
#   o "Formal" signatures, where functions must match exactly
#   o "Fuzzy" signatures, where functions must only resemble each other
#     in terms of data/call references.
#   o String-based signatures, where functions are identified based on
#     unique string references.
#   o Immediate-based signatures, where functions are identified based
#     on immediate value references.
#
# These signatures are applied based on accuracy, that is, formal
# signatures are applied first, then string and immediate based
# signatures, and finally fuzzy signatures.
#
# Further, functions are identified based on call references. Consider,
# for example, two functions, one named 'foo', the other named 'bar'.
# The 'foo' function is fairly unique and a reliable signature is easily
# generated for it, but the 'bar' function is more difficult to reliably
# identify. However, 'foo' calls 'bar', and thus once 'foo' is identified,
# 'bar' can also be identified by association.
#
# Craig Heffner
# @devttys0
##########################################################################
import idc
import idaapi
import idautils

import os
import sys
import time
import pickle       # http://natashenka.ca/pickle/
import tempfile
import collections

class RizzoSignatures(object):
    '''
    Simple wrapper class for storing signature info.
    '''

    SHOW = []

    def __init__(self):
        self.fuzzy = {}
        self.formal = {}
        self.strings = {}
        self.functions = {}
        self.immediates = {}

        self.fuzzydups = set()
        self.formaldups = set()
        self.stringdups = set()
        self.immediatedups = set()

    def show(self):
        if not self.SHOW:
            return

        print "\n\nGENERATED FORMAL SIGNATURES FOR:"
        for (key, ea) in self.formal.iteritems():
            func = RizzoFunctionDescriptor(self.formal, self.functions, key)
            if func.name in self.SHOW:
                print func.name

        print "\n\nGENERATRED FUZZY SIGNATURES FOR:"
        for (key, ea) in self.fuzzy.iteritems():
            func = RizzoFunctionDescriptor(self.fuzzy, self.functions, key)
            if func.name in self.SHOW:
                print func.name

class RizzoStringDescriptor(object):
    '''
    Wrapper class for easily accessing necessary string information.
    '''

    def __init__(self, string):
        self.ea = string.ea
        self.value = str(string)
        self.xrefs = [x.frm for x in idautils.XrefsTo(self.ea)]

class RizzoBlockDescriptor(object):
    '''
    Code block info is stored in tuples, which minimize pickle storage space.
    This class provides more Pythonic (and sane) access to values of interest for a given block.
    '''

    def __init__(self, block):
        self.formal = block[0]
        self.fuzzy = block[1]
        self.immediates = block[2]
        self.functions = block[3]

    def match(self, nblock, fuzzy=False):
        # TODO: Fuzzy matching at the block level gets close, but produces a higher number of
        #       false positives; for example, it confuses hmac_md5 with hmac_sha1.
        #return ((self.formal == nblock.formal or (fuzzy and self.fuzzy == nblock.fuzzy)) and
        return (self.formal == nblock.formal and
                len(self.immediates) == len(nblock.immediates) and
                len(self.functions) == len(nblock.functions))

class RizzoFunctionDescriptor(object):
    '''
    Function signature info is stored in dicts and tuples, which minimize pickle storage space.
    This class provides more Pythonic (and sane) access to values of interest for a given function.
    '''

    def __init__(self, signatures, functions, key):
        self.ea = signatures[key]
        self.name = functions[self.ea][0]
        self.blocks = functions[self.ea][1]

class Rizzo(object):
    '''
    Workhorse class which performs the primary logic and functionality.
    '''

    DEFAULT_SIGNATURE_FILE = "rizzo.sig"

    def __init__(self, sigfile=None):
        if sigfile:
            self.sigfile = sigfile
        else:
            self.sigfile = os.path.join(tempfile.gettempdir(), self.DEFAULT_SIGNATURE_FILE)

        # Useful for quickly identifying string xrefs from individual instructions
        self.strings = {}
        for string in idautils.Strings():
            self.strings[string.ea] = RizzoStringDescriptor(string)

        start = time.time()
        self.signatures = self.generate()
        end = time.time()

        print "Generated %d formal signatures and %d fuzzy signatures for %d functions in %.2f seconds." % (len(self.signatures.formal), len(self.signatures.fuzzy), len(self.signatures.functions), (end-start))

    def save(self):
        print ("Saving signatures to %s..." % self.sigfile),
        fp = open(self.sigfile, "wb")
        pickle.dump(self.signatures, fp)
        fp.close()
        print "done."

    def load(self):
        print ("Loading signatures from %s..." % self.sigfile),
        fp = open(self.sigfile, "rb")
        sigs = pickle.load(fp)
        fp.close()
        print "done."
        return sigs

    def sighash(self, value):
        return hash(str(value)) & 0xFFFFFFFF

    def block(self, block):
        '''
        Returns a tuple: ([formal, block, signatures], [fuzzy, block, signatures], set([unique, immediate, values]), [called, function, names])
        '''
        formal = []
        fuzzy = []
        functions = []
        immediates = []

        ea = block.startEA
        while ea < block.endEA:
            idaapi.decode_insn(ea)

            # Get a list of all data/code references from the current instruction
            drefs = [x for x in idautils.DataRefsFrom(ea)]
            crefs = [x for x in idautils.CodeRefsFrom(ea, False)]

            # Add all instruction mnemonics to the formal block hash
            formal.append(idc.GetMnem(ea))

            # If this is a call instruction, be sure to note the name of the function
            # being called. This is used to apply call-based signatures to functions.
            #
            # For fuzzy signatures, we can't use the actual name or EA of the function,
            # but rather just want to note that a function call was made.
            #
            # Formal signatures already have the call instruction mnemonic, which is more
            # specific than just saying that a call was made.
            if idaapi.is_call_insn(ea):
                for cref in crefs:
                    func_name = idc.Name(cref)
                    if func_name:
                        functions.append(func_name)
                        fuzzy.append("funcref")
            # If there are data references from the instruction, check to see if any of them
            # are strings. These are looked up in the pre-generated strings dictionary.
            #
            # String values are easily identifiable, and are used as part of both the fuzzy
            # and the formal signatures.
            #
            # It is more difficult to determine if non-string values are constants or not;
            # for both fuzzy and formal signatures, just use "data" to indicate that some data
            # was referenced.
            elif drefs:
                for dref in drefs:
                    if self.strings.has_key(dref):
                        formal.append(self.strings[dref].value)
                        fuzzy.append(self.strings[dref].value)
                    else:
                        formal.append("dataref")
                        fuzzy.append("dataref")
            # If there are no data or code references from the instruction, use every operand as
            # part of the formal signature.
            #
            # Fuzzy signatures are only concerned with interesting immediate values, that is, values
            # that are greater than 65,535, are not memory addresses, and are not displayed as
            # negative values.
            elif not drefs and not crefs:
                for n in range(0, len(idaapi.cmd.Operands)):
                    opnd_text = idc.GetOpnd(ea, n)
                    formal.append(opnd_text)
                    if idaapi.cmd.Operands[n].type == idaapi.o_imm and not opnd_text.startswith('-'):
                        if idaapi.cmd.Operands[n].value >= 0xFFFF:
                            if idaapi.getFlags(idaapi.cmd.Operands[n].value) == 0:
                                fuzzy.append(str(idaapi.cmd.Operands[n].value))
                                immediates.append(idaapi.cmd.Operands[n].value)

            ea = idc.NextHead(ea)

        return (self.sighash(''.join(formal)), self.sighash(''.join(fuzzy)), immediates, functions)

    def function(self, func):
        '''
        Returns a list of blocks.
        '''
        blocks = []

        for block in idaapi.FlowChart(func):
            blocks.append(self.block(block))

        return blocks

    def generate(self):
        signatures = RizzoSignatures()

        # Generate unique string-based function signatures
        for (ea, string) in self.strings.iteritems():
            # Only generate signatures on reasonably long strings with one xref
            if len(string.value) >= 8 and len(string.xrefs) == 1:
                func = idaapi.get_func(string.xrefs[0])
                if func:
                    strhash = self.sighash(string.value)

                    # Check for and remove string duplicate signatures (the same
                    # string can appear more than once in an IDB).
                    # If no duplicates, add this to the string signature dict.
                    if signatures.strings.has_key(strhash):
                        del signatures.strings[strhash]
                        signatures.stringdups.add(strhash)
                    elif strhash not in signatures.stringdups:
                        signatures.strings[strhash] = func.startEA

        # Generate formal, fuzzy, and immediate-based function signatures
        for ea in idautils.Functions():
            func = idaapi.get_func(ea)
            if func:
                # Generate a signature for each block in this function
                blocks = self.function(func)

                # Build function-wide formal and fuzzy signatures by simply
                # concatenating the individual function block signatures.
                formal = self.sighash(''.join([str(e) for (e, f, i, c) in blocks]))
                fuzzy = self.sighash(''.join([str(f) for (e, f, i, c) in blocks]))

                # Add this signature to the function dictionary.
                signatures.functions[func.startEA] = (idc.Name(func.startEA), blocks)

                # Check for and remove formal duplicate signatures.
                # If no duplicates, add this to the formal signature dict.
                if signatures.formal.has_key(formal):
                    del signatures.formal[formal]
                    signatures.formaldups.add(formal)
                elif formal not in signatures.formaldups:
                    signatures.formal[formal] = func.startEA

                # Check for and remove fuzzy duplicate signatures.
                # If no duplicates, add this to the fuzzy signature dict.
                if signatures.fuzzy.has_key(fuzzy):
                    del signatures.fuzzy[fuzzy]
                    signatures.fuzzydups.add(fuzzy)
                elif fuzzy not in signatures.fuzzydups:
                    signatures.fuzzy[fuzzy] = func.startEA

                # Check for and remove immediate duplicate signatures.
                # If no duplicates, add this to the immediate signature dict.
                for (e, f, immediates, c) in blocks:
                    for immediate in immediates:
                        if signatures.immediates.has_key(immediate):
                            del signatures.immediates[immediate]
                            signatures.immediatedups.add(immediate)
                        elif immediate not in signatures.immediatedups:
                            signatures.immediates[immediate] = func.startEA

        # These need not be maintained across function calls,
        # and only add to the size of the saved signature file.
        signatures.fuzzydups = set()
        signatures.formaldups = set()
        signatures.stringdups = set()
        signatures.immediatedups = set()

        # DEBUG
        signatures.show()

        return signatures

    def match(self, extsigs):
        fuzzy = {}
        formal = {}
        strings = {}
        immediates = {}

        # Match formal function signatures
        start = time.time()
        for (extsig, ext_func_ea) in extsigs.formal.iteritems():
            if self.signatures.formal.has_key(extsig):
                newfunc = RizzoFunctionDescriptor(extsigs.formal, extsigs.functions, extsig)
                curfunc = RizzoFunctionDescriptor(self.signatures.formal, self.signatures.functions, extsig)
                formal[curfunc] = newfunc
        end = time.time()
        print "Found %d formal matches in %.2f seconds." % (len(formal), (end-start))

        # Match fuzzy function signatures
        start = time.time()
        for (extsig, ext_func_ea) in extsigs.fuzzy.iteritems():
            if self.signatures.fuzzy.has_key(extsig):
                curfunc = RizzoFunctionDescriptor(self.signatures.fuzzy, self.signatures.functions, extsig)
                newfunc = RizzoFunctionDescriptor(extsigs.fuzzy, extsigs.functions, extsig)
                # Only accept this as a valid match if the functions have the same number of basic code blocks
                if len(curfunc.blocks) == len(newfunc.blocks):
                    fuzzy[curfunc] = newfunc
        end = time.time()
        print "Found %d fuzzy matches in %.2f seconds." % (len(fuzzy), (end-start))

        # Match string based function signatures
        start = time.time()
        for (extsig, ext_func_ea) in extsigs.strings.iteritems():
            if self.signatures.strings.has_key(extsig):
                curfunc = RizzoFunctionDescriptor(self.signatures.strings, self.signatures.functions, extsig)
                newfunc = RizzoFunctionDescriptor(extsigs.strings, extsigs.functions, extsig)
                strings[curfunc] = newfunc
        end = time.time()
        print "Found %d string matches in %.2f seconds." % (len(strings), (end-start))

        # Match immediate baesd function signatures
        start = time.time()
        for (extsig, ext_func_ea) in extsigs.immediates.iteritems():
            if self.signatures.immediates.has_key(extsig):
                curfunc = RizzoFunctionDescriptor(self.signatures.immediates, self.signatures.functions, extsig)
                newfunc = RizzoFunctionDescriptor(extsigs.immediates, extsigs.functions, extsig)
                immediates[curfunc] = newfunc
        end = time.time()
        print "Found %d immediate matches in %.2f seconds." % (len(immediates), (end-start))

        # Return signature matches in the order we want them applied
        # The second tuple of each match is set to True if it is a fuzzy match, e.g.:
        #
        #   ((match, fuzzy), (match, fuzzy), ...)
        return ((formal, False), (strings, False), (immediates, False), (fuzzy, True))

    def rename(self, ea, name):
        # Don't rely on the name in curfunc, as it could have already been renamed
        curname = idc.Name(ea)
        # Don't rename if the name is a special identifier, or if the ea has already been named
        # TODO: What's a better way to check for reserved name prefixes?
        if curname.startswith('sub_') and name.split('_')[0] not in set(['sub', 'loc', 'unk', 'dword', 'word', 'byte']):
            # Don't rename if the name already exists in the IDB
            if idc.LocByName(name) == idc.BADADDR:
                if idc.MakeName(ea, name):
                    idc.SetFunctionFlags(ea, (idc.GetFunctionFlags(ea) | idc.FUNC_LIB))
                    #print "%s  =>  %s" % (curname, name)
                    return 1
            #else:
            #    print "WARNING: Attempted to rename '%s' => '%s', but '%s' already exists!" % (curname, name, name)
        return 0

    def apply(self, extsigs):
        count = 0

        start = time.time()

        # This applies formal matches first, then fuzzy matches
        for (match, fuzzy) in self.match(extsigs):
            # Keeps track of all function names that we've identified candidate functions for
            rename = {}

            for (curfunc, newfunc) in match.iteritems():
                if not rename.has_key(newfunc.name):
                    rename[newfunc.name] = []

                # Attempt to rename this function
                rename[newfunc.name].append(curfunc.ea)

                bm = {}
                duplicates = set()

                # Search for unique matching code blocks inside this function
                for nblock in newfunc.blocks:
                    nblock = RizzoBlockDescriptor(nblock)
                    for cblock in curfunc.blocks:
                        cblock = RizzoBlockDescriptor(cblock)

                        if cblock.match(nblock, fuzzy):
                            if bm.has_key(cblock):
                                del bm[cblock]
                                duplicates.add(cblock)
                            elif cblock not in duplicates:
                                bm[cblock] = nblock

                # Rename known function calls from each unique identified code block
                for (cblock, nblock) in bm.iteritems():
                    for n in range(0, len(cblock.functions)):
                        ea = idc.LocByName(cblock.functions[n])
                        if ea != idc.BADADDR:
                            if rename.has_key(nblock.functions[n]):
                                rename[nblock.functions[n]].append(ea)
                            else:
                                rename[nblock.functions[n]] = [ea]

                # Rename the identified functions
                for (name, candidates) in rename.iteritems():
                    if candidates:
                        winner = collections.Counter(candidates).most_common(1)[0][0]
                        count += self.rename(winner, name)

        end = time.time()
        print "Renamed %d functions in %.2f seconds." % (count, (end-start))

def RizzoBuild(sigfile=None):
    print "Building Rizzo signatures, this may take a few minutes..."
    start = time.time()
    r = Rizzo(sigfile)
    r.save()
    end = time.time()
    print "Built signatures in %.2f seconds" % (end-start)

def RizzoApply(sigfile=None):
    print "Applying Rizzo signatures, this may take a few minutes..."
    start = time.time()
    r = Rizzo(sigfile)
    s = r.load()
    r.apply(s)
    end = time.time()
    print "Signatures applied in %.2f seconds" % (end-start)




class RizzoPlugin(idaapi.plugin_t):
    flags = 0
    comment = "Function signature"
    help = ""
    wanted_name = "Rizzo"
    wanted_hotkey = ""

    NAME = "rizzo.py"

    def init(self):
        self.menu_context_load = idaapi.add_menu_item("File/Load file/", "Rizzo signature file...", "", 0, self.rizzo_load, (None,))
        self.menu_context_produce = idaapi.add_menu_item("File/Produce file/", "Rizzo signature file...", "", 0, self.rizzo_produce, (True,))
        return idaapi.PLUGIN_KEEP

    def term(self):
        idaapi.del_menu_item(self.menu_context_load)
        idaapi.del_menu_item(self.menu_context_produce)
        return None

    def run(self, arg):
        return None

    def rizzo_script(self):
        idaapi.IDAPython_ExecScript(self.script, globals())

    def rizzo_produce(self, arg):
        fname = idc.AskFile(1, "*.riz", "Save signature file as")
        if fname:
            if '.' not in fname:
                fname += ".riz"
            RizzoBuild(fname)
        return None

    def rizzo_load(self, arg):
        fname = idc.AskFile(0, "*.riz", "Load signature file")
        if fname:
            RizzoApply(fname)
        return None

def PLUGIN_ENTRY():
    return RizzoPlugin()


