Python IDA Patcher

Storm Shadow

Administrator
Staff member
Developer
Ida Pro Expert
Elite Cracker
Note its only for ida 6.5

Installation

Simply copy idapatcher.py into IDA's plugins folder. The plugin will be automatically loaded the next time you start IDA Pro.
Compatibility

The plugin uses pure IDA Python API, so it should be compatible with all versions of IDA on different platforms. However, it was only extensively tested on IDA Pro 6.5 for Windows with x86, x86-64 and ARM binaries.
User guide

This guide will walk you through various features of the plugin by examining and patching a simple program below:
.text:00401000 ; int __cdecl main(int argc, char **argv)
.text:00401000 _main proc near
.text:00401000
.text:00401000 argc = dword ptr 8
.text:00401000 argv = dword ptr 0Ch
.text:00401000
.text:00401000 push ebp
.text:00401001 mov ebp, esp
.text:00401003 cmp [ebp+argc], 0
.text:00401007 jz short $LN6
.text:00401009 push offset Format ; "Terminating...\n"
.text:0040100E call ds:__imp__printf
.text:00401014 add esp, 4
.text:00401017 push 1 ; Code
.text:00401019 call ds:__imp__exit
.text:0040101F ; -------------------------------------------------------------------
.text:0040101F
.text:0040101F $LN6: ; CODE XREF: _main+7j
.text:0040101F push offset aYouHavePatched ; "patched..."
.text:00401024 call ds:__imp__printf
.text:0040102A add esp, 4
.text:0040102D xor eax, eax
.text:0040102F pop ebp
.text:00401030 retn
.text:00401030 _main endp
.text:00401030

As you can see, the program will always follow the "Terminating..." branch since the conditional jump at 00401007 will never be true (argc is normally greater than 0).
Let's patch the application to reverse the logic and change jz to jnz. The traditional way to do this in IDA is to open the Hex subview and change the appropriate byte corresponding to jz (0x74) to jnz (0x75) as follows:

At this point it is impossible to revert the patched byte to its original form or manage all of the patched bytes in the database without developing several one off IDA scripts.
IDA Patcher addresses this and many other use cases to make binary patching in IDA easy and convenient.
Patches subview

In order to quickly navigate and manage all of the applied patches, the plugin includes a new subview simply called Patches. To open the subview select View->Open subviews->Patches. It is the very last item after the Problems subview.

NOTE: You may need to select Refresh from the Edit submenu or press Ctrl-U to see the latest changes.
In the view above you can see the patch we have just made to the database together with the exact address, name of the function and segment, size, modified and original bytes as well as any comments at that location.
TIP: To quickly jump to the location of the patch in the IDA subview simply double-click on one of the entries in the Patches subview.
Fill selection

Let's make another modification to the database by filling a range of addresses with NOPs. To do this select a range of instructions or hex bytes from IDA or Hex subviews respectively. Next, open the Fill selection dialog from Edit->Patch program and populate the value field with 0x90:

The start and end addresses of the selection are automatically populated; however, you can adjust them from the dialog. Press the Fill button to apply changes in the database and switch back to the Patches subview:

Notice that single byte patches were combined into larger consecutive buffers to make it easier to manage. The reason why there are two separate entries with NOPs instead of a single large one is due to the presence of the 0x90 byte somewhere in the original binary blob. As a result, IDA Patcher produced two separate entries since one byte in the middle was not actually updated. Here is the source of the issue:
.text:00401009 68 00 21 40 00 push offset Format
.text:0040100E FF 15
90
20 40 00 call ds:__imp__printf
.text:00401014 83 C4 04 add esp, 4
.text:00401017 6A 01 push 1 ; Code
.text:00401019 FF 15 8C 20 40 00 call ds:__imp__exit

Filling a range of bytes with the same value is useful when you need to quickly NOP-out a series of instructions or simply fill a memory area with a known value in the debugger (e.g. 0xCC to break execution when landing in that memory area).
Restore original byte(s)

At this point you may decide to restore original byte values since it wasn't really necessary to overwrite that many bytes. To accomplish this select one or more entries from the Patches subview and click on Restore original byte(s)... menu entry by right clicking anywhere within the subview:

For each of the items in the selection, you will get a popup similar to the one below confirming the change:

NOTE: You will not be able to edit bytes when restoring them. If you want to edit patched bytes, use one of the methods described below
Press Restore to apply changes to the database.
Edit patch byte(s)

There are several ways to edit bytes in IDA Patcher depending on whether you are editing an existing patch or applying a new one.
The edit dialog to alter already patched bytes can be triggered by selecting Edit from the right-click context menu in the Patches subview:

