├── Readme.txt ├── docs ├── css │ └── style.css ├── idasim │ ├── architecture.html │ ├── handler.html │ ├── idasim.html │ ├── index.html │ └── mmu.html ├── idasimulator │ ├── images │ │ ├── activate_handler.png │ │ ├── activate_plugin.png │ │ ├── idasimulator_command_line.png │ │ ├── membase.png │ │ ├── new_handler.png │ │ ├── python_expression.png │ │ ├── register_config_defaults.png │ │ ├── register_config_expression.png │ │ ├── register_config_main.png │ │ ├── registers_configured.png │ │ ├── right_click_menu.png │ │ └── start_idasimulator.png │ └── index.html └── index.html ├── install.py └── src ├── plugins ├── idasimlib │ ├── __init__.py │ ├── fuzz.py │ ├── libc.py │ ├── libcsman.py │ ├── libnvram.py │ ├── pthread.py │ └── stdio.py └── idasimulator.py └── python └── idasim ├── __init__.py ├── application.py ├── architecture.py ├── exceptions.py ├── handler.py ├── idasim.py └── mmu.py /Readme.txt: -------------------------------------------------------------------------------- 1 | IDASimulator is a plugin that extends IDA's conditional breakpoint support, making it easy to augment / replace complex executable code inside a debugged process with Python code. 2 | Specifically, IDASimulator makes use of conditional breakpoints in the IDA debugger to hijack the execution flow of a process and invoke Python handler functions whenever particular code blocks are executed. With support for multiple target architectures, it handles details such as register initialization, memory allocation, pointers, function arguments and return values seamlessly and transparently, making it easy to replace, modify and subvert existing functionality (or lack thereof) in the target process. 3 | IDASimulator also includes the IDASim python module, on which IDASimulator is based. This allows for all of the features of IDASimulator to be integrated into more complex IDAPython scripts. 4 | IDASimulator currently supports the x86, x86_64, ARM and MIPS32 architectures. Porting to other architectures is very easy. 5 | 6 | Why? 7 | 8 | ** The ability to dynamically intercept, replace or modify process logic is useful in a variety of situations, such as: 9 | ** Monitoring function calls 10 | ** Non-intrusive function hooking 11 | ** Simulating library functions when emulating code snippets in IDA 12 | ** Simulating hardware-dependant functionality (embedded firmware, custom hardware, unsupported ioctl's, etc) 13 | ** Any situation where the application code does one thing and you want it to do something else 14 | 15 | Introduction 16 | 17 | IDASimulator is a plugin that allows IDA users to easily augment / replace executable code inside a debugged process with Python code. 18 | Specifically, IDASimulator makes use of conditional breakpoints in the IDA debugger to hijack the execution flow of a process and invoke Python handler functions whenever particular code blocks are executed. With support for multiple target architectures, it handles details such as register initialization, memory allocation, pointers, function arguments and return values seamlessly and transparently, making it easy to replace, modify and subvert existing functionality (or lack thereof) in the target process. 19 | IDASimulator currently supports the x86, x86_64, ARM and MIPS32 architectures. Porting to other architectures is very easy. 20 | Installation 21 | 22 | The IDASimulator plugin and its associated IDASim python module can be installed with the install.py script: 23 | $ python install.py /path/to/ida/install/directory 24 | ​ 25 | Using IDASimulator 26 | 27 | See DOCS 28 | 29 | Thanks to Storm shadow keeping a copy of this! -------------------------------------------------------------------------------- /docs/css/style.css: -------------------------------------------------------------------------------- 1 | .purple { color: purple; } 2 | .green { color: green; } 3 | .red { color: red; } 4 | .blue { color: DarkBlue; } 5 | .aqua { color: DarkCyan; } 6 | .gold { color: DarkOrange; } 7 | 8 | body { width: 60%; margin-left: auto; margin-right: auto; } 9 | blockquote { font-weight: bold; } 10 | h1 { font-size: 20pt; } 11 | h2 { font-size: 14pt; } 12 | img { display: block; margin-left: auto; margin-right: auto; margin-top: 20px; margin-bottom: 25px; border: 1px solid black; } 13 | 14 | 15 | /* #usage p { margin-top: 20px; } */ 16 | -------------------------------------------------------------------------------- /docs/idasim/architecture.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Architecture 4 | 5 | 8 | 9 | 10 | 11 |

class Architecture:

12 | 13 |

def __init__(self)

14 |

15 |

16 |
 17 | 	Class constructor.
 18 | 
 19 | 	Returns None.
 20 | 		
21 |
22 |

23 | 24 |

def ToString(self, value, size=None)

25 |

26 |

27 |
 28 | 	Converts an integer value of size bytes into a raw string of bytes.
 29 | 
 30 | 	@value - Integer value to be represented as a raw string.
 31 | 	@size  - Size of the integer value, in bytes.
 32 | 
 33 | 	Returns a raw string containing the integer value in string form, and in the appropriate endianess.
 34 | 		
35 |
36 |

37 | 38 |

def Argument(self, n, value=None)

39 |

40 |

41 |
 42 | 	Read/write function arguments.
 43 | 
 44 | 	@n     - Argument index number, 0-indexed.
 45 | 	@value - If specified, the argument will be set to this value.
 46 | 
 47 | 	Returns the current argument value.
 48 | 		
49 |
50 |

51 | 52 |

def StackPointer(self, value=None)

53 |

54 |

55 |
 56 | 	Read/write the stack pointer register.
 57 | 
 58 | 	@value - If specified, the stack pointer register will be set to this value.
 59 | 
 60 | 	Returns the current stack pointer register value.
 61 | 		
62 |
63 |

64 | 65 |

def ReturnValue(self, value=None, n=0)

66 |

67 |

68 |
 69 | 	Read/write the function return register value.
 70 | 
 71 | 	@value - If specified, the return register will be set to this value.
 72 | 	@n     - Return register index number, for those architectures with multiple return registers.
 73 | 
 74 | 	Returns the current return register value.
 75 | 		
76 |
77 |

78 | 79 |

def ProgramCounter(self, value=None)

80 |

81 |

82 |
 83 | 	Read/write the program counter register.
 84 | 
 85 | 	@value - If specified, the program counter register will be set to this value.
 86 | 
 87 | 	Returns the current value of the program counter register.
 88 | 		
89 |
90 |

91 | 92 |

def ReturnAddress(self, value=None)

93 |

94 |

95 |
 96 | 	Read/write the return address.
 97 | 
 98 | 	@value - If specified, the return address will be set to this value.
 99 | 
100 | 	Returns the current return address value.
101 | 		
102 |
103 |

104 | 105 | 106 | -------------------------------------------------------------------------------- /docs/idasim/handler.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | IDASimFunctionHandler 4 | 5 | 8 | 9 | 10 |

class IDASimFunctionHandler:

11 | 12 |

def __init__(self, idbm, name=None, verbose=False)

13 |

14 |

15 |
16 | 	Class constructor.
17 | 
18 | 	@idbm    - Instance of IDASimMMU.
19 | 	@name    - Name that will be assigned to the class instance.
20 | 	@verbose - Enable verbose mode.
21 | 
22 | 	Returns None.
23 | 		
24 |
25 |

26 | 27 |

def RegisterHandler(self, name, handler, stubs=True)

28 |

29 |

30 |
31 | 	Registers a given function handler for a given function name.
32 | 
33 | 	@name    - Name of the function.
34 | 	@handler - The function handler to call.
35 | 	@stubs   - If True, handle calls to both extern and stub addresses.
36 | 
37 | 	Returns True on success, False on failure.
38 | 		
39 | 40 |

41 | 42 |

def RegisterHandlers(self, handlers, stubs=True)

43 |

44 |

45 |
46 | 	Registers a set of function handlers.
47 | 
48 | 	@handlers - A dictionary consisting of 'name':handler pairs.
49 | 	@stubs   - If True, handle calls to both extern and stub addresses.
50 |                 
51 | 	Returns the number of handlers successfully registered.
52 | 		
53 |
54 |

55 | 56 |

def UnregisterHandler(self, name, stubs=True)

57 |

58 |

59 |
60 | 	Removes a function handler by name.
61 | 
62 | 	@name  - The name of the function handler to be removed.
63 | 	@stubs - If True, corresponding function stub handlers that were automatically created by RegisterHandler will also be removed.
64 | 
65 | 	Returns None.
66 | 		
67 |
68 |

69 | 70 |

def UnregisterHandlers(self, purge=False)

71 |

72 |

73 |
74 | 	Deletes breakpoints for all registered handlers.
75 | 
76 | 	@purge - If True, removes all handlers for all instances of IDASimFunctionHandler.
77 | 	         If False, only handlers for this instance of IDASimFunctionHandler will be removed.
78 | 
79 | 	Returns None.
80 | 		
81 |
82 |

83 | 84 | 85 | -------------------------------------------------------------------------------- /docs/idasim/idasim.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | IDASim 4 | 5 | 8 | 9 | 10 |

class IDASim:

11 | 12 |

def __init__(self, handlers={}, debug=False, attach=False, membase=None)

13 |

14 |

15 |
 16 | 	Class constructor.
 17 | 
 18 | 	@handlers  - A dict of function names/addresses to simulate and their corresponding handlers.
 19 | 	@debug     - Set to True to automatically start debugging.
 20 | 	@attach    - Set to True to attach to a process, rather than directly running the debugger.
 21 | 	@membase   - Specify the base address to start at when allocating memory.
 22 | 
 23 | 	Returns None.
 24 | 		
25 |
26 |

27 | 28 |

def WaitForDebugger(self)

29 |

30 |

31 |
 32 | 	Waits for the debugger event (WFNE_CONT | WFNE_SUSP).
 33 | 	Called internally by StartDebugger and AttachDebugger.
 34 | 
 35 | 	Returns None.
 36 | 		
37 | 38 |

39 | 40 |

def StartDebugger(self)

41 |

42 |

43 |
 44 | 	Starts the debugger (equivalent of pressing F9).
 45 | 	Called internally by __init__ if debug=True.
 46 | 
 47 | 	Returns None.
 48 | 		
49 |
50 |

51 | 52 |

def AttachDebugger(self, pid=-1)

53 |

54 |

55 |
 56 | 	Attaches the debugger to a running process.
 57 | 	Called internally by __init__ if attach=True
 58 | 
 59 | 	@pid - The PID of the process to attach to (user will be prompted if not specified).
 60 | 
 61 | 	Returns None.
 62 | 		
63 |
64 |

65 | 66 |

def vsprintf(self, fmt, index)

67 |

68 |

69 |
 70 | 	Formats format strings.
 71 | 
 72 | 	@fmt   - The format string.
 73 | 	@index - The index of the function argument containing the first format string argument (0-based).
 74 | 
 75 | 	Returns a formatted string.
 76 | 		
77 |
78 |

79 | 80 |

def GetArguments(self, index, n)

81 |

82 |

83 |
 84 | 	Get a list of function arguments. Any valid string pointers will be converted to strings.
 85 | 
 86 | 	@index - First argument index, 0-based.
 87 | 	@n     - The number of arguments to retrieve.
 88 | 
 89 | 	Returns a list of n arguments.
 90 | 		
91 |
92 |

93 | 94 |

def Malloc(self, data=None, size=0)

95 |

96 |

97 |
 98 | 	Allocates space in the debugger's memory.
 99 | 
100 | 	@data - Fill the allocated space with this data.
101 | 	@size - If data is None, allocate and zero out size bytes of memory.
102 | 
103 | 	Returns the address of the allocated memory.
104 | 		
105 |
106 |

107 | 108 |

def ARGV(self, argv)

109 |

110 |

111 |
112 | 	Allocates space for an argv data structure.
113 | 
114 | 	@argv - A list of argv strings.
115 | 
116 | 	Returns the address of the argv array of pointers.
117 | 		
118 |
119 |

120 | 121 |

def String(self, string, raw=False)

122 |

123 |

124 |
125 | 	Creates a NULL-terminated string in the debugger's memory.
126 | 
127 | 	@string - The string, or list of strings, to place into memory.
128 | 	@raw    - If set to True, the string will not be NULL terminated.
129 | 
130 | 	Returns the address, or list of addresses, of the string(s) in memory.
131 | 		
132 |
133 |

134 | 135 |

def Int(self, value, size)

136 |

137 |

138 |
139 | 	Creates an integer value of size bytes in the debugger's memory.
140 | 
141 | 	@value - The integer value, or list of values, to place into memory.
142 | 	@size  - The size of the interger value(s), in bytes.
143 | 
144 | 	Returns the address, or a list of addresses, of the integer(s) in memory.
145 | 		
146 |
147 |

148 | 149 |

def DoubleWord(self, dword)

150 |

151 |

152 |
153 | 	Places an 8 byte integer into the debugger's memory.
154 | 
155 | 	@dword - The 8 byte register value, or list of values, to place into memory.
156 | 
157 | 	Returns the address, or a list of addresses, of the dword(s) in memory.
158 | 		
159 |
160 | 161 |

def Word(self, word)

162 |

163 |

164 |
165 | 	Places a four byte integer into the debugger's memory.
166 | 
167 | 	@word - The four byte integer value, or list of values, to place into memory.
168 | 
169 | 	Returns the address, or a list of addresses, of the word(s) in memory.
170 | 		
171 |
172 |

173 | 174 |

def HalfWord(self, hword)

175 |

176 |

177 |
178 | 	Places two bytes of data into the debugger's memory.
179 | 
180 | 	@hword - The two byte value, or list of values, to place into memory.
181 | 
182 | 	Returns the address, or a list of addresses, of the half word(s) in memory.
183 | 		
184 |
185 |

186 | 187 |

def Byte(self, byte)

188 |

189 |

190 |
191 | 	Places one byte of data into the debugger's memory.
192 |                 
193 | 	@byte - The byte value, or list of values, to place into memory.
194 |                 
195 | 	Returns the address, or a list of addresses, of the byte(s) in memory.
196 | 		
197 |
198 |

199 | 200 |

def Cleanup(self)

201 |

202 |

203 |
204 | 	Removes all registered function simulation hooks.
205 | 
206 | 	Returns None.
207 | 		
208 |
209 |

210 | 211 |

self.mmu

212 |

213 |

214 |
215 | 	Instance of the IDASimMMU class.
216 | 	This instance must be used for all direct IDASimMMU method calls.
217 | 		
218 |
219 |

220 | 221 |

self.cpu

222 |

223 |

224 |
           
225 | 	Instance of the Architecture class.
226 | 		
227 |
228 |

229 | 230 |

self.FunctionHandler

231 |

232 |

233 |
234 | 	Instance of the IDASimFunctionHandler class.
235 | 	This instance must be used for all direct IDASimFunctionHandler method calls.
236 | 		
237 |
238 |