Notice the variety of valid hex input representations that can be mixed together. You can safely input fewer or greater number of bytes than the original patch buffer. In the case of fewer bytes, the original bytes at the end will be restored. Inputting more than the original length will simply continue overwriting the database as expected.
You can also bring up a similar edit dialog by selecting a range of addresses from IDA or Hex views and clicking on the Edit->Patch Program->Edit Selection... menu item. All of the bytes in the selected range will be already populated in the dialog edit box; however, you are free to edit more or less than the selected buffer just like in the previous example. While similar to the previous method, this dialog allows you to create new patches instead of editing existing ones.
Import data

A powerful way of importing binary blobs into the database involves the use of Import data dialog. It can be opened from the Edit menu while IDA or Hex subviews are active. By selecting a range of memory addresses (or at least a starting point), you can paste large blobs of binary data from external files or pasted as hex bytes or string literals. The example below illustrates how to quickly inject shellcode into the stack memory area to aid in exploit development:

You can switch between different import types to change where the data is coming from and how to interpret it. For example, string literal type will interpret pasted buffer as raw bytes without trying to decode it as a hex values.
The trim to selection checkbox will ensure that imported data does not exceed specified address ranges by trimming whatever extra bytes at the end.
Applying Patches

With all of the modifications done to the IDA database you may want to apply them to the original input file. You have two identical ways of accomplishing this: using IDA's own Edit->Apply patches to input file... or the plugin's replica dialog which can be called from the right-click menu in the Patches subview:

It is important to note that not all binary modifications in the database can be applied to the input file. For example, modifications done in the debugger example above can't be applied to the input file because stack memory area is dynamically generated and not linked to any particular segment in the original file. The screenshot above illustrates different icons associated with each patch entry: a little save disk icon for the patches that can be applied and a little red cross for the ones that can't be applied.
Just like in the original Apply patches to input file dialog, you have an option to create a backup file or simply restore the previously patched file to its original form. Once you click OK, all of the patches which can be applied will be stored in the specified input file.
Known bugs

  • 64-bit version of IDA Pro 6.5 for Windows does not correctly report selected address ranges (most significant 4 bytes appear to be zeroed out).
Special Note

Thank you Ilfak Guilfanov and Hex-Rays development team for the excellent product and quick support. The plugin would not be possible if not for Chris Eagle's excellent IDA Pro book from which I have learned about the world of IDA and reverse engineering, thank you sir!


Source http://thesprawl.org/projects/ida-patcher/#applying-patches
 

Storm Shadow

Administrator
Staff member
Developer
Ida Pro Expert
Elite Cracker
I tried to convert but first i get the error
Code:
error module
 
on the code
 
idaapi.visit_patched_bytes(0, idaapi.BADADDR, self.get_patch_byte)

I tried to fixed with the line

Code:
r = idaapi.byte_patched (self.apply_patch_byte, start_ea, end_ea)
after runing the code from Ida Python

Code:
Python>help ("idaapi.IDB_Hooks.byte_patched")
Help on method byte_patched in idaapi.IDB_Hooks:
 
idaapi.IDB_Hooks.byte_patched = byte_patched(self, *args) unbound idaapi.IDB_Hooks method
    byte_patched(self, ea_t arg0) -> int
 
Still got this error
Ww8dnIp.png
:(
 

computerline

New member
Ida Pro Expert
Fix 1 :
Code:
https://mega.co.nz/#!JMVSkKCb!HOKOK04Hy90UQ0e4E0PTiicQSn_kML-Wi6DVzSujjfk

Apply patch to file still not working :)
 

computerline

New member
Ida Pro Expert
Fix 2 :
Code:
https://mega.co.nz/#!NVEgkCab!cZkLpuuwsJc6PyxwyTrd25hQ82hqHhauNU10fYmJc5o
Apply patch direct to input file
 

computerline

New member
Ida Pro Expert
I has some minor change, if too many change in idb, plugin run very low, so, I remove some track change
Python:
#!/usr/bin/env python
#
# IDA Patcher is a plugin for Hex-Ray's IDA Pro disassembler designed to
# enhance IDA's ability to patch binary files and memory. The plugin is
# useful for tasks related to malware analysis, exploit development as well
# as bug patching. IDA Patcher blends into the standard IDA user interface
# through the addition of a subview and several menu items.
#
# VERSION 0.1.1.4
#
# Copyright (C) 2013 Peter Kacherginsky
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
#	list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
#	this list of conditions and the following disclaimer in the documentation
#	and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
# BUG FIXED
# rev 0.0.1.4
# [*] 2014-04-01 : Auto delete from list after restore ogirnal
#
 