239 | 240 | 241 | -------------------------------------------------------------------------------- /docs/idasim/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | IDASim User Guide 4 | 5 | 6 | 7 | 8 | 9 |

Introduction

10 |
11 |

12 | IDASim is an IDAPython module that supports simple simulation / replacement of executable code 13 | with Python code. 14 | Pesky details such as allocating memory space for strings and data, accessing function arguments, 15 | and setting return values are all handled by IDASim. 16 |

17 | 18 |

19 | IDASim is especially useful for: 20 | 21 |

26 |

27 | 28 |
29 |
30 | 31 |

Using IDASim

32 |
33 |

34 | To use IDASim, you must: 35 | 36 |

    37 |
  1. Import the idasim Python module
  2. 38 |
  3. Create function handlers to replace existing functions in the running process
  4. 39 |
  5. Instantiate the IDASim class object
  6. 40 |
41 |

42 | 43 | 44 |

Simulating Functions

45 |

46 | Suppose we have a target binary which contains the following internal function: 47 | 48 |

49 |
 50 | 		void trace(int trace_level, char *format_string, ...);
 51 | 				
52 |
53 | 54 | This function is used to display dynamically generated debug information, and is called repeatedly throughout 55 | the code. However, the contents of this function were #defined out during the production build of our target, 56 | so none of this information ever gets displayed. We can change that: 57 | 58 |
59 |
 60 | 		from idasim import *
 61 | 
 62 | 		def mytrace(trace_level, format_string=''):
 63 | 			print sim.vsprintf(format_string, 2)
 64 | 			return 0
 65 | 
 66 | 		sim = IDASim(handlers={'trace' : mytrace})
 67 | 				
68 |
69 | 70 | Here, we've told IDASim that we want all calls to the function named 'trace' redirected to our handler function, mytrace(). 71 | Note that the class instace must be assigned a global name, in this case 'sim', even if it is not 72 | subsequently referenced by your script. 73 |

74 | 75 | The mytrace handler defines the first two arguments for the function (the rest are obviously variable arguments): trace_level 76 | and format_string. Since we know that the second argument, format_string, will be a pointer to a NULL-terminated string, we've 77 | defined the default value for format_string as a string type. This means that instead of being passed the raw value for the 78 | format_string argument (a pointer to the NULL-terminated format string), the actual format string will be passed to mytrace. 79 |

80 | 81 | Format string parsing can typically be handled by the built in IDASim.vsprintf() method. We pass the format_string as the first argument to 82 | IDASim.vsprintf(), and also tell it that the format string arguments start at function argument index 2 (i.e., the third argument 83 | to the trace function will be the first format string argument). 84 |

85 | 86 | IDASim.vsprintf() will return the formatted string, so we simply print that out to the IDA console and return 0. The 87 | return register will be automatically populated with our return value, and execution of the process will jump back 88 | to the return address specified by the caller. 89 |

90 | 91 | 92 |

Returning String/Data Pointers

93 |

94 | What about handlers that need to return string/data pointers? Let's see what would be needed to simulate libc's strdup(): 95 | 96 |

97 |
 98 | 		def mystrdup(string=''):
 99 | 			return string + "\x00"
100 | 				
101 |
102 | 103 | When a handler returns a string type, that string data gets placed into the running process's memory, the return 104 | register is populated with a pointer to the data, and process execution is transferred back to the caller's return address. 105 |

106 | 107 | Note that since strdup() needs to return a NULL terminated string, the handler function needs to ensure that a NULL byte 108 | is placed at the end of the string that it returns. 109 |

110 | 111 | 112 |

Controlling Execution Flow

113 |

114 | OK, but what if we only want to simulate a function under specific circumstances? Each function handler can control if the 115 | original function is executed or not. If a handler returns None, nothing further is done and the process continues execution normally: 116 | 117 |

118 |
119 | 		def myhandler():
120 | 			if sim.cpu.ReturnAddress() == 0x0040BAAD:
121 | 				return None
122 | 			else:
123 | 				return 0
124 | 				
125 | 126 |

127 | 128 |

129 | Handlers can also directly control where the process continues its execution from by raising a JumpTo or GoTo exception: 130 | 131 |

132 |
133 | 		def gotohandler():
134 | 			raise GoTo(0x0040BAAD)
135 | 
136 | 		def jumptohandler():
137 | 			raise JumpTo(8)
138 | 				
139 |
140 | 141 | In the above example, gotohandler() causes process execution to continue from the address 0x0040BAAD. By contrast, jumptohandler() 142 | causes the process execution to continue from the current program counter address plus 8 bytes. 143 |

144 | 145 | 146 |

Starting the Debugger

147 |

148 | If the debug or attach arguments to IDASim() are set to true, the IDASim class constructor will respectively attempt to automatically 149 | start the debugger or attach to a remote debugger before returning: 150 | 151 |

152 |
153 | 		sim = IDASim(debug=True)
154 | 		sim = IDASim(attach=True)
155 | 				
156 |
157 | 158 | This is especially useful if you wish to automate the initialization of memory / registers before debugging the target process. 159 | The IDASim class provides various methods for allocating strings, integers, or raw data in process memory. As an example, let's 160 | use IDASim to configure the argc and argv arguments when running the main() function of a target binary as a code snippet: 161 | 162 |
163 |
164 | 		sim = IDASim(debug=True)
165 | 		sim.cpu.Argument(0, value=2)
166 | 		sim.cpu.Argument(1, value=sim.ARGV(['./foo', 'bar']))
167 | 				
168 |
169 | 170 | This uses the Architecture.Argument() method for setting the values of the main function's first argument (argc) and second argument (argv). 171 | Note that the special IDASim.ARGV() method is used for allocating space for the argv strings and generating an array of pointers to 172 | those strings. 173 |

174 | 175 | 176 |

Memory Management

177 |

178 | The IDASim class provides various methods for allocating data in process memory. Strings, integers and raw data can be placed directly 179 | into process memory using the following methods (where applicable, endianess is handled automatically): 180 | 181 |

182 |
183 | 		sim.String("Put this string in memory and ensure it is NULL terminated.")
184 | 		sim.Word(0x0040BAAD)
185 | 		sim.HalfWord(0xBAAD)
186 | 		sim.Byte(0xAD)
187 | 		sim.Malloc("\x00\x01\x02\x03\x04\x05")
188 | 				
189 |
190 | 191 | When allocating space for strings / data, IDASim needs to have some memory available where it can place data without interfering with 192 | the memory used by the running process. By default, IDASim looks for a segment of memory named 'MMU' to use. If this does not exist 193 | it looks to see if the last segment of memory is named 'MEMORY' or 'RAM', and if so, uses that. Else, it will begin allocating data 194 | from the bottom of the stack segment. If this also fails, it will default to using a base address of 0x100000. 195 |

196 | While this usually works fine, you may need to explicitly specify a memory segment to use in order to ensure data integrity. This can be 197 | done by defining a memory segment named 'MMU', or by passing the start address to use for data storage to the IDASim class constructor: 198 | 199 |
200 |
201 | 		sim = IDASim(membase=0x0040BAAD)
202 | 				
203 |
204 |

205 |
206 | 207 |
208 | 209 |

Porting

210 |
211 |

212 | To add support for a new architecture, add a new entry to the Architecture.ARCH dictionary: 213 | 214 |

215 |
216 | 		ARCH = {
217 | 
218 | 			...
219 | 	
220 | 			# Dictionary key is the architecture name
221 | 			'mips'  : {
222 | 				# Offset from the stack pointer where function arguments start
223 | 				'spoffset'      : 0x10,
224 | 				# Registers used to pass function arguments
225 | 				'argreg'        : ['a0', 'a1', 'a2', 'a3'],
226 | 				# Register(s) used to store function return values
227 | 				'retval'        : ['v0', 'v1'],
228 | 				# Name of the stack pointer register
229 | 				'sp'            : 'sp',
230 | 				# Name of the return address register
231 | 				'ra'            : 'ra',
232 | 				# Name of the program counter register
233 | 				'pc'            : 'pc',
234 | 			},
235 | 	
236 | 			...
237 | 		}
238 | 				
239 |
240 | 241 | Note that any of the required values can be specified as pointer values if necessary. For example, in x86, the return 242 | address is not stored in a register, but rather pushed onto the stack such that the stack pointer register points 243 | to the return address on the stack at the beginning of any function. Thus, the value for the 'ra' key can be specified 244 | as '*ESP', indicating that the ESP register contains a pointer to the memory location where the return address is located. 245 |

246 | 247 | If your target architecture is supported but your specific target processor is not supported, all 248 | that is required is to add a new entry to Architecture.PROCESSORS dictionary: 249 | 250 |
251 |
252 | 		PROCESSORS = {
253 | 
254 | 			...
255 | 
256 | 			# Dictionary key is the name of the IDA processory module
257 | 			'mipsl' : [{
258 | 				# The architecture key value, as specified in Architecture.ARCH
259 | 				'architecture'  : 'mips',
260 | 				# The endianess of this processor module, one of: 'little', 'big'
261 | 				'endianess'     : 'little',
262 | 				# The default bus width of this processory module
263 | 				'bits'          : 32
264 | 			}],
265 | 
266 | 			...
267 | 		}
268 | 				
269 | 270 |

271 |
272 |
273 | 274 |

Class Definitions

275 |
276 |

277 |

283 |

284 |
285 |
286 | 287 | 288 | 289 | -------------------------------------------------------------------------------- /docs/idasim/mmu.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | IDASimMMU 4 | 5 | 8 | 9 | 10 |

class IDASimMMU:

11 | 12 |

def __init__(self, base=None)

13 |

14 |

15 |
16 | 	Class constructor.
17 | 
18 | 	@base - Specify the base address to start at when allocating memory. Will be auto-detected if not specified.
19 | 
20 | 	Returns None.
21 | 		
22 |
23 |

24 | 25 |

def reset(self)

26 |

27 |

28 |
29 | 	Resets the current memory allocation pointer to the base memory allocation address.
30 | 	Called automatically when the debugger is started/stopped.
31 | 
32 | 	Returns None.
33 | 		
34 | 35 |

36 | 37 |

def base(self, base=None)

38 |

39 |

40 |
41 | 	Sets the base memory allocation address.
42 | 	
43 | 	@base - Specify the base address to start at when allocating memory.
44 | 	
45 | 	Returns None.
46 | 		
47 |
48 |

49 | 50 |

def malloc(self, data=None, size=0)

51 |

52 |

53 |
54 | 	Allocates space for data in the debugger's memory and populates it.
55 |         
56 | 	@data - Data to place into memory. If None, NULL bytes will be used.
57 | 	@size - Size of memory to allocate. If 0, len(data) bytes will be allocated.
58 |         
59 | 	Returns the address of the allocated memory.
60 | 		
61 |
62 |

63 | 64 | 65 | -------------------------------------------------------------------------------- /docs/idasimulator/images/activate_handler.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nihilus/IDASimulator/06e385e79fd57b860e0d7ee8089a769a2f03e829/docs/idasimulator/images/activate_handler.png -------------------------------------------------------------------------------- /docs/idasimulator/images/activate_plugin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nihilus/IDASimulator/06e385e79fd57b860e0d7ee8089a769a2f03e829/docs/idasimulator/images/activate_plugin.png -------------------------------------------------------------------------------- /docs/idasimulator/images/idasimulator_command_line.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nihilus/IDASimulator/06e385e79fd57b860e0d7ee8089a769a2f03e829/docs/idasimulator/images/idasimulator_command_line.png -------------------------------------------------------------------------------- /docs/idasimulator/images/membase.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nihilus/IDASimulator/06e385e79fd57b860e0d7ee8089a769a2f03e829/docs/idasimulator/images/membase.png -------------------------------------------------------------------------------- /docs/idasimulator/images/new_handler.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nihilus/IDASimulator/06e385e79fd57b860e0d7ee8089a769a2f03e829/docs/idasimulator/images/new_handler.png -------------------------------------------------------------------------------- /docs/idasimulator/images/python_expression.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nihilus/IDASimulator/06e385e79fd57b860e0d7ee8089a769a2f03e829/docs/idasimulator/images/python_expression.png -------------------------------------------------------------------------------- /docs/idasimulator/images/register_config_defaults.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nihilus/IDASimulator/06e385e79fd57b860e0d7ee8089a769a2f03e829/docs/idasimulator/images/register_config_defaults.png -------------------------------------------------------------------------------- /docs/idasimulator/images/register_config_expression.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nihilus/IDASimulator/06e385e79fd57b860e0d7ee8089a769a2f03e829/docs/idasimulator/images/register_config_expression.png -------------------------------------------------------------------------------- /docs/idasimulator/images/register_config_main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nihilus/IDASimulator/06e385e79fd57b860e0d7ee8089a769a2f03e829/docs/idasimulator/images/register_config_main.png -------------------------------------------------------------------------------- /docs/idasimulator/images/registers_configured.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nihilus/IDASimulator/06e385e79fd57b860e0d7ee8089a769a2f03e829/docs/idasimulator/images/registers_configured.png -------------------------------------------------------------------------------- /docs/idasimulator/images/right_click_menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nihilus/IDASimulator/06e385e79fd57b860e0d7ee8089a769a2f03e829/docs/idasimulator/images/right_click_menu.png -------------------------------------------------------------------------------- /docs/idasimulator/images/start_idasimulator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nihilus/IDASimulator/06e385e79fd57b860e0d7ee8089a769a2f03e829/docs/idasimulator/images/start_idasimulator.png -------------------------------------------------------------------------------- /docs/idasimulator/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | IDASimulator User Guide 4 | 5 | 6 | 7 | 8 | 9 |

Introduction

10 |
11 |

12 | IDASimulator is a plugin that allows IDA users to easily augment / replace executable code inside a debugged process with Python code. 13 |

14 | 15 |

16 | Specifically, IDASimulator makes use of conditional breakpoints in the IDA debugger to hijack the execution flow of a process 17 | and invoke Python handler functions whenever particular code blocks are executed. With support for multiple target architectures, 18 | it handles details such as register initialization, memory allocation, pointers, function arguments and return values seamlessly 19 | and transparently, making it easy to replace, modify and subvert existing functionality (or lack thereof) in the target process. 20 |

21 | 22 |

23 | IDASimulator currently supports the x86, x86_64, ARM and MIPS32 architectures. Porting to other architectures is very easy. 24 |

25 |
26 |
27 | 28 |

Installation

29 |
30 |

31 | The IDASimulator plugin and its associated IDASim python module can be installed with the install.py script: 32 | 33 |