import idaapi
import idautils
import idc
 
from idaapi import Form, Choose2, plugin_t
 
import os
import shutil
import struct
import binascii
 
IDAPATCHER_VERSION = "0.1.1.4"
 
#--------------------------------------------------------------------------
# Forms
#--------------------------------------------------------------------------
class PatchRestoreForm(Form):
	"""
	Form to aid in restoring patched bytes to their original values.
	"""
	def __init__(self, addr_str, fpos_str, patch_str, org_str):
		Form.__init__(self,
r"""BUTTON YES* Restore
BUTTON CANCEL Cancel
Restore patch bytes (Please don't edit)
 
Address		{strAddr}
File offset	{strFpos}
<:{strOrg}>
""", {
		'strAddr': Form.StringLabel(addr_str),
		'strFpos': Form.StringLabel(fpos_str),
		#'strOrg': Form.MultiLineTextControl(text=org_str, flags = Form.MultiLineTextControl.TXTF_FIXEDFONT | Form.MultiLineTextControl.TXTF_READONLY),
		'strOrg': Form.StringInput(tp=None, width=1024, swidth=40, hlp=None, value=org_str, size=None),
		})
		self.Compile()
 
#--------------------------------------------------------------------------
class PatchEditForm(Form):
	"""
	Form to edit patched bytes.
	"""
	def __init__(self, addr_str, fpos_str, patch_str, org_str):
		Form.__init__(self,
r"""Edit patch bytes
 
Address		{strAddr}
File offset	{strFpos}
<:{strPatch}>
""", {
		'strAddr':  Form.StringLabel(addr_str),
		'strFpos':  Form.StringLabel(fpos_str),
		#'strPatch': Form.MultiLineTextControl(text=patch_str, flags = Form.MultiLineTextControl.TXTF_FIXEDFONT),
		'strPatch': Form.StringInput(tp=None, width=1024, swidth=40, hlp=None, value=patch_str, size=None),
		})
 
		self.Compile()
 
#--------------------------------------------------------------------------
class PatchApplyForm(Form):
	"""
	Form to prompt for target file, backup file, and the address
	range to save patched bytes.
	"""
	def __init__(self, start_ea, end_ea, org_file, bkp_file):
		Form.__init__(self,
r"""Apply patches to input file
 
{FormChangeCb}
<##Start EA   :{intStartEA}>
<##End EA	 :{intEndEA}>
<##Input file :{orgFile}>
<##Backup file:{bkpFile}>
 
<##Create backup:{rBackup}>
<##Restore original bytes:{rRestore}>{cGroup1}>
""", {
		'intStartEA': Form.NumericInput(swidth=40,tp=Form.FT_ADDR,value=start_ea),
		'intEndEA': Form.NumericInput(swidth=40,tp=Form.FT_ADDR,value=end_ea),
		'orgFile': Form.FileInput(swidth=50, open=True, value=org_file),
		'bkpFile': Form.FileInput(swidth=50, open=True, value=bkp_file),
		'cGroup1': Form.ChkGroupControl(("rBackup", "rRestore")),
		'FormChangeCb': Form.FormChangeCb(self.OnFormChange),
		})
 
		self.Compile()
 
	def OnFormChange(self, fid):
		# Set initial state
		if fid == -1:
			self.EnableField(self.bkpFile, False)
 
		# Toggle backup checkbox
		elif fid == self.rBackup.id:
			self.rBackup.checked = not self.rBackup.checked
			self.EnableField(self.bkpFile, self.rBackup.checked)
 
		# Toggle restore checkbox
		elif fid == self.rRestore.id:
			self.rRestore.checked = not self.rRestore.checked
 
		return 1
 
#--------------------------------------------------------------------------
class PatchFillForm(Form):
	"""
	Form to fill a range of addresses with a specified byte value.
	"""
	def __init__(self, start_ea, end_ea, fill_value):
 
		Form.__init__(self,
r"""BUTTON YES* Fill
Fill bytes
 
<##Start EA   :{intStartEA}>
<##End EA	 :{intEndEA}>
<##Value	  :{intPatch}>
""", {
		'intStartEA': Form.NumericInput(swidth=40,tp=Form.FT_ADDR,value=start_ea),
		'intEndEA': Form.NumericInput(swidth=40,tp=Form.FT_ADDR,value=end_ea),
		'intPatch': Form.NumericInput(swidth=40,tp=Form.FT_HEX,value=fill_value),
		})
 
		self.Compile()
 
class DataImportForm(Form):
	"""
	Form to import data of various types into selected area.
	"""
	def __init__(self, start_ea, end_ea):
		Form.__init__(self,
r"""BUTTON YES* Import
Import data
 
{FormChangeCb}
<##Start EA   :{intStartEA}>
<##End EA	 :{intEndEA}>
 
Import type:					Patching options:
<hex string:{rHex}><##Trim to selection:{cSize}>{cGroup}>
<string literal:{rString}>
<binary file:{rFile}>{rGroup}>
 
<:{strPatch}>
<##Import BIN file:{impFile}>
""", {	   
		'intStartEA': Form.NumericInput(swidth=40,tp=Form.FT_ADDR,value=start_ea),
		'intEndEA': Form.NumericInput(swidth=40,tp=Form.FT_ADDR,value=end_ea),
 
		'cGroup': Form.ChkGroupControl(("cSize",)),
		'rGroup': Form.RadGroupControl(("rHex", "rString", "rFile")),
 
		#'strPatch': Form.MultiLineTextControl(swidth=80, flags=Form.MultiLineTextControl.TXTF_FIXEDFONT),
		'strPatch': Form.StringInput(tp=None, width=1024, swidth=40, hlp=None, value=None, size=None),
		'impFile': Form.FileInput(swidth=50, open=True),
 
		'FormChangeCb': Form.FormChangeCb(self.OnFormChange),
		})
 
		self.Compile()
 
	def OnFormChange(self, fid):
		# Form initialization
		if fid == -1:
			self.SetFocusedField(self.strPatch)
			self.EnableField(self.strPatch, True)
			self.EnableField(self.impFile, False)
 
		# Form OK pressed
		elif fid == -2:
			pass
 
		# Form from text box
		elif fid == self.rHex.id or fid == self.rString.id:
			self.SetFocusedField(self.strPatch)
			self.EnableField(self.strPatch, True)
			self.EnableField(self.impFile, False)
 
		# Form import from file
		elif fid == self.rFile.id:
			self.SetFocusedField(self.rFile)
			self.EnableField(self.impFile, True)
			self.EnableField(self.strPatch, False)
 
		return 1
 