34 |
 35 | 	$ python install.py /path/to/ida/install/directory
 36 | 			
37 |
38 |

39 |
40 |
41 | 42 |

Using IDASimulator

43 |
44 |

45 | The IDASimulator plugin can be activated through the plugins menu, or by pressing the Alt+0 hotkey: 46 |

47 | 48 |

49 | 50 | 51 |

52 | The IDASimulator window displays all of the named locations in the current IDB that it has handlers for: 53 |

54 | 55 |

56 | 57 |

58 | 59 |

60 | Double-clicking on an entry will enable/disable the selected handler: 61 |

62 | 63 |

64 | 65 |

66 | 67 |

68 | The right-click menu in the IDASimulator window provides a variety of additional options: 69 |

70 | 71 |

72 | 73 |

74 | 75 |

76 | The Enable/disable selected handlers option is synonymous with double-clicking on a handler, as described above. 77 |

78 | 79 |

80 | The Enable all handlers and Disable all handlers options will respecitvely enable or disable all displayed handlers. 81 |

82 | 83 |

84 | The Reset to defaults option will remove all changes you have made and disable all handlers. 85 |

86 | 87 |

88 | The Show / hide unsupported imports option displays or hides all imports that do not have associated IDASimulator handlers. 89 |

90 | 91 |

92 | The Jump to selected name option will take you to the location of the selected name in IDA's disassembly window. 93 |

94 | 95 |

96 | The Save settings option saves all of the current IDASimulator settings for that IDB. This is also done automatically whenever the IDASimulator 97 | window is closed. 98 |

99 | 100 |

101 | The Quit option will save the current settings and disable the IDASimulator plugin (just closing the IDASimulator window does not disable the plugin). 102 |

103 | 104 |

The Set MMU base address option allows you to manually control the start address of where IDASimulator handler data (such as strings) 105 | is allocated in memory. This is normally auto-detected, but in some cases may need to be explicitly controlled: 106 |

107 | 108 |

109 | 110 |

111 | 112 |

113 | The Set initialization values option allows you to define the initialization values of registers and define python statements, 114 | as well as control when those initializations are exercised. By default, they will be invoked when the debugger is started/attached: 115 |

116 | 117 |

118 | 119 |

120 | 121 |

122 | When process execution reaches the specified named location, the specified settings will be applied: 123 |

124 | 125 |

126 | 127 |

128 | 129 |

130 | Note that if the location named in the Initialization Configuration conflicts with a handler name, the handler for that 131 | named location will be removed and replaced with the Initialization Configuration handler. 132 |

133 | 134 |

135 | Up to ten registers may be specified. Their values may be assigned an integer value (decimal or hexadecimal), a quoted string value (the string will 136 | be allocated in memory and the register will be populated with the address of the string), or as the result of an 137 | IDASim or Achitecture method call: 138 |

139 | 140 |

141 | 142 |

143 | 144 |

145 | Valid Python expressions may also be included in the register assignment: 146 |

147 | 148 |

149 | 150 |

151 | 152 |

153 | And if no register is specified, arbitrary Python statements may be entered into the value field: 154 |

155 | 156 |

157 | 158 |

159 | 160 |
161 |
162 | 163 |

Command Line

164 |
165 |

166 | When the IDASimulator plugin is activated, it creates a global instance of the IDASim class named 'IDASIM'. 167 | This global class instance is available for use in any IDAPython statement, and can be invoked manually from the command line during a debugging session: 168 |

169 | 170 |

171 | 172 |

173 | 174 |

175 | IDAPython scripts invoked while the IDASimulator plugin is active should use the IDASIM class instance rather than creating a new class instance. 176 | Multiple class instances in use at the same time can result in corruption of data allocated by IDASim, due to multiple active instances of 177 | IDASimMMU. 178 | If multiple instances of IDASim are necessary, care should be taken to ensure that the base addresses used for memory allocation by the various 179 | instances of IDASimMMU are sufficiently different. 180 |

181 | 182 |
183 |
184 | 185 |

Writing IDASimulator Handlers

186 |
187 |

188 | Although IDASimulator comes with a set of handlers for common library and API functions, its real power is the ability to easily 189 | craft custom handlers for your target process. 190 |

191 | 192 |

193 | IDASimulator handler files are installed to the plugins/idasimlib subdirectory of the IDA install path. Each handler file must have a .py 194 | file extension, and must contain at least one uniquely named class. The constructor for each class must accept one argument, which will be 195 | an instance of the IDASim class: 196 | 197 |

198 |
199 | 	class MyHandlerClass:
200 | 
201 | 		def __init__(self, idasim):
202 | 			self.idasim = idasim
203 | 			
204 |
205 |

206 | 207 |

208 | Every member of the class that does not start with an underscore will be loaded as a handler function: 209 | 210 |

211 |
212 | 	class MyHandlerClass:
213 | 
214 | 		def __init__(self, idasim):
215 | 			self.idasim = idasim
216 | 
217 | 		# Handler for the function named 'strcpy'
218 | 		# Writes a NULL-terminated string from src to the dst address.
219 | 		def strcpy(self, dst, srcstr=''):
220 | 			'''
221 | 			A test to simulate strcpy functionality.
222 | 			'''
223 | 			idc.DbgWrite(dst, srcstr + "\x00")
224 | 			return dst
225 | 
226 | 		# Handler for the function named 'strdup'
227 | 		# Returns a NULL-terminated string, which will be automatically allocated in memory
228 | 		def strdup(self, src=''):
229 | 			return src + "\x00"
230 | 			
231 |
232 |

233 | 234 |

235 | If a handler accepts arguments, the appropriate arguments will be passed to the handler, just as they were passed to the 236 | original function. For arguments that accept pointers to NULL-terminated strings, the default value may be defined as a string type. 237 | In this case, instead of being passed the raw argument value (the pointer to the string), the actual string value will be supplied. 238 |

239 | 240 |

241 | Handlers can also access function arguments via the Architecture.Arguments method. 242 | An instance of the Architecture class is available through the cpu member of the IDASim class instance. 243 |

244 | 245 |

246 | A handler can return an integer, string, None, or raise a JumpTo or GoTo exception. 247 | See the IDASim documentation for more details. 248 |

249 | 250 |

251 | With the handlers written and the file placed in the idasimlib directory, refresh the IDASimulator window to load the new handlers (Right Click -> Refresh, or Ctl+U): 252 |

253 | 254 |

255 | 256 |

257 |
258 |
259 | 260 | 261 | 262 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | IDASimulator 4 | 5 | 6 | 7 | 8 | 9 |

What?

10 |
11 |

12 | IDASimulator is a plugin that extends IDA's conditional breakpoint support, making it easy to augment / replace complex executable code inside a debugged process with Python code. 13 |

14 | 15 |

16 | Specifically, IDASimulator makes use of conditional breakpoints in the IDA debugger to hijack the execution flow of a process 17 | and invoke Python handler functions whenever particular code blocks are executed. With support for multiple target architectures, 18 | it handles details such as register initialization, memory allocation, pointers, function arguments and return values seamlessly 19 | and transparently, making it easy to replace, modify and subvert existing functionality (or lack thereof) in the target process. 20 |

21 | 22 |

23 | IDASimulator also includes the IDASim python module, on which IDASimulator is based. This allows for all of the features of IDASimulator 24 | to be integrated into more complex IDAPython scripts. 25 |

26 | 27 |

28 | IDASimulator currently supports the x86, x86_64, ARM and MIPS32 architectures. Porting to other architectures is very easy. 29 |

30 |
31 |
32 | 33 |

Why?

34 |
35 |

36 | The ability to dynamically intercept, replace or modify process logic is useful in a variety of situations, such as: 37 | 38 |

45 |

46 |
47 |
48 | 49 |

How?

50 |
51 |

52 | For detailed usage of the IDASimulator plugin and the IDASim python module, see: 53 | 54 |

59 |

60 |
61 |
62 | 63 |

License

64 |
65 |

66 | This software is distributed Copyright (c) 2012, Craig Heffner, Tactical Network Solutions, under the MIT license. 67 |