#--------------------------------------------------------------------------
# Chooser
#--------------------------------------------------------------------------
class PatchView(Choose2):
	"""
	Chooser class to display and manage patched bytes in the database.
	"""
	def __init__(self):
		Choose2.__init__(self,
						 "Patches",
						 [ ["Address",  10 | Choose2.CHCOL_HEX],
						   ["Name",	 18 | Choose2.CHCOL_PLAIN],
						   ["Size",	  4 | Choose2.CHCOL_DEC],
						   ["Modified", 10 | Choose2.CHCOL_HEX],
						   ["Original", 10 | Choose2.CHCOL_HEX],
						   ["Comment",  30 | Choose2.CHCOL_PLAIN]
						 ],
						 flags = Choose2.CH_MULTI_EDIT)
 
		self.popup_names = ["Insert", "Delete", "Edit", "Refresh"]
	  
		self.icon = 47
 
		# Items for display and corresponding data
		# NOTE: Could become desynchronized, so to avoid this
		#	   refresh the view after each change.
		self.items = []
		self.items_data  = []
 
		# Initialize/Refresh the view
		self.refreshitems()
 
		self.timer = None
		#self.timer = Timer(0.5, self.refreshitems, ).start()
 
		# Data members
		self.patch_file = None
		self.restore = False
 
		# Command callbacks
		self.cmd_apply_patches = None
		self.cmd_restore_bytes = None
 
	def show(self):
		# Attempt to open the view
		if self.Show() < 0: return False
 
		# Add extra context menu commands
		# NOTE: Make sure you check for duplicates.
		if self.cmd_apply_patches == None:
			self.cmd_apply_patches = self.AddCommand("Apply patches to input file...", flags = idaapi.CHOOSER_POPUP_MENU | idaapi.CHOOSER_NO_SELECTION, icon=27)
		if self.cmd_restore_bytes == None:
			self.cmd_restore_bytes = self.AddCommand("Restore original byte(s)...", flags = idaapi.CHOOSER_POPUP_MENU | idaapi.CHOOSER_MULTI_SELECTION, icon=139)
 
		return True
 
	# Patch byte visitor callback to apply the patches
	# NOTE: Only bytes with fpos > -1 can be applied.
	def apply_patch_byte(self, ea, fpos, org_val, patch_val):
		if fpos != -1:
			self.patch_file.seek(fpos)
 
			if self.restore:
				self.patch_file.write(struct.pack('B', org_val))
			else:
				self.patch_file.write(struct.pack('B', patch_val))
 
		return 0
 
	# Patch byte visitor callback to collect and aggregate bytes
	def get_patch_byte(self, ea, fpos, org_val, patch_val):
 
		# Aggregate contiguous bytes (base ea + length)
		# NOTE: Looking at the last item [-1] is sufficient
		#	   since we are dealing with sorted data.
		if len(self.items_data) and (ea - self.items_data[-1][0] == self.items_data[-1][2]):
 
			# Increment length
			self.items_data[-1][2] += 1
			self.items[-1][2] = str(self.items_data[-1][2])
 
			# Append patched bytes
			self.items_data[-1][3].append(patch_val)
			self.items[-1][3] = " ".join(["%02X" % x for x in self.items_data[-1][3]])
 
			# Append original bytes
			self.items_data[-1][4].append(org_val)
			self.items[-1][4] =  " ".join(["%02X" % x for x in self.items_data[-1][4]])
 
 
		# Add new patch byte to the list
		else:
 
			name = SegName(ea)
 
			if GetFunctionName(ea) or Name(ea):
				name += ": %s" % GetFunctionName(ea) or Name(ea)
 
 
			comment = Comment(ea) or RptCmt(ea) or "[No Comment]"
			# DATA STORAGE FORMAT:	  address, function / fpos, len,	patched byte(s), original byte(s), comments
			self.items.append(	 ["%08X" % ea,			name, "1", "%02X" % patch_val, "%02X" % org_val, comment])
			self.items_data.append([		 ea,			fpos,   1,		[patch_val],		[org_val], None]   )
 
		return 0
 
	def refreshitems(self):
		global f_byte_changed
		if f_byte_changed:
			print "[*] IdaPatcher::refeshitems()"
			self.items_data = []
			self.items = []
			visit_patched_bytes(0, idaapi.BADADDR, self.get_patch_byte)
			f_byte_changed = False
 
	def OnCommand(self, n, cmd_id):
 
		# Apply patches to a file
		if cmd_id == self.cmd_apply_patches:
 
			# Set initial start/end EA values
			start_ea = 0x0
			end_ea = idaapi.cvar.inf.maxEA
 
			# Set initial output file values
			org_file = GetInputFilePath()
			bkp_file = "%s.bak" % org_file
 
			# Create the form
			f = PatchApplyForm(start_ea, end_ea, org_file, bkp_file)
 
			# Execute the form
			ok = f.Execute()
			if ok == 1:
				# Get restore checkbox
				self.restore = f.rRestore.checked
 
				# Get updated ea max/min
				start_ea = f.intStartEA.value
				end_ea = f.intEndEA.value
 
				# Get updated file path
				new_org_file = f.orgFile.value
 
				# Backup the file before replacing
				if f.rBackup.checked:
					bkp_file = f.bkpFile.value
					shutil.copyfile(org_file, bkp_file)
 
				# Apply patches
				try:
					self.patch_file = open(new_org_file,'rb+')
				except Exception, e:
					idaapi.warning("Cannot update file '%s'" % new_org_file)
				else:
					#r = idaapi.visit_patched_bytes(start_ea, end_ea, self.apply_patch_byte)
					r = visit_patched_bytes(start_ea, end_ea, self.apply_patch_byte)
					self.patch_file.close()
					self.restore = False
 
					# Update db input file, so we are working
					# with a patched version.
					#if not org_file == new_org_file:
					#	idaapi.set_root_filename(new_org_file)
					#	org_file = new_org_file
 
			# Dispose the form
			f.Free()
 
		# Restore selected byte(s)
		elif cmd_id == self.cmd_restore_bytes:
 
			# Empty list
 
			if n == -2 or n ==-3:
				return 1
 
			elif not len(self.items) > 0:
				idaapi.warning("There are no patches to restore.")
				return 1
 
			# Nothing selected
			elif n == -1:
				idaapi.warning("Please select bytes to restore.")
				return 1
 
			ea = self.items_data[n][0]
			fpos =  self.items_data[n][1]
			buf = self.items_data[n][4]
 
			addr_str = "%#x" % ea
			fpos_str = "%#x" % fpos if fpos != -1 else "N/A" 
			patch_str = self.items[n][3]
			org_str = self.items[n][4]
 
			# Create the form
			f = PatchRestoreForm(addr_str, fpos_str, patch_str, org_str)
 
			# Execute the form
			ok = f.Execute()
			if ok == 1:
 
				# Restore original bytes
				buf_length = len(buf)
				idaapi.put_many_bytes(ea, struct.pack("B" * buf_length, *buf))
				for i in xrange(0, buf_length):
					if patched_bytes_dict.has_key(ea + i):
						patched_bytes_dict[ea + i][3] = 0x0
						print patched_bytes_dict[ea + i]
				# Refresh all IDA views
				global f_byte_changed
				f_byte_changed = True
				self.refreshitems()
 
				idaapi.refresh_idaview_anyway()
 
			# Dispose the form
			f.Free()
 
		return 1
 
	def OnClose(self):
		self.cmd_apply_patches = None
		self.cmd_restore_bytes = None
		if self.timer != None:
			self.timer.stop()
			self.timer = None
 
	def OnEditLine(self, n):
 
		# Empty list
		if n == -1:
			return
 
		# Multiselect START_SEL/END_SEL protocol
		if n == -2 or n ==-3:
			return
 
		ea = self.items_data[n][0]
		fpos =  self.items_data[n][1]
		patch_buf = self.items_data[n][3]
		orig_buf = self.items_data[n][4]
 
		addr_str = "%#x" % ea
		fpos_str = "%#x" % fpos if fpos != -1 else "N/A"   
		patch_str = self.items[n][3]
		org_str = self.items[n][4]   
 
		# Create the form
		f = PatchEditForm(addr_str, fpos_str, patch_str, org_str)
 
		# Execute the form
		ok = f.Execute()
		if ok == 1:
 
			# Convert hex bytes to binary
			buf = f.strPatch.value
			buf = buf.replace(' ','')	   # remove spaces
			buf = buf.replace('\\x','')	 # remove '\x' prefixes
			buf = buf.replace('0x','')	  # remove '0x' prefixes
			try:
				buf = binascii.unhexlify(buf)   # convert to bytes
			except Exception, e:
				idaapi.warning("Invalid input: %s" % e)
				f.Free()
				return
 
			# Restore original bytes first
			idaapi.put_many_bytes(ea, struct.pack("B"*len(orig_buf), *orig_buf))
 
			# Now apply newly patched bytes
			idaapi.patch_many_bytes(ea, buf)
 
			# Refresh all IDA views
			self.refreshitems()
			idaapi.refresh_idaview_anyway()
 
		# Dispose the form
		f.Free()
 
	def OnSelectLine(self, n):
		idaapi.jumpto(self.items_data[n][0])
 
	def OnGetLine(self, n):
		return self.items[n]
 
	def OnGetIcon(self, n):
 
		# Empty list
		if not len(self.items) > 0:
			return -1
 
		if self.items_data[n][1] == -1:
			return 138
		else:
			return 137
 
	def OnGetSize(self):
		return len(self.items)
 
	def OnRefresh(self, n):
		self.refreshitems()
		return n
 
	def OnActivate(self):
		self.refreshitems()
 