68 | 69 |
70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /install.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | import sys 5 | import shutil 6 | 7 | python_dir = 'idasim' 8 | plugin_file = 'idasimulator.py' 9 | plugin_dir = 'idasimlib' 10 | 11 | try: 12 | ida_directory = sys.argv[1] 13 | except: 14 | print "Full path to your IDA install directory: ", 15 | ida_directory = sys.stdin.readline().strip() 16 | print "" 17 | 18 | python_dir_path = os.path.join(ida_directory, 'python', python_dir) 19 | plugin_file_path = os.path.join(ida_directory, 'plugins', plugin_file) 20 | plugin_dir_path = os.path.join(ida_directory, 'plugins', plugin_dir) 21 | plugin_src_dir_path = os.path.join('src', 'plugins', plugin_dir) 22 | 23 | if not os.path.exists(plugin_dir_path): 24 | os.mkdir(plugin_dir_path) 25 | 26 | if os.path.exists(python_dir_path): 27 | shutil.rmtree(python_dir_path) 28 | 29 | shutil.copytree(os.path.join('src', 'python', python_dir), python_dir_path) 30 | shutil.copyfile(os.path.join('src', 'plugins', plugin_file), plugin_file_path) 31 | 32 | for file_name in os.listdir(plugin_src_dir_path): 33 | src = os.path.join(plugin_src_dir_path, file_name) 34 | dst = os.path.join(plugin_dir_path, file_name) 35 | if not os.path.isdir(src): 36 | shutil.copyfile(src, dst) 37 | 38 | print "IDASimulator installed to %s" % ida_directory 39 | 40 | -------------------------------------------------------------------------------- /src/plugins/idasimlib/__init__.py: -------------------------------------------------------------------------------- 1 | import os as _os 2 | 3 | # Load all modules 4 | for _module in _os.listdir(_os.path.dirname(__file__)): 5 | if _module != '__init__.py' and _module[-3:] == '.py': 6 | __import__(_module[:-3], locals(), globals()) 7 | 8 | del _os 9 | del _module 10 | -------------------------------------------------------------------------------- /src/plugins/idasimlib/fuzz.py: -------------------------------------------------------------------------------- 1 | import idc 2 | 3 | class _FuzzHelper: 4 | 5 | def __init__(self, idasim): 6 | self.idasim = idasim 7 | 8 | def sanitize(self, data): 9 | try: 10 | return data.replace('"', '\\"') 11 | except: 12 | return data 13 | 14 | def display(self, message): 15 | print "%-25s %s" % (idc.GetFuncOffset(self.idasim.cpu.ReturnAddress()), message) 16 | 17 | class Fuzz: 18 | 19 | def __init__(self, idasim): 20 | self.helper = _FuzzHelper(idasim) 21 | self.idasim = idasim 22 | 23 | def strcpy(self, dst, src=''): 24 | self.helper.display('strcpy(0x%X, "%s")' % (dst, self.helper.sanitize(src))) 25 | return None 26 | 27 | def strcat(self, dst='', src=''): 28 | self.helper.display('strcat("%s", "%s")' % (self.helper.sanitize(dst), self.helper.sanitize(src))) 29 | return None 30 | 31 | def sprintf(self, dst, fmt=''): 32 | string = self.idasim.vsprintf(fmt, 2) 33 | self.helper.display('sprintf(0x%X, "%s")' % (dst, self.helper.sanitize(string))) 34 | return None 35 | 36 | def system(self, cmd=''): 37 | self.helper.display('system("%s")' % self.helper.sanitize(cmd)) 38 | return None 39 | 40 | def popen(self, cmd='', attrib=''): 41 | self.helper.display('popen("%s", "%s")' % (self.helper.sanitize(cmd), self.helper.sanitize(attrib))) 42 | return None 43 | 44 | def strncpy(self, dst, src='', n=0): 45 | if len(src) >= n: 46 | self.helper.display('strncpy(0x%X, "%s", %d)' % (dst, self.helper.sanitize(src), n)) 47 | return None 48 | 49 | def snprintf(self, dst, size, fmt=''): 50 | string = self.idasim.vsprintf(fmt, 3) 51 | if len(string) >= size: 52 | self.helper.display('snprintf(0x%X, %d, "%s")' % (dst, size, self.helper.sanitize(string))) 53 | return None 54 | 55 | def printf(self, fmt=''): 56 | if '%' not in fmt: 57 | self.helper.display('printf("%s")' % self.helper.sanitize(fmt)) 58 | return None 59 | 60 | def fprintf(self, fd, fmt=''): 61 | if '%' not in fmt: 62 | self.helper.display('fprintf(%d, "%s")' % (fd, self.helper.sanitize(fmt))) 63 | return None 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /src/plugins/idasimlib/libc.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | import idc 4 | import idaapi 5 | import idautils 6 | 7 | class LibC: 8 | ''' 9 | Class containing simulators for various common libc functions. 10 | ''' 11 | 12 | __IDASIM_DEFAULT_HANDLER_CLASS__ = True 13 | 14 | def __init__(self, idasim=None): 15 | ''' 16 | Class constructor. 17 | ''' 18 | self.idasim = idasim 19 | 20 | def sleep(self, t): 21 | time.sleep(t) 22 | return 0 23 | 24 | def atoi(self, string=''): 25 | return int(string) 26 | 27 | def atol(self, string=''): 28 | return self.atoi(string) 29 | 30 | def atoll(self, string=''): 31 | return self.atoi(string) 32 | 33 | def atoq(self, string=''): 34 | return self.atoi(string) 35 | 36 | def strlen(self, string=''): 37 | return len(string) 38 | 39 | def getenv(self, envar=''): 40 | return os.getenv(envar) + "\x00" 41 | 42 | def malloc(self, n): 43 | return "\x00" * n 44 | 45 | def memset(self, buf, c, n): 46 | idc.DbgWrite(buf, (chr(c) * n)) 47 | return buf 48 | 49 | def memcpy(self, dst, src, n): 50 | idc.DbgWrite(dst, idc.GetManyBytes(src, n, use_dbg=False)) 51 | return dst 52 | 53 | def strcpy(self, dst, src=''): 54 | ''' 55 | Monitors, reports and simulates the strcpy function. 56 | ''' 57 | print 'strcpy(0x%X, "%s")' % (dst, src) 58 | idc.DbgWrite(dst, src + "\x00") 59 | return dst 60 | 61 | def strcat(self, dst, src=''): 62 | ''' 63 | Monitors, reports and simulates the strcat function. 64 | ''' 65 | print 'strcat(0x%X, "%s")' % (dst, src) 66 | addr = dst + len(idc.GetString(dst)) 67 | idc.DbgWrite(addr, src + "\x00") 68 | return dst 69 | 70 | def strncpy(self, dst, src='', n=0): 71 | idc.DbgWrite(dst, src + "\x00", length=n) 72 | return dst 73 | 74 | def strncat(self, dst, src='', n=0): 75 | addr = dst + len(idc.GetString(dst)) 76 | idc.DbgWrite(addr, src + "\x00", length=n) 77 | return dst 78 | 79 | def strdup(self, string=''): 80 | return string + "\x00" 81 | 82 | def strcmp(self, s1='', s2=''): 83 | if s1 == s2: 84 | return 0 85 | else: 86 | return 1 87 | 88 | def strncmp(self, s1='', s2='', n=0): 89 | if s1[:n] == s2[:n]: 90 | return 0 91 | else: 92 | return 1 93 | 94 | def memcmp(self, dp1, dp2, n): 95 | d1 = idc.DbgRead(dp1, n) 96 | d2 = idc.DbgRead(dp2, n) 97 | 98 | if d1 == d2: 99 | return 0 100 | else: 101 | return 1 102 | 103 | def memchr(self, dp, c, n): 104 | c = chr(c) 105 | data = idc.DbgRead(dp, n) 106 | 107 | offset = data.find(c) 108 | 109 | if offset == -1: 110 | return 0 111 | else: 112 | return dp + offset 113 | 114 | def system(self, command=''): 115 | ''' 116 | Displays the system() command, does not execute. 117 | ''' 118 | print '0x%X : system("%s");' % (self.idasim.cpu.ReturnAddress(), command) 119 | return 0 120 | 121 | def strstr(self, hayptr, needle=''): 122 | haystack = idc.GetString(hayptr) 123 | offset = haystack.find(needle) 124 | 125 | if offset == -1: 126 | return 0 127 | else: 128 | return hayptr + offset 129 | 130 | def strchr(self, hayptr, needle): 131 | haystack = idc.GetString(hayptr) 132 | needle = chr(needle) 133 | offset = haystack.find(needle) 134 | 135 | if offset == -1: 136 | return 0 137 | else: 138 | return hayptr + offset 139 | 140 | def daemon(self): 141 | ''' 142 | Fakes a daemon(), returns 0. 143 | ''' 144 | return 0 145 | 146 | def fork(self): 147 | ''' 148 | Fakes a fork(), always returns 0. 149 | ''' 150 | return 0 151 | 152 | def free(self, address): 153 | ''' 154 | Frees heap data not allocated by IDASimulator. 155 | ''' 156 | if self.idasim.mmu.allocated_addresses.has_key(address): 157 | return 0 158 | else: 159 | return None 160 | 161 | def strtol(self, string='', base=0): 162 | return int(string, base) 163 | 164 | def strtoul(self, string='', base=0): 165 | return self.strtol(string, base) 166 | 167 | def strtod(self, string='', base=0): 168 | return self.strtod(string, base) 169 | 170 | def strcasecmp(self, s1='', s2=''): 171 | if s1.lower() == s2.lower(): 172 | return 0 173 | else: 174 | return 1 175 | 176 | def strncasecmp(self, s1='', s2='', n=0): 177 | if s1[:n].lower() == s2[:n].lower(): 178 | return 0 179 | else: 180 | return 1 181 | 182 | def exit(self, code): 183 | ''' 184 | Prints exit code and stops debugger. 185 | ''' 186 | print "Exit code:", code 187 | idc.StopDebugger() 188 | 189 | def setgroups(self): 190 | ''' 191 | Fakes setgroups(), returns 0. 192 | ''' 193 | return 0 194 | 195 | -------------------------------------------------------------------------------- /src/plugins/idasimlib/libcsman.py: -------------------------------------------------------------------------------- 1 | import idc 2 | 3 | class LibCSMAN: 4 | 5 | _CONFIG = "/tmp/nvram.cfg" 6 | 7 | __IDASIM_DEFAULT_HANDLER_CLASS__ = True 8 | 9 | def __init__(self, idasim): 10 | self.idasim = idasim 11 | 12 | self.config = {} 13 | 14 | try: 15 | for line in open(self._CONFIG_FILE).readlines(): 16 | if '=' in line: 17 | kv = line.strip().split('=') 18 | name = kv[0] 19 | key = int(kv[1], 16) 20 | if len(kv) == 3: 21 | value = kv[2].decode('string_escape') 22 | else: 23 | value = "\x00" 24 | 25 | if not self.config.has_key(key): 26 | self.config[key] = { 27 | 'name' : name, 28 | 'value' : value 29 | } 30 | except: 31 | pass 32 | 33 | def open_csman(self): 34 | return 128 35 | 36 | def close_csman(self): 37 | return 0 38 | 39 | def write_csman(self, fd, key, buf, size, default): 40 | return 0 41 | 42 | def read_csman(self, fd, key, value, size, default): 43 | if self.config.has_key(key): 44 | print "read_csman(%s)" % self.config[key]['name'] 45 | idc.DbgWrite(value, self.config[key]['value']) 46 | else: 47 | print "UNKNOWN CSID: 0x%.8X called from 0x%.8X" % (key, self.idasim.cpu.ReturnAddress()) 48 | 49 | return 0 50 | 51 | -------------------------------------------------------------------------------- /src/plugins/idasimlib/libnvram.py: -------------------------------------------------------------------------------- 1 | import idc 2 | 3 | class LibNVRAM: 4 | 5 | _CONFIG_FILE = "/tmp/nvram.cfg" 6 | 7 | __IDASIM_DEFAULT_HANDLER_CLASS__ = True 8 | 9 | def __init__(self, idasim): 10 | self.idasim = idasim 11 | 12 | self.config = {} 13 | 14 | try: 15 | for line in open(self._CONFIG_FILE).readlines(): 16 | if '=' in line: 17 | kv = line.strip().split('=') 18 | if not self.config.has_key(kv[0]): 19 | self.config[kv[0]] = kv[1] 20 | except: 21 | pass 22 | 23 | def nvram_init(self): 24 | return 0 25 | 26 | def nvram_get(self, zero, key=''): 27 | return self.nvram_bufget(zero, key) 28 | 29 | def nvram_bufget(self, zero, key=''): 30 | try: 31 | value = self.config[key] 32 | except: 33 | value = '' 34 | 35 | print "nvram_get: {'%s' : '%s'}" % (key, value) 36 | return value + "\x00" 37 | 38 | def nvram_bufset(self, zero, key='', value=''): 39 | self.config[key] = value 40 | print "nvram_set: {'%s' : '%s'}" % (key, value) 41 | return 0 42 | 43 | def nvram_get_ex(self, key='', dst=0, size=0): 44 | idc.DbgWrite(dst, self.nvram_bufget(0, key)[:size]) 45 | return 0 46 | 47 | def nvram_match(self, key='', match=''): 48 | if self.nvram_bufget(0, key)[:-1] == match: 49 | return 1 50 | return 0 51 | 52 | def nvram_invmatch(self, key='', match=''): 53 | if self.nvram_match(key, match): 54 | return 0 55 | return 1 56 | 57 | -------------------------------------------------------------------------------- /src/plugins/idasimlib/pthread.py: -------------------------------------------------------------------------------- 1 | 2 | class PThread: 3 | 4 | __IDASIM_DEFAULT_HANDLER_CLASS__ = True 5 | 6 | def __init__(self, idasim): 7 | self.idasim = idasim 8 | 9 | def pthread_create(self, thread, attr, start_routine, arg): 10 | ''' 11 | Calls the start_routine, does not create a thread. 12 | ''' 13 | self.idasim.app.Call(start_routine, arguments=[arg], block_until_return=False) 14 | return None 15 | -------------------------------------------------------------------------------- /src/plugins/idasimlib/stdio.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import idc 4 | 5 | class Stdio: 6 | ''' 7 | Class containing simulators for common stdio functions. 8 | ''' 9 | 10 | __IDASIM_DEFAULT_HANDLER_CLASS__ = True 11 | 12 | def __init__(self, sim=None): 13 | ''' 14 | Class constructor. 15 | ''' 16 | if sim is not None: 17 | self.mmu = sim.mmu 18 | self.cpu = sim.cpu 19 | self.sim = sim 20 | 21 | self.file_descriptors = { 22 | 0 : sys.stdin, 23 | 1 : sys.stdout, 24 | 2 : sys.stderr, 25 | } 26 | 27 | def _next_fd(self): 28 | i = 0 29 | while self.file_descriptors.has_key(i): 30 | i += 1 31 | return i 32 | 33 | def _add_fd(self, fp): 34 | fd = self._next_fd() 35 | self.file_descriptors[fd] = fp 36 | return fd 37 | 38 | def _close(self, fd): 39 | if self.file_descriptors.has_key(fd): 40 | self.file_descriptors[fd].close() 41 | del self.file_descriptors[fd] 42 | 43 | def _open(self, fname, mode="rwb"): 44 | try: 45 | fp = open(fname, mode) 46 | fd = self._add_fd(fp) 47 | except: 48 | fd = -1 49 | 50 | return fd 51 | 52 | def _read(self, fd, size): 53 | data = "" 54 | if self.file_descriptors.has_key(fd): 55 | data = self.file_descriptors[fd].read(size) 56 | return data 57 | 58 | def _write(self, fd, data): 59 | if self.file_descriptors.has_key(fd): 60 | self.file_descriptors[fd].write(data) 61 | return len(data) 62 | 63 | def puts(self, string=''): 64 | print string 65 | return 0 66 | 67 | def printf(self, fmt=''): 68 | print self.sim.vsprintf(fmt, 1), 69 | return 0 70 | 71 | def syslog(self, i, fmt=''): 72 | print self.sim.vsprintf(fmt, 2) 73 | return 0 74 | 75 | def fprintf(self, fd, fmt=''): 76 | formatted_string = self.sim.vsprintf(fmt, 2) 77 | 78 | if self.file_descriptors.has_key(fd) and fd != 0: 79 | self._write(fd, formatted_string) 80 | else: 81 | print formatted_string, 82 | return 0 83 | 84 | def sprintf(self, dst, fmt=''): 85 | ''' 86 | Monitors, reports and simulates sprintf. 87 | ''' 88 | data = self.sim.vsprintf(ftm, 2) 89 | print 'sprintf(0x%X, "%s")' % (dst, data) 90 | idc.DbgWrite(dst, data + "\x00") 91 | return len(data) 92 | 93 | def snprintf(self, dst, n, fmt=''): 94 | idc.DbgWrite(dst, self.sim.vsprintf(fmt, 3)[:n] + "\x00") 95 | return dst 96 | 97 | def popen(self, command='', mode=''): 98 | ''' 99 | Displays the popen() command, does not execute. 100 | ''' 101 | print '0x%X : popen("%s", "%s");' % (self.cpu.ReturnAddress(), command, mode) 102 | #fp = os.popen(command, mode) 103 | #return self._add_fd(fp) 104 | return 0 105 | 106 | def pclose(self, fd): 107 | self._close(fd) 108 | return 0 109 | 110 | def fopen(self, fname='', modes=''): 111 | fd = self._open(fname, modes) 112 | if fd > -1: 113 | return fd 114 | else: 115 | return 0 116 | 117 | def fclose(self, fd): 118 | self._close(fd) 119 | return 0 120 | 121 | def fread(self, ptr, size, nmemb, fd): 122 | data = self._read(fd, (size * nmemb)) 123 | idc.DbgWrite(ptr, data) 124 | return len(data) 125 | 126 | def fwrite(self, ptr, size, nmemb, fd): 127 | data = idc.DbgRead(ptr, (size * nmemb)) 128 | self._write(fd, data) 129 | return len(data) 130 | 131 | def fflush(self, fd): 132 | if self.file_descriptors.has_key(fd): 133 | self.file_descriptors[fd].flush() 134 | return 0 135 | 136 | def fgets(self, dst, size, fd): 137 | if self.file_descriptors.has_key(fd): 138 | while not data.endswith('\n') and len(data) < (size-1): 139 | data += self._read(fd, 1) 140 | 141 | data += "\x00" 142 | idc.DbgWrite(dst, data, len(data)) 143 | 144 | return dst 145 | 146 | def fseek(self, fd, offset, whence): 147 | if self.file_descriptors.has_key(fd): 148 | self.file_descriptors[fd].seek(offset, whence) 149 | return self.file_descriptors[fd].tell() 150 | return -1 151 | 152 | def rewind(self, fd): 153 | if self.file_descriptors.has_key(fd): 154 | self.file_descriptors[fd].seek(0, 0) 155 | return 0 156 | 157 | def ftell(self, fd): 158 | if self.file_descriptors.has_key(fd): 159 | return self.file_descriptors[fd].tell() 160 | return -1 161 | 162 | 163 | -------------------------------------------------------------------------------- /src/plugins/idasimulator.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pickle 3 | import inspect 4 | import idaapi 5 | import idautils 6 | import idc 7 | import idasim 8 | 9 | IDASIM = None 10 | 11 | class IDASimConfiguration: 12 | ''' 13 | Responsible for loading, saving and cleaning the idasimulator configuration file. 14 | Configuration data is a dictionary stored in pickle format: 15 | 16 | cfg = { 17 | '/path/to/database.idb' : { 18 | 'handlers' : [enabled handlers], 19 | 'startup' : 'startup script', 20 | 'startname' : named location to set breakpoint for setting startup values, 21 | 'membase' : membase value 22 | } 23 | } 24 | ''' 25 | CONFIG_FILE = 'idasimulator.cfg' 26 | 27 | def __init__(self, sim): 28 | ''' 29 | Class constructor. 30 | 31 | @sim - idasimulator_t class instance. 32 | 33 | Returns None. 34 | ''' 35 | self.cfg = None 36 | self.sim = sim 37 | self.idb = idc.GetIdbPath() 38 | self.confile = os.path.join(idaapi.get_user_idadir(), self.CONFIG_FILE) 39 | 40 | def _load_config_file(self): 41 | ''' 42 | Loads the entire configuration file, cleaning out stale config entries in the process. 43 | 44 | Returns the entire configuration dictionary. 45 | ''' 46 | cfg = {} 47 | stale = [] 48 | 49 | try: 50 | cfg = pickle.load(open(self.confile, "rb")) 51 | 52 | # The IDB path is used as the configuration key. If a IDB no longer exists, its config data is useless. 53 | # Mark any IDB's that no longer exist. 54 | for (idb, config) in cfg.iteritems(): 55 | if not os.path.exists(idb): 56 | stale.append(idb) 57 | 58 | # Delete any stale config entries and save the data back to the config file. 59 | if len(stale) > 0: 60 | for idb in stale: 61 | del cfg[idb] 62 | self._save_config_file(cfg) 63 | except Exception, e: 64 | pass 65 | 66 | return cfg 67 | 68 | def _save_config_file(self, fdata): 69 | ''' 70 | Saves data to the config file, in pickle format. 71 | 72 | @fdata - Configuration file dictionary. 73 | 74 | Returns None. 75 | ''' 76 | try: 77 | pickle.dump(fdata, open(self.confile, "wb")) 78 | except Exception, e: 79 | print "Failed to save %s: %s" % (self.confile, str(e)) 80 | 81 | def _load_config_data(self, idb_path): 82 | ''' 83 | Loads configuration data for this IDB from the config file. 84 | 85 | Returns this IDB's configuration data. 86 | ''' 87 | data = {} 88 | 89 | if os.path.exists(self.confile): 90 | cfgdata = self._load_config_file() 91 | if cfgdata.has_key(idb_path): 92 | data = cfgdata[idb_path] 93 | return data 94 | 95 | def _populate_config_data(self, data): 96 | ''' 97 | Populates the current running configuration from data. 98 | 99 | @data - Configuration dictionary. 100 | 101 | Returns None. 102 | ''' 103 | for name in data['handlers']: 104 | self.sim.EnableHandler(name) 105 | 106 | self.sim.SetInitValues(data['startname'], data['startup']) 107 | self.sim.idasim.mmu.base(data['membase']) 108 | 109 | def _save_config_data(self, idb_path, fdata): 110 | ''' 111 | Saves the current running configuration to disk. 112 | 113 | @idb_path - Path to the IDB file. 114 | @fdata - Configuration file dictionary. 115 | 116 | Returns None. 117 | ''' 118 | fdata[idb_path] = {} 119 | fdata[idb_path]['handlers'] = self.sim.EnabledHandlers() 120 | fdata[idb_path]['membase'] = self.sim.idasim.mmu.base() 121 | 122 | (start_name, start_script) = self.sim.GetInitValues() 123 | fdata[idb_path]['startname'] = start_name 124 | fdata[idb_path]['startup'] = start_script 125 | 126 | self._save_config_file(fdata) 127 | 128 | def Load(self): 129 | ''' 130 | Loads the saved configuration data into the running configuration. 131 | 132 | Returns None. 133 | ''' 134 | data = self._load_config_data(self.idb) 135 | if data: 136 | self._populate_config_data(data) 137 | 138 | def Save(self): 139 | ''' 140 | Saves the running configuration to disk. 141 | 142 | Returns None. 143 | ''' 144 | fdata = self._load_config_file() 145 | self._save_config_data(self.idb, fdata) 146 | 147 | 148 | class IDASimFunctionChooser(idaapi.Choose2): 149 | ''' 150 | Primary IDASimulator UI. 151 | ''' 152 | 153 | def __init__(self, sim): 154 | idaapi.Choose2.__init__(self, "IDA Simulator", [ 155 | ["Handler", 20 | Choose2.CHCOL_PLAIN], 156 | ["Name", 15 | Choose2.CHCOL_PLAIN], 157 | ["Description", 30 | Choose2.CHCOL_PLAIN], 158 | ["Status", 10 | Choose2.CHCOL_PLAIN], 159 | ]) 160 | self.icon = 41 161 | self.sim = sim 162 | self.save_cmd = None 163 | self.quit_cmd = None 164 | self.goto_cmd = None 165 | self.reset_cmd = None 166 | self.mbase_cmd = None 167 | self.toggle_cmd = None 168 | self.config_cmd = None 169 | self.enable_all_cmd = None 170 | self.disable_all_cmd = None 171 | 172 | self.PopulateItems() 173 | 174 | def PopulateItems(self): 175 | ''' 176 | Populates the chooser window with named locations that have registered handlers. 177 | ''' 178 | self.items = [] 179 | 180 | for (name, info) in self.sim.functions.iteritems(): 181 | addr = idc.LocByName(info['function']) 182 | 183 | if addr != idc.BADADDR: 184 | if self.sim.IsSimulated(name): 185 | status = "Enabled" 186 | else: 187 | status = "Disabled" 188 | 189 | self.items.append([name, info['function'], self.sim.GetHandlerDesc(name), status, addr]) 190 | 191 | def OnSelectLine(self, n): 192 | ''' 193 | Invoked when the user double-clicks on a selection in the chooser. 194 | ''' 195 | self.sim.ToggleHandler(self.items[n][0]) 196 | # Not sure why, but the displayed items aren't refreshed if PopulateItems isn't called here. 197 | # Interestingly, this is NOT required when OnSelectLine is invoked via the OnCommand method. 198 | self.PopulateItems() 199 | self.Refresh() 200 | 201 | def OnGetLine(self, n): 202 | return self.items[n] 203 | 204 | def OnGetSize(self): 205 | return len(self.items) 206 | 207 | def OnDeleteLine(self, n): 208 | ''' 209 | Invoked when a user deletes a selection from the chooser. 210 | ''' 211 | return n 212 | 213 | def OnRefresh(self, n): 214 | ''' 215 | Refreshes the display. 216 | ''' 217 | self.sim.Refresh() 218 | self.PopulateItems() 219 | return n 220 | 221 | def OnCommand(self, n, cmd_id): 222 | ''' 223 | Handles custom right-click commands. 224 | ''' 225 | if self.sim.idasim is not None: 226 | if cmd_id == self.reset_cmd: 227 | self.reset() 228 | elif cmd_id == self.goto_cmd: 229 | idc.Jump(self.items[n][-1]) 230 | elif cmd_id == self.toggle_cmd: 231 | self.OnSelectLine(n) 232 | elif cmd_id == self.enable_all_cmd: 233 | self.enable_all() 234 | elif cmd_id == self.disable_all_cmd: 235 | self.disable_all() 236 | elif cmd_id == self.mbase_cmd: 237 | self.set_mbase() 238 | elif cmd_id == self.config_cmd: 239 | self.configure_form() 240 | elif cmd_id == self.save_cmd: 241 | self.sim.config.Save() 242 | elif cmd_id == self.quit_cmd: 243 | self.quit_idasim() 244 | return 1 245 | 246 | def OnClose(self): 247 | ''' 248 | Save the current settings when the chooser window is closed. 249 | ''' 250 | if self.sim.idasim is not None: 251 | self.sim.config.Save() 252 | return None 253 | 254 | def quit_idasim(self): 255 | ''' 256 | Quits IDASimulator, disabling everything. 257 | ''' 258 | self.sim.config.Save() 259 | self.sim.Cleanup(closegui=False) 260 | 261 | def set_mbase(self): 262 | ''' 263 | Sets the memory base address for the IDASimMMU instance. 264 | ''' 265 | mbase = AskAddr(self.sim.idasim.mmu.base(), "Configure base memory allocation address") 266 | if mbase != idc.BADADDR: 267 | if mbase == 0: 268 | mbase = idc.BADADDR 269 | self.sim.idasim.mmu.base(mbase) 270 | 271 | def configure_form(self): 272 | ''' 273 | Displays the configuration form for setting up startup register values. 274 | ''' 275 | script_file = AskFile(0, '*.py', 'Select a script to run on process init/attach.') 276 | if script_file: 277 | self.sim.SetInitValues(None, open(script_file, 'rb').read()) 278 | 279 | def enable_all(self): 280 | ''' 281 | Enables all handlers. 282 | ''' 283 | for i in range(0, len(self.items)): 284 | self.sim.EnableHandler(self.items[i][0]) 285 | self.Refresh() 286 | 287 | def disable_all(self): 288 | ''' 289 | Disable all handlers. 290 | ''' 291 | for i in range(0, len(self.items)): 292 | self.sim.DisableHandler(self.items[i][0]) 293 | self.Refresh() 294 | 295 | def reset(self): 296 | ''' 297 | Resets all settings to the defaults. 298 | ''' 299 | if idc.AskYN(0, "Are you sure you want to undo all changes and reset?") == 1: 300 | self.sim.Reset() 301 | self.Refresh() 302 | 303 | def show(self): 304 | ''' 305 | Displays the chooser, initializes the custom right-click options. 306 | ''' 307 | if self.Show(modal=False) < 0: 308 | return False 309 | 310 | self.toggle_cmd = self.AddCommand("Enable / disable selected handler") 311 | self.enable_all_cmd = self.AddCommand("Enable all handlers") 312 | self.disable_all_cmd = self.AddCommand("Disable all handlers") 313 | self.config_cmd = self.AddCommand("Load startup script") 314 | self.mbase_cmd = self.AddCommand("Set MMU base address") 315 | self.reset_cmd = self.AddCommand("Reset to defaults") 316 | self.goto_cmd = self.AddCommand("Jump to selected name") 317 | self.save_cmd = self.AddCommand("Save settings") 318 | self.quit_cmd = self.AddCommand("Quit") 319 | return True 320 | 321 | 322 | class idasimulator_t(idaapi.plugin_t): 323 | ''' 324 | Primary IDASimulator plugin class. 325 | ''' 326 | 327 | flags = 0 328 | comment = "IDA Simulator Plugin" 329 | help = "Simulate excutable logic in Python" 330 | wanted_name = "IDA Simulator" 331 | wanted_hotkey = "" 332 | 333 | def init(self): 334 | ''' 335 | Initialize some default values for class variables. 336 | ''' 337 | self.gui = None 338 | self.idasim = None 339 | self.config = None 340 | self.functions = {} 341 | self.startup_script = '' 342 | self.startup_name = None 343 | self.stubs = True 344 | self.menu_context = idaapi.add_menu_item("Options/", "Simulate functions and code blocks...", "Alt-0", 0, self.run, (None,)) 345 | return idaapi.PLUGIN_KEEP 346 | 347 | def term(self): 348 | ''' 349 | Cleanup IDASimulator and breakpoints when terminated. 350 | ''' 351 | self.Cleanup() 352 | idaapi.del_menu_item(self.menu_context) 353 | return None 354 | 355 | def run(self, arg): 356 | ''' 357 | Initialize IDASimulator and chooser GUI, if not already initialized. 358 | ''' 359 | global IDASIM 360 | 361 | if IDASIM is None: 362 | IDASIM = idasim.IDASim() 363 | print "%s enabled." % self.wanted_name 364 | 365 | self.idasim = IDASIM 366 | 367 | self.__parse_libraries() 368 | 369 | self.config = IDASimConfiguration(self) 370 | self.config.Load() 371 | 372 | self.gui = IDASimFunctionChooser(self) 373 | self.gui.show() 374 | 375 | def GetInitValues(self): 376 | ''' 377 | Returns the named initialization location and the array of startup Python statements. 378 | ''' 379 | return (self.startup_name, self.startup_script) 380 | 381 | def SetInitValues(self, name=None, script=''): 382 | ''' 383 | Sets the named initialization location and the array of startup Python statements. 384 | 385 | @name - Named location. 386 | @lines - Array of tuples (register name, Python statement). 387 | 388 | Returns the named initialization location and the array of startup Python statements. 389 | ''' 390 | self.idasim.ExecuteOnStart(None, None, disable=True) 391 | 392 | if not name: 393 | disable = True 394 | else: 395 | disable = False 396 | 397 | self.startup_name = name 398 | self.startup_script = script 399 | self.idasim.ExecuteOnStart(self.startup_script, self.startup_name, disable=disable) 400 | 401 | return (self.startup_name, self.startup_script) 402 | 403 | def IsSimulated(self, name): 404 | ''' 405 | Checks if a named location has an active IDASimulator handler. 406 | 407 | @name - Named location. 408 | 409 | Returns True if a handler is active, False if not. 410 | ''' 411 | if self.functions.has_key(name): 412 | return self.functions[name]['enabled'] 413 | else: 414 | return False 415 | 416 | def GetHandlerDesc(self, name): 417 | ''' 418 | Get a handler description for a given named location. 419 | 420 | @name - Handler name. 421 | 422 | Returns the handler description, if it exists. Else, returns None. 423 | ''' 424 | if name == self.startup_name: 425 | return 'Initialization handler.' 426 | else: 427 | return self.functions[name]['description'] 428 | 429 | def ToggleHandler(self, name): 430 | ''' 431 | Enables/disables the handler for the named location. 432 | 433 | @name - Named location. 434 | 435 | Returns None. 436 | ''' 437 | if self.IsSimulated(name): 438 | self.DisableHandler(name) 439 | else: 440 | self.EnableHandler(name) 441 | 442 | def EnableHandler(self, name): 443 | ''' 444 | Enables the handler for the named location. 445 | 446 | @name - Named location. 447 | 448 | Returns None. 449 | ''' 450 | existing_handler = self.idasim.FunctionHandler.GetHandler(self.functions[name]['function']) 451 | if existing_handler: 452 | self.DisableHandler(self.__get_handler_name(existing_handler.im_class.__name__, existing_handler.__name__)) 453 | 454 | self.idasim.FunctionHandler.RegisterHandler(self.functions[name]['function'], self.functions[name]['handler'], self.stubs) 455 | self.functions[name]['enabled'] = True 456 | 457 | def EnabledHandlers(self): 458 | ''' 459 | Returns a list of all named locations that have an active handler. 460 | ''' 461 | return [name for (name, info) in self.functions.iteritems() if info['enabled']] 462 | 463 | def DisableHandler(self, name): 464 | ''' 465 | Disables the handler for the named location. 466 | 467 | @name - Named location. 468 | 469 | Returns None. 470 | ''' 471 | self.idasim.FunctionHandler.UnregisterHandler(self.functions[name]['function'], self.stubs) 472 | self.functions[name]['enabled'] = False 473 | 474 | def Refresh(self): 475 | ''' 476 | Refreshes the internal list of supported handlers. 477 | ''' 478 | if self.idasim is not None: 479 | self.__parse_libraries() 480 | 481 | for name in self.EnabledHandlers(): 482 | if name != self.startup_name: 483 | self.idasim.FunctionHandler.RegisterHandler(self.functions[name]['function'], self.functions[name]['handler'], self.stubs) 484 | 485 | def Reset(self): 486 | ''' 487 | Resets all IDASimulator settings to the defaults. 488 | ''' 489 | self.SetInitValues(None, None) 490 | self.idasim.Cleanup() 491 | self.idasim.mmu.base(idc.BADADDR) 492 | self.__parse_libraries() 493 | 494 | def Cleanup(self, closegui=True): 495 | ''' 496 | Cleans up all IDASimulator changes and disables the plugin. 497 | ''' 498 | global IDASIM 499 | 500 | try: 501 | if closegui and self.gui is not None: 502 | self.gui.Close() 503 | except: 504 | pass 505 | 506 | try: 507 | if self.idasim is not None: 508 | self.idasim.Cleanup() 509 | except: 510 | pass 511 | 512 | IDASIM = None 513 | self.gui = None 514 | self.idasim = None 515 | self.functions = {} 516 | print "%s disabled." % self.wanted_name 517 | 518 | def __get_handler_name(self, class_name, method_name): 519 | ''' 520 | Builds a handler key name from the class and method names. 521 | ''' 522 | return class_name + '.' + method_name 523 | 524 | def __generate_handler_entry(self, instance, method, name=None): 525 | ''' 526 | Creates a single handler dictionary entry for the provided class instance and method. 527 | ''' 528 | if not name: 529 | name = method 530 | 531 | handler = getattr(instance, method) 532 | class_name = instance.__class__.__name__ 533 | handler_name = self.__get_handler_name(class_name, method) 534 | 535 | entry = {} 536 | entry[handler_name] = {} 537 | 538 | entry[handler_name]['class'] = class_name 539 | entry[handler_name]['handler'] = handler 540 | entry[handler_name]['function'] = name 541 | entry[handler_name]['enabled'] = False 542 | 543 | existing_handler = self.idasim.FunctionHandler.GetHandler(name) 544 | if existing_handler: 545 | if self.__get_handler_name(existing_handler.im_class.__name__, existing_handler.__name__) == handler_name: 546 | entry[handler_name]['enabled'] = True 547 | try: 548 | entry[handler_name]['description'] = handler.__doc__.strip().split('\n')[0].strip() 549 | except: 550 | entry[handler_name]['description'] = 'Simulates the ' + name + ' function.' 551 | 552 | return entry 553 | 554 | def __parse_library(self, lib): 555 | ''' 556 | Parses a loaded library for all handlers. 557 | 558 | @lib - Class instance. 559 | 560 | Returns a dictionary of handlers. 561 | ''' 562 | ignore = ['__init__', '__del__', '__enter__', '__exit__'] 563 | handlers = {} 564 | instance = lib(IDASIM) 565 | 566 | for (name, obj) in inspect.getmembers(lib, inspect.ismethod): 567 | if name not in ignore: 568 | handlers.update(self.__generate_handler_entry(instance, name)) 569 | 570 | self.functions.update(handlers) 571 | return handlers 572 | 573 | def __parse_libraries(self): 574 | ''' 575 | Loads/reloads and parses all IDASimulator handlers. 576 | ''' 577 | import idasimlib 578 | reload(idasimlib) 579 | self.functions = {} 580 | 581 | for module_name in dir(idasimlib): 582 | # Don't process modules whose file names begin with a double underscore 583 | if not module_name.startswith('__'): 584 | try: 585 | module = getattr(idasimlib, module_name) 586 | reload(module) 587 | for (class_name, class_obj) in inspect.getmembers(module, inspect.isclass): 588 | # Don't process classes whose names begin with an underscore 589 | if not class_name.startswith('_'): 590 | self.__parse_library(getattr(module, class_name)) 591 | except Exception, e: 592 | print "WARNING: Failed to load %s: %s" % (module_name, str(e)) 593 | continue 594 | 595 | def PLUGIN_ENTRY(): 596 | return idasimulator_t() 597 | 598 | -------------------------------------------------------------------------------- /src/python/idasim/__init__.py: -------------------------------------------------------------------------------- 1 | __all__ = ['IDASim'] 2 | 3 | from idasim import * 4 | -------------------------------------------------------------------------------- /src/python/idasim/application.py: -------------------------------------------------------------------------------- 1 | __all__ = ['Application'] 2 | 3 | import idc 4 | from architecture import Architecture 5 | 6 | class Application: 7 | ''' 8 | Class for invoking functions in the target process. 9 | ''' 10 | 11 | def __init__(self): 12 | ''' 13 | Class constructor. 14 | ''' 15 | self.cpu = Architecture() 16 | 17 | def Call(self, function, arguments=[], retaddr=0, block_until_return=True): 18 | ''' 19 | Call a given function. Arguments must already be configured. 20 | This should not be used to call functions hooked with IDASimulator or it likely won't work. 21 | 22 | @function - The name or address of the function to call. 23 | @arguments - A list of function arguments. 24 | @retaddr - The address to return to. 25 | @block_until_return - If set to True, this method will not return until the function does. 26 | If set to False, this method will return immediately after calling the function. 27 | 28 | Returns the return value of the function on success. 29 | Returns None on failure, or if block_until_return is False. 30 | ''' 31 | retval = None 32 | 33 | # Process should already be paused, but just in case... 34 | idc.PauseProcess() 35 | 36 | # If a function name was specified, get its address 37 | if isinstance(function, type('')): 38 | function = idc.LocByName('.' + function) 39 | 40 | if function == idc.BADADDR: 41 | function = idc.LocByName(function) 42 | 43 | if function != idc.BADADDR: 44 | if not retaddr: 45 | retaddr = self.cpu.ProgramCounter() 46 | 47 | # Set the specified function arguments 48 | self.cpu.SetArguments(arguments) 49 | 50 | # Do any arch-specific initialization before the function call 51 | self.cpu.PreFunctionCall(function) 52 | 53 | # Set up the return address and point the program counter to the start of the target function 54 | self.cpu.ReturnAddress(value=retaddr) 55 | self.cpu.ProgramCounter(value=function) 56 | idc.Jump(function) 57 | 58 | if block_until_return: 59 | # Resume process and wait for the target function to return 60 | idc.StepUntilRet() 61 | idc.GetDebuggerEvent(idc.WFNE_CONT|idc.WFNE_SUSP, -1) 62 | idc.Jump(retaddr) 63 | retval = self.cpu.ReturnValue() 64 | else: 65 | idc.ResumeProcess() 66 | 67 | return retval 68 | -------------------------------------------------------------------------------- /src/python/idasim/architecture.py: -------------------------------------------------------------------------------- 1 | __all__ = ['Architecture'] 2 | 3 | import idc 4 | 5 | class Architecture: 6 | ''' 7 | Abstraction class for accessing CPU-specific registers and data. 8 | ''' 9 | 10 | BIG = 'big' 11 | LITTLE = 'little' 12 | 13 | ARCH = { 14 | 'mips' : { 15 | 'spoffset' : 0x10, 16 | 'argreg' : ['a0', 'a1', 'a2', 'a3'], 17 | 'retval' : ['v0', 'v1'], 18 | 'sp' : 'sp', 19 | 'ra' : 'ra', 20 | 'pc' : 'pc', 21 | # Common calling convention for MIPS GCC is to place the address of the function 22 | # into $t9 and then jalr $t9. The callee then expects $t9 to point to the beginning 23 | # of itself, so $t9 is used to calculate the relative offset to the global pointer. 24 | # If $t9 is not set appropriately, any data/code xrefs that rely on $gp will fail. 25 | 'callreg' : 't9' 26 | }, 27 | 'arm' : { 28 | 'spoffset' : 0x10, 29 | 'argreg' : ['R0', 'R1', 'R2', 'R3'], 30 | 'retval' : ['R0', 'R1'], 31 | 'sp' : 'SP', 32 | 'ra' : 'LR', 33 | 'pc' : 'PC', 34 | }, 35 | 'ppc' : { 36 | 'spoffset' : 8, 37 | 'argreg' : ['R3', 'R4', 'R5', 'R6', 'R7', 'R8', 'R9', 'R10'], 38 | 'retval' : ['R3'], 39 | 'sp' : 'R1', 40 | 'ra' : 'LR', 41 | 'pc' : 'PC', 42 | # GDB stubs for PPC are special... 43 | 'bpt_size' : 1, 44 | 'bpt_type' : idc.BPT_EXEC 45 | }, 46 | 'ia32' : { 47 | 'spoffset' : 4, 48 | 'argreg' : [], 49 | 'retval' : ['EAX'], 50 | 'sp' : 'ESP', 51 | 'ra' : '*ESP', 52 | 'pc' : 'EIP', 53 | }, 54 | 'ia64' : { 55 | 'spoffset' : 8, 56 | 'argreg' : ['RDI', 'RSI', 'RDX', 'RCX', 'R8', 'R9'], 57 | 'retval' : ['RAX'], 58 | 'sp' : 'RSP', 59 | 'ra' : '*RSP', 60 | 'pc' : 'RIP', 61 | }, 62 | 'win64' : { 63 | 'spoffset' : 8, 64 | 'argreg' : ['RDX', 'RCX', 'R8', 'R9'], 65 | 'retval' : ['RAX'], 66 | 'sp' : 'RSP', 67 | 'ra' : '*RSP', 68 | 'pc' : 'RIP', 69 | } 70 | } 71 | 72 | PROCESSORS = { 73 | 'mipsl' : [{ 74 | 'architecture' : 'mips', 75 | 'endianess' : LITTLE, 76 | 'bits' : 32, 77 | }], 78 | 'mipsb' : [{ 79 | 'architecture' : 'mips', 80 | 'endianess' : BIG, 81 | 'bits' : 32, 82 | }], 83 | 'arm' : [{ 84 | 'architecture' : 'arm', 85 | 'endianess' : LITTLE, 86 | 'bits' : 32, 87 | }], 88 | 'armb' : [{ 89 | 'architecture' : 'arm', 90 | 'endianess' : BIG, 91 | 'bits' : 32, 92 | }], 93 | 'ppc' : [{ 94 | 'architecture' : 'ppc', 95 | 'endianess' : BIG, 96 | 'bits' : 32, 97 | }], 98 | 'metapc': [{ 99 | 'architecture' : 'ia32', 100 | 'endianess' : LITTLE, 101 | 'bits' : 32, 102 | }, 103 | { 104 | 'architecture' : 'win64', 105 | 'endianess' : LITTLE, 106 | 'bits' : 64, 107 | # Windows passes args differently in x86_64 108 | 'file_types' : [idc.FT_PE, idc.FT_EXE] 109 | }, 110 | { 111 | 'architecture' : 'ia64', 112 | 'endianess' : LITTLE, 113 | 'bits' : 64, 114 | } 115 | ], 116 | 117 | } 118 | 119 | def __init__(self): 120 | ''' 121 | Class constructor. 122 | 123 | Returns None. 124 | ''' 125 | self.cpu = None 126 | self.cpu_name = None 127 | self.architecture = None 128 | self.bits64 = False 129 | self.bits = 0 130 | self.bsize = 0 131 | 132 | self.__cpu_id() 133 | 134 | if self.cpu == None: 135 | if self.bits64: 136 | bits = '64' 137 | else: 138 | # This is an assumption, but it's only for the error message. 139 | # TODO: How to determine if a target is 16/32 bit from IDA's API? 140 | bits = '32' 141 | 142 | raise Exception("Unsupported cpu type: %s.%s" % (self.cpu_name, bits)) 143 | 144 | def __stack_dword(self, n, value=None): 145 | addr = self.StackPointer() + self.cpu['spoffset'] + (n * self.bsize) 146 | 147 | if value is not None: 148 | sval = self.ToString(value, size=self.bsize) 149 | idc.DbgWrite(addr, sval) 150 | 151 | return idc.DbgDword(addr) 152 | 153 | def __reg_value(self, reg, value=None): 154 | if value is not None: 155 | if reg.startswith('*'): 156 | idc.DbgWrite(idc.GetRegValue(reg[1:]), self.ToString(value)) 157 | else: 158 | idc.SetRegValue(value, reg) 159 | 160 | if reg.startswith('*'): 161 | return idc.DbgDword(idc.GetRegValue(reg[1:])) 162 | else: 163 | return idc.GetRegValue(reg) 164 | 165 | def __cpu_id(self): 166 | self.cpu_name = idc.GetShortPrm(idc.INF_PROCNAME).lower() 167 | 168 | if (idc.GetShortPrm(idc.INF_LFLAGS) & idc.LFLG_64BIT) == idc.LFLG_64BIT: 169 | self.bits64 = True 170 | else: 171 | self.bits64 = False 172 | 173 | for (processor, architectures) in self.PROCESSORS.iteritems(): 174 | if self.cpu_name == processor: 175 | for arch in architectures: 176 | # Only use 64-bit processor modules for a 64 bit binary 177 | if (self.bits64 and arch['bits'] != 64) or (not self.bits64 and arch['bits'] == 64): 178 | continue 179 | 180 | # If specific file types were specified for this processor module, make sure the target file is in that list 181 | if arch.has_key('file_types') and idc.GetShortPrm(idc.INF_FILETYPE) not in arch['file_types']: 182 | continue 183 | 184 | self.cpu = self.ARCH[arch['architecture']] 185 | self.architecture = arch['architecture'] 186 | self.endianess = arch['endianess'] 187 | self.bits = arch['bits'] 188 | self.bsize = self.bits / 8 189 | break 190 | 191 | if self.cpu: 192 | break 193 | return None 194 | 195 | def ToString(self, value, size=None): 196 | ''' 197 | Converts an integer value of size bytes into a raw string of bytes. 198 | 199 | @value - Integer value to be represented as a raw string. 200 | @size - Size of the integer value, in bytes. 201 | 202 | Returns a raw string containing the integer value in string form, and in the appropriate endianess. 203 | ''' 204 | data = "" 205 | 206 | if size is None: 207 | size = self.bsize 208 | 209 | for i in range(0, size): 210 | data += chr((value >> (8*i)) & 0xFF) 211 | 212 | if self.endianess != self.LITTLE: 213 | data = data[::-1] 214 | 215 | return data 216 | 217 | def FromString(self, data, size=None): 218 | ''' 219 | Converts raw string data into an integer value, with appropriate endianess. 220 | 221 | @data - Raw string data. 222 | @size - Number of bytes to convert. 223 | 224 | Returns an integer value. 225 | ''' 226 | i = 0 227 | value = 0 228 | 229 | if size is None: 230 | size = len(data) 231 | 232 | if self.endianess != self.LITTLE: 233 | data = data[::-1] 234 | 235 | for c in data[:size]: 236 | value += (ord(c) << (8*i)) 237 | i += 1 238 | 239 | return value 240 | 241 | def GetArguments(self, index, n): 242 | ''' 243 | Get a list of function arguments. Any valid string pointers will be converted to strings. 244 | 245 | @index - First argument index, 0-indexed. 246 | @n - The number of arguments to retrieve. 247 | 248 | Returns a list of n arguments. 249 | ''' 250 | args = [] 251 | 252 | for j in range(index, n+index): 253 | arg = self.Argument(j) 254 | try: 255 | sval = idc.GetString(arg) 256 | except: 257 | sval = None 258 | 259 | if sval is not None: 260 | args.append(sval) 261 | else: 262 | args.append(arg) 263 | 264 | return args 265 | 266 | def SetArguments(self, arguments): 267 | ''' 268 | Sets a list of function arguments. 269 | 270 | @arguments - List of function arguments. 271 | 272 | Returns None. 273 | ''' 274 | for i in range(0, len(arguments)): 275 | self.Argument(i, value=arguments[i]) 276 | 277 | def Argument(self, n, value=None): 278 | ''' 279 | Read/write function arguments. 280 | 281 | @n - Argument index number, 0-indexed. 282 | @value - If specified, the argument will be set to this value. 283 | 284 | Returns the current argument value. 285 | ''' 286 | regn = len(self.cpu['argreg']) 287 | 288 | if value is not None: 289 | if n < regn: 290 | self.__reg_value(self.cpu['argreg'][n], value) 291 | else: 292 | self.__stack_dword(n-regn, value) 293 | 294 | if n < regn: 295 | return self.__reg_value(self.cpu['argreg'][n]) 296 | else: 297 | return self.__stack_dword(n-regn) 298 | 299 | def StackPointer(self, value=None): 300 | ''' 301 | Read/write the stack pointer register. 302 | 303 | @value - If specified, the stack pointer register will be set to this value. 304 | 305 | Returns the current stack pointer register value. 306 | ''' 307 | return self.__reg_value(self.cpu['sp'], value) 308 | 309 | def ReturnValue(self, value=None, n=0): 310 | ''' 311 | Read/write the function return register value. 312 | 313 | @value - If specified, the return register will be set to this value. 314 | @n - Return register index number, for those architectures with multiple return registers. 315 | 316 | Returns the current return register value. 317 | ''' 318 | return self.__reg_value(self.cpu['retval'][n], value) 319 | 320 | def ProgramCounter(self, value=None): 321 | ''' 322 | Read/write the program counter register. 323 | 324 | @value - If specified, the program counter register will be set to this value. 325 | 326 | Returns the current value of the program counter register. 327 | ''' 328 | return self.__reg_value(self.cpu['pc'], value) 329 | 330 | def ReturnAddress(self, value=None): 331 | ''' 332 | Read/write the return address. 333 | 334 | @value - If specified, the return address will be set to this value. 335 | 336 | Returns the current return address value. 337 | ''' 338 | return self.__reg_value(self.cpu['ra'], value) 339 | 340 | def StackCleanup(self): 341 | ''' 342 | Cleans up values automatically pushed onto the stack by some architectures (return address in x86 for example). 343 | ''' 344 | if self.cpu['ra'].startswith('*') and self.cpu['ra'][1:] == self.cpu['sp']: 345 | self.StackPointer(self.StackPointer() + self.bsize) 346 | 347 | def SetBreakpoint(self, address): 348 | ''' 349 | Some GDB stubs for various architectures require different breakpoint settings. 350 | This method sets the appropriate breakpoint for the selected architecture. 351 | 352 | @address - The breakpoint address. 353 | 354 | Returns True on success, False on failure. 355 | ''' 356 | bpt_size = 0 357 | bpt_type = idc.BPT_SOFT 358 | 359 | if self.cpu.has_key('bpt_size'): 360 | bpt_size = self.cpu['bpt_size'] 361 | if self.cpu.has_key('bpt_type'): 362 | bpt_type = self.cpu['bpt_type'] 363 | 364 | return idc.AddBptEx(address, bpt_size, bpt_type) 365 | 366 | def PreFunctionCall(self, function): 367 | ''' 368 | Configure architecture-specific pre-requisites before calling a function. 369 | Called internally by Application.Call. 370 | 371 | @function - The address of the function to call. 372 | 373 | Returns None. 374 | ''' 375 | if self.cpu.has_key('callreg'): 376 | idc.SetRegValue(function, self.cpu['callreg']) 377 | -------------------------------------------------------------------------------- /src/python/idasim/exceptions.py: -------------------------------------------------------------------------------- 1 | class JumpTo(Exception): 2 | pass 3 | 4 | class GoTo(Exception): 5 | pass 6 | -------------------------------------------------------------------------------- /src/python/idasim/handler.py: -------------------------------------------------------------------------------- 1 | __all__ = ['IDASimFunctionHandler'] 2 | 3 | import idc 4 | import idaapi 5 | import inspect 6 | import traceback 7 | from exceptions import * 8 | from architecture import Architecture 9 | 10 | class IDASimFunctionHandler: 11 | ''' 12 | Registers and manages function simulators. 13 | ''' 14 | FUNCTION_HANDLERS = {} 15 | DEFAULT_HANDLER = None 16 | BPT_CND = '%s.Handler()' 17 | STUB_NAMING_CONVENTION = '.%s' 18 | 19 | def __init__(self, idbm, name=None, verbose=False): 20 | ''' 21 | Class constructor. 22 | 23 | @idbm - Instance of IDASimMMU. 24 | @name - Name that will be assigned to the class instance. 25 | @verbose - Enable verbose mode. 26 | 27 | Returns None. 28 | ''' 29 | self.idbm = idbm 30 | self.name = name 31 | self.verbose = verbose 32 | self.cpu = Architecture() 33 | 34 | if self.name == None: 35 | self.name = self.__get_my_name() 36 | 37 | self.bpt_cnd = self.BPT_CND % self.name 38 | 39 | self.UnregisterHandlers() 40 | 41 | # Eval this IDC expression to ensure that Python is set as the 42 | # preferred external scripting language. This is necessary for the 43 | # Python function handler to operate correctly. 44 | idc.Eval('RunPlugin("python", 3)') 45 | 46 | def cleanup(self): 47 | idc.Eval('RunPlugin("python", 4)') 48 | 49 | def __del__(self): 50 | self.cleanup() 51 | 52 | def __get_my_name(self): 53 | ''' 54 | This is a hack to get the name of the class instance variable. For internal use only. 55 | ''' 56 | i = -3 57 | (filename, line_number, function_name, text) = traceback.extract_stack()[i] 58 | name = text[:text.find('=')].strip() 59 | while 'self' in name: 60 | i -= 1 61 | (filename, line_number, function_name, text) = traceback.extract_stack()[i] 62 | name = name.replace('self', text[:text.find('=')].strip()) 63 | return name 64 | 65 | def SetHandlerBreakpoint(self, address): 66 | ''' 67 | Sets a handler breakpoint on the specified address. 68 | 69 | @address - Address to set the breakpoint at. 70 | 71 | Returns True on success, False on failure. 72 | ''' 73 | # Some remote debugger stubs have special needs for different architectures (e.g., gdb). 74 | # Thus, setting breakpoints should be done through the architecture abstraction class, 75 | # rather than directly through AddBpt/AddBptEx. 76 | self.cpu.SetBreakpoint(address) 77 | 78 | # A bug in versions of IDAPython shipped with IDA prior to 6.4sp1 improperly interpreted 79 | # the is_lowcnd value set via SetBptCnd/SetBptCndEx. Do this directly through idaapi 80 | # ourselves in order to support older versions. 81 | bpt = idaapi.bpt_t() 82 | idaapi.get_bpt(address, bpt) 83 | bpt.condition = self.bpt_cnd 84 | bpt.flags &= ~idc.BPT_LOWCND 85 | return idaapi.update_bpt(bpt) 86 | 87 | def __register_internal_handler(self, name, handler): 88 | ''' 89 | Internal handler registration function. For internal use only. 90 | ''' 91 | if type(name) == type(""): 92 | address = idc.LocByName(name) 93 | else: 94 | address = name 95 | 96 | if address != idc.BADADDR: 97 | bpt_result = self.SetHandlerBreakpoint(address) 98 | 99 | if bpt_result: 100 | self.FUNCTION_HANDLERS[name] = {} 101 | self.FUNCTION_HANDLERS[name]["handler"] = handler 102 | self.FUNCTION_HANDLERS[name]["address"] = address 103 | 104 | return bpt_result 105 | else: 106 | return False 107 | 108 | def Handler(self): 109 | ''' 110 | Breakpoint condition handler, called by IDA to evaluate conditional brekpoints. It in turn calls the 111 | appropriate function handler, populates the return value and puts execution back at the return address. 112 | 113 | This is a (slight) abuse of IDA's conditional breakpoints; this function always returns 0, indicating that 114 | the breakpoint condition has not been met. However, it does ensure that every call to a given function 115 | can be intercepted and simulated, regardless of whether the process is running freely, or the function has 116 | been stepped over, stepped into, etc. 117 | ''' 118 | retval = 0 119 | retaddr = None 120 | 121 | if self.verbose: 122 | print self.FUNCTION_HANDLERS 123 | 124 | for (name, properties) in self.FUNCTION_HANDLERS.iteritems(): 125 | if self.cpu.ProgramCounter() == properties["address"]: 126 | handler = properties["handler"] 127 | break 128 | 129 | # If no explicit handler was found, use the default handler 130 | if not handler and self.DEFAULT_HANDLER: 131 | handler = self.DEFAULT_HANDLER 132 | 133 | if handler: 134 | if self.verbose: 135 | print "Using function handler:", handler.__name__ 136 | 137 | parameters = {} 138 | 139 | # Enumerate the arguments and default values for the handler 140 | args, varargs, keywords, defaults = inspect.getargspec(handler) 141 | try: 142 | defaults = dict(zip(reversed(args), reversed(defaults))) 143 | except: 144 | defaults = {} 145 | 146 | # Build the handler parameters 147 | try: 148 | i = 0 149 | for arg in args: 150 | if arg != 'self': 151 | parameters[arg] = self.cpu.Argument(i) 152 | 153 | if defaults.has_key(arg): 154 | # If default value is of type string, get the string automatically 155 | if type(defaults[arg]) == type(''): 156 | parameters[arg] = idc.GetString(parameters[arg]) 157 | # If default value is of type list, get an array of bytes 158 | elif type(defaults[arg]) == type([]) and len(defaults[arg]) == 1: 159 | parameters[arg] = [c for c in idc.DbgRead(parameters[arg], defaults[arg][0])] 160 | i += 1 161 | except Exception, e: 162 | print "WARNING: Failed to parse handler parameters:", str(e) 163 | parameters = {} 164 | 165 | try: 166 | retval = handler(**parameters) 167 | except JumpTo, offset: 168 | retaddr = self.cpu.ReturnAddress() + offset.message 169 | except GoTo, addr: 170 | retaddr = addr.message 171 | except Exception, e: 172 | print "WARNING: Failed to simulate function '%s': %s" % (handler.__name__, str(e)) 173 | retval = 0 174 | 175 | if retval is not None: 176 | if retaddr is None: 177 | retaddr = self.cpu.ReturnAddress() 178 | 179 | # If a string type was returned by the handler, place the string in memory and return a pointer 180 | if type(retval) == type(""): 181 | retval = self.idbm.malloc(retval) 182 | # Map python's True and False to 1 and 0 repsectively 183 | elif retval == True: 184 | retval = 1 185 | elif retval == False: 186 | retval = 0 187 | 188 | self.cpu.ReturnValue(retval) 189 | self.cpu.ProgramCounter(retaddr) 190 | self.cpu.StackCleanup() 191 | 192 | # Since the PC register is manually manipulated, a breakpoint set on the return 193 | # address won't be triggered. In this case, make sure we pause the process manually. 194 | if idc.CheckBpt(self.cpu.ProgramCounter()) > 0: 195 | idc.PauseProcess() 196 | 197 | return 0 198 | 199 | def RegisterDefaultHandler(self, handler): 200 | ''' 201 | Register a default "catch-all" handler. 202 | 203 | @handler - Method/function handler. 204 | 205 | Returns None. 206 | ''' 207 | self.DEFAULT_HANDLER = handler 208 | 209 | def UnregisterDefaultHandler(self): 210 | ''' 211 | Unregister a default "catch-all" handler. 212 | 213 | Returns None. 214 | ''' 215 | self.DEFAULT_HANDLER = None 216 | 217 | def RegisterHandler(self, name, handler, stubs=True): 218 | ''' 219 | Registers a given function handler for a given function name. 220 | 221 | @name - Name of the function. 222 | @handler - The function handler to call. 223 | @stubs - If True, handle calls to both extern and stub addresses. 224 | 225 | Returns True on success, False on failure. 226 | ''' 227 | 228 | retval = self.__register_internal_handler(name, handler) 229 | 230 | if retval and stubs and type(name) == type(""): 231 | stub_name = self.STUB_NAMING_CONVENTION % name 232 | retval = self.__register_internal_handler(stub_name, handler) 233 | 234 | return retval 235 | 236 | def RegisterHandlers(self, handlers, stubs=True): 237 | ''' 238 | Registers a set of function handlers. 239 | 240 | @handlers - A dictionary consisting of 'name':handler pairs. 241 | @stubs - If True, handle calls to both extern and stub addresses. 242 | 243 | Returns the number of handlers successfully registered. 244 | ''' 245 | count = 0 246 | 247 | for (name, handler) in handlers.iteritems(): 248 | if self.RegisterHandler(name, handler, stubs): 249 | count += 1 250 | 251 | return count 252 | 253 | def UnregisterHandler(self, name, stubs=True): 254 | ''' 255 | Removes a function handler by name. 256 | 257 | @name - The name of the function handler to be removed. 258 | @stubs - If True, corresponding function stub handlers that were automatically created by RegisterHandler will also be removed. 259 | 260 | Returns None. 261 | ''' 262 | addr = None 263 | stub_name = None 264 | stub_addr = None 265 | 266 | if name is not None: 267 | try: 268 | stub_name = self.STUB_NAMING_CONVENTION % name 269 | except: 270 | pass 271 | 272 | if self.FUNCTION_HANDLERS.has_key(name): 273 | addr = self.FUNCTION_HANDLERS[name]['address'] 274 | 275 | if self.FUNCTION_HANDLERS.has_key(stub_name): 276 | stub_addr = self.FUNCTION_HANDLERS[stub_name]['address'] 277 | 278 | if addr is not None and name is not None: 279 | idc.DelBpt(addr) 280 | del self.FUNCTION_HANDLERS[name] 281 | 282 | if stubs and stub_addr is not None and stub_name is not None: 283 | idc.DelBpt(stub_addr) 284 | del self.FUNCTION_HANDLERS[stub_name] 285 | 286 | def UnregisterHandlers(self, purge=False): 287 | ''' 288 | Deletes breakpoints for all registered handlers. 289 | 290 | @purge - Removes all handlers for all instances of IDBFunctionHandler. 291 | 292 | Returns None. 293 | ''' 294 | self.UnregisterDefaultHandler() 295 | 296 | if not purge: 297 | # Only remove this instance's handlers 298 | for (name, info) in self.FUNCTION_HANDLERS.iteritems(): 299 | condition = idc.GetBptAttr(info['address'], idc.BPTATTR_COND) 300 | 301 | if condition == self.bpt_cnd: 302 | idc.DelBpt(info['address']) 303 | else: 304 | # Try to remove ALL instance's handlers (this could remove other conditional breakpoints...) 305 | for i in range(0, idc.GetBptQty()): 306 | ea = idc.GetBptEA(i) 307 | condition = idc.GetBptAttr(ea, idc.BPTATTR_COND) 308 | if condition.endswith(self.BPT_CND % ''): 309 | idc.DelBpt(ea) 310 | 311 | self.FUNCTION_HANDLERS = {} 312 | 313 | def GetHandler(self, name): 314 | ''' 315 | Returns the current handler for the named location. 316 | 317 | @name - Function/location name. 318 | 319 | Returns the handler instance. 320 | ''' 321 | if self.FUNCTION_HANDLERS.has_key(name): 322 | return self.FUNCTION_HANDLERS[name]["handler"] 323 | else: 324 | return None 325 | 326 | -------------------------------------------------------------------------------- /src/python/idasim/idasim.py: -------------------------------------------------------------------------------- 1 | __all__ = ['IDASim'] 2 | 3 | import idc 4 | import idaapi 5 | import idautils 6 | from mmu import * 7 | from handler import * 8 | from exceptions import * 9 | from application import * 10 | from architecture import * 11 | 12 | class IDASimDbgHook(idaapi.DBG_Hooks): 13 | ''' 14 | Resets the IDASimMMU base address (MP) whenever the debugger is started/stopped. 15 | Executes startup code when the debugger is started, if specified. 16 | Only used internally by the IDASim class. 17 | ''' 18 | def dbg_init(self, idasim): 19 | self.debugging = False 20 | self.sim = idasim 21 | self.hook() 22 | 23 | def dbg_process_start(self, pid, tid, ea, name, base, size): 24 | self.sim.mmu.reset() 25 | if not self.debugging: 26 | self.debugging = True 27 | self.sim.InitHandler(init=True) 28 | 29 | def dbg_process_exit(self, pid, tid, ea, code): 30 | self.debugging = False 31 | self.sim.mmu.reset() 32 | 33 | def dbg_process_attach(self, pid, tid, ea, name, base, size): 34 | self.sim.mmu.reset() 35 | if not self.debugging: 36 | self.debugging = True 37 | self.sim.InitHandler(init=True) 38 | 39 | def dbg_process_detatch(self, pid, tid, ea): 40 | self.debugging = False 41 | self.sim.mmu.reset() 42 | 43 | class IDASim(object): 44 | ''' 45 | Class for easily simulating library function calls and initializing memory/registers when debugging emulated code in IDA. 46 | ''' 47 | 48 | def __init__(self, handlers={}, debug=False, attach=False, membase=None): 49 | ''' 50 | Class constructor. 51 | 52 | @handlers - A dictionary of function names/addresses to simulate and their corresponding handlers. 53 | @debug - Set to True to automatically start debugging. 54 | @attach - Set to True to attach to a process, rather than directly running the debugger. 55 | @membase - Specify the base address to start at when allocating memory. 56 | 57 | Returns None. 58 | ''' 59 | self.user_handlers = handlers 60 | self.script = None 61 | self.script_name = None 62 | 63 | self.cpu = Architecture() 64 | self.mmu = IDASimMMU() 65 | self.app = Application() 66 | self.FunctionHandler = IDASimFunctionHandler(self.mmu) 67 | self.dbg_hook = IDASimDbgHook() 68 | self.dbg_hook.dbg_init(self) 69 | 70 | self.__register_handlers() 71 | 72 | if attach: 73 | self.AttachDebugger() 74 | elif debug: 75 | self.StartDebugger() 76 | 77 | if membase is not None: 78 | self.mmu.base(membase) 79 | 80 | def __register_handlers(self): 81 | ''' 82 | Registers function names and handlers with the IDB function handler. 83 | For internal use only. 84 | ''' 85 | for (name, handler) in self.user_handlers.iteritems(): 86 | self.FunctionHandler.RegisterHandler(name, handler) 87 | 88 | def __get_instance_methods(self, instance): 89 | methods = {} 90 | 91 | for name in dir(instance): 92 | if not name.startswith('_'): 93 | obj = getattr(instance, name) 94 | if 'method' in type(obj).__name__: 95 | methods[name] = obj 96 | 97 | return methods 98 | 99 | def InitHandler(self, init=False): 100 | if self.script is not None: 101 | if (self.script_name is not None and not init) or (self.script_name is None and init): 102 | script_globals = { 103 | 'IDASIM' : self, 104 | 'idc' : idc, 105 | 'idaapi' : idaapi, 106 | 'idautils' : idautils, 107 | } 108 | 109 | script_globals.update(self.__get_instance_methods(self)) 110 | script_globals.update(self.__get_instance_methods(self.cpu)) 111 | 112 | try: 113 | exec(self.script, script_globals) 114 | except Exception, e: 115 | print "Failed to exec startup script:", str(e) 116 | print "################" 117 | print self.script 118 | print "################" 119 | return None 120 | 121 | def ExecuteOnStart(self, script=None, name=None, disable=False): 122 | ''' 123 | Specify a Python string to be evaluated when the debugger is started/attahced. 124 | 125 | @script - Python string to be evaluated. If None, this feature will be disabled. 126 | 127 | Returns None. 128 | ''' 129 | self.script = script 130 | 131 | if disable: 132 | self.FunctionHandler.UnregisterHandler(self.script_name) 133 | self.script_name = None 134 | elif name is not None and script is not None: 135 | self.FunctionHandler.RegisterHandler(name, self.InitHandler) 136 | 137 | self.script_name = name 138 | 139 | def vsprintf(self, fmt, index): 140 | ''' 141 | Builds a string from a format string and format arguments. 142 | 143 | @fmt - The format string. 144 | @index - The function argument number at which the format string arguments start (0-indexed). 145 | 146 | Returns a formatted string. 147 | ''' 148 | n = 0 149 | for i in range(0, len(fmt)-1): 150 | if fmt[i] == '%' and fmt[i+1] != '%': 151 | n += 1 152 | 153 | return fmt % tuple(self.cpu.GetArguments(index, n)) 154 | 155 | def WaitForDebugger(self): 156 | ''' 157 | Waits for the debugger event (WFNE_CONT | WFNE_SUSP). 158 | Called internally by StartDebugger and AttachDebugger. 159 | 160 | Returns None. 161 | ''' 162 | idc.GetDebuggerEvent(idc.WFNE_CONT | idc.WFNE_SUSP, -1) 163 | 164 | def StartDebugger(self): 165 | ''' 166 | Starts the debugger (equivalent of pressing F9). 167 | 168 | Returns None. 169 | ''' 170 | idc.StartDebugger('', '', '') 171 | self.WaitForDebugger() 172 | 173 | def AttachDebugger(self, pid=-1): 174 | ''' 175 | Attaches the debugger to a running process. 176 | 177 | @pid - The PID of the process to attach to (user will be prompted if not specified). 178 | 179 | Returns None. 180 | ''' 181 | idc.AttachProcess(pid, -1) 182 | self.WaitForDebugger() 183 | 184 | def Malloc(self, data=None, size=0): 185 | ''' 186 | Allocates space in the debugger's memory. 187 | 188 | @data - Fill the allocated space with this data. 189 | @size - If data is None, allocate and zero out size bytes of memory. 190 | 191 | Returns the address of the allocated memory. 192 | ''' 193 | return self.mmu.malloc(data, size) 194 | 195 | def String(self, string, raw=False): 196 | ''' 197 | Creates a NULL-terminated string in the debugger's memory. 198 | 199 | @string - The string, or list of strings, to place into memory. 200 | @raw - If set to True, the string will not be NULL terminated. 201 | 202 | Returns the address, or list of addresses, of the string(s) in memory. 203 | ''' 204 | addrs = [] 205 | 206 | if type(string) == type(""): 207 | array = [string] 208 | else: 209 | array = string 210 | 211 | for s in array: 212 | if not raw: 213 | s = s + "\x00" 214 | addrs.append(self.Malloc(s)) 215 | 216 | if type(string) == type(""): 217 | addrs = addrs[0] 218 | 219 | return addrs 220 | 221 | def Int(self, value, size): 222 | ''' 223 | Creates an integer value of size bytes in the debugger's memory. 224 | 225 | @value - The integer value, or list of values, to place into memory. 226 | @size - The size of the interger value(s), in bytes. 227 | 228 | Returns the address, or a list of addresses, of the integer(s) in memory. 229 | ''' 230 | data = [] 231 | 232 | if type(value) != type([]): 233 | value = [value] 234 | 235 | for d in value: 236 | data.append(self.cpu.ToString(d, size)) 237 | 238 | return self.String(data, raw=True) 239 | 240 | def DoubleWord(self, dword): 241 | ''' 242 | Places a double word integer into the debugger's memory. 243 | 244 | @dword - The value, or list of values, to place into memory. 245 | 246 | Returns the address, or a list of addresses, of the dword(s) in memory. 247 | ''' 248 | return self.Int(dword, self.cpu.bsize*2) 249 | 250 | def Word(self, word): 251 | ''' 252 | Places a word-sized integer into the debugger's memory. 253 | 254 | @word - The four byte integer value, or list of values, to place into memory. 255 | 256 | Returns the address, or a list of addresses, of the word(s) in memory. 257 | ''' 258 | return self.Int(word, self.cpu.bsize) 259 | 260 | def HalfWord(self, hword): 261 | ''' 262 | Places a half-word sized integer into the debugger's memory. 263 | 264 | @hword - The two byte value, or list of values, to place into memory. 265 | 266 | Returns the address, or a list of addresses, of the half word(s) in memory. 267 | ''' 268 | return self.Int(hword, self.cpu.bsize/2) 269 | 270 | def Byte(self, byte): 271 | ''' 272 | Places one byte of data into the debugger's memory. 273 | 274 | @byte - The byte value, or list of values, to place into memory. 275 | 276 | Returns the address, or a list of addresses, of the byte(s) in memory. 277 | ''' 278 | return self.Int(byte, 1) 279 | 280 | def ARGV(self, argv): 281 | ''' 282 | Allocates space for an argv data structure. 283 | 284 | @argv - A list of argv strings. 285 | 286 | Returns the address of the argv array of pointers. 287 | ''' 288 | return self.Word(self.String(argv))[0] 289 | 290 | def Cleanup(self): 291 | ''' 292 | Removes all registered function simulation hooks. 293 | 294 | Returns None. 295 | ''' 296 | self.FunctionHandler.UnregisterHandlers() 297 | 298 | -------------------------------------------------------------------------------- /src/python/idasim/mmu.py: -------------------------------------------------------------------------------- 1 | __all__ = ['IDASimMMU'] 2 | 3 | import idc 4 | import idaapi 5 | from application import Application 6 | from architecture import Architecture 7 | 8 | class IDASimMMU: 9 | ''' 10 | Manages the allocation of memory while running in the debugger. 11 | The term 'manage' is used very loosely here; it really only allocates memory. 12 | ''' 13 | ALIGN = 4 14 | DEFAULT_MP = 0x100000 15 | SEGNAME = 'MMU' 16 | LAST_SEGNAME = ['MEMORY', 'RAM'] 17 | 18 | def __init__(self, base=None): 19 | ''' 20 | Class constructor. 21 | ''' 22 | # Disable this for now, it doesn't work. 23 | self.use_native_malloc = False 24 | self.allocated_addresses = {} 25 | 26 | self.app = Application() 27 | self.cpu = Architecture() 28 | 29 | if base is not None: 30 | self.MP = self.BASE_MP = base 31 | else: 32 | self.MP = self.BASE_MP = idc.BADADDR 33 | 34 | def _detect_membase(self): 35 | ''' 36 | Attempts to locate a section of memory for IDBMMU's internal memory allocation. 37 | For internal use only. 38 | ''' 39 | if self.BASE_MP == idc.BADADDR: 40 | 41 | # Look for the MMU segment 42 | ea = idc.SegByName(self.SEGNAME) 43 | 44 | # No MMU segment? 45 | if ea == idc.BADADDR: 46 | ea = 0 47 | 48 | # Find the very last defined segment 49 | while True: 50 | segea = idc.NextSeg(ea) 51 | 52 | if segea == idc.BADADDR: 53 | break 54 | else: 55 | ea = segea 56 | 57 | # Is it not a memory segment? 58 | if idc.SegName(ea) not in self.LAST_SEGNAME: 59 | try: 60 | # Find the start of the stack 61 | ea = idc.SegStart(self.cpu.StackPointer()) 62 | 63 | # Still nothing? Use the default. 64 | if ea == idc.BADADDR: 65 | ea = self.DEFAULT_MP 66 | except: 67 | if not self.use_native_malloc: 68 | raise Exception("No available segments for memory allocation! Try defining segment %s." % self.SEGNAME) 69 | self.BASE_MP = ea 70 | 71 | if self.MP == idc.BADADDR: 72 | self.MP = self.BASE_MP 73 | 74 | return self.BASE_MP 75 | 76 | def reset(self): 77 | ''' 78 | Resets the current allocation address. 79 | ''' 80 | self.MP = idc.BADADDR 81 | self.allocated_addresses = {} 82 | 83 | def base(self, base=None): 84 | ''' 85 | Set the base address at which to start allocating memory. Default: 0x100000. 86 | 87 | @base - The base address. If specified BASE_MP will be set to this value. 88 | 89 | Returns the current BASE_MP value. 90 | ''' 91 | if base is not None: 92 | self.MP = self.BASE_MP = base 93 | return self.BASE_MP 94 | 95 | def malloc(self, data=None, size=0): 96 | ''' 97 | Allocates space for data in the debugger's memory and populates it. 98 | 99 | @data - Data to place into memory. If None, NULL bytes will be used. 100 | @size - Size of memory to allocate. If 0, len(data) bytes will be allocated. 101 | 102 | Returns the address of the allocated memory. 103 | ''' 104 | if size == 0 and data is not None: 105 | size = len(data) 106 | 107 | if data is None: 108 | data = "\x00" * size 109 | 110 | if self.use_native_malloc: 111 | addr = self.app.Call('malloc', arguments=[size], retaddr=self.cpu.ReturnAddress()) 112 | else: 113 | self._detect_membase() 114 | 115 | addr = self.MP 116 | self.MP += size 117 | # This ensures memory addresses are 4-byte aligned. This is important for some architectures. 118 | if (self.MP % self.ALIGN) > 0: 119 | self.MP += (self.ALIGN - (self.MP % self.ALIGN)) 120 | 121 | # Keep a dictionary of allocated addresses and their sizes 122 | self.allocated_addresses[addr] = size 123 | 124 | idc.DbgWrite(addr, data) 125 | 126 | return addr 127 | 128 | --------------------------------------------------------------------------------