#--------------------------------------------------------------------------
# Manager
#--------------------------------------------------------------------------
class PatchManager():
	""" Class that manages GUI forms and patching methods of the plugin. """
  
	def __init__(self):
		self.addmenu_item_ctxs = list()
		self.patch_view = PatchView()
 
	#--------------------------------------------------------------------------
	# Menu Items
	#--------------------------------------------------------------------------
	def add_menu_item_helper(self, menupath, name, hotkey, flags, pyfunc, args):
 
		# add menu item and report on errors
		addmenu_item_ctx = idaapi.add_menu_item(menupath, name, hotkey, flags, pyfunc, args)
		if addmenu_item_ctx is None:
			return 1 # Error
		else:
			self.addmenu_item_ctxs.append(addmenu_item_ctx)
			print "[*] Add Menu Item -> %s" % (menupath + name)
			return 0
 
	def add_menu_items(self):
		#print "[*] IdaPatcher::add_menu_items()"
		if self.add_menu_item_helper("View/Open subviews/Problems", "Patches", "", 1, self.show_patches_view, None):
			return 1 # Error
		if self.add_menu_item_helper("Edit/Patch program/", "Edit selection...", "", 0, self.show_edit_form, None): 
			return 1 # Error
		if self.add_menu_item_helper("Edit/Patch program/", "Fill selection...", "", 0, self.show_fill_form, None): 
			return 1 # Error
		if self.add_menu_item_helper("Edit/", "Import data...", "Shift-I", 0, self.show_import_form, None): 
			return 1 # Error
		# The last must be fail :(  EPIC FAIL HERE !!!!
		#if self.add_menu_item_helper("Edit/Export data...", "Import data...", "Shift-I", 1, self.show_import_form, None):   return 1
		return 0 # OK
 
	def del_menu_items(self):
		for addmenu_item_ctx in self.addmenu_item_ctxs:
			idaapi.del_menu_item(addmenu_item_ctx)
 
	#--------------------------------------------------------------------------
	# View Callbacks
	#-------------------------------------------------------------------------- 
  
	# Patches View
	def show_patches_view(self):
		#print "[*] IdaPatcher::show_fatches_view()"
		self.patch_view.show()
 
	# Patches Edit Dialog
	def show_edit_form(self):
		selection, start_ea, end_ea = idaapi.read_selection()
	  
		if not selection:
			start_ea = idaapi.get_screen_ea()
			end_ea = start_ea + 1
		if start_ea != idaapi.BADADDR and end_ea != idaapi.BADADDR:
			if end_ea > start_ea:
				buf_len = end_ea - start_ea
				buf = get_many_bytes(start_ea, buf_len) or "\xFF"*buf_len
				buf_str = " ".join(["%02X" % ord(x) for x in buf])
 
				fpos = idaapi.get_fileregion_offset(start_ea)
 
				addr_str = "%#X" % start_ea
				fpos_str = "%#x" % fpos if fpos != -1 else "N/A"
 
				f = PatchEditForm(addr_str, fpos_str, buf_str, buf_str)
 
				# Execute the form
				ok = f.Execute()
				if ok == 1:
 
					# Convert hex bytes to binary
					buf = f.strPatch.value
					buf = buf.replace(' ','')	   # remove spaces
					buf = buf.replace('\\x','')	 # remove '\x' prefixes
					buf = buf.replace('0x','')	  # remove '0x' prefixes
					try:
						buf = binascii.unhexlify(buf)   # convert to bytes
					except Exception, e:
						idaapi.warning("Invalid input: %s" % e)
						f.Free()
						return
 
					# Now apply newly patched bytes
					idaapi.patch_many_bytes(start_ea, buf)
 
					# Refresh all IDA views
					self.patch_view.refreshitems()
					idaapi.refresh_idaview_anyway()
 
				# Dispose the form
				f.Free()
		return
 
	# Fill range with a value form
	def show_fill_form(self):
		selection, start_ea, end_ea = idaapi.read_selection()
	  
		if not selection:
			start_ea = idaapi.get_screen_ea()
			end_ea = start_ea + 1
	  
		# Default fill value
		fill_value = 0x90 #nop
 
		# Create the form
		f = PatchFillForm(start_ea, end_ea, fill_value)
 
		# Execute the form
		ok = f.Execute()
		if ok == 1:
 
			# Get updated values
			start_ea = f.intStartEA.value
			end_ea = f.intEndEA.value
			fill_value = f.intPatch.value
 
			# Now apply newly patched bytes
			# NOTE: fill_value is expected to be one byte
			#	   so if a user provides a larger patch_byte()
			#	   will trim the value as expected.
 
 
			for ea in range(start_ea, end_ea):
				idaapi.patch_byte(ea, fill_value)
 
			# Refresh all IDA views
			self.patch_view.refreshitems()
			idaapi.refresh_idaview_anyway()
 
		# Dispose the form
		f.Free()
 
	# Import data form
	def show_import_form(self):
		selection, start_ea, end_ea = idaapi.read_selection()
 
		if not selection:
			start_ea = idaapi.get_screen_ea()
			end_ea = start_ea + 1
 
		# Create the form
		f = DataImportForm(start_ea, end_ea);
 
		# Execute the form
		ok = f.Execute()
		if ok == 1:
 
			start_ea = f.intStartEA.value
			end_ea = f.intEndEA.value
 
			if f.rFile.selected:
				imp_file = f.impFile.value
 
				try:
					f_imp_file = open(imp_file,'rb+')
				except Exception, e:
					idaapi.warning("File I/O error({0}): {1}".format(e.errno, e.strerror))
					return
				else:
					buf = f_imp_file.read()
					f_imp_file.close()
 
			else:
 
				buf = f.strPatch.value
 
				# Hex values, unlike string literal, needs additional processing
				if f.rHex.selected:
					buf = buf.replace(' ','')	   # remove spaces
					buf = buf.replace('\\x','')	 # remove '\x' prefixes
					buf = buf.replace('0x','')	  # remove '0x' prefixes
					try:
						buf = binascii.unhexlify(buf)   # convert to bytes
					except Exception, e:
						idaapi.warning("Invalid input: %s" % e)
						f.Free()
						return
 
			if not len(buf):
				idaapi.warning("There was nothing to import.")
				return
 
			# Trim to selection if needed:
			if f.cSize.checked:
				buf_size = end_ea - start_ea
				buf = buf[0:buf_size]
 
			# Now apply newly patched bytes
			idaapi.patch_many_bytes(start_ea, buf)
 
			# Refresh all IDA views
			self.patch_view.refreshitems()
			idaapi.refresh_idaview_anyway()
 
		# Dispose the form
		f.Free()
 
#--------------------------------------------------------------------------
# Patch Notify Hook
#--------------------------------------------------------------------------
 
patched_bytes_dict = {}
global f_byte_changed
 
# Dummy function
def visit_patched_bytes(startEA, endEA, callback):
	for key, val in patched_bytes_dict.items():
		if val[3]: # status flag, 1 : Active, 0 : Unactive
			callback(key, val[0], val[1], val[2])
	return 0
 
 
class PatchNotifyHook(idaapi.IDB_Hooks):
	# Overided
	def byte_patched(self, ea):
		global f_byte_changed
		if ea != idaapi.BADADDR:
			patched_byte = get_byte(ea)
			orginal_byte = get_original_byte(ea)
			file_offset = idaapi.get_fileregion_offset(ea)
			if file_offset != -1:
				if patched_bytes_dict.has_key(ea):
					if patched_bytes_dict[ea][1] != patched_byte:
						patched_bytes_dict[ea][2] = patched_byte
						patched_bytes_dict[ea][3] = 1 #Byte Changed
						f_byte_changed = True
					else:
						patched_bytes_dict[ea][3] = 0  #Byte Don't Changed
						f_byte_changed = False
				else:
					patched_bytes_dict[ea] = [file_offset, orginal_byte, patched_byte, 1]
					f_byte_changed = True
				print "[*] Byte patched at (ea %x file_offset %x) : %x -> %x" % (ea, file_offset, orginal_byte, patched_byte)
			#else:
			#	patched_bytes_dict[ea] = [-1, orginal_byte, patched_byte, 0] 
		return 0
 
#--------------------------------------------------------------------------
# Plugin
#--------------------------------------------------------------------------
class idapatcher_t(plugin_t):
 
	flags = idaapi.PLUGIN_UNL
	comment = "Enhances manipulation and application of patched bytes."
	help = "Enhances manipulation and application of patched bytes."
	wanted_name = "IDA Patcher"
	wanted_hotkey = ""
 
	def init(self): 
		global idapatcher_manager
		global patch_notify_hook
		global f_byte_changed
	  
 
		f_byte_changed = False
		print("[*] IDA Patcher v%s (c) Peter Kacherginsky <iphelix@thesprawl.org>" % IDAPATCHER_VERSION)
		print("[*] Initialize ...")
		# Check if already initialized
		if not 'idapatcher_manager' in globals():
			if not 'patch_notify_hook' in globals():
				idapatcher_manager = PatchManager()
				patch_notify_hook = PatchNotifyHook()
				patch_notify_hook.hook() # Install the hook
				# if add_menu_items() return 1, then we known that faild to add menu
				if idapatcher_manager.add_menu_items():
					print "[!] Failed to initialize IDA Patcher."
					idapatcher_manager.del_menu_items()
					del idapatcher_manager
					return idaapi.PLUGIN_SKIP
				else:
					print("[*] IDA Patcher v%s (c) Peter Kacherginsky <iphelix@thesprawl.org>" % IDAPATCHER_VERSION)
		print("[*] Initialize Finished ! Ctrl + U to Refesh Patched Windows")	   
		return idaapi.PLUGIN_KEEP
 
	# NOTE : run() get call and terminate term(), so we
	#		   better don't cause run
	def run(self, arg):
		pass
		#print("[*] IDA Patcher : Run ...")
		#global idapatcher_manager
 
		#idapatcher_manager.show_patches_view()
 
	# NOTE: term() gets called immediately after run()
	#	   as the plugin is expected to complete execution
	#	   so just pass as this plugin is expected to run
	#	   for the duration of IDA session.
	def term(self):
		global idapatcher_manager
		global patch_notify_hook
		global timer
		if 'patch_notify_hook' in globals():
			patch_notify_hook.unhook()
			del patch_notify_hook
		if 'idapatcher_manager' in globals():
			idapatcher_manager.del_menu_items()
			del idapatcher_manager
		print("[*] IDA Patcher : Term ...")
		pass
	  
#---------------------------------------------------------------------
# Init Plugin
#---------------------------------------------------------------------
def PLUGIN_ENTRY():
	return idapatcher_t()
 
#--------------------------------------------------------------------------
# Script / Testing
#--------------------------------------------------------------------------
def idapatcher_main():
	global idapatcher_manager
 
	if 'idapatcher_manager' in globals():
		idapatcher_manager.del_menu_items()
		del idapatcher_manager
 
	idapatcher_manager = PatchManager()
	idapatcher_manager.add_menu_items()
	idapatcher_manager.show_patches_view()
 
if __name__ == '__main__':
	pass
	#idapatcher_main()
 
Last edited by a moderator:
Top