├── LICENSE ├── README.md ├── images ├── after_codatify.png ├── after_mipslocalvars.png ├── before_codatify.png ├── before_mipslocalvars.png ├── defined_bytes.png ├── how_to_run_localxrefs.png ├── how_to_run_mipslocalvars.png ├── how_to_run_mipsrop.png ├── how_to_use_codatify.png ├── localxrefs_highlight.png ├── localxrefs_output.png ├── mipsrop_find.png ├── mipsrop_summary.png ├── rizzo_apply.png ├── rizzo_generate.png ├── undefined_bytes.png └── where_does_s2_get_set.png ├── modules ├── README.md ├── install.py └── md5hash │ └── md5hash.py ├── plugins ├── README.md ├── alleycat │ ├── README.md │ └── alleycat.py ├── codatify │ ├── README.md │ └── codatify.py ├── fluorescence │ ├── README.md │ └── fluorescence.py ├── funcprofiler │ └── funcprofiler.py ├── install.py ├── leafblower │ └── leafblower.py ├── localxrefs │ ├── README.md │ └── localxrefs.py ├── mipslocalvars │ ├── README.md │ └── mipslocalvars.py ├── mipsrop │ ├── README.md │ └── mipsrop.py ├── rizzo │ ├── README.md │ ├── rizzo.py │ └── vxworks │ │ └── mips │ │ └── 5.5 │ │ └── vxworks_5_5_mips.riz └── shims │ └── ida_shims.py └── scripts └── wpsearch.py /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 devttys0 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ida 2 | === 3 | 4 | Collection of IDA Python plugins/scripts/modules. 5 | -------------------------------------------------------------------------------- /images/after_codatify.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grayhatacademy/ida/faf13a13c4f5070fe8f0666c1508f928ffe40e23/images/after_codatify.png -------------------------------------------------------------------------------- /images/after_mipslocalvars.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grayhatacademy/ida/faf13a13c4f5070fe8f0666c1508f928ffe40e23/images/after_mipslocalvars.png -------------------------------------------------------------------------------- /images/before_codatify.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grayhatacademy/ida/faf13a13c4f5070fe8f0666c1508f928ffe40e23/images/before_codatify.png -------------------------------------------------------------------------------- /images/before_mipslocalvars.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grayhatacademy/ida/faf13a13c4f5070fe8f0666c1508f928ffe40e23/images/before_mipslocalvars.png -------------------------------------------------------------------------------- /images/defined_bytes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grayhatacademy/ida/faf13a13c4f5070fe8f0666c1508f928ffe40e23/images/defined_bytes.png -------------------------------------------------------------------------------- /images/how_to_run_localxrefs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grayhatacademy/ida/faf13a13c4f5070fe8f0666c1508f928ffe40e23/images/how_to_run_localxrefs.png -------------------------------------------------------------------------------- /images/how_to_run_mipslocalvars.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grayhatacademy/ida/faf13a13c4f5070fe8f0666c1508f928ffe40e23/images/how_to_run_mipslocalvars.png -------------------------------------------------------------------------------- /images/how_to_run_mipsrop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grayhatacademy/ida/faf13a13c4f5070fe8f0666c1508f928ffe40e23/images/how_to_run_mipsrop.png -------------------------------------------------------------------------------- /images/how_to_use_codatify.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grayhatacademy/ida/faf13a13c4f5070fe8f0666c1508f928ffe40e23/images/how_to_use_codatify.png -------------------------------------------------------------------------------- /images/localxrefs_highlight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grayhatacademy/ida/faf13a13c4f5070fe8f0666c1508f928ffe40e23/images/localxrefs_highlight.png -------------------------------------------------------------------------------- /images/localxrefs_output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grayhatacademy/ida/faf13a13c4f5070fe8f0666c1508f928ffe40e23/images/localxrefs_output.png -------------------------------------------------------------------------------- /images/mipsrop_find.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grayhatacademy/ida/faf13a13c4f5070fe8f0666c1508f928ffe40e23/images/mipsrop_find.png -------------------------------------------------------------------------------- /images/mipsrop_summary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grayhatacademy/ida/faf13a13c4f5070fe8f0666c1508f928ffe40e23/images/mipsrop_summary.png -------------------------------------------------------------------------------- /images/rizzo_apply.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grayhatacademy/ida/faf13a13c4f5070fe8f0666c1508f928ffe40e23/images/rizzo_apply.png -------------------------------------------------------------------------------- /images/rizzo_generate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grayhatacademy/ida/faf13a13c4f5070fe8f0666c1508f928ffe40e23/images/rizzo_generate.png -------------------------------------------------------------------------------- /images/undefined_bytes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grayhatacademy/ida/faf13a13c4f5070fe8f0666c1508f928ffe40e23/images/undefined_bytes.png -------------------------------------------------------------------------------- /images/where_does_s2_get_set.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grayhatacademy/ida/faf13a13c4f5070fe8f0666c1508f928ffe40e23/images/where_does_s2_get_set.png -------------------------------------------------------------------------------- /modules/README.md: -------------------------------------------------------------------------------- 1 | IDA Python Modules 2 | === 3 | 4 | Collection of IDA Python modules that I've written to help with RE work. 5 | 6 | To install all modules, run: 7 | 8 | ``` 9 | $ python ./install.py /path/to/your/ida/install/directory 10 | ``` 11 | 12 | To install only selected modules, just drop the .py file(s) into IDA's `python` directory. 13 | 14 | md5hash 15 | ---------- 16 | 17 | * Pure Python MD5 hash implementation (IDA's hashlib is crippled) 18 | 19 | -------------------------------------------------------------------------------- /modules/install.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | import sys 5 | import shutil 6 | 7 | try: 8 | py_module_path = os.path.realpath(os.path.join(sys.argv[1], 'python')) 9 | try: 10 | install = sys.argv[2] != '--remove' 11 | except IndexError as e: 12 | install = True 13 | except IndexError as e: 14 | print ("Usage: %s [--install | --remove]" % sys.argv[0]) 15 | sys.exit(1) 16 | 17 | source_path = os.path.dirname(os.path.realpath(__file__)) 18 | 19 | if install: 20 | print ("Installing python modules from %s to %s..." % (source_path, py_module_path)) 21 | else: 22 | print ("Removing python modules from %s..." % py_module_path) 23 | 24 | for py_module in next(os.walk('.'))[1]: 25 | src = os.path.join(source_path, py_module, py_module + '.py') 26 | dst = os.path.join(py_module_path, py_module + '.py') 27 | 28 | if install: 29 | print ("Installing %s..." % py_module) 30 | shutil.copyfile(src, dst) 31 | else: 32 | print ("Removing %s..." % py_module) 33 | os.remove(dst) 34 | 35 | print ("Done.") 36 | -------------------------------------------------------------------------------- /modules/md5hash/md5hash.py: -------------------------------------------------------------------------------- 1 | # The Python bundled with IDA doesn't have a functioning hashlib. 2 | # Unfortunately, sometimes it is necessary or more convenient to use the bundled python. 3 | # Many scripts/plugins *assume* hashlib works, causing them to fail. 4 | # This is a replacement. 5 | 6 | """A sample implementation of MD5 in pure Python. 7 | 8 | This is an implementation of the MD5 hash function, as specified by 9 | RFC 1321, in pure Python. It was implemented using Bruce Schneier's 10 | excellent book "Applied Cryptography", 2nd ed., 1996. 11 | 12 | Surely this is not meant to compete with the existing implementation 13 | of the Python standard library (written in C). Rather, it should be 14 | seen as a Python complement that is more readable than C and can be 15 | used more conveniently for learning and experimenting purposes in 16 | the field of cryptography. 17 | 18 | This module tries very hard to follow the API of the existing Python 19 | standard library's "md5" module, but although it seems to work fine, 20 | it has not been extensively tested! (But note that there is a test 21 | module, test_md5py.py, that compares this Python implementation with 22 | the C one of the Python standard library. 23 | 24 | BEWARE: this comes with no guarantee whatsoever about fitness and/or 25 | other properties! Specifically, do not use this in any production 26 | code! License is Python License! 27 | 28 | Special thanks to Aurelian Coman who fixed some nasty bugs! 29 | 30 | Dinu C. Gherman 31 | """ 32 | 33 | 34 | __date__ = '2001-10-1' 35 | __version__ = 0.9 36 | 37 | 38 | import struct, string, copy 39 | 40 | 41 | # ====================================================================== 42 | # Bit-Manipulation helpers 43 | # 44 | # _long2bytes() was contributed by Barry Warsaw 45 | # and is reused here with tiny modifications. 46 | # ====================================================================== 47 | 48 | def _long2bytes(n, blocksize=0): 49 | """Convert a long integer to a byte string. 50 | 51 | If optional blocksize is given and greater than zero, pad the front 52 | of the byte string with binary zeros so that the length is a multiple 53 | of blocksize. 54 | """ 55 | 56 | # After much testing, this algorithm was deemed to be the fastest. 57 | s = '' 58 | pack = struct.pack 59 | while n > 0: 60 | ### CHANGED FROM '>I' TO '> 32 64 | 65 | # Strip off leading zeros. 66 | for i in range(len(s)): 67 | if s[i] <> '\000': 68 | break 69 | else: 70 | # Only happens when n == 0. 71 | s = '\000' 72 | i = 0 73 | 74 | s = s[i:] 75 | 76 | # Add back some pad bytes. This could be done more efficiently 77 | # w.r.t. the de-padding being done above, but sigh... 78 | if blocksize > 0 and len(s) % blocksize: 79 | s = (blocksize - len(s) % blocksize) * '\000' + s 80 | 81 | return s 82 | 83 | 84 | def _bytelist2long(list): 85 | "Transform a list of characters into a list of longs." 86 | 87 | imax = len(list)/4 88 | hl = [0L] * imax 89 | 90 | j = 0 91 | i = 0 92 | while i < imax: 93 | b0 = long(ord(list[j])) 94 | b1 = (long(ord(list[j+1]))) << 8 95 | b2 = (long(ord(list[j+2]))) << 16 96 | b3 = (long(ord(list[j+3]))) << 24 97 | hl[i] = b0 | b1 |b2 | b3 98 | i = i+1 99 | j = j+4 100 | 101 | return hl 102 | 103 | 104 | def _rotateLeft(x, n): 105 | "Rotate x (32 bit) left n bits circularly." 106 | 107 | return (x << n) | (x >> (32-n)) 108 | 109 | 110 | # ====================================================================== 111 | # The real MD5 meat... 112 | # 113 | # Implemented after "Applied Cryptography", 2nd ed., 1996, 114 | # pp. 436-441 by Bruce Schneier. 115 | # ====================================================================== 116 | 117 | # F, G, H and I are basic MD5 functions. 118 | 119 | def F(x, y, z): 120 | return (x & y) | ((~x) & z) 121 | 122 | def G(x, y, z): 123 | return (x & z) | (y & (~z)) 124 | 125 | def H(x, y, z): 126 | return x ^ y ^ z 127 | 128 | def I(x, y, z): 129 | return y ^ (x | (~z)) 130 | 131 | 132 | def XX(func, a, b, c, d, x, s, ac): 133 | """Wrapper for call distribution to functions F, G, H and I. 134 | 135 | This replaces functions FF, GG, HH and II from "Appl. Crypto. 136 | Rotation is separate from addition to prevent recomputation 137 | (now summed-up in one function). 138 | """ 139 | 140 | res = 0L 141 | res = res + a + func(b, c, d) 142 | res = res + x 143 | res = res + ac 144 | res = res & 0xffffffffL 145 | res = _rotateLeft(res, s) 146 | res = res & 0xffffffffL 147 | res = res + b 148 | 149 | return res & 0xffffffffL 150 | 151 | 152 | class MD5: 153 | "An implementation of the MD5 hash function in pure Python." 154 | 155 | def __init__(self): 156 | "Initialisation." 157 | 158 | # Initial 128 bit message digest (4 times 32 bit). 159 | self.A = 0L 160 | self.B = 0L 161 | self.C = 0L 162 | self.D = 0L 163 | 164 | # Initial message length in bits(!). 165 | self.length = 0L 166 | self.count = [0, 0] 167 | 168 | # Initial empty message as a sequence of bytes (8 bit characters). 169 | self.input = [] 170 | 171 | # Length of the final hash (in bytes). 172 | self.HASH_LENGTH = 16 173 | 174 | # Length of a block (the number of bytes hashed in every transform). 175 | self.DATA_LENGTH = 64 176 | 177 | # Call a separate init function, that can be used repeatedly 178 | # to start from scratch on the same object. 179 | self.init() 180 | 181 | 182 | def init(self): 183 | "Initialize the message-digest and set all fields to zero." 184 | 185 | self.length = 0L 186 | self.input = [] 187 | 188 | # Load magic initialization constants. 189 | self.A = 0x67452301L 190 | self.B = 0xefcdab89L 191 | self.C = 0x98badcfeL 192 | self.D = 0x10325476L 193 | 194 | 195 | def _transform(self, inp): 196 | """Basic MD5 step transforming the digest based on the input. 197 | 198 | Note that if the Mysterious Constants are arranged backwards 199 | in little-endian order and decrypted with the DES they produce 200 | OCCULT MESSAGES! 201 | """ 202 | 203 | a, b, c, d = A, B, C, D = self.A, self.B, self.C, self.D 204 | 205 | # Round 1. 206 | 207 | S11, S12, S13, S14 = 7, 12, 17, 22 208 | 209 | a = XX(F, a, b, c, d, inp[ 0], S11, 0xD76AA478L) # 1 210 | d = XX(F, d, a, b, c, inp[ 1], S12, 0xE8C7B756L) # 2 211 | c = XX(F, c, d, a, b, inp[ 2], S13, 0x242070DBL) # 3 212 | b = XX(F, b, c, d, a, inp[ 3], S14, 0xC1BDCEEEL) # 4 213 | a = XX(F, a, b, c, d, inp[ 4], S11, 0xF57C0FAFL) # 5 214 | d = XX(F, d, a, b, c, inp[ 5], S12, 0x4787C62AL) # 6 215 | c = XX(F, c, d, a, b, inp[ 6], S13, 0xA8304613L) # 7 216 | b = XX(F, b, c, d, a, inp[ 7], S14, 0xFD469501L) # 8 217 | a = XX(F, a, b, c, d, inp[ 8], S11, 0x698098D8L) # 9 218 | d = XX(F, d, a, b, c, inp[ 9], S12, 0x8B44F7AFL) # 10 219 | c = XX(F, c, d, a, b, inp[10], S13, 0xFFFF5BB1L) # 11 220 | b = XX(F, b, c, d, a, inp[11], S14, 0x895CD7BEL) # 12 221 | a = XX(F, a, b, c, d, inp[12], S11, 0x6B901122L) # 13 222 | d = XX(F, d, a, b, c, inp[13], S12, 0xFD987193L) # 14 223 | c = XX(F, c, d, a, b, inp[14], S13, 0xA679438EL) # 15 224 | b = XX(F, b, c, d, a, inp[15], S14, 0x49B40821L) # 16 225 | 226 | # Round 2. 227 | 228 | S21, S22, S23, S24 = 5, 9, 14, 20 229 | 230 | a = XX(G, a, b, c, d, inp[ 1], S21, 0xF61E2562L) # 17 231 | d = XX(G, d, a, b, c, inp[ 6], S22, 0xC040B340L) # 18 232 | c = XX(G, c, d, a, b, inp[11], S23, 0x265E5A51L) # 19 233 | b = XX(G, b, c, d, a, inp[ 0], S24, 0xE9B6C7AAL) # 20 234 | a = XX(G, a, b, c, d, inp[ 5], S21, 0xD62F105DL) # 21 235 | d = XX(G, d, a, b, c, inp[10], S22, 0x02441453L) # 22 236 | c = XX(G, c, d, a, b, inp[15], S23, 0xD8A1E681L) # 23 237 | b = XX(G, b, c, d, a, inp[ 4], S24, 0xE7D3FBC8L) # 24 238 | a = XX(G, a, b, c, d, inp[ 9], S21, 0x21E1CDE6L) # 25 239 | d = XX(G, d, a, b, c, inp[14], S22, 0xC33707D6L) # 26 240 | c = XX(G, c, d, a, b, inp[ 3], S23, 0xF4D50D87L) # 27 241 | b = XX(G, b, c, d, a, inp[ 8], S24, 0x455A14EDL) # 28 242 | a = XX(G, a, b, c, d, inp[13], S21, 0xA9E3E905L) # 29 243 | d = XX(G, d, a, b, c, inp[ 2], S22, 0xFCEFA3F8L) # 30 244 | c = XX(G, c, d, a, b, inp[ 7], S23, 0x676F02D9L) # 31 245 | b = XX(G, b, c, d, a, inp[12], S24, 0x8D2A4C8AL) # 32 246 | 247 | # Round 3. 248 | 249 | S31, S32, S33, S34 = 4, 11, 16, 23 250 | 251 | a = XX(H, a, b, c, d, inp[ 5], S31, 0xFFFA3942L) # 33 252 | d = XX(H, d, a, b, c, inp[ 8], S32, 0x8771F681L) # 34 253 | c = XX(H, c, d, a, b, inp[11], S33, 0x6D9D6122L) # 35 254 | b = XX(H, b, c, d, a, inp[14], S34, 0xFDE5380CL) # 36 255 | a = XX(H, a, b, c, d, inp[ 1], S31, 0xA4BEEA44L) # 37 256 | d = XX(H, d, a, b, c, inp[ 4], S32, 0x4BDECFA9L) # 38 257 | c = XX(H, c, d, a, b, inp[ 7], S33, 0xF6BB4B60L) # 39 258 | b = XX(H, b, c, d, a, inp[10], S34, 0xBEBFBC70L) # 40 259 | a = XX(H, a, b, c, d, inp[13], S31, 0x289B7EC6L) # 41 260 | d = XX(H, d, a, b, c, inp[ 0], S32, 0xEAA127FAL) # 42 261 | c = XX(H, c, d, a, b, inp[ 3], S33, 0xD4EF3085L) # 43 262 | b = XX(H, b, c, d, a, inp[ 6], S34, 0x04881D05L) # 44 263 | a = XX(H, a, b, c, d, inp[ 9], S31, 0xD9D4D039L) # 45 264 | d = XX(H, d, a, b, c, inp[12], S32, 0xE6DB99E5L) # 46 265 | c = XX(H, c, d, a, b, inp[15], S33, 0x1FA27CF8L) # 47 266 | b = XX(H, b, c, d, a, inp[ 2], S34, 0xC4AC5665L) # 48 267 | 268 | # Round 4. 269 | 270 | S41, S42, S43, S44 = 6, 10, 15, 21 271 | 272 | a = XX(I, a, b, c, d, inp[ 0], S41, 0xF4292244L) # 49 273 | d = XX(I, d, a, b, c, inp[ 7], S42, 0x432AFF97L) # 50 274 | c = XX(I, c, d, a, b, inp[14], S43, 0xAB9423A7L) # 51 275 | b = XX(I, b, c, d, a, inp[ 5], S44, 0xFC93A039L) # 52 276 | a = XX(I, a, b, c, d, inp[12], S41, 0x655B59C3L) # 53 277 | d = XX(I, d, a, b, c, inp[ 3], S42, 0x8F0CCC92L) # 54 278 | c = XX(I, c, d, a, b, inp[10], S43, 0xFFEFF47DL) # 55 279 | b = XX(I, b, c, d, a, inp[ 1], S44, 0x85845DD1L) # 56 280 | a = XX(I, a, b, c, d, inp[ 8], S41, 0x6FA87E4FL) # 57 281 | d = XX(I, d, a, b, c, inp[15], S42, 0xFE2CE6E0L) # 58 282 | c = XX(I, c, d, a, b, inp[ 6], S43, 0xA3014314L) # 59 283 | b = XX(I, b, c, d, a, inp[13], S44, 0x4E0811A1L) # 60 284 | a = XX(I, a, b, c, d, inp[ 4], S41, 0xF7537E82L) # 61 285 | d = XX(I, d, a, b, c, inp[11], S42, 0xBD3AF235L) # 62 286 | c = XX(I, c, d, a, b, inp[ 2], S43, 0x2AD7D2BBL) # 63 287 | b = XX(I, b, c, d, a, inp[ 9], S44, 0xEB86D391L) # 64 288 | 289 | A = (A + a) & 0xffffffffL 290 | B = (B + b) & 0xffffffffL 291 | C = (C + c) & 0xffffffffL 292 | D = (D + d) & 0xffffffffL 293 | 294 | self.A, self.B, self.C, self.D = A, B, C, D 295 | 296 | 297 | # Down from here all methods follow the Python Standard Library 298 | # API of the md5 module. 299 | 300 | def update(self, inBuf): 301 | """Add to the current message. 302 | 303 | Update the md5 object with the string arg. Repeated calls 304 | are equivalent to a single call with the concatenation of all 305 | the arguments, i.e. m.update(a); m.update(b) is equivalent 306 | to m.update(a+b). 307 | """ 308 | leninBuf = long(len(inBuf)) 309 | 310 | # Compute number of bytes mod 64. 311 | index = (self.count[0] >> 3) & 0x3FL 312 | 313 | # Update number of bits. 314 | self.count[0] = self.count[0] + (leninBuf << 3) 315 | if self.count[0] < (leninBuf << 3): 316 | self.count[1] = self.count[1] + 1 317 | self.count[1] = self.count[1] + (leninBuf >> 29) 318 | 319 | partLen = 64 - index 320 | 321 | if leninBuf >= partLen: 322 | self.input[index:] = map(None, inBuf[:partLen]) 323 | self._transform(_bytelist2long(self.input)) 324 | i = partLen 325 | while i + 63 < leninBuf: 326 | self._transform(_bytelist2long(map(None, inBuf[i:i+64]))) 327 | i = i + 64 328 | else: 329 | self.input = map(None, inBuf[i:leninBuf]) 330 | else: 331 | i = 0 332 | self.input = self.input + map(None, inBuf) 333 | 334 | 335 | def digest(self): 336 | """Terminate the message-digest computation and return digest. 337 | 338 | Return the digest of the strings passed to the update() 339 | method so far. This is a 16-byte string which may contain 340 | non-ASCII characters, including null bytes. 341 | """ 342 | 343 | A = self.A 344 | B = self.B 345 | C = self.C 346 | D = self.D 347 | input = [] + self.input 348 | count = [] + self.count 349 | 350 | index = (self.count[0] >> 3) & 0x3fL 351 | 352 | if index < 56: 353 | padLen = 56 - index 354 | else: 355 | padLen = 120 - index 356 | 357 | padding = ['\200'] + ['\000'] * 63 358 | self.update(padding[:padLen]) 359 | 360 | # Append length (before padding). 361 | bits = _bytelist2long(self.input[:56]) + count 362 | 363 | self._transform(bits) 364 | 365 | # Store state in digest. 366 | digest = _long2bytes(self.A << 96, 16)[:4] + \ 367 | _long2bytes(self.B << 64, 16)[4:8] + \ 368 | _long2bytes(self.C << 32, 16)[8:12] + \ 369 | _long2bytes(self.D, 16)[12:] 370 | 371 | self.A = A 372 | self.B = B 373 | self.C = C 374 | self.D = D 375 | self.input = input 376 | self.count = count 377 | 378 | return digest 379 | 380 | 381 | def hexdigest(self): 382 | """Terminate and return digest in HEX form. 383 | 384 | Like digest() except the digest is returned as a string of 385 | length 32, containing only hexadecimal digits. This may be 386 | used to exchange the value safely in email or other non- 387 | binary environments. 388 | """ 389 | 390 | d = map(None, self.digest()) 391 | d = map(ord, d) 392 | d = map(lambda x:"%02x" % x, d) 393 | d = string.join(d, '') 394 | 395 | return d 396 | 397 | 398 | def copy(self): 399 | """Return a clone object. 400 | 401 | Return a copy ('clone') of the md5 object. This can be used 402 | to efficiently compute the digests of strings that share 403 | a common initial substring. 404 | """ 405 | 406 | return copy.deepcopy(self) 407 | 408 | 409 | # ====================================================================== 410 | # Mimick Python top-level functions from standard library API 411 | # for consistency with the md5 module of the standard library. 412 | # ====================================================================== 413 | 414 | def new(arg=None): 415 | """Return a new md5 object. 416 | 417 | If arg is present, the method call update(arg) is made. 418 | """ 419 | 420 | md5 = MD5() 421 | if arg: 422 | md5.update(arg) 423 | 424 | return md5 425 | 426 | 427 | def md5(arg=None): 428 | """Same as new(). 429 | 430 | For backward compatibility reasons, this is an alternative 431 | name for the new() function. 432 | """ 433 | 434 | return new(arg) 435 | -------------------------------------------------------------------------------- /plugins/README.md: -------------------------------------------------------------------------------- 1 | IDA Plugins 2 | === 3 | 4 | Collection of IDA plugins that I've written to help with RE work. 5 | 6 | To install all plugins, run: 7 | 8 | ``` 9 | $ python ./install.py --install -d /path/to/your/ida/install/directory 10 | ``` 11 | 12 | To install only selected plugins, just drop the .py file(s) into IDA's `plugins` directory. 13 | 14 | alleycat 15 | ---------- 16 | 17 | * Finds paths between two or more functions 18 | * Generates interactive call graphs 19 | * Fully scriptable 20 | 21 | codatify 22 | -------- 23 | 24 | * Defines ASCII strings that IDA's auto analysis missed 25 | * Defines functions/code that IDA's auto analysis missed 26 | * Converts all undefined bytes in the data segment into DWORDs (thus allowing IDA to resolve function and jump table pointers) 27 | 28 | fluorescence 29 | ------------ 30 | 31 | * Highlights all call instructions in an IDB. 32 | 33 | leafblower 34 | ---------- 35 | 36 | * Assists in identifying standard POSIX functions in MIPS/ARM code. 37 | 38 | localxrefs 39 | ---------- 40 | 41 | * Finds references from within the current function to any highlighted text 42 | 43 | mipslocalvars 44 | ------------- 45 | 46 | * Names stack variables used by the compiler for storing registers on the stack, simplifying stack data analysis (MIPS only) 47 | 48 | mipsrop 49 | ------- 50 | 51 | * Allows you to search for suitable ROP gadgets in MIPS executable code (MIPS only) 52 | 53 | rizzo 54 | ----- 55 | 56 | * Performs function signature generation and matching to identify common functions between different IDBs 57 | 58 | -------------------------------------------------------------------------------- /plugins/alleycat/README.md: -------------------------------------------------------------------------------- 1 | alleycat.py 2 | ================ 3 | 4 | Features 5 | -------- 6 | 7 | * Finds paths to a given code block inside a function 8 | * Finds paths between two or more functions 9 | * Generates interactive call graphs 10 | * Fully scriptable 11 | 12 | Usage 13 | ----- 14 | 15 | In IDA's UI, navigate to `View->Graphs->Find paths from the current function to...`; this will search for call paths from the function your cursor is currently in to one or more destination functions. 16 | 17 | Select a function from the function chooser dialog and click `OK`. 18 | 19 | You will be prompted again to choose another function; you may continue this process to select as many destination functions as you'd like. Once you are finished, click `Cancel` or press the `Esc` button. 20 | 21 | The call graph is interactive; double-clicking on a graph node will jump to that location in IDA's disassembly window. You may also dynamically change which nodes are displayed at any time using the following hotkeys: 22 | 23 | * To only show paths that traverse a particular node: right-click the graph, select `Include node`, and then click on the node. 24 | * To exclude all paths that traverse a particular node: right-click the graph, select `Exclude node` and then click on the node. 25 | * To undo any of the above actions: right-click the graph, and select `Undo`. 26 | * To redo an undo: right-click the graph, and select `Redo`. 27 | * To reset the graph: right-click the graph, and select `Reset graph`. 28 | 29 | For practical purposes, there is a maximum depth limit imposed on path searches. You can increase or decrease this limit in the IDAPython terminal: 30 | 31 | ``` 32 | Python> print ALLEYCAT_LIMIT 33 | 10000 34 | Python> ALLEYCAT_LIMIT = 2500 35 | ``` 36 | 37 | Scripting 38 | --------- 39 | 40 | To generate a list of unique paths between two functions, use the `AlleyCatFunctionPaths` class: 41 | 42 | ``` 43 | Python> print AlleyCatFunctionPaths(ScreenEA(), idc.LocByName('strcpy')).paths 44 | ``` 45 | 46 | To generate a list of unique paths from the between two code blocks inside a function, use the `AlleyCatCodePaths` class: 47 | 48 | ``` 49 | Python> print AlleyCatCodePaths(idaapi.get_func(idc.ScreenEA()).startEA, idc.ScreenEA()).paths 50 | ``` 51 | 52 | To create an interactive graph, use the `AlleyCatGraph` class: 53 | 54 | ``` 55 | Python> paths = AlleyCatFunctionPaths(ScreenEA(), idc.LocByName('strcpy')).paths 56 | Python> graph = AlleyCatGraph(paths) 57 | Python> graph.Show() 58 | ``` 59 | 60 | Installation 61 | ------------ 62 | 63 | Just copy alleycat.py into your IDA `plugins` directory. 64 | 65 | -------------------------------------------------------------------------------- /plugins/alleycat/alleycat.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import idc 3 | import time 4 | import idaapi 5 | import idautils 6 | 7 | from shims import ida_shims 8 | 9 | # This limits the depth of any individual path, as well as the maximum 10 | # number of paths that will be searched; this is needed for practical 11 | # reasons, as IDBs with tens of thousands of functions take a long time 12 | # to exhaust all possible paths without some practical limitation. 13 | # 14 | # This is global so it's easy to change from the IDAPython prompt. 15 | ALLEYCAT_LIMIT = 10000 16 | 17 | 18 | def add_to_namespace(namespace, source, name, variable): 19 | ''' 20 | Add a variable to a different namespace, likely __main__. 21 | ''' 22 | import importlib 23 | 24 | importer_module = sys.modules[namespace] 25 | if source in list(sys.modules.keys()): 26 | if not (sys.version_info.major == 3 and sys.version_info.minor >= 4): 27 | import imp 28 | imp.reload(sys.modules[source]) 29 | else: 30 | importlib.reload(sys.modules[source]) 31 | else: 32 | m = importlib.import_module(source, None) 33 | sys.modules[source] = m 34 | 35 | setattr(importer_module, name, variable) 36 | 37 | 38 | class AlleyCatException(Exception): 39 | pass 40 | 41 | 42 | class AlleyCat(object): 43 | ''' 44 | Class which resolves code paths. This is where most of the work is done. 45 | ''' 46 | 47 | def __init__(self, start, end, quiet=False): 48 | ''' 49 | Class constructor. 50 | 51 | @start - The start address. 52 | @end - The end address. 53 | 54 | Returns None. 55 | ''' 56 | global ALLEYCAT_LIMIT 57 | self.limit = ALLEYCAT_LIMIT 58 | self.paths = [] 59 | self.quiet = quiet 60 | 61 | # We work backwards via xrefs, so we start at the end and end at the 62 | # start 63 | if not self.quiet: 64 | print("Generating call paths from %s to %s..." % (self._name(end), 65 | self._name(start))) 66 | self._build_paths(start, end) 67 | 68 | def _name(self, ea): 69 | name = ida_shims.get_name(ea) 70 | if not name: 71 | name = ida_shims.get_func_off_str(ea) 72 | if not name: 73 | name = '0x%X' % ea 74 | return name 75 | 76 | def _add_path(self, path): 77 | if path not in self.paths: 78 | self.paths.append(path) 79 | 80 | def _build_paths(self, start, end=idc.BADADDR): 81 | partial_paths = [[start]] 82 | 83 | # Loop while there are still unresolve paths and while all path sizes 84 | # have not exceeded ALLEYCAT_LIMIT 85 | while partial_paths and \ 86 | len(self.paths) < self.limit and \ 87 | len(partial_paths) < self.limit: 88 | callers = set() 89 | 90 | # Callee is the last entry of the first path in partial paths. 91 | # The first path list will change as paths are completed and 92 | # popped from the list. 93 | callee = partial_paths[0][-1] 94 | 95 | # Find all unique functions that reference the callee, assuming this 96 | # path has not exceeded ALLEYCAT_LIMIT. 97 | if len(partial_paths[0]) < self.limit: 98 | for xref in idautils.XrefsTo(callee): 99 | caller = self._get_code_block(xref.frm) 100 | if caller: 101 | start_ea = ida_shims.start_ea(caller) 102 | 103 | if start_ea not in callers: 104 | callers.add(start_ea) 105 | 106 | # If there are callers to the callee, remove the callee's current 107 | # path and insert new ones with the new callers appended. 108 | if callers: 109 | base_path = partial_paths.pop(0) 110 | for caller in callers: 111 | 112 | # Don't want to loop back on ourselves in the same path 113 | if caller in base_path: 114 | continue 115 | 116 | # If we've reached the desired end node, don't go any 117 | # further down this path 118 | if caller == end: 119 | self._add_path((base_path + [caller])[::-1]) 120 | else: 121 | partial_paths.append(base_path + [caller]) 122 | # Else, our end node is not in this path, so don't include it in the 123 | # finished path list. 124 | elif end not in partial_paths[0]: 125 | partial_paths.pop(0) 126 | # If there were no callers then this path has been exhausted and 127 | # should be popped from the partial path list into the finished path list. 128 | elif end in partial_paths[0]: 129 | # Paths start with the end function and end with the start 130 | # function; reverse it. 131 | self._add_path(partial_paths.pop(0)[::-1]) 132 | 133 | def _get_code_block(self, ea): 134 | return idaapi.get_func(ea) 135 | 136 | 137 | class AlleyCatFunctionPaths(AlleyCat): 138 | 139 | def __init__(self, start_ea, end_ea, quiet=False): 140 | 141 | # We work backwards via xrefs, so we start at the end and end at the start 142 | try: 143 | func = idaapi.get_func(end_ea) 144 | start = ida_shims.start_ea(func) 145 | except: 146 | raise AlleyCatException("Address 0x%X is not part of a function!" % 147 | end_ea) 148 | try: 149 | func = idaapi.get_func(start_ea) 150 | end = ida_shims.start_ea(func) 151 | except: 152 | end = idc.BADADDR 153 | 154 | super(AlleyCatFunctionPaths, self).__init__(start, end, quiet) 155 | 156 | 157 | class AlleyCatCodePaths(AlleyCat): 158 | 159 | def __init__(self, start_ea, end_ea, quiet=False): 160 | end_func = idaapi.get_func(end_ea) 161 | start_func = idaapi.get_func(start_ea) 162 | 163 | if not start_func: 164 | raise AlleyCatException("Address 0x%X is not part of a function!" % 165 | start_ea) 166 | if not end_func: 167 | raise AlleyCatException("Address 0x%X is not part of a function!" % 168 | end_ea) 169 | 170 | start_func_ea = ida_shims.start_ea(start_func) 171 | end_func_ea = ida_shims.start_ea(end_func) 172 | 173 | if start_func_ea != end_func_ea: 174 | raise AlleyCatException("The start and end addresses are not part " 175 | "of the same function!") 176 | 177 | self.func = start_func 178 | self.blocks = [block for block in idaapi.FlowChart(self.func)] 179 | 180 | # We work backwards via xrefs, so we start at the end and end at the start 181 | end_block = self._get_code_block(start_ea) 182 | start_block = self._get_code_block(end_ea) 183 | 184 | if not end_block: 185 | raise AlleyCatException("Failed to find the code block associated " 186 | "with address 0x%X" % start_ea) 187 | if not start_block: 188 | raise AlleyCatException("Failed to find the code block associated " 189 | "with address 0x%X" % end_ea) 190 | 191 | start_block_ea = ida_shims.start_ea(start_block) 192 | end_block_ea = ida_shims.start_ea(end_block) 193 | 194 | super(AlleyCatCodePaths, self).__init__( 195 | start_block_ea, end_block_ea, quiet) 196 | 197 | def _get_code_block(self, ea): 198 | for block in self.blocks: 199 | start_ea = ida_shims.start_ea(block) 200 | end_ea = ida_shims.end_ea(block) 201 | if start_ea <= ea and end_ea > ea: 202 | return block 203 | return None 204 | 205 | 206 | # Everything below here is just IDA UI/Plugin stuff 207 | 208 | 209 | class AlleyCatGraphHistory(object): 210 | ''' 211 | Manages include/exclude graph history. 212 | ''' 213 | 214 | INCLUDE_ACTION = 0 215 | EXCLUDE_ACTION = 1 216 | 217 | def __init__(self): 218 | self.history = [] 219 | self.includes = [] 220 | self.excludes = [] 221 | self.history_index = 0 222 | self.include_index = 0 223 | self.exclude_index = 0 224 | 225 | def reset(self): 226 | self.history = [] 227 | self.includes = [] 228 | self.excludes = [] 229 | self.history_index = 0 230 | self.include_index = 0 231 | self.exclude_index = 0 232 | 233 | def update_history(self, action): 234 | if self.excludes and len(self.history)-1 != self.history_index: 235 | self.history = self.history[0:self.history_index+1] 236 | self.history.append(action) 237 | self.history_index = len(self.history)-1 238 | 239 | def add_include(self, obj): 240 | if self.includes and len(self.includes)-1 != self.include_index: 241 | self.includes = self.includes[0:self.include_index+1] 242 | self.includes.append(obj) 243 | self.include_index = len(self.includes)-1 244 | self.update_history(self.INCLUDE_ACTION) 245 | 246 | def add_exclude(self, obj): 247 | if len(self.excludes)-1 != self.exclude_index: 248 | self.excludes = self.excludes[0:self.exclude_index+1] 249 | self.excludes.append(obj) 250 | self.exclude_index = len(self.excludes)-1 251 | self.update_history(self.EXCLUDE_ACTION) 252 | 253 | def get_includes(self): 254 | return set(self.includes[0:self.include_index+1]) 255 | 256 | def get_excludes(self): 257 | return set(self.excludes[0:self.exclude_index+1]) 258 | 259 | def undo(self): 260 | if self.history: 261 | if self.history[self.history_index] == self.INCLUDE_ACTION: 262 | if self.include_index >= 0: 263 | self.include_index -= 1 264 | elif self.history[self.history_index] == self.EXCLUDE_ACTION: 265 | if self.exclude_index >= 0: 266 | self.exclude_index -= 1 267 | 268 | self.history_index -= 1 269 | if self.history_index < 0: 270 | self.history_index = 0 271 | 272 | def redo(self): 273 | self.history_index += 1 274 | if self.history_index >= len(self.history): 275 | self.history_index = len(self.history)-1 276 | 277 | if self.history[self.history_index] == self.INCLUDE_ACTION: 278 | if self.include_index < len(self.includes)-1: 279 | self.include_index += 1 280 | elif self.history[self.history_index] == self.EXCLUDE_ACTION: 281 | if self.exclude_index < len(self.excludes)-1: 282 | self.exclude_index += 1 283 | 284 | 285 | class AlleyCatGraph(idaapi.GraphViewer): 286 | ''' 287 | Displays the graph and manages graph actions. 288 | ''' 289 | def __init__(self, results, title="AlleyCat Graph"): 290 | idaapi.GraphViewer.__init__(self, title) 291 | self.results = results 292 | 293 | self.nodes_ea2id = {} 294 | self.nodes_id2ea = {} 295 | self.edges = {} 296 | self.end_nodes = [] 297 | self.edge_nodes = [] 298 | self.start_nodes = [] 299 | 300 | self.history = AlleyCatGraphHistory() 301 | self.include_on_click = False 302 | self.exclude_on_click = False 303 | 304 | self.cmd_undo = None 305 | self.cmd_redo = None 306 | self.cmd_reset = None 307 | self.cmd_exclude = None 308 | self.cmd_include = None 309 | self.cmd_unhighlight = None 310 | 311 | def Show(self): 312 | ''' 313 | Display the graph. 314 | 315 | Returns True on success, False on failure. 316 | ''' 317 | if not idaapi.GraphViewer.Show(self): 318 | return False 319 | else: 320 | self.cmd_undo = self.AddCommand("Undo", "") 321 | self.cmd_redo = self.AddCommand("Redo", "") 322 | self.cmd_reset = self.AddCommand("Reset graph", "") 323 | self.cmd_exclude = self.AddCommand("Exclude node", "") 324 | self.cmd_include = self.AddCommand("Include node", "") 325 | self.cmd_unhighlight = self.AddCommand( 326 | "Temporarily un-highlight all paths", "") 327 | return True 328 | 329 | def OnRefresh(self): 330 | # Clear the graph before refreshing 331 | self.clear() 332 | self.nodes_ea2id = {} 333 | self.nodes_id2ea = {} 334 | self.edges = {} 335 | self.end_nodes = [] 336 | self.edge_nodes = [] 337 | self.start_nodes = [] 338 | 339 | includes = self.history.get_includes() 340 | excludes = self.history.get_excludes() 341 | 342 | for path in self.results: 343 | parent_node = None 344 | 345 | # Check to see if this path contains all nodes marked for explicit 346 | # inclusion 347 | if (set(path) & includes) != includes: 348 | continue 349 | 350 | # Check to see if this path contains any nodes marked for explicit 351 | # exclusion 352 | if (set(path) & excludes) != set(): 353 | continue 354 | 355 | for ea in path: 356 | # If this node already exists, use its existing node ID 357 | if ea in self.nodes_ea2id: 358 | this_node = self.nodes_ea2id[ea] 359 | # Else, add this node to the graph 360 | else: 361 | this_node = self.AddNode(self.get_name_by_ea(ea)) 362 | self.nodes_ea2id[ea] = this_node 363 | self.nodes_id2ea[this_node] = ea 364 | 365 | # If there is a parent node, add an edge between the parent node 366 | # and this one 367 | if parent_node is not None: 368 | self.AddEdge(parent_node, this_node) 369 | if this_node not in self.edges[parent_node]: 370 | self.edges[parent_node].append(this_node) 371 | 372 | # Update the parent node for the next loop 373 | parent_node = this_node 374 | if parent_node not in self.edges: 375 | self.edges[parent_node] = [] 376 | 377 | # Highlight this node in the disassembly window 378 | self.highlight(ea) 379 | 380 | try: 381 | # Track the first, last, and next to last nodes in each path for 382 | # proper colorization in self.OnGetText. 383 | self.start_nodes.append(self.nodes_ea2id[path[0]]) 384 | self.end_nodes.append(self.nodes_ea2id[path[-1]]) 385 | self.edge_nodes.append(self.nodes_ea2id[path[-2]]) 386 | except: 387 | pass 388 | 389 | return True 390 | 391 | def OnGetText(self, node_id): 392 | color = idc.DEFCOLOR 393 | 394 | if node_id in self.edge_nodes: 395 | color = 0x00ffff 396 | elif node_id in self.start_nodes: 397 | color = 0x00ff00 398 | elif node_id in self.end_nodes: 399 | color = 0x0000ff 400 | 401 | return self[node_id], color 402 | 403 | def OnHint(self, node_id): 404 | hint = "" 405 | 406 | try: 407 | for edge_node in self.edges[node_id]: 408 | hint += "%s\n" % self[edge_node] 409 | except: 410 | pass 411 | 412 | return hint 413 | 414 | def OnCommand(self, cmd_id): 415 | if self.cmd_undo == cmd_id: 416 | if self.include_on_click or self.exclude_on_click: 417 | self.include_on_click = False 418 | self.exclude_on_click = False 419 | else: 420 | self.history.undo() 421 | self.Refresh() 422 | elif self.cmd_redo == cmd_id: 423 | self.history.redo() 424 | self.Refresh() 425 | elif self.cmd_include == cmd_id: 426 | self.include_on_click = True 427 | elif self.cmd_exclude == cmd_id: 428 | self.exclude_on_click = True 429 | elif self.cmd_reset == cmd_id: 430 | self.include_on_click = False 431 | self.exclude_on_click = False 432 | self.history.reset() 433 | self.Refresh() 434 | elif self.cmd_unhighlight == cmd_id: 435 | self.unhighlight_all() 436 | 437 | def OnClick(self, node_id): 438 | if self.include_on_click: 439 | self.history.add_include(self.nodes_id2ea[node_id]) 440 | self.include_on_click = False 441 | elif self.exclude_on_click: 442 | self.history.add_exclude(self.nodes_id2ea[node_id]) 443 | self.exclude_on_click = False 444 | self.Refresh() 445 | 446 | def OnDblClick(self, node_id): 447 | xref_locations = [] 448 | node_ea = self.get_ea_by_name(self[node_id]) 449 | 450 | if node_id in self.edges: 451 | for edge_node_id in self.edges[node_id]: 452 | 453 | edge_node_name = self[edge_node_id] 454 | edge_node_ea = self.get_ea_by_name(edge_node_name) 455 | 456 | if edge_node_ea != idc.BADADDR: 457 | for xref in idautils.XrefsTo(edge_node_ea): 458 | # Is the specified node_id the source of this xref? 459 | if self.match_xref_source(xref, node_ea): 460 | xref_locations.append((xref.frm, edge_node_ea)) 461 | 462 | if xref_locations: 463 | xref_locations.sort() 464 | 465 | print("") 466 | print("Path Xrefs from %s:" % self[node_id]) 467 | print("-" * 100) 468 | for (xref_ea, dst_ea) in xref_locations: 469 | print("%-50s => %s" % (self.get_name_by_ea(xref_ea), 470 | self.get_name_by_ea(dst_ea))) 471 | print("-" * 100) 472 | print("") 473 | 474 | ida_shims.jumpto(xref_locations[0][0]) 475 | else: 476 | ida_shims.jumpto(node_ea) 477 | 478 | def OnClose(self): 479 | if ida_shims.ask_yn(1, "Path nodes have been highlighted in the " 480 | "disassembly window. Undo highlighting?") == 1: 481 | self.unhighlight_all() 482 | 483 | def match_xref_source(self, xref, source): 484 | return ((xref.type != idc.fl_F) and 485 | (ida_shims.get_func_attr(xref.frm, idc.FUNCATTR_START) == source)) 486 | 487 | def get_ea_by_name(self, name): 488 | ''' 489 | Get the address of a location by name. 490 | 491 | @name - Location name 492 | 493 | Returns the address of the named location, or idc.BADADDR on failure. 494 | ''' 495 | # This allows support of the function offset style names (e.g., main+0C) 496 | ea = 0 497 | if '+' in name: 498 | (func_name, offset) = name.split('+') 499 | base_ea = ida_shims.get_name_ea_simple(func_name) 500 | if base_ea != idc.BADADDR: 501 | try: 502 | ea = base_ea + int(offset, 16) 503 | except: 504 | ea = idc.BADADDR 505 | else: 506 | ea = ida_shims.get_name_ea_simple(name) 507 | if ea == idc.BADADDR: 508 | try: 509 | ea = int(name, 0) 510 | except: 511 | ea = idc.BADADDR 512 | 513 | return ea 514 | 515 | def clear(self): 516 | # Clears the graph and unhighlights the disassembly 517 | self.Clear() 518 | self.unhighlight_all() 519 | 520 | def get_name_by_ea(self, ea): 521 | ''' 522 | Get the name of the specified address. 523 | 524 | @ea - Address. 525 | 526 | Returns a name for the address, one of idc.Name, idc.GetFuncOffset or 527 | 0xXXXXXXXX. 528 | ''' 529 | name = ida_shims.get_name(ea) 530 | if not name: 531 | name = ida_shims.get_func_off_str(ea) 532 | if not name: 533 | name = "0x%X" % ea 534 | return name 535 | 536 | def colorize_node(self, ea, color): 537 | # Colorizes an entire code block 538 | func = idaapi.get_func(ea) 539 | if func: 540 | for block in idaapi.FlowChart(func): 541 | block_start_ea = ida_shims.start_ea(block) 542 | block_end_ea = ida_shims.end_ea(block) 543 | 544 | if block_start_ea <= ea and block_end_ea > ea: 545 | ea = block_start_ea 546 | while ea < block_end_ea: 547 | idaapi.set_item_color(ea, color) 548 | ea = ida_shims.next_head(ea) 549 | break 550 | 551 | def highlight(self, ea): 552 | # Highlights an entire code block 553 | self.colorize_node(ea, 0x00FF00) 554 | 555 | def unhighlight(self, ea): 556 | # Unhighlights an entire code block 557 | self.colorize_node(ea, idc.DEFCOLOR) 558 | 559 | def unhighlight_all(self): 560 | # Unhighlights all code blocks 561 | for path in self.results: 562 | for ea in path: 563 | self.unhighlight(ea) 564 | 565 | 566 | class AlleyCatPaths(object): 567 | def _current_function(self): 568 | function = idaapi.get_func(ida_shims.get_screen_ea()) 569 | return ida_shims.start_ea(function) 570 | 571 | def _find_and_plot_paths(self, sources, targets, klass=AlleyCatFunctionPaths): 572 | results = [] 573 | 574 | for target in targets: 575 | for source in sources: 576 | s = time.time() 577 | r = klass(source, target).paths 578 | e = time.time() 579 | print("Found %d paths in %f seconds." % (len(r), (e-s))) 580 | 581 | if r: 582 | results += r 583 | else: 584 | name = ida_shims.get_name(target) 585 | if not name: 586 | name = "0x%X" % target 587 | print("No paths found to", name) 588 | 589 | if results: 590 | # Be sure to close any previous graph before creating a new one. 591 | # Failure to do so may crash IDA. 592 | try: 593 | self.graph.Close() 594 | except: 595 | pass 596 | 597 | self.graph = AlleyCatGraph(results, 'Path Graph') 598 | self.graph.Show() 599 | 600 | def _get_user_selected_functions(self, many=False): 601 | functions = [] 602 | ea = ida_shims.get_screen_ea() 603 | try: 604 | current_function = ida_shims.get_func_attr(ea, idc.FUNCATTR_START) 605 | except: 606 | current_function = None 607 | 608 | while True: 609 | function = ida_shims.choose_func( 610 | "Select a function and click 'OK' until all functions have " 611 | "been selected. When finished, click 'Cancel' to display the " 612 | "graph.") 613 | 614 | if ida_shims.get_screen_ea() != ea: 615 | ida_shims.jumpto(ea) 616 | 617 | if not function or \ 618 | function == idc.BADADDR or function == current_function: 619 | break 620 | elif function not in functions: 621 | functions.append(function) 622 | 623 | if not many: 624 | break 625 | 626 | return functions 627 | 628 | def FindPathsToCodeBlock(self): 629 | target = ida_shims.get_screen_ea() 630 | source = self._current_function() 631 | 632 | if source: 633 | self._find_and_plot_paths( 634 | [source], [target], klass=AlleyCatCodePaths) 635 | 636 | def FindPathsToMany(self): 637 | source = self._current_function() 638 | 639 | if source: 640 | targets = self._get_user_selected_functions(many=True) 641 | if targets: 642 | self._find_and_plot_paths([source], targets) 643 | 644 | def FindPathsFromMany(self): 645 | target = self._current_function() 646 | 647 | if target: 648 | sources = self._get_user_selected_functions(many=True) 649 | if sources: 650 | self._find_and_plot_paths(sources, [target]) 651 | 652 | 653 | # Helper functions to execute commands selected from dropdown menus. 654 | # args parameter is required for IDA version < 7.0 655 | def find_paths_from_many(arg=None): 656 | AlleyCatPaths().FindPathsFromMany() 657 | 658 | 659 | def find_paths_to_many(arg=None): 660 | AlleyCatPaths().FindPathsToMany() 661 | 662 | 663 | def find_paths_to_code_block(args=None): 664 | AlleyCatPaths().FindPathsToCodeBlock() 665 | 666 | 667 | try: 668 | class ToCurrentFromAction(idaapi.action_handler_t): 669 | def __init__(self): 670 | idaapi.action_handler_t.__init__(self) 671 | 672 | def activate(self, ctx): 673 | find_paths_from_many() 674 | return 1 675 | 676 | def update(self, ctx): 677 | return idaapi.AST_ENABLE_ALWAYS 678 | 679 | 680 | class FromCurrentToAction(idaapi.action_handler_t): 681 | def __init__(self): 682 | idaapi.action_handler_t.__init__(self) 683 | 684 | def activate(self, ctx): 685 | find_paths_to_many() 686 | return 1 687 | 688 | def update(self, ctx): 689 | return idaapi.AST_ENABLE_ALWAYS 690 | 691 | class InCurrentFunctionToCurrentCodeBlockAction(idaapi.action_handler_t): 692 | def __init__(self): 693 | idaapi.action_handler_t.__init__(self) 694 | 695 | def activate(self, ctx): 696 | find_paths_to_code_block() 697 | return 1 698 | 699 | def update(self, ctx): 700 | return idaapi.AST_ENABLE_ALWAYS 701 | except AttributeError: 702 | pass 703 | 704 | 705 | class idapathfinder_t(idaapi.plugin_t): 706 | flags = 0 707 | comment = '' 708 | help = '' 709 | wanted_name = 'AlleyCat' 710 | wanted_hotkey = '' 711 | menu_name = 'View/Graphs/' 712 | menu_context = [] 713 | 714 | to_from_action_name = 'tocurrfrom:action' 715 | from_to_action_name = 'fromcurrto:action' 716 | curr_func_curr_block_action_name = 'currfunccurrblock:action' 717 | 718 | to_from_menu_name = 'Find paths to the current function from...' 719 | from_to_menu_name = 'Find paths from the current function to...' 720 | curr_func_curr_block_menu_name = 'Find paths in the current function to ' \ 721 | 'the current code block' 722 | 723 | def init(self): 724 | if idaapi.IDA_SDK_VERSION >= 700: 725 | # Add ALLEYCAT_LIMIT variable to the global namespace so it can be 726 | # accessed from the IDA python terminal. 727 | global ALLEYCAT_LIMIT 728 | add_to_namespace( 729 | '__main__', 'alleycat', 'ALLEYCAT_LIMIT', ALLEYCAT_LIMIT) 730 | 731 | # Add functions to global namespace. 732 | add_to_namespace( 733 | '__main__', 'alleycat', 'AlleyCatFunctionPaths', 734 | AlleyCatFunctionPaths) 735 | add_to_namespace( 736 | '__main__', 'alleycat', 'AlleyCatCodePaths', AlleyCatCodePaths) 737 | add_to_namespace( 738 | '__main__', 'alleycat', 'AlleyCatGraph', AlleyCatGraph) 739 | 740 | to_curr_from_desc = idaapi.action_desc_t( 741 | self.to_from_action_name, self.to_from_menu_name, 742 | ToCurrentFromAction(), self.wanted_hotkey, 743 | 'Find paths to the current function from...', 199) 744 | 745 | from_curr_to_desc = idaapi.action_desc_t( 746 | self.from_to_action_name, self.from_to_menu_name, 747 | FromCurrentToAction(), self.wanted_hotkey, 748 | 'Find paths from the current function to...', 199) 749 | 750 | curr_func_to_block_desc = idaapi.action_desc_t( 751 | self.curr_func_curr_block_action_name, 752 | self.curr_func_curr_block_menu_name, 753 | InCurrentFunctionToCurrentCodeBlockAction(), 754 | self.wanted_hotkey, 755 | 'Find paths in the current function to the current code block', 756 | 199) 757 | 758 | idaapi.register_action(to_curr_from_desc) 759 | idaapi.register_action(from_curr_to_desc) 760 | idaapi.register_action(curr_func_to_block_desc) 761 | 762 | idaapi.attach_action_to_menu( 763 | self.menu_name, self.to_from_action_name, idaapi.SETMENU_APP) 764 | idaapi.attach_action_to_menu( 765 | self.menu_name, self.from_to_action_name, idaapi.SETMENU_APP) 766 | idaapi.attach_action_to_menu( 767 | self.menu_name, self.curr_func_curr_block_action_name, 768 | idaapi.SETMENU_APP) 769 | 770 | else: 771 | self.menu_context.append( 772 | idaapi.add_menu_item( 773 | self.menu_name, self.to_from_menu_name, "", 0, 774 | find_paths_from_many, (None,))) 775 | 776 | self.menu_context.append( 777 | idaapi.add_menu_item( 778 | self.menu_name, self.from_to_menu_name, "", 0, 779 | find_paths_to_many, (None,))) 780 | 781 | self.menu_context.append( 782 | idaapi.add_menu_item( 783 | self.menu_name, self.curr_func_curr_block_menu_name, "", 0, 784 | find_paths_to_code_block, (None,))) 785 | 786 | return idaapi.PLUGIN_KEEP 787 | 788 | def term(self): 789 | if idaapi.IDA_SDK_VERSION >= 700: 790 | idaapi.detach_action_from_menu( 791 | self.menu_name, self.to_from_action_name) 792 | idaapi.detach_action_from_menu( 793 | self.menu_name, self.from_to_action_name) 794 | idaapi.detach_action_from_menu( 795 | self.menu_name, self.curr_func_curr_block_action_name) 796 | else: 797 | for context in self.menu_context: 798 | idaapi.del_menu_item(context) 799 | return None 800 | 801 | def run(self, arg): 802 | pass 803 | 804 | 805 | def PLUGIN_ENTRY(): 806 | return idapathfinder_t() 807 | -------------------------------------------------------------------------------- /plugins/codatify/README.md: -------------------------------------------------------------------------------- 1 | codatify.py 2 | =========== 3 | 4 | Features 5 | -------- 6 | 7 | * Defines ASCII strings that IDA's auto analysis missed 8 | * Defines functions/code that IDA's auto analysis missed 9 | * Converts all undefined bytes in the data segment into DWORDs (thus allowing IDA to resolve function and jump table pointers) 10 | 11 | Usage 12 | ----- 13 | 14 | Blob of data before running codatify: 15 | 16 | ![Before codatify.py](../../images/undefined_bytes.png) 17 | 18 | Running codatify: 19 | 20 | ![Running codatify.py](../../images/how_to_use_codatify.png) 21 | 22 | Blob of data after running codatify: 23 | 24 | ![After codatify.py](../../images/defined_bytes.png) 25 | 26 | Installation 27 | ------------ 28 | 29 | Just copy codatify.py into your IDA *plugins* directory. 30 | -------------------------------------------------------------------------------- /plugins/codatify/codatify.py: -------------------------------------------------------------------------------- 1 | # IDA plugin that converts all data in data segments to defined data types, and 2 | # all data in code segments to code. 3 | # 4 | # Use by going to Options->Define data and code. 5 | # 6 | # Craig Heffner 7 | # Tactical Network Solutions 8 | 9 | from __future__ import print_function 10 | import idc 11 | import string 12 | import idaapi 13 | import idautils 14 | 15 | from shims import ida_shims 16 | 17 | 18 | class StructEntry(object): 19 | def __init__(self, ea): 20 | self.ea = ea 21 | self.dword = ida_shims.get_wide_dword(self.ea) 22 | self.type = None 23 | self.value = None 24 | 25 | string = ida_shims.get_strlit_contents(self.dword) 26 | name = ida_shims.get_func_name(self.dword) 27 | if ida_shims.get_name_ea_simple(name) != self.dword: 28 | name = '' 29 | 30 | if name: 31 | self.type = int 32 | self.value = name 33 | elif string: 34 | self.type = str 35 | self.value = string 36 | 37 | 38 | class StructCast(object): 39 | def __init__(self, ea, n=16): 40 | self.entries = [] 41 | 42 | for i in range(0, n): 43 | self.entries.append(StructEntry(ea+(4*i))) 44 | 45 | 46 | class StructPatternDetector(object): 47 | def __init__(self, ea, n=16): 48 | self.address = ea 49 | self.stop = None 50 | self.start = None 51 | self.element_size = 4 52 | self.num_entries = 0 53 | self.num_elements = 0 54 | self.function_element = -1 55 | self.name_element = -1 56 | entry_element_count = None 57 | 58 | structure = StructCast(ea, n) 59 | 60 | if structure.entries[0].type is not None: 61 | (p1, p2) = self.get_entry_pair(structure) 62 | 63 | pattern_to_match = [x.type for x in structure.entries[p1:p2+1]] 64 | 65 | for i in range(0, n): 66 | address = ea + ((p2+i)*self.element_size) 67 | struct2 = StructCast(address, n) 68 | this_pattern = [x.type for x in struct2.entries[p1:p2+1]] 69 | if this_pattern == pattern_to_match: 70 | entry_element_count = int((address - ea) / self.element_size) 71 | break 72 | 73 | if entry_element_count is not None and entry_element_count > 0: 74 | num_entries = self.get_entry_count(ea, 75 | entry_element_count, 76 | [x.type for x in structure.entries[p1:entry_element_count]]) 77 | 78 | self.start = self.address 79 | self.num_elements = entry_element_count 80 | self.num_entries = num_entries 81 | self.stop = self.address + (self.num_elements * 82 | self.element_size * 83 | self.num_entries) 84 | 85 | if structure.entries[p1].type == str: 86 | self.name_element = p1 87 | self.function_element = p2 88 | else: 89 | self.name_element = p2 90 | self.function_element = p1 91 | 92 | def get_entry_count(self, start, entry_element_count, expected_pattern): 93 | entry_count = 1 94 | 95 | while True: 96 | address = start + (entry_element_count * self.element_size * entry_count) 97 | struct = StructCast(address, entry_element_count) 98 | this_pattern = [x.type for x in struct.entries[0:entry_element_count]] 99 | if this_pattern != expected_pattern: 100 | break 101 | else: 102 | entry_count += 1 103 | 104 | return entry_count 105 | 106 | def get_entry_pair(self, structure): 107 | end = 0 108 | start = 0 109 | count = 0 110 | 111 | for entry in structure.entries: 112 | if entry.type is not None and \ 113 | entry.type != structure.entries[0].type: 114 | end = count 115 | break 116 | count += 1 117 | 118 | return start, end 119 | 120 | 121 | class Struct(object): 122 | 123 | def __init__(self, **kwargs): 124 | for (k, v) in kwargs.items(): 125 | setattr(self, k, v) 126 | 127 | 128 | class StructFinder(object): 129 | def __init__(self): 130 | (self.start, self.stop) = self.get_data_section() 131 | 132 | def get_data_section(self): 133 | ea = idc.BADADDR 134 | seg = ida_shims.get_first_seg() 135 | stop = idc.BADADDR 136 | 137 | while seg != idc.BADADDR: 138 | if ea == idc.BADADDR and \ 139 | ida_shims.get_segm_attr(seg, idc.SEGATTR_TYPE) == 2: 140 | ea = seg 141 | 142 | stop = ida_shims.get_segm_end(seg) 143 | seg = ida_shims.get_next_seg(seg) 144 | 145 | return ea, stop 146 | 147 | def valid_function_name(self, name): 148 | allowed_characters = set(string.digits + string.ascii_letters + '_') 149 | allowed_first_character = set(string.ascii_letters + '_') 150 | 151 | if not name: 152 | return False 153 | elif name[0] not in allowed_first_character: 154 | return False 155 | elif len([x for x in name if x not in allowed_characters]) > 0: 156 | return False 157 | 158 | return True 159 | 160 | def search(self): 161 | patterns = [] 162 | 163 | print("Searching for data structure arrays from %s to %s" % \ 164 | (hex(self.start), hex(self.stop))) 165 | 166 | ea = self.start 167 | while ea < self.stop: 168 | pattern = StructPatternDetector(ea) 169 | if pattern.num_entries > 0: 170 | patterns.append(pattern) 171 | ea = pattern.stop 172 | else: 173 | ea += pattern.element_size 174 | 175 | i = 0 176 | while i < len(patterns): 177 | # Consolidate obvious duplicate consecutive structure arrays 178 | j = i + 1 179 | while j < len(patterns): 180 | if (patterns[i].stop == patterns[j].start and 181 | patterns[i].num_elements == patterns[j].num_elements and 182 | patterns[i].function_element == patterns[j].function_element and 183 | patterns[i].name_element == patterns[i].name_element): 184 | patterns[i].stop = patterns[j].stop 185 | patterns[i].num_entries += patterns[j].num_entries 186 | del patterns[j] 187 | else: 188 | j += 1 189 | 190 | print("Found an array of %d structures at 0x%X - 0x%X. Each " \ 191 | "entry has %d elements of %d bytes each." % \ 192 | (patterns[i].num_entries, patterns[i].start, patterns[i].stop, 193 | patterns[i].num_elements, patterns[i].element_size)) 194 | 195 | print("Array element #%d is the address pointer, and element #%d " \ 196 | "is the address pointer name.\n" % \ 197 | (patterns[i].function_element, patterns[i].name_element)) 198 | 199 | i += 1 200 | 201 | return patterns 202 | 203 | def parse_function_tables(self): 204 | count = 0 205 | 206 | for pattern in self.search(): 207 | name2func = {} 208 | 209 | ea = pattern.start 210 | while ea < pattern.stop: 211 | string_address = ida_shims.get_wide_dword( 212 | ea + (pattern.name_element * pattern.element_size)) 213 | function_address = ida_shims.get_wide_dword( 214 | ea + (pattern.function_element * pattern.element_size)) 215 | 216 | new_function_name = ida_shims.get_strlit_contents( 217 | string_address).decode("utf8") 218 | current_function_name = ida_shims.get_name(function_address) 219 | 220 | if not self.valid_function_name(new_function_name): 221 | print("ERROR: '%s' is not a valid function name. This is " \ 222 | "likely not a function table, or I have parsed it " \ 223 | "incorrectly!" % new_function_name) 224 | print(" Ignoring all entries in the structures " \ 225 | "between 0x%X and 0x%X.\n" % (pattern.start, 226 | pattern.stop)) 227 | name2func = {} 228 | break 229 | elif current_function_name.startswith("sub_"): 230 | name2func[new_function_name] = function_address 231 | 232 | ea += (pattern.num_elements * pattern.element_size) 233 | 234 | for (name, address) in name2func.items(): 235 | print("0x%.8X => %s" % (address, name)) 236 | ida_shims.set_name(address, name) 237 | count += 1 238 | 239 | print("Renamed %d functions!" % count) 240 | 241 | 242 | class FunctionNameology(object): 243 | def __init__(self): 244 | pass 245 | 246 | def rename_functions(self, debug=True, dry_run=False): 247 | ''' 248 | Renames functions starting with "sub_" based on unique string xrefs. 249 | 250 | @debug - Set to False to suppress debug output. 251 | @dry_run - Set to True to perform a dry run (functions will not actually 252 | be renamed). 253 | 254 | Returns the number of renamed functions. 255 | ''' 256 | count = 0 257 | 258 | for (function_address, function_name) in self.func2str_mappings().items(): 259 | if ida_shims.get_name(function_address).startswith("sub_"): 260 | if dry_run or ida_shims.set_name(function_address, function_name): 261 | if debug: 262 | print("0x%.8X => %s" % (function_address, 263 | function_name)) 264 | count += 1 265 | 266 | if debug: 267 | print("Renamed %d functions based on unique string xrefs!" % count) 268 | 269 | return count 270 | 271 | def func2str_mappings(self): 272 | ''' 273 | Resolve unique mappings between functions and strings that those 274 | functions reference. 275 | 276 | Returns a dictionary of {int(function_address) : str(function_name)}. 277 | ''' 278 | function_map = {} 279 | 280 | for string in idautils.Strings(): 281 | if self.is_valid_function_name(str(string)): 282 | function_address = self.str2func(string.ea) 283 | if function_address is not None: 284 | if function_address not in function_map: 285 | function_map[function_address] = [] 286 | function_map[function_address].append(str(string)) 287 | 288 | # Each function must have only one candidate string 289 | for function_address in list(function_map.keys()): 290 | if len(function_map[function_address]) == 1: 291 | function_map[function_address] = function_map[function_address][0] 292 | else: 293 | del function_map[function_address] 294 | 295 | return function_map 296 | 297 | def is_valid_function_name(self, string): 298 | ''' 299 | Determines if a string is a valid function name. 300 | 301 | @string - The string to check 302 | 303 | Returns True or False. 304 | ''' 305 | valid_first_characters = ['_'] + [chr(x) for x in range(65, 91)] + [chr(x) for x in range(97, 123)] 306 | valid_characters = valid_first_characters + [chr(x) for x in range(48, 58)] 307 | 308 | return (len(string) in range(1, 256) and 309 | string[0] in valid_first_characters and 310 | set(string) <= set(valid_characters)) 311 | 312 | def str2func(self, ea): 313 | ''' 314 | Identifies a unique function associated with a given string. 315 | 316 | @ea - The effective address of the string 317 | 318 | Returns the address of the associated function, or None. 319 | ''' 320 | functions = [] 321 | 322 | for xref in idautils.XrefsTo(ea): 323 | func = idaapi.get_func(xref.frm) 324 | if func and func.startEA not in functions: 325 | functions.append(func.startEA) 326 | 327 | # Each string must be referenced by only one function 328 | if len(functions) == 1: 329 | return functions[0] 330 | else: 331 | return None 332 | 333 | 334 | class Codatify(object): 335 | CODE = 2 336 | DATA = 3 337 | SEARCH_DEPTH = 25 338 | 339 | def __init__(self): 340 | if self.get_start_ea(self.DATA) == idc.BADADDR: 341 | if ida_shims.ask_yn(0, "There are no data segments defined! This " 342 | "probably won't end well. Continue?") != 1: 343 | raise Exception("Action cancelled by user.") 344 | 345 | # Get the start of the specified segment type (2 == code, 3 == data) 346 | def get_start_ea(self, attr): 347 | ea = idc.BADADDR 348 | seg = ida_shims.get_first_seg() 349 | 350 | while seg != idc.BADADDR: 351 | if ida_shims.get_segm_attr(seg, idc.SEGATTR_TYPE) == attr: 352 | ea = seg 353 | break 354 | else: 355 | seg = ida_shims.get_next_seg(seg) 356 | 357 | return ea 358 | 359 | # Creates ASCII strings 360 | def stringify(self): 361 | n = 0 362 | ea = self.get_start_ea(self.DATA) 363 | 364 | if ea == idc.BADADDR: 365 | ea = ida_shims.get_first_seg() 366 | 367 | print("Looking for possible strings starting at: 0x%X..." % ea, end=' ') 368 | 369 | for s in idautils.Strings(): 370 | if s.ea >= ea: 371 | if not ida_shims.is_strlit(ida_shims.get_full_flags(s.ea)) \ 372 | and ida_shims.create_strlit(s.ea, 0): 373 | n += 1 374 | 375 | print("created %d new ASCII strings" % n) 376 | 377 | # Converts remaining data into DWORDS. 378 | def datify(self): 379 | ea = self.get_start_ea(self.DATA) 380 | if ea == idc.BADADDR: 381 | ea = ida_shims.get_first_seg() 382 | 383 | print("Converting remaining data to DWORDs...", end=' ') 384 | 385 | while ea != idc.BADADDR: 386 | flags = ida_shims.get_full_flags(ea) 387 | 388 | if (ida_shims.is_unknown(flags) or ida_shims.is_byte(flags)) and \ 389 | ((ea % 4) == 0): 390 | ida_shims.create_dword(ea) 391 | ida_shims.op_plain_offset(ea, 0, 0) 392 | 393 | ea = ida_shims.next_addr(ea) 394 | 395 | print("done.") 396 | 397 | self._fix_data_offsets() 398 | 399 | def pointify(self): 400 | counter = 0 401 | 402 | print("Renaming pointers...", end=' ') 403 | 404 | for (name_ea, name) in idautils.Names(): 405 | for xref in idautils.XrefsTo(name_ea): 406 | xref_name = ida_shims.get_name(xref.frm) 407 | if xref_name and xref_name.startswith("off_"): 408 | i = 0 409 | new_name = name + "_ptr" 410 | while ida_shims.get_name_ea_simple(new_name) != idc.BADADDR: 411 | new_name = name + "_ptr%d" % i 412 | i += 1 413 | 414 | if ida_shims.set_name(xref.frm, new_name): 415 | counter += 1 416 | #else: 417 | # print "Failed to create name '%s'!" % new_name 418 | 419 | print("renamed %d pointers" % counter) 420 | 421 | def _fix_data_offsets(self): 422 | ea = 0 423 | count = 0 424 | 425 | print("Fixing unresolved offset xrefs...", end=' ') 426 | 427 | while ea != idaapi.BADADDR: 428 | (ea, n) = idaapi.find_notype(ea, idaapi.SEARCH_DOWN) 429 | if ida_shims.can_decode(ea): 430 | insn = ida_shims.decode_insn(ea) 431 | ops = ida_shims.get_operands(insn) 432 | for i in range(0, len(ops)): 433 | op = ops[i] 434 | if op.type == idaapi.o_imm and idaapi.getseg(op.value): 435 | idaapi.add_dref(ea, op.value, 436 | (idaapi.dr_O | idaapi.XREF_USER)) 437 | count += 1 438 | 439 | print("created %d new data xrefs" % count) 440 | 441 | # Creates functions and code blocks 442 | def codeify(self, ea=idc.BADADDR): 443 | func_count = 0 444 | code_count = 0 445 | 446 | if ea == idc.BADADDR: 447 | ea = self.get_start_ea(self.CODE) 448 | if ea == idc.BADADDR: 449 | ea = ida_shims.get_first_seg() 450 | 451 | print("\nLooking for undefined code starting at: %s:0x%X" % \ 452 | (ida_shims.get_segm_name(ea), ea)) 453 | 454 | while ea != idc.BADADDR: 455 | try: 456 | if ida_shims.get_segm_attr(ea, idc.SEGATTR_TYPE) == self.CODE: 457 | if ida_shims.get_func_name(ea) != '': 458 | ea = ida_shims.find_func_end(ea) 459 | continue 460 | else: 461 | if ida_shims.add_func(ea): 462 | func_count += 1 463 | elif ida_shims.create_insn(ea): 464 | code_count += 1 465 | except: 466 | pass 467 | 468 | ea = ida_shims.next_addr(ea) 469 | 470 | print("Created %d new functions and %d new code blocks\n" % \ 471 | (func_count, code_count)) 472 | 473 | 474 | try: 475 | class CodatifyFixupCode(idaapi.action_handler_t): 476 | def __init__(self): 477 | idaapi.action_handler_t.__init__(self) 478 | 479 | def activate(self, ctx): 480 | cd = Codatify() 481 | cd.codeify() 482 | return 1 483 | 484 | def update(self, ctx): 485 | return idaapi.AST_ENABLE_ALWAYS 486 | 487 | 488 | class CodatifyFixupData(idaapi.action_handler_t): 489 | def __init__(self): 490 | idaapi.action_handler_t.__init__(self) 491 | 492 | def activate(self, ctx): 493 | cd = Codatify() 494 | cd.stringify() 495 | cd.datify() 496 | cd.pointify() 497 | StructFinder().parse_function_tables() 498 | return 1 499 | 500 | def update(self, ctx): 501 | return idaapi.AST_ENABLE_ALWAYS 502 | except AttributeError: 503 | pass 504 | 505 | 506 | def fix_code(arg=None): 507 | cd = Codatify() 508 | cd.codeify() 509 | 510 | 511 | def fix_data(arg=None): 512 | cd = Codatify() 513 | cd.stringify() 514 | cd.datify() 515 | cd.pointify() 516 | StructFinder().parse_function_tables() 517 | FunctionNameology().rename_functions() 518 | 519 | 520 | class codatify_t(idaapi.plugin_t): 521 | flags = 0 522 | comment = "" 523 | help = "" 524 | wanted_name = "Fixup Code and Data" 525 | wanted_hotkey = "" 526 | code_action_name = 'fixupcode:action' 527 | data_action_name = 'fixupdata:action' 528 | menu_tab = 'Options/' 529 | menu_context = [] 530 | 531 | def init(self): 532 | if idaapi.IDA_SDK_VERSION >= 700: 533 | code_desc = idaapi.action_desc_t(self.code_action_name, 534 | 'Fixup Code', 535 | CodatifyFixupCode(), 536 | self.wanted_hotkey, 537 | 'Fixup Code', 538 | 199) 539 | 540 | data_desc = idaapi.action_desc_t(self.data_action_name, 541 | 'Fixup Data', 542 | CodatifyFixupData(), 543 | self.wanted_hotkey, 544 | 'Fixup Data', 545 | 199) 546 | 547 | idaapi.register_action(code_desc) 548 | idaapi.register_action(data_desc) 549 | 550 | idaapi.attach_action_to_menu( 551 | self.menu_tab, self.code_action_name, idaapi.SETMENU_APP) 552 | idaapi.attach_action_to_menu( 553 | self.menu_tab, self.data_action_name, idaapi.SETMENU_APP) 554 | else: 555 | self.menu_context.append( 556 | idaapi.add_menu_item( 557 | "Options/", "Fixup code", "", 0, fix_code, (None,))) 558 | self.menu_context.append( 559 | idaapi.add_menu_item( 560 | "Options/", "Fixup data", "", 0, fix_data, (None,))) 561 | 562 | return idaapi.PLUGIN_KEEP 563 | 564 | def term(self): 565 | if idaapi.IDA_SDK_VERSION >= 700: 566 | idaapi.detach_action_from_menu(self.menu_tab, self.code_action_name) 567 | idaapi.detach_action_from_menu(self.menu_tab, self.data_action_name) 568 | else: 569 | for context in self.menu_context: 570 | idaapi.del_menu_item(context) 571 | return None 572 | 573 | def run(self, arg): 574 | pass 575 | 576 | 577 | def PLUGIN_ENTRY(): 578 | return codatify_t() 579 | -------------------------------------------------------------------------------- /plugins/fluorescence/README.md: -------------------------------------------------------------------------------- 1 | fluorescence.py 2 | ========== 3 | 4 | Features 5 | ---------- 6 | 7 | * Un/highlights function call instructions 8 | 9 | Installation 10 | ------------ 11 | 12 | Just copy fluorescence.py into your IDA *plugins* directory. 13 | -------------------------------------------------------------------------------- /plugins/fluorescence/fluorescence.py: -------------------------------------------------------------------------------- 1 | # Plugin to highlight and un-highlight call instructions. 2 | import idc 3 | import idaapi 4 | import idautils 5 | 6 | from shims import ida_shims 7 | 8 | 9 | class CallHighlighter(object): 10 | COLOR = 0xFF99FF # BBGGRR 11 | 12 | def highlight(self): 13 | for ea in idautils.Heads(): 14 | flags = ida_shims.get_full_flags(ea) 15 | if ida_shims.is_code(flags) and idaapi.is_call_insn(ea): 16 | current_color = idaapi.get_item_color(ea) 17 | if current_color == self.COLOR: 18 | idaapi.set_item_color(ea, idc.DEFCOLOR) 19 | elif current_color == idc.DEFCOLOR: 20 | idaapi.set_item_color(ea, self.COLOR) 21 | 22 | 23 | try: 24 | class FluorescenceActionHandler(idaapi.action_handler_t): 25 | def __init__(self): 26 | idaapi.action_handler_t.__init__(self) 27 | 28 | def activate(self, ctx): 29 | CallHighlighter().highlight() 30 | return 1 31 | 32 | def update(self, ctx): 33 | return idaapi.AST_ENABLE_ALWAYS 34 | except AttributeError: 35 | pass 36 | 37 | 38 | class fluorescence_blower_t(idaapi.plugin_t): 39 | flags = 0 40 | comment = "Highlights function calls" 41 | help = '' 42 | wanted_name = 'fluorescence' 43 | wanted_hotkey = '' 44 | fluorescence_action_name = 'fluorescence:action' 45 | menu_tab = 'Options/' 46 | menu_name = 'Un/highlight call instructions' 47 | context_menu = None 48 | 49 | def init(self): 50 | if idaapi.IDA_SDK_VERSION >= 700: 51 | fluorescence_desc = idaapi.action_desc_t( 52 | self.fluorescence_action_name, self.menu_name, 53 | FluorescenceActionHandler(), self.wanted_hotkey, 54 | 'Highlights function calls.', 199) 55 | 56 | idaapi.register_action(fluorescence_desc) 57 | idaapi.attach_action_to_menu(self.menu_tab, 58 | self.fluorescence_action_name, 59 | idaapi.SETMENU_APP) 60 | else: 61 | self.context_menu = idaapi.add_menu_item(self.menu_tab, 62 | self.menu_name, 63 | "", 64 | 0, 65 | self.highlight, 66 | (None,)) 67 | return idaapi.PLUGIN_KEEP 68 | 69 | def term(self): 70 | if idaapi.IDA_SDK_VERSION >= 700: 71 | idaapi.detach_action_from_menu(self.menu_tab, 72 | self.fluorescence_action_name) 73 | else: 74 | if self.context_menu is not None: 75 | idaapi.del_menu_item(self.context_menu) 76 | return None 77 | 78 | def run(self, arg): 79 | pass 80 | 81 | def highlight(self, arg): 82 | CallHighlighter().highlight() 83 | 84 | 85 | def PLUGIN_ENTRY(): 86 | return fluorescence_blower_t() 87 | 88 | -------------------------------------------------------------------------------- /plugins/funcprofiler/funcprofiler.py: -------------------------------------------------------------------------------- 1 | import re 2 | import idc 3 | import idaapi 4 | import idautils 5 | from shims import ida_shims 6 | from operator import attrgetter 7 | 8 | # Legacy IDA fix-up for the Choose class. 9 | try: 10 | import ida_kernwin 11 | choose = ida_kernwin.Choose 12 | except ImportError: 13 | choose = idaapi.Choose2 14 | 15 | IDA_FUNCTION_PROFILES = None 16 | 17 | 18 | class IDAProfilerXref(object): 19 | def __init__(self, **kwargs): 20 | for (k, v) in kwargs.items(): 21 | setattr(self, k, v) 22 | 23 | 24 | class IDAFunctionProfiler(object): 25 | def __init__(self): 26 | self.functions = {} 27 | self._build_string_xrefs() 28 | self._build_function_xrefs() 29 | self._sort_functions() 30 | 31 | def _build_string_xrefs(self): 32 | for string in idautils.Strings(): 33 | key_string = str(string) 34 | 35 | for xref in idautils.XrefsTo(string.ea): 36 | func = idaapi.get_func(xref.frm) 37 | if func: 38 | start_ea = ida_shims.start_ea(func) 39 | if start_ea not in self.functions: 40 | self.functions[start_ea] = list() 41 | 42 | xref = IDAProfilerXref(ea=string.ea, string=key_string, 43 | xref=xref.frm, type=str) 44 | self.functions[start_ea].append(xref) 45 | 46 | def _build_function_xrefs(self): 47 | for function in idautils.Functions(): 48 | for xref in idautils.XrefsTo(function): 49 | func = idaapi.get_func(xref.frm) 50 | if func: 51 | start_ea = ida_shims.start_ea(func) 52 | if start_ea not in self.functions: 53 | self.functions[start_ea] = list() 54 | 55 | self.functions[start_ea].append(IDAProfilerXref( 56 | ea=function, string=ida_shims.get_name(function), 57 | xref=xref.frm, type=callable)) 58 | 59 | def _sort_functions(self): 60 | for function in list(self.functions.keys()): 61 | self.functions[function] = sorted(self.functions[function], 62 | key=attrgetter('xref')) 63 | 64 | 65 | class IDAFunctionProfilerChooser(choose): 66 | DELIM_COL_1 = '-' * 125 67 | DELIM_COL_2 = '-' * 175 68 | 69 | def __init__(self): 70 | global IDA_FUNCTION_PROFILES 71 | choose.__init__(self, 72 | "Strings Profile", 73 | [["Function", 50 | choose.CHCOL_PLAIN], 74 | ["Xrefs", 75 | choose.CHCOL_PLAIN], ]) 75 | 76 | self.icon = 41 77 | 78 | if not IDA_FUNCTION_PROFILES: 79 | IDA_FUNCTION_PROFILES = IDAFunctionProfiler() 80 | 81 | self.items = [] 82 | self.rename_function_cmd = None 83 | self.rename_regex_test_cmd = None 84 | self.rename_regex_cmd = None 85 | self.rename_regex_fuzzy_cmd = None 86 | 87 | self.string_filters = set() 88 | self.function_filters = set() 89 | 90 | self.profile = IDA_FUNCTION_PROFILES 91 | self.populate_items() 92 | 93 | def OnSelectLine(self, n): 94 | ida_shims.jumpto(self.items[n][2]) 95 | 96 | def OnGetSize(self): 97 | return len(self.items) 98 | 99 | def OnGetLine(self, n): 100 | return self.items[n] 101 | 102 | def OnClose(self): 103 | pass 104 | 105 | def OnCommand(self, n, cmd): 106 | if cmd == self.rename_function_cmd and \ 107 | self.items[n][0] != self.DELIM_COL_1: 108 | self.rename_function(n) 109 | elif cmd == self.rename_regex_cmd: 110 | self.rename_regex(n) 111 | elif cmd == self.rename_regex_test_cmd: 112 | self.rename_regex(n, dryrun=True) 113 | elif cmd == self.rename_regex_fuzzy_cmd: 114 | self.rename_fuzzy(n) 115 | 116 | self.populate_items() 117 | return 0 118 | 119 | def set_internal_filter(self, functions=set(), strings=set()): 120 | self.string_filters = strings 121 | self.function_filters = functions 122 | self.populate_items() 123 | 124 | def populate_items(self): 125 | self.items = [] 126 | 127 | for (func_ea, xrefs) in self.profile.functions.items(): 128 | if not self.function_filters or func_ea in self.function_filters: 129 | orig_items_len = len(self.items) 130 | 131 | for xref in xrefs: 132 | if not self.string_filters or \ 133 | xref.string in self.string_filters: 134 | if xref.type == callable: 135 | display_string = xref.string + "()" 136 | elif xref.type == str: 137 | display_string = '"%s"' % xref.string 138 | else: 139 | display_string = xref.string 140 | 141 | self.items.append([ida_shims.get_name(func_ea), 142 | display_string, 143 | xref.xref, func_ea]) 144 | 145 | if len(self.items) != orig_items_len: 146 | self.items.append([self.DELIM_COL_1, self.DELIM_COL_2, 147 | idc.BADADDR, idc.BADADDR]) 148 | 149 | # Remove the last delimiter column 150 | if self.items and self.items[-1][-1] == idc.BADADDR: 151 | self.items.pop(-1) 152 | 153 | def show(self): 154 | if self.Show(modal=False) < 0: 155 | return False 156 | 157 | self.rename_function_cmd = self.AddCommand("Rename this function") 158 | self.rename_regex_test_cmd = self.AddCommand("Test a regex renaming rule") 159 | self.rename_regex_cmd = self.AddCommand("Apply a regex renaming rule") 160 | self.rename_regex_fuzzy_cmd = self.AddCommand("Apply generic fuzzy naming rules") 161 | 162 | return True 163 | 164 | def rename_function(self, n): 165 | new_name = ida_shims.ask_ident(self.items[n][0], "New function name") 166 | if new_name: 167 | ida_shims.set_name(self.items[n][3], new_name) 168 | 169 | def rename_fuzzy(self, n): 170 | if idc.AskYN(0, "Really rename functions based on fuzzy string " 171 | "matching? (Save your database first!)") == 1: 172 | self.rename_regex(n, regex_str="\[(.*?)\]") 173 | self.rename_regex(n, regex_str="(\w*)\(\)") 174 | self.rename_regex(n, regex_str="^In.(\w*)(?i)") 175 | self.rename_regex(n, regex_str="(^\w*):") 176 | self.rename_regex(n, regex_str="(\w*)\.c(?i)") 177 | self.rename_regex(n, regex_str="(\w*):") 178 | self.rename_regex(n, regex_str="(^\w*)") 179 | 180 | def rename_regex(self, n, regex_str="", dryrun=False): 181 | count = 0 182 | if not regex_str: 183 | regex_str = idc.AskStr("", "Regex rename rule") 184 | 185 | if regex_str: 186 | if dryrun: 187 | print("Testing regex rename rule: '%s'" % regex_str) 188 | 189 | regex = re.compile(regex_str) 190 | 191 | # Look at all the profiled functions 192 | for (function, xrefs) in self.profile.functions.items(): 193 | new_function_name = "" 194 | 195 | # Don't rename functions that have already been renamed 196 | if not idc.Name(function).startswith("sub_"): 197 | continue 198 | 199 | # Look through all the strings referenced by this function 200 | for string in [x.string for x in xrefs if x.type == str]: 201 | 202 | # Does the string match the given regex? 203 | m = regex.search(string) 204 | if m: 205 | # Take the last group from the regex match 206 | potential_function_name = m.groups()[-1].split(" ")[0] 207 | 208 | # Replace common bad chars with underscores 209 | for c in ['-', '>']: 210 | potential_function_name = \ 211 | potential_function_name.replace(c, '_') 212 | 213 | if idaapi.isident(potential_function_name) and \ 214 | '%' not in potential_function_name: 215 | if len(potential_function_name) > len(new_function_name): 216 | new_function_name = potential_function_name 217 | 218 | if new_function_name: 219 | # Append _n to the function name, if it already exists 220 | n = 1 221 | orig_new_function_name = new_function_name 222 | while idc.LocByName(new_function_name) != idc.BADADDR: 223 | new_function_name = "%s_%d" % (orig_new_function_name, 224 | n) 225 | n += 1 226 | 227 | if dryrun: 228 | print("%s => %s" % (idc.Name(function), 229 | new_function_name)) 230 | count += 1 231 | else: 232 | if ida_shims.set_name(function, new_function_name): 233 | count += 1 234 | 235 | print("Renamed %d functions" % count) 236 | 237 | 238 | def from_function_profiler(arg=None): 239 | try: 240 | chooser = IDAFunctionProfilerChooser() 241 | cur_loc = ida_shims.get_screen_ea() 242 | func = idaapi.get_func(cur_loc) 243 | if func: 244 | start_ea = ida_shims.start_ea(func) 245 | chooser.set_internal_filter(functions=set([start_ea])) 246 | else: 247 | raise Exception("Can't limit profile to just this function, " 248 | "because 0x%X is not inside a function!" % cur_loc) 249 | chooser.show() 250 | except Exception as e: 251 | print("IDAFunctionProfiler ERROR: %s" % str(e)) 252 | 253 | 254 | def all_functions_profiler(arg=None): 255 | try: 256 | chooser = IDAFunctionProfilerChooser() 257 | chooser.show() 258 | except Exception as e: 259 | print("IDAFunctionProfiler ERROR: %s" % str(e)) 260 | 261 | 262 | try: 263 | class FunctionProfilerFromFunctionActionHandler(idaapi.action_handler_t): 264 | def __init__(self): 265 | idaapi.action_handler_t.__init__(self) 266 | 267 | def activate(self, ctx): 268 | from_function_profiler() 269 | return 1 270 | 271 | def update(self, ctx): 272 | return idaapi.AST_ENABLE_ALWAYS 273 | 274 | 275 | class FunctionProfilerAllFunctionsActionHandler(idaapi.action_handler_t): 276 | def __init__(self): 277 | idaapi.action_handler_t.__init__(self) 278 | 279 | def activate(self, ctx): 280 | all_functions_profiler() 281 | return 1 282 | 283 | def update(self, ctx): 284 | return idaapi.AST_ENABLE_ALWAYS 285 | except AttributeError: 286 | pass 287 | 288 | 289 | class IDAFunctionProfilerPlugin(idaapi.plugin_t): 290 | flags = 0 291 | comment = "Function xref profiler" 292 | help = "" 293 | wanted_name = "Function Profiler" 294 | wanted_hotkey = "" 295 | xref_current_func_action_name = 'xrefcurrentfunction:action' 296 | all_xref_action_name = 'allxrefs:action' 297 | xref_current_func_menu_name = 'Xrefs from the current function' 298 | all_xref_menu_name = 'All function xrefs' 299 | menu_tab = 'View/Open subviews/' 300 | menu_context = [] 301 | 302 | def init(self): 303 | if idaapi.IDA_SDK_VERSION >= 700: 304 | xref_current_func_desc = idaapi.action_desc_t( 305 | self.xref_current_func_action_name, 306 | self.xref_current_func_menu_name, 307 | FunctionProfilerFromFunctionActionHandler(), 308 | self.wanted_hotkey, 'Xrefs from the current function.', 199) 309 | 310 | all_xref_desc = idaapi.action_desc_t( 311 | self.all_xref_action_name, self.all_xref_menu_name, 312 | FunctionProfilerAllFunctionsActionHandler(), self.wanted_hotkey, 313 | 'All functions xref.', 199) 314 | 315 | idaapi.register_action(xref_current_func_desc) 316 | idaapi.register_action(all_xref_desc) 317 | 318 | idaapi.attach_action_to_menu( 319 | self.menu_tab, self.xref_current_func_action_name, 320 | idaapi.SETMENU_APP) 321 | 322 | idaapi.attach_action_to_menu( 323 | self.menu_tab, self.all_xref_action_name, idaapi.SETMENU_APP) 324 | else: 325 | self.menu_context.append( 326 | idaapi.add_menu_item(self.menu_tab, 327 | self.xref_current_func_menu_name, 328 | "", 329 | 0, 330 | from_function_profiler, 331 | (True,))) 332 | 333 | self.menu_context.append( 334 | idaapi.add_menu_item(self.menu_tab, 335 | self.all_xref_menu_name, 336 | "", 337 | 0, 338 | all_functions_profiler, 339 | (False,))) 340 | return idaapi.PLUGIN_KEEP 341 | 342 | def term(self): 343 | if idaapi.IDA_SDK_VERSION >= 700: 344 | idaapi.detach_action_from_menu(self.menu_tab, 345 | self.xref_current_func_action_name) 346 | idaapi.detach_action_from_menu(self.menu_tab, 347 | self.all_xref_action_name) 348 | else: 349 | for context in self.menu_context: 350 | idaapi.del_menu_item(context) 351 | return None 352 | 353 | def run(self): 354 | pass 355 | 356 | 357 | def IDAFunctionProfilerRefresh(): 358 | global IDA_FUNCTION_PROFILES 359 | IDA_FUNCTION_PROFILES = IDAFunctionProfiler() 360 | 361 | 362 | def PLUGIN_ENTRY(): 363 | return IDAFunctionProfilerPlugin() 364 | 365 | -------------------------------------------------------------------------------- /plugins/install.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from __future__ import print_function 3 | 4 | import os 5 | import shutil 6 | import argparse 7 | 8 | 9 | def get_plugin_directories(source_path): 10 | """ 11 | Find IDA plugins in the provided source directory. 12 | 13 | :param source_path: Path to look for directories. 14 | :type source_path: str 15 | 16 | :returns: List of directories in the source path. Only returns the 17 | directory name and not the full path. 18 | :rtype: list(str) 19 | """ 20 | plugins = [] 21 | for plugin in os.listdir(source_path): 22 | if not os.path.isdir(os.path.join(source_path, plugin)): 23 | continue 24 | plugins.append(plugin) 25 | return plugins 26 | 27 | 28 | def install_plugins(ida_install_path): 29 | """ 30 | Install the plugins. 31 | 32 | :param ida_install_path: Full IDA installation path. 33 | :type ida_install_path: str 34 | """ 35 | install_path = os.path.realpath(os.path.join(ida_install_path, 'plugins')) 36 | source_path = os.path.dirname(os.path.realpath(__file__)) 37 | print("Installing plugins from %s to %s..." % (source_path, install_path)) 38 | 39 | plugins = get_plugin_directories(source_path) 40 | for plugin in plugins: 41 | print("Installing %s..." % plugin, end=''), 42 | if 'shims' in plugin: 43 | shims_dir = os.path.join(install_path, 'shims') 44 | try: 45 | os.mkdir(shims_dir) 46 | except OSError: 47 | pass 48 | src = os.path.join(source_path, plugin, 'ida_' + plugin + '.py') 49 | dst = os.path.join(shims_dir, 'ida_' + plugin + '.py') 50 | open(os.path.join(shims_dir, '__init__.py'), 'a').close() 51 | else: 52 | src = os.path.join(source_path, plugin, plugin + '.py') 53 | dst = os.path.join(install_path, plugin + '.py') 54 | 55 | try: 56 | shutil.copyfile(src, dst) 57 | except IOError: 58 | print('%s is not a known plugin. Skipping copy...' % plugin, end='') 59 | print('Done') 60 | 61 | 62 | def remove_plugins(ida_install_path): 63 | """ 64 | Remove plugins from the IDA installation directory. 65 | 66 | :param ida_install_path: Full IDA installation path. 67 | :type ida_install_path: str 68 | """ 69 | install_path = os.path.realpath(os.path.join(ida_install_path, 'plugins')) 70 | source_path = os.path.dirname(os.path.realpath(__file__)) 71 | print("Removing plugins from %s..." % install_path) 72 | 73 | plugins = get_plugin_directories(source_path) 74 | for plugin in plugins: 75 | print("Removing %s..." % plugin, end='') 76 | try: 77 | if 'shims' in plugin: 78 | dst = os.path.join(install_path, 'shims', 'ida_' + 79 | plugin + '.py') 80 | shutil.rmtree(os.path.dirname(dst)) 81 | else: 82 | dst = os.path.join(install_path, plugin + '.py') 83 | os.remove(dst) 84 | except OSError: 85 | print('%s was not installed' % plugin) 86 | 87 | print('Done') 88 | 89 | 90 | if __name__ == '__main__': 91 | parser = argparse.ArgumentParser(description='Install IDA plugins.') 92 | 93 | objective = parser.add_mutually_exclusive_group() 94 | objective.add_argument('-i', '--install', action='store_true', 95 | help='Install plugins.') 96 | objective.add_argument('-r', '--remove', action='store_true', 97 | help='Remove plugins.') 98 | 99 | parser.add_argument('-d', '--directory', help='IDA installation directory.') 100 | 101 | args = parser.parse_args() 102 | 103 | if not os.path.exists(args.directory): 104 | raise Exception('IDA installation, %s, does not exist.' % 105 | args.directory) 106 | 107 | if args.install: 108 | install_plugins(args.directory) 109 | elif args.remove: 110 | remove_plugins(args.directory) 111 | 112 | print('Done.') 113 | -------------------------------------------------------------------------------- /plugins/leafblower/leafblower.py: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # 3 | # A plugin to help identify common POSIX functions such as printf, sprintf, 4 | # memcmp, strcpy, etc. 5 | # 6 | # This plugin will really only work with RISC architectures, as it assumes a 7 | # fixed instruction size, and that function arguments are passed via registers. 8 | # 9 | ################################################################################ 10 | import idc 11 | import idaapi 12 | import idautils 13 | 14 | from shims import ida_shims 15 | 16 | # Legacy IDA fix-up for the Choose class. 17 | try: 18 | import ida_kernwin 19 | choose = ida_kernwin.Choose 20 | except ImportError: 21 | choose = idaapi.Choose2 22 | 23 | 24 | class LeafBlowerFunctionChooser(choose): 25 | MIN_XREFS = 25 26 | MUST_HAVE_LOOP = True 27 | 28 | def __init__(self, lbobj): 29 | self.lb = lbobj 30 | self.min_xrefs = self.MIN_XREFS 31 | self.must_have_loop = self.MUST_HAVE_LOOP 32 | self.items = [] 33 | 34 | choose.__init__(self, self.lb.TITLE, self.lb.COLUMNS) 35 | self.icon = 41 36 | 37 | self.populate_items() 38 | self.show_all_toggle_cmd = None 39 | self.rename_cmd = None 40 | 41 | def OnSelectLine(self, n): 42 | ida_shims.jumpto(ida_shims.get_name_ea_simple(self.items[n][0])) 43 | 44 | def OnGetSize(self): 45 | return len(self.items) 46 | 47 | def OnGetLine(self, n): 48 | return self.items[n] 49 | 50 | def OnRefresh(self, n): 51 | self.populate_items() 52 | return len(self.items) 53 | 54 | def OnClose(self): 55 | pass 56 | 57 | def AddCommand(self, caption): 58 | pass 59 | 60 | def OnCommand(self, n, cmd): 61 | if cmd == self.show_all_toggle_cmd: 62 | 63 | if self.min_xrefs == self.MIN_XREFS: 64 | self.min_xrefs = 0 65 | if self.min_xrefs != self.MIN_XREFS: 66 | self.min_xrefs = self.MIN_XREFS 67 | 68 | if self.must_have_loop == self.MUST_HAVE_LOOP: 69 | self.must_have_loop = False 70 | else: 71 | self.must_have_loop = self.MUST_HAVE_LOOP 72 | 73 | elif cmd == self.rename_cmd: 74 | response = ida_shims.ask_yn( 75 | 0, "Are you sure you want to rename all 'sub_XXXXXX' functions " 76 | "to 'leaf_XXXXXX'?") 77 | if response: 78 | for item in self.items: 79 | # Is this a leaf function? 80 | if item[-1] is True: 81 | current_name = item[0] 82 | if current_name.startswith('sub_'): 83 | new_name = current_name.replace('sub_', 'leaf_') 84 | ida_shims.set_name( 85 | ida_shims.get_name_ea_simple(current_name), 86 | new_name) 87 | self.populate_items() 88 | return 0 89 | 90 | def populate_items(self): 91 | self.items = [] 92 | 93 | for function in self.lb.functions: 94 | candidates = [] 95 | 96 | if function.leaf: 97 | if function.xrefs < self.min_xrefs: 98 | continue 99 | if function.loop is False and self.must_have_loop is True: 100 | continue 101 | 102 | for candidate in function.candidates: 103 | candidates.append(candidate) 104 | 105 | if function.xrefs: 106 | xrefs = str(function.xrefs) 107 | else: 108 | xrefs = "*" 109 | 110 | if function.argc is not None: 111 | argc = str(function.argc) 112 | else: 113 | argc = "*" 114 | 115 | if function.leaf: 116 | loops = str(function.loop) 117 | else: 118 | loops = "*" 119 | 120 | name = ida_shims.get_name(function.start) 121 | 122 | self.items.append([name, xrefs, argc, loops, ', '.join(candidates), 123 | function.leaf]) 124 | 125 | def show(self): 126 | if self.Show(modal=False) < 0: 127 | return False 128 | self.show_all_toggle_cmd = self.AddCommand("Toggle 'show all leaf functions'") 129 | self.rename_cmd = self.AddCommand("Rename all sub_XXXXXX leaf functions to 'leaf_XXXXXX'") 130 | 131 | 132 | class ArchitectureSettings(object): 133 | def __init__(self, **kwargs): 134 | for (k, v) in kwargs.items(): 135 | setattr(self, k, v) 136 | 137 | 138 | class ArchitectureSpecific(object): 139 | ''' 140 | Architecture specific configuration / code. 141 | ''' 142 | ARCHES = [ 143 | ArchitectureSettings(name="MIPS", 144 | argv=['$a0', '$a1', '$a2', '$a3'], 145 | delay=1, size=4), 146 | ArchitectureSettings(name="ARM", 147 | argv=['R0', 'R1', 'R2', 'R3'], 148 | delay=0, size=4) 149 | ] 150 | 151 | def __init__(self): 152 | self.registers = idaapi.ph_get_regnames() 153 | 154 | # Find the list of function argument registers that fit the current 155 | # processor module 156 | # TODO: Probably better to look this up based on the processor module 157 | # name, as it can't distinguish between ARM and Thumb. 158 | self.argv = None 159 | for arch in self.ARCHES: 160 | if not (set(arch.argv) - set(self.registers)): 161 | self.argv = list(arch.argv) 162 | self.delay_slot = arch.delay 163 | self.insn_size = arch.size 164 | break 165 | 166 | if self.argv is None: 167 | self.argv = [] 168 | self.registers = [] 169 | self.delay_slot = False 170 | self.insn_size = 0 171 | self.unknown = True 172 | else: 173 | self.unknown = False 174 | 175 | 176 | class Prototype(object): 177 | def __init__(self, name, argc, argv=[], loop=True, fmtarg=None): 178 | self.name = name 179 | self.argc = argc 180 | self.loop = loop 181 | self.argv = argv 182 | self.fmtarg = fmtarg 183 | 184 | 185 | class Function(object): 186 | ''' 187 | A wrapper class for storing function related info. 188 | ''' 189 | 190 | PROTOTYPES = [ 191 | Prototype(name="atoi", argc=1), 192 | Prototype(name="atol", argc=1), 193 | Prototype(name="strlen", argc=1), 194 | Prototype(name="strcpy", argc=2), 195 | Prototype(name="strcat", argc=2), 196 | Prototype(name="strcmp", argc=2), 197 | Prototype(name="strstr", argc=2), 198 | Prototype(name="strchr", argc=2), 199 | Prototype(name="strrchr", argc=2), 200 | Prototype(name="bzero", argc=2), 201 | Prototype(name="strtol", argc=3), 202 | Prototype(name="strncpy", argc=3), 203 | Prototype(name="strncat", argc=3), 204 | Prototype(name="strncmp", argc=3), 205 | Prototype(name="memcpy", argc=3), 206 | Prototype(name="memmove", argc=3), 207 | Prototype(name="bcopy", argc=3), 208 | Prototype(name="memcmp", argc=3), 209 | Prototype(name="memset", argc=3), 210 | 211 | Prototype(name="printf", argc=1, fmtarg=0), 212 | Prototype(name="sprintf", argc=2, fmtarg=1), 213 | Prototype(name="snprintf", argc=3, fmtarg=2), 214 | Prototype(name="fprintf", argc=2, fmtarg=1), 215 | Prototype(name="fscanf", argc=2, fmtarg=1), 216 | Prototype(name="sscanf", argc=2, fmtarg=1), 217 | ] 218 | 219 | def __init__(self, **kwargs): 220 | self.argc = None 221 | self.loop = False 222 | self.leaf = False 223 | self.xrefs = None 224 | self.fmtarg = None 225 | self.start = idc.BADADDR 226 | self.end = idc.BADADDR 227 | self.candidates = {} 228 | self.argp = ArgParser() 229 | 230 | for (k,v) in kwargs.items(): 231 | setattr(self, k, v) 232 | 233 | self.name = ida_shims.get_name(self.start) 234 | 235 | if self.xrefs is None: 236 | self.xrefs = len([x for x in idautils.XrefsTo(self.start)]) 237 | 238 | if not self.candidates: 239 | for prototype in self.PROTOTYPES: 240 | if self.leaf and prototype.fmtarg is None and \ 241 | prototype.argc == self.argc and prototype.loop == self.loop: 242 | if prototype.name in self.candidates: 243 | self.candidates[prototype.name] += 1 244 | else: 245 | self.candidates[prototype.name] = 1 246 | elif not self.leaf and \ 247 | self.fmtarg is not None and \ 248 | prototype.fmtarg is not None and \ 249 | self.fmtarg == prototype.fmtarg: 250 | if prototype.name in self.candidates: 251 | self.candidates[prototype.name] += 1 252 | else: 253 | self.candidates[prototype.name] = 1 254 | 255 | 256 | class ArgParser(object): 257 | ''' 258 | Attempts to identify the number of arguments a function takes as well as 259 | what type of arguments a function takes. 260 | ''' 261 | 262 | # An iterable list of canonical flags indicating an operand has been changed 263 | CHANGE_OPND = [ 264 | idaapi.CF_CHG1, 265 | idaapi.CF_CHG2, 266 | idaapi.CF_CHG3, 267 | idaapi.CF_CHG4, 268 | idaapi.CF_CHG5, 269 | idaapi.CF_CHG6, 270 | ] 271 | 272 | # An iterable list of canonical flags indicating an operand has been used 273 | USE_OPND = [ 274 | idaapi.CF_USE1, 275 | idaapi.CF_USE2, 276 | idaapi.CF_USE3, 277 | idaapi.CF_USE4, 278 | idaapi.CF_USE5, 279 | idaapi.CF_USE6, 280 | ] 281 | 282 | def __init__(self): 283 | self.arch = ArchitectureSpecific() 284 | 285 | def argc(self, function): 286 | ''' 287 | Counts the number of arguments used by the specified function. 288 | ''' 289 | argv = set() 290 | notargv = set() 291 | ea = ida_shims.start_ea(function) 292 | end_ea = ida_shims.end_ea(function) 293 | 294 | if self.arch.unknown: 295 | return 0 296 | 297 | while ea < end_ea: 298 | insn = ida_shims.decode_insn(ea) 299 | features = ida_shims.get_canon_feature(insn) 300 | 301 | for n in range(0, len(self.USE_OPND)): 302 | ops = ida_shims.get_operands(insn) 303 | if ops[n].type in [idaapi.o_reg, idaapi.o_displ, 304 | idaapi.o_phrase]: 305 | try: 306 | regname = self.arch.registers[ops[n].reg] 307 | index = self.arch.argv.index(regname) 308 | except ValueError: 309 | continue 310 | 311 | if features & self.USE_OPND[n] and regname not in notargv: 312 | argv.update(self.arch.argv[:index+1]) 313 | 314 | for n in range(0, len(self.CHANGE_OPND)): 315 | ops = ida_shims.get_operands(insn) 316 | if ops[n].type in [idaapi.o_reg, idaapi.o_displ, 317 | idaapi.o_phrase]: 318 | try: 319 | regname = self.arch.registers[ops[n].reg] 320 | index = self.arch.argv.index(regname) 321 | except ValueError: 322 | continue 323 | 324 | if regname not in argv: 325 | notargv.update(self.arch.argv[index:]) 326 | 327 | if argv.union(notargv) == set(self.arch.argv): 328 | break 329 | 330 | # TODO: Use idc.NextHead(ea) instead... 331 | ea += self.arch.insn_size 332 | 333 | return len(argv) 334 | 335 | def trace(self, ea): 336 | ''' 337 | Given an EA where an argument register is set, attempt to trace what 338 | function call that argument is passed to. 339 | 340 | @ea - The address of an instruction that modifies a function argument 341 | register. 342 | 343 | Returns a tuple of (function EA, argv index, argument register name) on 344 | success. 345 | Returns None on failure. 346 | ''' 347 | insn = ida_shims.decode_insn(ea) 348 | features = ida_shims.get_canon_feature(insn) 349 | 350 | if self.arch.unknown: 351 | return (None, None, None) 352 | 353 | for n in range(0, len(self.CHANGE_OPND)): 354 | ops = ida_shims.get_operands(insn) 355 | if ops[n].type in [idaapi.o_reg, idaapi.o_displ, idaapi.o_phrase]: 356 | try: 357 | regname = self.arch.registers[ops[n].reg] 358 | index = self.arch.argv.index(regname) 359 | except ValueError: 360 | continue 361 | 362 | if features & self.CHANGE_OPND[n]: 363 | ea = ea - (self.arch.delay_slot * self.arch.insn_size) 364 | 365 | while True: 366 | insn = ida_shims.decode_insn(ea) 367 | 368 | if idaapi.is_call_insn(ea): 369 | for xref in idautils.XrefsFrom(ea): 370 | if xref.type in [idaapi.fl_CF, idaapi.fl_CN]: 371 | return (xref.to, index, regname) 372 | # If we couldn't figure out where the function call 373 | # was going to, just quit 374 | break 375 | 376 | try: 377 | is_block_end = idaapi.is_basic_block_end(ea) 378 | except TypeError: 379 | is_block_end = idaapi.is_basic_block_end(ea, True) 380 | 381 | if is_block_end: 382 | break 383 | 384 | # TODO: Use idc.NextHead(ea) instead... 385 | ea += self.arch.insn_size 386 | 387 | return (None, None, None) 388 | 389 | def argv(self, func): 390 | ''' 391 | Attempts to identify what types of arguments are passed to a given 392 | function. Currently unused. 393 | ''' 394 | args = [None for x in self.arch.argv] 395 | 396 | if not self.arch.unknown: 397 | start_ea = ida_shims.start_ea(func) 398 | for xref in idautils.XrefsTo(start_ea): 399 | if idaapi.is_call_insn(xref.frm): 400 | insn = ida_shims.decode_insn(xref.frm) 401 | 402 | ea = xref.frm + (self.arch.delay_slot * self.arch.insn_size) 403 | end_ea = (xref.frm - (self.arch.insn_size * 10)) 404 | 405 | while ea >= end_ea: 406 | if idaapi.is_basic_block_end(ea) or \ 407 | (ea != xref.frm and idaapi.is_call_insn(ea)): 408 | break 409 | 410 | insn = ida_shims.decode_insn(ea) 411 | features = ida_shims.get_canon_feature(insn) 412 | 413 | for n in range(0, len(self.CHANGE_OPND)): 414 | ops = ida_shims.get_operands(insn) 415 | if ops[n].type in [idaapi.o_reg, idaapi.o_displ, 416 | idaapi.o_phrase]: 417 | try: 418 | regname = self.arch.registers[ops[n].reg] 419 | index = self.arch.argv.index(regname) 420 | except ValueError: 421 | continue 422 | 423 | if features & self.CHANGE_OPND[n]: 424 | for xref in idautils.XrefsFrom(ea): 425 | # TODO: Where is this xref type defined? 426 | if xref.type == 1: 427 | string = \ 428 | ida_shims.get_strlit_contents( 429 | xref.to) 430 | if string and len(string) > 4: 431 | args[index] = str 432 | break 433 | 434 | ea -= self.arch.insn_size 435 | 436 | yield args 437 | 438 | 439 | class LeafFunctionFinder(object): 440 | ''' 441 | Class that searches for functions that do not call any other functions. 442 | ''' 443 | 444 | TITLE = "Leaf functions" 445 | 446 | COLUMNS = [ 447 | ["Function", 25 | choose.CHCOL_PLAIN], 448 | ["Xrefs", 8 | choose.CHCOL_PLAIN], 449 | ["argc", 8 | choose.CHCOL_PLAIN], 450 | ["Has Loop(s)", 8 | choose.CHCOL_PLAIN], 451 | ["Possible candidate(s)", 50 | choose.CHCOL_PLAIN], 452 | ] 453 | 454 | def __init__(self): 455 | self.functions = [] 456 | self.arch = ArchitectureSpecific() 457 | self.argp = ArgParser() 458 | self._find_leafs() 459 | 460 | def _find_leafs(self): 461 | # Loop through every function 462 | for func_ea in idautils.Functions(): 463 | # Count the number of xrefs to this function 464 | func = idaapi.get_func(func_ea) 465 | if func: 466 | leaf_function = True 467 | ea = ida_shims.start_ea(func) 468 | end_ea = ida_shims.end_ea(func) 469 | 470 | # Loop through all instructions in this function looking 471 | # for call instructions; if found, then this is not a leaf. 472 | while ea <= end_ea: 473 | insn = ida_shims.decode_insn(ea) 474 | if idaapi.is_call_insn(ea): 475 | leaf_function = False 476 | break 477 | 478 | ea = ida_shims.next_head(ea) 479 | 480 | if leaf_function: 481 | self.functions.append( 482 | Function(start=ida_shims.start_ea(func), 483 | end=ida_shims.end_ea(func), leaf=True, 484 | loop=self.has_loop(func), 485 | argc=self.argp.argc(func))) 486 | 487 | # Sort leafs by xref count, largest first 488 | self.functions.sort(key=lambda f: f.xrefs, reverse=True) 489 | 490 | def has_loop(self, func): 491 | ''' 492 | A naive method for checking to see if a function contains a loop. 493 | Works pretty well for simple functions though. 494 | ''' 495 | func_start_ea = ida_shims.start_ea(func) 496 | 497 | blocks = [func_start_ea] 498 | for block in idaapi.FlowChart(func): 499 | end_ea = ida_shims.end_ea(block) 500 | blocks.append(end_ea) 501 | 502 | for block in blocks: 503 | for xref in idautils.XrefsTo(block): 504 | xref_func = idaapi.get_func(xref.frm) 505 | xref_start_ea = ida_shims.start_ea(xref_func) 506 | 507 | if xref_func and xref_start_ea == func_start_ea: 508 | if xref.frm >= block: 509 | return True 510 | return False 511 | 512 | 513 | class FormatStringFunctionFinder(object): 514 | 515 | TITLE = "Format string functions" 516 | 517 | COLUMNS = [ 518 | ["Function", 25 | choose.CHCOL_PLAIN], 519 | ["Xrefs", 8 | choose.CHCOL_PLAIN], 520 | ["Format string argv index", 15 | choose.CHCOL_PLAIN], 521 | ["Has Loop(s)", 8 | choose.CHCOL_PLAIN], 522 | ["Possible candidate(s)", 50 | choose.CHCOL_PLAIN], 523 | ] 524 | 525 | def __init__(self): 526 | self.functions = [] 527 | self.argp = ArgParser() 528 | self._find_format_string_functions() 529 | 530 | def _find_format_string_functions(self): 531 | processed_func_eas = set() 532 | 533 | for string in idautils.Strings(): 534 | if '%' in str(string): 535 | for xref in idautils.XrefsTo(string.ea): 536 | (func_ea, argn, regname) = self.argp.trace(xref.frm) 537 | if func_ea is not None and \ 538 | func_ea not in processed_func_eas: 539 | self.functions.append(Function(start=func_ea, 540 | argc=argn, 541 | fmtarg=argn)) 542 | processed_func_eas.add(func_ea) 543 | 544 | # Sort format string functions by xref count, largest first 545 | self.functions.sort(key=lambda f: f.xrefs, reverse=True) 546 | 547 | 548 | def leaf_from_menu(arg=None): 549 | LeafBlowerFunctionChooser(LeafFunctionFinder()).show() 550 | 551 | 552 | def format_from_menu(arg=None): 553 | LeafBlowerFunctionChooser(FormatStringFunctionFinder()).show() 554 | 555 | 556 | try: 557 | class LeafFunctionAction(idaapi.action_handler_t): 558 | def __init__(self): 559 | idaapi.action_handler_t.__init__(self) 560 | 561 | def activate(self, ctx): 562 | leaf_from_menu() 563 | return 1 564 | 565 | def update(self, ctx): 566 | return idaapi.AST_ENABLE_ALWAYS 567 | 568 | 569 | class FormatStringFunctionAction(idaapi.action_handler_t): 570 | def __init__(self): 571 | idaapi.action_handler_t.__init__(self) 572 | 573 | def activate(self, ctx): 574 | format_from_menu() 575 | return 1 576 | 577 | def update(self, ctx): 578 | return idaapi.AST_ENABLE_ALWAYS 579 | except AttributeError: 580 | pass 581 | 582 | 583 | class leaf_blower_t(idaapi.plugin_t): 584 | flags = 0 585 | comment = "Assists in identifying common POSIX functions in RISC " \ 586 | "architectures" 587 | help = '' 588 | wanted_name = 'leafblower' 589 | wanted_hotkey = '' 590 | leaf_function_action_name = 'leaffunction:action' 591 | format_string_action_name = 'formatstring:action' 592 | leaf_function_name = 'leaf functions' 593 | format_string_name = 'format string functions' 594 | leaf_function_tooltip = 'Find leaf functions' 595 | format_string_tooltip = 'Find format string functions' 596 | menu_tab = 'Search/' 597 | menu_context = [] 598 | 599 | def init(self): 600 | if idaapi.IDA_SDK_VERSION >= 700: 601 | leaf_desc = idaapi.action_desc_t( 602 | self.leaf_function_action_name, self.leaf_function_name, 603 | LeafFunctionAction(), self.wanted_hotkey, 604 | self.leaf_function_tooltip, 199) 605 | 606 | format_string_desc = idaapi.action_desc_t( 607 | self.format_string_action_name, self.format_string_name, 608 | FormatStringFunctionAction(), self.wanted_hotkey, 609 | self.format_string_tooltip, 199) 610 | 611 | idaapi.register_action(leaf_desc) 612 | idaapi.register_action(format_string_desc) 613 | 614 | idaapi.attach_action_to_menu( 615 | self.menu_tab, self.leaf_function_action_name, 616 | idaapi.SETMENU_APP) 617 | 618 | idaapi.attach_action_to_menu( 619 | self.menu_tab, self.format_string_action_name, 620 | idaapi.SETMENU_APP) 621 | else: 622 | self.menu_context.append( 623 | idaapi.add_menu_item( 624 | self.menu_tab, self.leaf_function_name, "", 0, 625 | leaf_from_menu, (None,))) 626 | 627 | self.menu_context.append( 628 | idaapi.add_menu_item( 629 | self.menu_tab, self.format_string_name, "", 0, 630 | format_from_menu, (None,))) 631 | 632 | return idaapi.PLUGIN_KEEP 633 | 634 | def term(self): 635 | if idaapi.IDA_SDK_VERSION >= 700: 636 | idaapi.detach_action_from_menu( 637 | self.menu_tab, self.leaf_function_action_name) 638 | idaapi.detach_action_from_menu( 639 | self.menu_tab, self.format_string_action_name) 640 | else: 641 | for context in self.menu_context: 642 | idaapi.del_menu_item(context) 643 | return None 644 | 645 | def run(self): 646 | pass 647 | 648 | 649 | def PLUGIN_ENTRY(): 650 | return leaf_blower_t() 651 | 652 | -------------------------------------------------------------------------------- /plugins/localxrefs/README.md: -------------------------------------------------------------------------------- 1 | localxrefs.py 2 | ============= 3 | 4 | Features 5 | -------- 6 | 7 | * Finds references to any selected text from within the current function 8 | 9 | Usage 10 | ----- 11 | 12 | Here's some MIPS code. Where does that $s2 register get set? 13 | 14 | ![Before localxrefs.py](../../images/where_does_s2_get_set.png) 15 | 16 | Running localxrefs: 17 | 18 | ![Running localxrefs.py](../../images/how_to_run_localxrefs.png) 19 | 20 | All references to $s2 in the current function are clearly listed: 21 | 22 | ![After localxrefs.py](../../images/localxrefs_output.png) 23 | 24 | And, these references can be highlighted in the disassembly view by running *localxrefs.highlight()* in IDA's Python terminal: 25 | 26 | ![Highlight localxrefs.py](../../images/localxrefs_highlight.png) 27 | 28 | (To un-highlight, run *localxrefs.unhighlight()*) 29 | 30 | Installation 31 | ------------ 32 | 33 | Just copy localxrefs.py into your IDA *plugins* directory. 34 | -------------------------------------------------------------------------------- /plugins/localxrefs/localxrefs.py: -------------------------------------------------------------------------------- 1 | # IDA Plugin to search for cross references only within the current defined 2 | # function. Useful, for example, to find instructions that use a particular 3 | # register, or that reference a literal value. 4 | # 5 | # Invoke by highlighting the desired text in IDA, then going to 6 | # Jump->List local xrefs. Highlighting is also supported; once xrefs are found, 7 | # Type the following in the Python command window: 8 | # 9 | # Python> localxrefs.highlight() <-- Highlight all xrefs 10 | # Python> localxrefs.highlight(False) <-- Un-highlight all xrefs 11 | # 12 | # Craig Heffner 13 | # Tactical Network Solutions 14 | 15 | import sys 16 | import idc 17 | import idaapi 18 | import idautils 19 | 20 | from shims import ida_shims 21 | 22 | localxrefs = None 23 | 24 | 25 | def add_to_namespace(namespace, name, variable): 26 | ''' 27 | Add a variable to a different namespace, likely __main__. 28 | ''' 29 | import importlib 30 | 31 | importer_module = sys.modules[namespace] 32 | if name in sys.modules.keys(): 33 | if not (sys.version_info.major == 3 and sys.version_info.minor >= 4): 34 | import imp 35 | imp.reload(sys.modules[name]) 36 | else: 37 | importlib.reload(sys.modules[name]) 38 | else: 39 | m = importlib.import_module(name, None) 40 | sys.modules[name] = m 41 | 42 | setattr(importer_module, name, variable) 43 | 44 | 45 | class LocalXrefs(object): 46 | UP = 'Up ' 47 | DOWN = 'Down' 48 | THIS = '- ' 49 | 50 | READ = 'r' 51 | WRITE = 'w' 52 | 53 | OPND_WRITE_FLAGS = { 54 | 0: idaapi.CF_CHG1, 55 | 1: idaapi.CF_CHG2, 56 | 2: idaapi.CF_CHG3, 57 | 3: idaapi.CF_CHG4, 58 | 4: idaapi.CF_CHG5, 59 | 5: idaapi.CF_CHG6} 60 | 61 | def __init__(self): 62 | self.xrefs = {} 63 | self.function = '' 64 | self._profile_function() 65 | 66 | def _profile_function(self): 67 | current_ea = ida_shims.get_screen_ea() 68 | current_function = ida_shims.get_func_name(current_ea) 69 | current_function_ea = ida_shims.get_name_ea_simple(current_function) 70 | 71 | if current_function: 72 | self.function = current_function 73 | 74 | ea = ida_shims.get_func_attr(current_function_ea, idc.FUNCATTR_START) 75 | end_ea = ida_shims.get_func_attr(current_function_ea, idc.FUNCATTR_END) 76 | 77 | try: 78 | self.highlighted = ida_shims.get_highlighted_identifier() 79 | except TypeError: 80 | raise Exception("No text was highlighted. Nothing to show.") 81 | 82 | while ea < end_ea and ea != idc.BADADDR and self.highlighted: 83 | i = 0 84 | match = False 85 | optype = self.READ 86 | 87 | insn = ida_shims.decode_insn(ea) 88 | 89 | mnem = ida_shims.print_insn_mnem(ea) 90 | 91 | if self.highlighted in mnem: 92 | match = True 93 | elif idaapi.is_call_insn(ea): 94 | for xref in idautils.XrefsFrom(ea): 95 | if xref.type != 21: 96 | name = ida_shims.get_name(xref.to) 97 | if name and self.highlighted in name: 98 | match = True 99 | break 100 | else: 101 | while True: 102 | opnd = ida_shims.print_operand(ea, i) 103 | if opnd: 104 | if self.highlighted in opnd: 105 | canon_feature = ida_shims.get_canon_feature(insn) 106 | match = True 107 | if canon_feature & self.OPND_WRITE_FLAGS[i]: 108 | optype = self.WRITE 109 | i += 1 110 | else: 111 | break 112 | 113 | if not match: 114 | comment = idc.GetCommentEx(ea, 0) 115 | if comment and self.highlighted in comment: 116 | match = True 117 | else: 118 | comment = idc.GetCommentEx(ea, 1) 119 | if comment and self.highlighted in comment: 120 | match = True 121 | 122 | if match: 123 | if ea > current_ea: 124 | direction = self.DOWN 125 | elif ea < current_ea: 126 | direction = self.UP 127 | else: 128 | direction = self.THIS 129 | 130 | self.xrefs[ea] = { 131 | 'offset': ida_shims.get_func_off_str(ea), 132 | 'mnem': mnem, 133 | 'type': optype, 134 | 'direction': direction, 135 | 'text': idc.GetDisasm(ea), 136 | } 137 | 138 | ea = ida_shims.next_head(ea) 139 | 140 | def highlight(self, highlight=True, mnem=None, optype=None, direction=None, 141 | text=None): 142 | for (ea, info) in self.xrefs.items(): 143 | if mnem and info['mnem'] != mnem: 144 | highlight = False 145 | elif optype and info['optype'] != optype: 146 | highlight = False 147 | elif direction and info['direction'] != direction: 148 | highlight = False 149 | elif text and info['text'] != text: 150 | highlight = False 151 | 152 | if highlight: 153 | color = 0x00ff00 154 | else: 155 | color = idc.DEFCOLOR 156 | 157 | ida_shims.set_color(ea, idc.CIC_ITEM, color) 158 | 159 | def unhighlight(self): 160 | self.highlight(False) 161 | 162 | 163 | def show_local_xrefs(arg=None): 164 | delim = '-' * 86 + '\n' 165 | header = '\nXrefs to %s from %s:\n' 166 | 167 | global localxrefs 168 | fmt = '' 169 | 170 | r = LocalXrefs() 171 | localxrefs = r 172 | 173 | # offsets = r.xrefs.keys() 174 | # offsets.sort() 175 | 176 | offsets = sorted(r.xrefs) 177 | 178 | if r.highlighted: 179 | ida_shims.msg(header % (r.highlighted, r.function)) 180 | ida_shims.msg(delim) 181 | 182 | for ea in offsets: 183 | info = r.xrefs[ea] 184 | 185 | if not fmt: 186 | fmt = "%%s %%s %%-%ds %%s\n" % (len(info['offset']) + 15) 187 | 188 | ida_shims.msg(fmt % (info['direction'], info['type'], 189 | info['offset'], info['text'])) 190 | 191 | ida_shims.msg(delim) 192 | 193 | 194 | try: 195 | class LocalXrefHandler(idaapi.action_handler_t): 196 | def __init__(self): 197 | idaapi.action_handler_t.__init__(self) 198 | 199 | def activate(self, ctx): 200 | global localxrefs 201 | show_local_xrefs() 202 | add_to_namespace('__main__', 'localxrefs', localxrefs) 203 | return 1 204 | 205 | def update(self, ctx): 206 | return idaapi.AST_ENABLE_ALWAYS 207 | except AttributeError: 208 | pass 209 | 210 | 211 | class localizedxrefs_t(idaapi.plugin_t): 212 | flags = 0 213 | comment = "IDA Localized Xrefs" 214 | help = "" 215 | wanted_name = "Localized Xrefs" 216 | wanted_hotkey = "" 217 | menu_context = None 218 | menu_name = 'List local xrefs' 219 | action_name = 'localxrefs:action' 220 | menu_tab = 'Jump/' 221 | 222 | def init(self): 223 | if idaapi.IDA_SDK_VERSION >= 700: 224 | action_desc = idaapi.action_desc_t(self.action_name, 225 | self.menu_name, 226 | LocalXrefHandler(), 227 | self.wanted_hotkey, 228 | 'Localized Xrefs.', 229 | 199) 230 | idaapi.register_action(action_desc) 231 | idaapi.attach_action_to_menu( 232 | self.menu_tab, self.action_name, idaapi.SETMENU_APP) 233 | else: 234 | self.menu_context = idaapi.add_menu_item( 235 | self.menu_tab, self.menu_name, "", 0, show_local_xrefs, (None,)) 236 | return idaapi.PLUGIN_KEEP 237 | 238 | def term(self): 239 | if idaapi.IDA_SDK_VERSION >= 700: 240 | idaapi.detach_action_from_menu(self.menu_tab, self.action_name) 241 | else: 242 | if self.menu_context is not None: 243 | idaapi.del_menu_item(self.menu_context) 244 | return None 245 | 246 | def run(self): 247 | pass 248 | 249 | 250 | def PLUGIN_ENTRY(): 251 | return localizedxrefs_t() 252 | -------------------------------------------------------------------------------- /plugins/mipslocalvars/README.md: -------------------------------------------------------------------------------- 1 | mipslocalvars.py 2 | ================ 3 | 4 | Features 5 | -------- 6 | 7 | * Names stack variables used by the compiler for storing registers on the stack, simplifying stack data analysis (MIPS only) 8 | 9 | Usage 10 | ----- 11 | 12 | A function's stack layout before running mipslocalvars: 13 | 14 | ![Before mipslocalvars.py](../../images/before_mipslocalvars.png) 15 | 16 | Running mipslocalvars: 17 | 18 | ![Running mipslocalvars.py](../../images/how_to_run_mipslocalvars.png) 19 | 20 | The function's stack layout after running mipslocalvars: 21 | 22 | ![After mipslocalvars.py](../../images/after_mipslocalvars.png) 23 | 24 | Installation 25 | ------------ 26 | 27 | Just copy mipslocalvars.py into your IDA *plugins* directory. 28 | -------------------------------------------------------------------------------- /plugins/mipslocalvars/mipslocalvars.py: -------------------------------------------------------------------------------- 1 | # IDA plugin to name stack variables that are simply used to store register 2 | # values until a function returns ($ra, $s0-$s7, $fp, $gp). 3 | # 4 | # Invoke by going to Options->Name saved registers. 5 | # 6 | # Craig Heffner 7 | # Tactical Network Solutions 8 | 9 | from __future__ import print_function 10 | import idaapi 11 | import idautils 12 | 13 | from shims import ida_shims 14 | 15 | 16 | class NameMIPSSavedRegisters(object): 17 | INSIZE = 4 18 | SEARCH_DEPTH = 25 19 | 20 | ARCH = { 21 | 'arguments': ['$a0', '$a1', '$a2', '$a3'], 22 | 'savedregs': ['$s0', '$s1', '$s2', '$s3', '$s4', '$s5', '$s6', 23 | '$s7', '$fp', '$gp', '$ra'], 24 | } 25 | 26 | def __init__(self): 27 | print("Naming saved register locations...", end=' ') 28 | 29 | for ea in idautils.Functions(): 30 | mea = ea 31 | named_regs = [] 32 | last_iteration = False 33 | 34 | while mea < (ea + (self.INSIZE * self.SEARCH_DEPTH)): 35 | mnem = ida_shims.print_insn_mnem(mea) 36 | 37 | if mnem in ['sw', 'sd']: 38 | reg = ida_shims.print_operand(mea, 0) 39 | dst = ida_shims.print_operand(mea, 1) 40 | 41 | if reg in self.ARCH['savedregs'] and \ 42 | reg not in named_regs and \ 43 | dst.endswith('($sp)') and 'var_' in dst: 44 | split_string = 'var_' 45 | stack_position = "[sp-%d]" 46 | if '_s' in dst: 47 | split_string += 's' 48 | stack_position = "[sp+%d]" 49 | 50 | offset = int(dst.split(split_string)[1].split('(')[0], 51 | 16) 52 | ida_shims.define_local_var( 53 | ea, ida_shims.find_func_end(ea), 54 | stack_position % offset, "saved_%s" % reg[1:]) 55 | named_regs.append(reg) 56 | 57 | if last_iteration: 58 | break 59 | elif mnem.startswith('j') or mnem.startswith('b'): 60 | last_iteration = True 61 | 62 | mea += self.INSIZE 63 | 64 | print("done.") 65 | 66 | 67 | def name_saved_registers(arg=None): 68 | NameMIPSSavedRegisters() 69 | 70 | 71 | try: 72 | class MipsSavedRegistersAction(idaapi.action_handler_t): 73 | def __init__(self): 74 | idaapi.action_handler_t.__init__(self) 75 | 76 | def activate(self, ctx): 77 | name_saved_registers() 78 | return 1 79 | 80 | def update(self, ctx): 81 | return idaapi.AST_ENABLE_ALWAYS 82 | except AttributeError: 83 | pass 84 | 85 | 86 | class mips_saved_registers_t(idaapi.plugin_t): 87 | flags = 0 88 | comment = "" 89 | help = "" 90 | wanted_name = "Names MIPS registers saved on the stack" 91 | wanted_hotkey = "" 92 | menu_context = None 93 | menu_name = 'Name saved registers' 94 | action_name = 'savedregisters:action' 95 | wanted_tooltip = 'Name saved registers' 96 | menu_tab = 'Options/' 97 | 98 | def init(self): 99 | if idaapi.IDA_SDK_VERSION >= 700: 100 | action_desc = idaapi.action_desc_t(self.action_name, 101 | self.menu_name, 102 | MipsSavedRegistersAction(), 103 | self.wanted_hotkey, 104 | self.wanted_tooltip, 105 | 199) 106 | idaapi.register_action(action_desc) 107 | idaapi.attach_action_to_menu( 108 | self.menu_tab, self.action_name, idaapi.SETMENU_APP) 109 | else: 110 | self.menu_context = idaapi.add_menu_item(self.menu_tab, 111 | self.menu_name, 112 | "", 113 | 0, 114 | name_saved_registers, 115 | (None,)) 116 | return idaapi.PLUGIN_KEEP 117 | 118 | def term(self): 119 | if idaapi.IDA_SDK_VERSION >= 700: 120 | idaapi.detach_action_from_menu(self.menu_tab, self.action_name) 121 | else: 122 | if self.menu_context is not None: 123 | idaapi.del_menu_item(self.menu_context) 124 | return None 125 | 126 | def run(self, arg): 127 | pass 128 | 129 | 130 | def PLUGIN_ENTRY(): 131 | return mips_saved_registers_t() 132 | -------------------------------------------------------------------------------- /plugins/mipsrop/README.md: -------------------------------------------------------------------------------- 1 | mipsrop.py 2 | ========== 3 | 4 | Features 5 | ---------- 6 | 7 | * Allows you to search for suitable ROP gadgets in MIPS executable code 8 | * Built-in methods to search for common ROP gadgets 9 | 10 | Running mipsrop: 11 | 12 | ![Running mipsrop.py](../../images/how_to_run_mipsrop.png) 13 | 14 | Searching for ROP gadgets that put a stack address into the $a0 register: 15 | 16 | ![Using mipsrop.py](../../images/mipsrop_find.png) 17 | 18 | Listing a summary of marked ROP gadgets in the current IDB: 19 | 20 | ![Listing mipsrop.py](../../images/mipsrop_summary.png) 21 | 22 | Use *mipsrop.help()* to see all available options! 23 | 24 | Installation 25 | ------------ 26 | 27 | Just copy mipsrop.py into your IDA *plugins* directory. 28 | -------------------------------------------------------------------------------- /plugins/mipsrop/mipsrop.py: -------------------------------------------------------------------------------- 1 | # IDA plugin for identifying ROP gadgets in Linux MIPS binaries. 2 | # 3 | # Return Oriented Programming in Linux MIPS is more like Jump Oriented 4 | # Programming; the idea is to control enough of the stack/registers in order to 5 | # control various jumps. Since all instructions in MIPS must be 4-byte aligned, 6 | # You cannot "create" new instructions by returning into the middle 7 | # of existing instructions, as is possible with some other architectures. 8 | # 9 | # In any given MIPS function, various registers are saved onto the stack by 10 | # necessity: 11 | # 12 | # o $s0 - $s7 13 | # o $fp 14 | # o $ra 15 | # 16 | # These values are restored from the stack before the function returns, thus, 17 | # during a stack overflow one can control some or all of these register values. 18 | # The subroutine registers ($s*) are of particular interest, as they are 19 | # commonly used by the compiler to store function pointers. By convention, gcc 20 | # will move function pointers into the $t9 register, then call the function 21 | # using jalr: 22 | # 23 | # move $t9, $s0 <-- If we control $s0, we control where the jump is taken 24 | # jalr $t9 25 | # 26 | # While there are other jumps that are of use, and which this plugin searches 27 | # for, the premise is the same: control the stack/registers, and you control 28 | # various jumps allowing you to chain various blocks of code together. 29 | # 30 | # With a list of controllable jumps such as these, we then just need to search 31 | # the surrounding instructions to see if they perform some operation which may 32 | # be useful. For example, let's say we need to load the value 1 into the $a0 33 | # register; in this case, we would want to look for a controllable jump such as 34 | # this: 35 | # 36 | # move $t9, $s1 37 | # jalr $t9 38 | # li $a0, 1 <-- Remember MIPS has jump delay slots, so this instruction 39 | # is executed with the jump 40 | # 41 | # If we return to this piece of code (and if we control $s1), we can pre-load 42 | # $s1 with the address of the next ROP gadget; thus, $a0 will be loaded with 43 | # the value 1 and we can chain this block of code with other gadgets in order 44 | # to perform more complex operations. 45 | # 46 | # This plugin finds all potentially controllable jumps, and then allows you to 47 | # search for desired instructions surrounding these controllable jumps. Example: 48 | # 49 | # Python> mipsrop.find("li $a0, 1") 50 | # ------------------------------------------------- 51 | # | Address | Action | Control Jump | 52 | # ------------------------------------------------- 53 | # | 0x0002F0F8 | li $a0,1 | jalr $s4 | 54 | # | 0x00057E50 | li $a0,1 | jalr $s1 | 55 | # ------------------------------------------------- 56 | # 57 | # The output shows the offset of each ROP gadget, the instruction within the 58 | # gadget that your search matched, and the effective register that is jumped to 59 | # after that instruction is executed. 60 | # 61 | # The specified instruction can be a full instruction, such as the example 62 | # above, or a partial instruction. Regex is supported for any of the 63 | # instruction mnemonics or operands; for convenience, the dollar signs in front 64 | # of register names are automatically escaped. 65 | # 66 | # If .set_base(int) is called with a non-zero value then the output will 67 | # include base, offset and address. 68 | # Example 69 | # ------------------------------------------------------------------------- 70 | # | Base + Offset = Address | Action | Control Jump | 71 | # ------------------------------------------------------------------------- 72 | # 73 | # Craig Heffner 74 | # Tactical Network Solutions 75 | 76 | import re 77 | import idc 78 | import sys 79 | import idaapi 80 | import idautils 81 | 82 | from shims import ida_shims 83 | 84 | # Global instance of MIPSROPFinder 85 | mipsrop = None 86 | 87 | 88 | def add_to_namespace(namespace, name, variable): 89 | ''' 90 | Add a variable to a different namespace, likely __main__. 91 | ''' 92 | import importlib 93 | 94 | importer_module = sys.modules[namespace] 95 | if name in list(sys.modules.keys()): 96 | if not (sys.version_info.major == 3 and sys.version_info.minor >= 4): 97 | import imp 98 | imp.reload(sys.modules[name]) 99 | else: 100 | importlib.reload(sys.modules[name]) 101 | else: 102 | m = importlib.import_module(name, None) 103 | sys.modules[name] = m 104 | 105 | setattr(importer_module, name, variable) 106 | 107 | 108 | class MIPSInstruction(object): 109 | ''' 110 | Class for storing info about a specific instruction. 111 | ''' 112 | 113 | def __init__(self, mnem, opnd0=None, opnd1=None, opnd2=None, 114 | ea=idc.BADADDR): 115 | self.mnem = mnem 116 | self.operands = [opnd0, opnd1, opnd2] 117 | self.opnd0 = opnd0 118 | self.opnd1 = opnd1 119 | self.opnd2 = opnd2 120 | self.ea = ea 121 | 122 | def __str__(self): 123 | string = self.mnem + " " 124 | 125 | for op in self.operands: 126 | if op: 127 | string += "%s," % op 128 | else: 129 | break 130 | 131 | return string[:-1] 132 | 133 | 134 | class ROPGadget(object): 135 | ''' 136 | Class for storing information about a specific ROP gadget. 137 | ''' 138 | def __init__(self, control, jump, operation=None, description="ROP gaget", 139 | base=0): 140 | self.control = control 141 | self.exit = jump 142 | self.operation = operation 143 | self.description = description 144 | self.base = base 145 | self.h = '-' * 112 146 | if self.base != 0: 147 | self.h += '-' * 27 148 | 149 | if self.control.opnd1: 150 | self.control.register = self.control.opnd1 151 | else: 152 | self.control.register = self.control.opnd0 153 | 154 | if self.exit.opnd1: 155 | self.exit.register = self.exit.opnd1 156 | else: 157 | self.exit.register = self.exit.opnd0 158 | 159 | if self.operation: 160 | if self.operation.ea < self.control.ea: 161 | self.entry = self.operation 162 | else: 163 | self.entry = self.control 164 | else: 165 | self.operation = self.control 166 | self.entry = self.control 167 | 168 | 169 | def header(self): 170 | if self.base != 0: 171 | return self.h + "\n| Base + Offset = Address " \ 172 | "| Action " \ 173 | " | Control Jump " \ 174 | " |\n" + self.h 175 | else: 176 | return self.h + "\n| Address | Action " \ 177 | " | Control Jump " \ 178 | " |\n" + self.h 179 | 180 | def footer(self): 181 | return self.h 182 | 183 | def __str__(self): 184 | if self.base != 0: 185 | return "| 0x%.8X + 0x%.8X = 0x%.8X | %-50s | " \ 186 | " %-5s %-30s |" % \ 187 | (self.base, self.entry.ea, self.entry.ea + self.base, 188 | str(self.operation), self.exit.mnem, self.control.register) 189 | else: 190 | return "| 0x%.8X | %-50s | %-5s %-30s |" % \ 191 | (self.entry.ea, str(self.operation), self.exit.mnem, 192 | self.control.register) 193 | 194 | 195 | class BowcasterBuilder(object): 196 | ''' 197 | Class to generate bowcaster code from a list of selected ROP gadgets. WIP. 198 | ''' 199 | INSIZE = 4 200 | SEARCH_DEPTH = 25 201 | 202 | def __init__(self, gadgets): 203 | self.code = [] 204 | self.gadgets = gadgets 205 | 206 | def build_code(self): 207 | keys = list(self.gadgets.keys()) 208 | keys.sort() 209 | 210 | for key in keys[::-1]: 211 | last_instruction = False 212 | ea = self.gadgets[key] 213 | end_ea = ea + self.SEARCH_DEPTH 214 | 215 | while ea <= end_ea: 216 | mnem = idc.print_insn_mnem(ea) 217 | if mnem in ['jr', 'jalr']: 218 | last_instruction = True 219 | ea += self.INSIZE 220 | 221 | def print_code(self): 222 | for line in self.code: 223 | print(line) 224 | 225 | 226 | class MIPSROPFinder(object): 227 | ''' 228 | Primary ROP finder class. 229 | ''' 230 | CODE = 2 231 | DATA = 3 232 | INSIZE = 4 233 | SEARCH_DEPTH = 25 234 | 235 | def __init__(self): 236 | self.base = 0 237 | 238 | self._initial_find() 239 | 240 | if self.controllable_jumps or self.system_calls: 241 | print("MIPS ROP Finder activated, found %d controllable jumps " \ 242 | "between 0x%.8X and 0x%.8X" % (len(self.controllable_jumps), 243 | self.start, self.end)) 244 | else: 245 | print("No ROP gadgets found!") 246 | 247 | def _initial_find(self): 248 | self.start = idc.BADADDR 249 | self.end = idc.BADADDR 250 | self.system_calls = [] 251 | self.double_jumps = [] 252 | self.controllable_jumps = [] 253 | start = 0 254 | end = 0 255 | 256 | for (start, end) in self._get_segments(self.CODE): 257 | self.controllable_jumps += self._find_controllable_jumps(start, end) 258 | self.system_calls += self._find_system_calls(start, end) 259 | self.double_jumps += self._find_double_jumps(start, end) 260 | if self.start == idc.BADADDR: 261 | self.start = start 262 | self.end = end 263 | 264 | def _get_segments(self, attr): 265 | segments = [] 266 | seg = ida_shims.get_first_seg() 267 | 268 | while seg != idc.BADADDR: 269 | if ida_shims.get_segm_attr(seg, idc.SEGATTR_TYPE) == attr: 270 | start = ida_shims.get_segm_start(seg) 271 | end = ida_shims.get_segm_end(seg) 272 | segments.append((start, end)) 273 | seg = ida_shims.get_next_seg(seg) 274 | 275 | return segments 276 | 277 | def _get_instruction(self, ea): 278 | return MIPSInstruction(ida_shims.print_insn_mnem(ea), 279 | ida_shims.print_operand(ea, 0), 280 | ida_shims.print_operand(ea, 1), 281 | ida_shims.print_operand(ea, 2), ea) 282 | 283 | def _does_instruction_match(self, ea, instruction, regex=False): 284 | i = 0 285 | op_cnt = 0 286 | op_ok_cnt = 0 287 | match = False 288 | insn = ida_shims.decode_insn(ea) 289 | mnem = ida_shims.print_insn_mnem(ea) 290 | if (not instruction.mnem) or (instruction.mnem == mnem) or \ 291 | (regex and re.match(instruction.mnem, mnem)): 292 | for operand in instruction.operands: 293 | if operand: 294 | op_cnt += 1 295 | op = ida_shims.print_operand(ea, i) 296 | 297 | if regex: 298 | if re.match(operand, op): 299 | op_ok_cnt += 1 300 | elif operand == op: 301 | op_ok_cnt += 1 302 | i += 1 303 | 304 | if op_cnt == op_ok_cnt: 305 | match = True 306 | return match 307 | 308 | def _is_bad_instruction(self, ea, bad_instructions=['j', 'b'], 309 | no_clobber=[]): 310 | bad = False 311 | mnem = ida_shims.print_insn_mnem(ea) 312 | 313 | if mnem and mnem[0] in bad_instructions: 314 | bad = True 315 | else: 316 | insn = ida_shims.decode_insn(ea) 317 | feature = ida_shims.get_canon_feature(insn) 318 | for register in no_clobber: 319 | if (feature & idaapi.CF_CHG1) == idaapi.CF_CHG1: 320 | if ida_shims.print_operand(ea, 0) == register: 321 | bad = True 322 | 323 | return bad 324 | 325 | def _contains_bad_instruction(self, start_ea, end_ea, 326 | bad_instructions=['j', 'b'], no_clobber=[]): 327 | ea = start_ea 328 | 329 | while ea <= end_ea: 330 | if self._is_bad_instruction(ea, bad_instructions, no_clobber): 331 | return True 332 | else: 333 | ea += self.INSIZE 334 | 335 | return False 336 | 337 | def _find_prev_instruction_ea(self, start_ea, instruction, end_ea=0, 338 | no_baddies=True, regex=False, 339 | dont_overwrite=[]): 340 | instruction_ea = idc.BADADDR 341 | ea = start_ea 342 | 343 | while ea >= end_ea: 344 | if self._does_instruction_match(ea, instruction, regex): 345 | instruction_ea = ea 346 | break 347 | elif no_baddies and self._is_bad_instruction( 348 | ea, no_clobber=dont_overwrite): 349 | break 350 | 351 | ea -= self.INSIZE 352 | 353 | return instruction_ea 354 | 355 | def _find_next_instruction_ea(self, start_ea, instruction, 356 | end_ea=idc.BADADDR, no_baddies=False, 357 | regex=False, dont_overwrite=[]): 358 | instruction_ea = idc.BADADDR 359 | ea = start_ea 360 | 361 | while ea <= end_ea: 362 | if self._does_instruction_match(ea, instruction, regex): 363 | instruction_ea = ea 364 | break 365 | elif no_baddies and self._is_bad_instruction( 366 | ea, no_clobber=dont_overwrite): 367 | break 368 | 369 | ea += self.INSIZE 370 | 371 | return instruction_ea 372 | 373 | def _find_controllable_jumps(self, start_ea, end_ea): 374 | controllable_jumps = [] 375 | t9_controls = [ 376 | MIPSInstruction("move", "\$t9"), 377 | MIPSInstruction("addiu", "\$t9", "^\$"), 378 | ] 379 | t9_jumps = [ 380 | MIPSInstruction("jr", "\$t9"), 381 | ] 382 | ra_controls = [ 383 | MIPSInstruction("lw", "\$ra"), 384 | ] 385 | ra_jumps = [ 386 | # TODO: Search for jumps to registers other than $ra. 387 | MIPSInstruction("jr", "\$ra"), 388 | ] 389 | t9_musnt_clobber = ["$t9"] 390 | ra_musnt_clobber = ["$ra"] 391 | 392 | if idaapi.IDA_SDK_VERSION >= 740: 393 | t9_jumps.append(MIPSInstruction("jalr", "\$ra")) 394 | else: 395 | t9_jumps.append(MIPSInstruction("jalr", "\$t9")) 396 | 397 | for possible_control_instruction in t9_controls+ra_controls: 398 | ea = start_ea 399 | 400 | if possible_control_instruction in t9_controls: 401 | jumps = t9_jumps 402 | musnt_clobber = t9_musnt_clobber 403 | else: 404 | jumps = ra_jumps 405 | musnt_clobber = ra_musnt_clobber 406 | 407 | while ea <= end_ea: 408 | ea = self._find_next_instruction_ea( 409 | ea, possible_control_instruction, end_ea, regex=True) 410 | if ea != idc.BADADDR: 411 | insn = ida_shims.decode_insn(ea) 412 | 413 | control_instruction = self._get_instruction(ea) 414 | control_register = control_instruction.operands[1] 415 | 416 | if control_register: 417 | for jump in jumps: 418 | jump_ea = self._find_next_instruction_ea( 419 | ea+insn.size, jump, end_ea, no_baddies=True, 420 | regex=True, dont_overwrite=musnt_clobber) 421 | 422 | if jump_ea != idc.BADADDR: 423 | jump_instruction = self._get_instruction( 424 | jump_ea) 425 | controllable_jumps.append( 426 | ROPGadget( 427 | control_instruction, jump_instruction, 428 | description="Controllable Jump", 429 | base=self.base)) 430 | ea = jump_ea 431 | 432 | ea += insn.size 433 | 434 | return controllable_jumps 435 | 436 | def _find_system_calls(self, start_ea, end_ea): 437 | system_calls = [] 438 | system_load = MIPSInstruction("la", "$t9", "system") 439 | stack_arg_zero = MIPSInstruction("addiu", "$a0", "$sp") 440 | 441 | for xref in idautils.XrefsTo(ida_shims.get_name_ea_simple('system')): 442 | ea = xref.frm 443 | mnem = ida_shims.print_insn_mnem(ea) 444 | if mnem and ea >= start_ea and \ 445 | ea <= end_ea and mnem[0] in ['j', 'b']: 446 | a0_ea = self._find_next_instruction_ea( 447 | ea+self.INSIZE, stack_arg_zero, ea+self.INSIZE) 448 | 449 | if a0_ea == idc.BADADDR: 450 | a0_ea = self._find_prev_instruction_ea( 451 | ea, stack_arg_zero, ea-(self.SEARCH_DEPTH*self.INSIZE)) 452 | 453 | if a0_ea != idc.BADADDR: 454 | control_ea = self._find_prev_instruction_ea( 455 | ea-self.INSIZE, system_load, 456 | ea-(self.SEARCH_DEPTH*self.INSIZE)) 457 | 458 | if control_ea != idc.BADADDR: 459 | system_calls.append( 460 | ROPGadget( 461 | self._get_instruction(control_ea), 462 | self._get_instruction(ea), 463 | self._get_instruction(a0_ea), 464 | description="System call", base=self.base)) 465 | 466 | ea += self.INSIZE 467 | else: 468 | break 469 | 470 | return system_calls 471 | 472 | def _find_double_jumps(self, start_ea, end_ea): 473 | double_jumps = [] 474 | 475 | for i in range(0, len(self.controllable_jumps)): 476 | g1 = self.controllable_jumps[i] 477 | if g1.exit.mnem != 'jalr': 478 | continue 479 | 480 | for j in range(i+1, len(self.controllable_jumps)): 481 | g2 = self.controllable_jumps[j] 482 | distance = (g2.entry.ea - g1.exit.ea) 483 | 484 | if 0 < distance <= (self.SEARCH_DEPTH * self.INSIZE): 485 | if g1.control.register != g2.control.register: 486 | if not self._contains_bad_instruction( 487 | g1.exit.ea+self.INSIZE, 488 | g2.control.ea-self.INSIZE, 489 | no_clobber=[g2.control.register]): 490 | double_jumps.append(g1) 491 | break 492 | 493 | return double_jumps 494 | 495 | def _find_rop_gadgets(self, gadget): 496 | gadget_list = [] 497 | 498 | for controllable_jump in self.controllable_jumps: 499 | gadget_ea = idc.BADADDR 500 | 501 | ea = self._find_next_instruction_ea( 502 | controllable_jump.entry.ea, gadget, 503 | controllable_jump.exit.ea+self.INSIZE, regex=True) 504 | 505 | if ea != idc.BADADDR: 506 | gadget_ea = ea 507 | else: 508 | ea = self._find_prev_instruction_ea( 509 | controllable_jump.entry.ea, gadget, 510 | controllable_jump.entry.ea-(self.SEARCH_DEPTH*self.INSIZE), 511 | no_baddies=True, regex=True, 512 | dont_overwrite=[controllable_jump.entry.opnd1]) 513 | 514 | if ea != idc.BADADDR: 515 | gadget_ea = ea 516 | 517 | if gadget_ea != idc.BADADDR: 518 | gadget_list.append( 519 | ROPGadget( 520 | controllable_jump.entry, controllable_jump.exit, 521 | self._get_instruction(gadget_ea), base=self.base)) 522 | 523 | return gadget_list 524 | 525 | def _print_gadgets(self, gadgets): 526 | if gadgets: 527 | print(gadgets[0].header()) 528 | 529 | for gadget in gadgets: 530 | print(str(gadget)) 531 | 532 | if gadgets: 533 | print(gadgets[0].footer()) 534 | 535 | print("Found %d matching gadgets" % (len(gadgets))) 536 | 537 | def _get_marked_gadgets(self): 538 | rop_gadgets = {} 539 | 540 | for i in range(0 if idaapi.IDA_SDK_VERSION >= 690 else 1, 1024): 541 | marked_pos = ida_shims.get_bookmark(i) 542 | if marked_pos != idc.BADADDR: 543 | marked_comment = ida_shims.get_bookmark_desc(i) 544 | if marked_comment and marked_comment.lower().startswith("rop"): 545 | rop_gadgets[marked_comment] = marked_pos 546 | else: 547 | break 548 | 549 | return rop_gadgets 550 | 551 | def double(self): 552 | self.doubles() 553 | 554 | def doubles(self): 555 | ''' 556 | Prints a list of all "double jump" gadgets (useful for function calls). 557 | ''' 558 | self._print_gadgets(self.double_jumps) 559 | 560 | def stackfinder(self): 561 | self.stackfinders() 562 | 563 | def stackfinders(self): 564 | ''' 565 | Prints a list of all gadgets that put a stack address into a register. 566 | ''' 567 | self.find("addiu .*, $sp") 568 | 569 | def lia0(self): 570 | ''' 571 | Prints a list of all gadgets that load an immediate value number into 572 | $a0 (useful for setting up the argument to sleep). 573 | ''' 574 | self.find("li $a0") 575 | 576 | def tail(self): 577 | return self.tails() 578 | 579 | def tails(self): 580 | ''' 581 | Prints a lits of all tail call gadgets (useful for function calls). 582 | ''' 583 | return self.iret() 584 | 585 | def iret(self): 586 | ''' 587 | Prints a lits of all tail gadgets (useful for function calls). 588 | ''' 589 | tail_gadgets = [] 590 | 591 | for gadget in self._find_rop_gadgets(MIPSInstruction("move", "\$t9")): 592 | if gadget.exit.mnem == 'jr' and gadget.exit.register == '$t9': 593 | tail_gadgets.append(gadget) 594 | 595 | self._print_gadgets(tail_gadgets) 596 | 597 | def system(self): 598 | ''' 599 | Prints a list of gadgets that may be used to call system(). 600 | ''' 601 | sys_gadgets = self.system_calls + self._find_rop_gadgets( 602 | MIPSInstruction("addiu", "\$a0", "\$sp")) 603 | self._print_gadgets(sys_gadgets) 604 | 605 | def find(self, *args): 606 | ''' 607 | Locates all potential ROP gadgets that contain the specified 608 | instruction. 609 | 610 | @instruction_string - The instruction you need executed. This can be 611 | either a: 612 | 613 | o Full instruction - "li $a0, 1" 614 | o Partial instruction - "li $a0" 615 | o Regex instruction - "li $a0, .*" 616 | ''' 617 | count = 0 618 | good_gadgets = {} 619 | final_gadgets = [] 620 | registers = ['$v', '$s', '$a', '$t', '$k', '$pc', '$fp', '$ra', '$gp', 621 | '$at', '$zero'] 622 | 623 | for instruction_string in args: 624 | comma_split = instruction_string.split(',') 625 | instruction_parts = comma_split[0].split() 626 | if len(comma_split) > 1: 627 | instruction_parts += comma_split[1:] 628 | 629 | for i in range(0, 4): 630 | if i > len(instruction_parts) - 1: 631 | instruction_parts.append(None) 632 | else: 633 | instruction_parts[i] = instruction_parts[i].strip().\ 634 | strip(',').strip() 635 | for reg in registers: 636 | instruction_parts[i] = instruction_parts[i].replace( 637 | reg, "\\%s" % reg) 638 | 639 | instruction = MIPSInstruction( 640 | instruction_parts[0], instruction_parts[1], 641 | instruction_parts[2], instruction_parts[3]) 642 | 643 | gadgets = self._find_rop_gadgets(instruction) 644 | for gadget in gadgets: 645 | if count == 0: 646 | good_gadgets[gadget.control.ea] = {'gadget': gadget, 647 | 'count': 1} 648 | elif gadget.control.ea in good_gadgets: 649 | good_gadgets[gadget.control.ea]['count'] += 1 650 | break 651 | count += 1 652 | 653 | if good_gadgets: 654 | for (ea, info) in good_gadgets.items(): 655 | if info['count'] == count: 656 | final_gadgets.append(info['gadget']) 657 | else: 658 | print("Skipping gadget at 0x%X" % ea) 659 | if final_gadgets: 660 | self._print_gadgets(gadgets) 661 | else: 662 | print("No ROP gadgets found!") 663 | 664 | def summary(self): 665 | ''' 666 | Prints a summary of your currently marked ROP gadgets, in alphabetical 667 | order by the marked name. To mark a location as a ROP gadget, simply 668 | mark the position in IDA (Alt+M) with any name that starts with "ROP". 669 | ''' 670 | rop_gadgets = self._get_marked_gadgets() 671 | summaries = [] 672 | delim_char = "-" 673 | headings = { 674 | 'name': "Gadget Name", 675 | 'offset': "Gadget Offset", 676 | 'summary': "Gadget Summary" 677 | } 678 | if self.base != 0: 679 | headings['offset'] = "Gadget Base + Offset = Address " 680 | lengths = { 681 | 'name': len(headings['name']), 682 | 'offset': len(headings['offset']), 683 | 'summary': len(headings['summary']), 684 | } 685 | total_length = (3 * len(headings)) + 1 686 | 687 | if rop_gadgets: 688 | gadget_keys = list(rop_gadgets.keys()) 689 | gadget_keys.sort() 690 | 691 | for marked_comment in gadget_keys: 692 | if len(marked_comment) > lengths['name']: 693 | lengths['name'] = len(marked_comment) 694 | 695 | summary = [] 696 | ea = rop_gadgets[marked_comment] 697 | end_ea = ea + (self.SEARCH_DEPTH * self.INSIZE) 698 | 699 | while ea <= end_ea: 700 | summary.append(idc.GetDisasm(ea)) 701 | mnem = ida_shims.print_insn_mnem(ea) 702 | if len(mnem) > 0 and mnem[0].lower() in ['j', 'b']: 703 | summary.append(idc.GetDisasm(ea+self.INSIZE)) 704 | break 705 | 706 | ea += self.INSIZE 707 | 708 | if len(summary) == 0: 709 | summary.append('') 710 | 711 | for line in summary: 712 | if len(line) > lengths['summary']: 713 | lengths['summary'] = len(line) 714 | 715 | summaries.append(summary) 716 | 717 | for (heading, size) in lengths.items(): 718 | total_length += size 719 | 720 | delim = delim_char * total_length 721 | line_fmt = "| %%-%ds | %%-%ds | %%-%ds |" % \ 722 | (lengths['name'], lengths['offset'], lengths['summary']) 723 | 724 | print(delim) 725 | print(line_fmt % \ 726 | (headings['name'], headings['offset'], headings['summary'])) 727 | print(delim) 728 | 729 | for i in range(0, len(gadget_keys)): 730 | line_count = 0 731 | marked_comment = gadget_keys[i] 732 | if self.base != 0: 733 | offset = "0x%.8X + 0x%.8X = 0x%.8X" % \ 734 | (self.base, rop_gadgets[marked_comment], 735 | self.base + rop_gadgets[marked_comment]) 736 | else: 737 | offset = "0x%.8X" % rop_gadgets[marked_comment] 738 | summary = summaries[i] 739 | 740 | for line in summary: 741 | if line_count == 0: 742 | print(line_fmt % (marked_comment, offset, line)) 743 | else: 744 | print(line_fmt % ('', '', line)) 745 | 746 | line_count += 1 747 | 748 | print(delim) 749 | 750 | def build(self): 751 | ''' 752 | WIP. 753 | ''' 754 | gadgets = self._get_marked_gadgets() 755 | bc = BowcasterBuilder(gadgets) 756 | bc.build_code() 757 | 758 | def set_base(self, base=0): 759 | ''' 760 | Set base address used for display 761 | ''' 762 | self.base = base 763 | self._initial_find() 764 | 765 | def help(self): 766 | ''' 767 | Show help info. 768 | ''' 769 | delim = "-" * 140 770 | 771 | print("") 772 | print("mipsrop.find(instruction_string)") 773 | print(delim) 774 | print(self.find.__doc__) 775 | 776 | print("") 777 | print("mipsrop.system()") 778 | print(delim) 779 | print(self.system.__doc__) 780 | 781 | print("") 782 | print("mipsrop.doubles()") 783 | print(delim) 784 | print(self.doubles.__doc__) 785 | 786 | print("") 787 | print("mipsrop.stackfinders()") 788 | print(delim) 789 | print(self.stackfinders.__doc__) 790 | 791 | print("") 792 | print("mipsrop.tails()") 793 | print(delim) 794 | print(self.tails.__doc__) 795 | 796 | print("") 797 | print("mipsrop.set_base()") 798 | print(delim) 799 | print(self.set_base.__doc__) 800 | 801 | print("") 802 | print("mipsrop.summary()") 803 | print(delim) 804 | print(self.summary.__doc__) 805 | 806 | 807 | try: 808 | class MipsRopHandler(idaapi.action_handler_t): 809 | def __init__(self): 810 | idaapi.action_handler_t.__init__(self) 811 | 812 | def activate(self, ctx): 813 | global mipsrop 814 | mipsrop = MIPSROPFinder() 815 | add_to_namespace('__main__', 'mipsrop', mipsrop) 816 | return 1 817 | 818 | def update(self, ctx): 819 | return idaapi.AST_ENABLE_ALWAYS 820 | except AttributeError: 821 | pass 822 | 823 | 824 | class mipsropfinder_t(idaapi.plugin_t): 825 | flags = 0 826 | comment = 'MIPS ROP Finder' 827 | help = '' 828 | action_name = 'mipsrop:action' 829 | wanted_name = "MIPS ROP Finder" 830 | wanted_hotkey = "" 831 | wanted_tooltip = "MIPS ROP Gadget Finder" 832 | root_tab = 'Search/' 833 | menu_name = 'mips rop gadgets' 834 | menu_context = None 835 | 836 | def init(self): 837 | if idaapi.IDA_SDK_VERSION >= 700: 838 | # 199 is a default icon. 839 | action_desc = idaapi.action_desc_t(self.action_name, 840 | self.menu_name, 841 | MipsRopHandler(), 842 | self.wanted_hotkey, 843 | self.wanted_tooltip, 844 | 199) 845 | 846 | idaapi.register_action(action_desc) 847 | idaapi.attach_action_to_menu( 848 | self.root_tab, self.action_name, idaapi.SETMENU_APP) 849 | else: 850 | self.menu_context = idaapi.add_menu_item( 851 | "Search/", self.menu_name, "", 0, self.run, (None,)) 852 | return idaapi.PLUGIN_KEEP 853 | 854 | def term(self): 855 | if idaapi.IDA_SDK_VERSION >= 700: 856 | idaapi.detach_action_from_menu(self.root_tab, self.action_name) 857 | else: 858 | if self.menu_context is not None: 859 | idaapi.del_menu_item(self.menu_context) 860 | return None 861 | 862 | def run(self, arg): 863 | global mipsrop 864 | mipsrop = MIPSROPFinder() 865 | 866 | 867 | def PLUGIN_ENTRY(): 868 | return mipsropfinder_t() 869 | 870 | 871 | -------------------------------------------------------------------------------- /plugins/rizzo/README.md: -------------------------------------------------------------------------------- 1 | rizzo.py 2 | ========== 3 | 4 | Features 5 | ---------- 6 | 7 | Identifies and re-names functions between two or more IDBs based on: 8 | * Formal signatures (i.e., exact function signatures) 9 | * References to unique string 10 | * References to unique constants 11 | * Fuzzy signatures (i.e., similar function signatures) 12 | * Call graphs (e.g., identification by association) 13 | 14 | Usage 15 | ----- 16 | 17 | To generate signatures for functions in your current IDB: 18 | 19 | ![Generating Rizzo signatures](../../images/rizzo_generate.png) 20 | 21 | To apply generated signatures to your current IDB: 22 | 23 | ![Applying Rizzo signatures](../../images/rizzo_apply.png) 24 | 25 | Some pre-generated signatures are provided in the included sub-directories. 26 | 27 | Installation 28 | ------------ 29 | 30 | Just copy `rizzo.py` into your IDA `plugins` directory. 31 | -------------------------------------------------------------------------------- /plugins/rizzo/rizzo.py: -------------------------------------------------------------------------------- 1 | ########################################################################## 2 | # An IDAPython plugin that generates "fuzzy" function signatures that can 3 | # be shared and applied amongst different IDBs. 4 | # 5 | # There are multiple sets of signatures that are generated: 6 | # 7 | # o "Formal" signatures, where functions must match exactly 8 | # o "Fuzzy" signatures, where functions must only resemble each other 9 | # in terms of data/call references. 10 | # o String-based signatures, where functions are identified based on 11 | # unique string references. 12 | # o Immediate-based signatures, where functions are identified based 13 | # on immediate value references. 14 | # 15 | # These signatures are applied based on accuracy, that is, formal 16 | # signatures are applied first, then string and immediate based 17 | # signatures, and finally fuzzy signatures. 18 | # 19 | # Further, functions are identified based on call references. Consider, 20 | # for example, two functions, one named 'foo', the other named 'bar'. 21 | # The 'foo' function is fairly unique and a reliable signature is easily 22 | # generated for it, but the 'bar' function is more difficult to reliably 23 | # identify. However, 'foo' calls 'bar', and thus once 'foo' is identified, 24 | # 'bar' can also be identified by association. 25 | # 26 | # Craig Heffner 27 | # @devttys0 28 | ########################################################################## 29 | 30 | from __future__ import print_function 31 | import idc 32 | import idaapi 33 | import idautils 34 | 35 | import os 36 | import time 37 | import pickle # http://natashenka.ca/pickle/ 38 | import collections 39 | import hashlib 40 | 41 | from shims import ida_shims 42 | 43 | 44 | class RizzoSignatures(object): 45 | ''' 46 | Simple wrapper class for storing signature info. 47 | ''' 48 | SHOW = [] 49 | 50 | def __init__(self): 51 | self.fuzzy = {} 52 | self.formal = {} 53 | self.strings = {} 54 | self.functions = {} 55 | self.immediates = {} 56 | 57 | self.fuzzydups = set() 58 | self.formaldups = set() 59 | self.stringdups = set() 60 | self.immediatedups = set() 61 | 62 | def show(self): 63 | if not self.SHOW: 64 | return 65 | 66 | print("\n\nGENERATED FORMAL SIGNATURES FOR:") 67 | for (key, ea) in self.formal.items(): 68 | func = RizzoFunctionDescriptor(self.formal, self.functions, key) 69 | if func.name in self.SHOW: 70 | print(func.name) 71 | 72 | print("\n\nGENERATED FUZZY SIGNATURES FOR:") 73 | for (key, ea) in self.fuzzy.items(): 74 | func = RizzoFunctionDescriptor(self.fuzzy, self.functions, key) 75 | if func.name in self.SHOW: 76 | print(func.name) 77 | 78 | 79 | class RizzoStringDescriptor(object): 80 | ''' 81 | Wrapper class for easily accessing necessary string information. 82 | ''' 83 | 84 | def __init__(self, string): 85 | self.ea = string.ea 86 | self.value = str(string) 87 | self.xrefs = [x.frm for x in idautils.XrefsTo(self.ea)] 88 | 89 | 90 | class RizzoBlockDescriptor(object): 91 | ''' 92 | Code block info is stored in tuples, which minimize pickle storage space. 93 | This class provides more Pythonic (and sane) access to values of interest 94 | for a given block. 95 | ''' 96 | 97 | def __init__(self, block): 98 | self.formal = block[0] 99 | self.fuzzy = block[1] 100 | self.immediates = block[2] 101 | self.functions = block[3] 102 | 103 | def match(self, nblock, fuzzy=False): 104 | # TODO: Fuzzy matching at the block level gets close, but produces a 105 | # higher number of false positives; for example, it confuses 106 | # hmac_md5 with hmac_sha1. 107 | return (self.formal == nblock.formal and 108 | len(self.immediates) == len(nblock.immediates) and 109 | len(self.functions) == len(nblock.functions)) 110 | 111 | 112 | class RizzoFunctionDescriptor(object): 113 | ''' 114 | Function signature info is stored in dicts and tuples, which minimize pickle 115 | storage space. This class provides more Pythonic (and sane) access to 116 | values of interest for a given function. 117 | ''' 118 | 119 | def __init__(self, signatures, functions, key): 120 | self.ea = signatures[key] 121 | self.name = functions[self.ea][0] 122 | self.blocks = functions[self.ea][1] 123 | 124 | 125 | class Rizzo(object): 126 | ''' 127 | Workhorse class which performs the primary logic and functionality. 128 | ''' 129 | 130 | DEFAULT_SIGNATURE_FILE = "rizzo.sig" 131 | 132 | def __init__(self, sigfile=None): 133 | if sigfile: 134 | self.sigfile = sigfile 135 | else: 136 | self.sigfile = self.DEFAULT_SIGNATURE_FILE 137 | 138 | # Useful for identifying string xrefs from individual instructions 139 | self.strings = {} 140 | for string in idautils.Strings(): 141 | self.strings[string.ea] = RizzoStringDescriptor(string) 142 | 143 | start = time.time() 144 | self.signatures = self.generate() 145 | end = time.time() 146 | 147 | print("Generated %d formal signatures and %d fuzzy signatures for %d " \ 148 | "functions in %.2f seconds." % (len(self.signatures.formal), 149 | len(self.signatures.fuzzy), 150 | len(self.signatures.functions), 151 | (end-start))) 152 | 153 | def save(self): 154 | print(("Saving signatures to %s..." % self.sigfile), end=' ') 155 | fp = open(self.sigfile, "wb") 156 | pickle.dump(self.signatures, fp) 157 | fp.close() 158 | print("done.") 159 | 160 | def load(self): 161 | print(("Loading signatures from %s..." % self.sigfile), end=' ') 162 | fp = open(self.sigfile, "rb") 163 | sigs = pickle.load(fp) 164 | fp.close() 165 | print("done.") 166 | return sigs 167 | 168 | def sighash(self, value): 169 | h = hashlib.md5() 170 | h.update(value.encode("utf-8")) 171 | hash_value = h.hexdigest() 172 | del(h) 173 | return hash_value 174 | 175 | def block(self, block): 176 | ''' 177 | Returns a tuple: 178 | ([formal, block, signatures], [fuzzy, block, signatures], 179 | set([unique, immediate, values]), [called, function, names]) 180 | ''' 181 | formal = [] 182 | fuzzy = [] 183 | functions = [] 184 | immediates = [] 185 | 186 | ea = ida_shims.start_ea(block) 187 | while ea < ida_shims.end_ea(block): 188 | insn = ida_shims.decode_insn(ea) 189 | 190 | # Get a list of all data/code refs from the current instruction 191 | drefs = [x for x in idautils.DataRefsFrom(ea)] 192 | crefs = [x for x in idautils.CodeRefsFrom(ea, False)] 193 | 194 | # Add all instruction mnemonics to the formal block hash 195 | formal.append(ida_shims.print_insn_mnem(ea)) 196 | 197 | # If this is a call instruction, be sure to note the name of the 198 | # function being called. This is used to apply call-based 199 | # signatures to functions. 200 | # 201 | # For fuzzy signatures, we can't use the actual name or EA of the 202 | # function, but rather just want to note that a function call was 203 | # made. 204 | # 205 | # Formal signatures already have the call instruction mnemonic, 206 | # which is more specific than just saying that a call was made. 207 | if idaapi.is_call_insn(ea): 208 | for cref in crefs: 209 | func_name = ida_shims.get_name(cref) 210 | if func_name: 211 | functions.append(func_name) 212 | fuzzy.append("funcref") 213 | # If there are data references from the instruction, check to see 214 | # if any of them are strings. These are looked up in the 215 | # pre-generated strings dictionary. 216 | # 217 | # String values are easily identifiable, and are used as part of 218 | # both the fuzzy and the formal signatures. 219 | # 220 | # It is more difficult to determine if non-string values are 221 | # constants or not; for both fuzzy and formal signatures, just use 222 | # "data" to indicate that some data was referenced. 223 | elif drefs: 224 | for dref in drefs: 225 | if dref in self.strings: 226 | formal.append(self.strings[dref].value) 227 | fuzzy.append(self.strings[dref].value) 228 | else: 229 | formal.append("dataref") 230 | fuzzy.append("dataref") 231 | # If there are no data or code references from the instruction, use 232 | # every operand as part of the formal signature. 233 | # 234 | # Fuzzy signatures are only concerned with interesting immediate 235 | # values, that is, values that are greater than 65,535, are not 236 | # memory addresses, and are not displayed as negative values. 237 | elif not drefs and not crefs: 238 | ops = ida_shims.get_operands(insn) 239 | for n in range(0, len(ops)): 240 | opnd_text = ida_shims.print_operand(ea, n) 241 | formal.append(opnd_text) 242 | if ops[n].type == idaapi.o_imm and \ 243 | not opnd_text.startswith('-'): 244 | if ops[n].value >= 0xFFFF: 245 | if ida_shims.get_full_flags(ops[n].value) == 0: 246 | fuzzy.append(str(ops[n].value)) 247 | immediates.append(ops[n].value) 248 | 249 | ea = ida_shims.next_head(ea) 250 | 251 | return (self.sighash(''.join(formal)), 252 | self.sighash(''.join(fuzzy)), 253 | immediates, functions) 254 | 255 | def function(self, func): 256 | ''' 257 | Returns a list of blocks. 258 | ''' 259 | blocks = [] 260 | 261 | for block in idaapi.FlowChart(func): 262 | blocks.append(self.block(block)) 263 | 264 | return blocks 265 | 266 | def generate(self): 267 | signatures = RizzoSignatures() 268 | 269 | # Generate unique string-based function signatures 270 | for (ea, string) in self.strings.items(): 271 | # Only generate signatures on reasonably long strings with one xref 272 | if len(string.value) >= 8 and len(string.xrefs) == 1: 273 | func = idaapi.get_func(string.xrefs[0]) 274 | if func: 275 | str_hash = self.sighash(string.value) 276 | 277 | # Check for and remove string duplicate signatures (the same 278 | # string can appear more than once in an IDB). 279 | # If no duplicates, add this to the string signature dict. 280 | if str_hash in signatures.strings: 281 | del signatures.strings[str_hash] 282 | signatures.stringdups.add(str_hash) 283 | elif str_hash not in signatures.stringdups: 284 | signatures.strings[str_hash] = ida_shims.start_ea(func) 285 | 286 | # Generate formal, fuzzy, and immediate-based function signatures 287 | for ea in idautils.Functions(): 288 | func = idaapi.get_func(ea) 289 | if func: 290 | # Generate a signature for each block in this function 291 | blocks = self.function(func) 292 | 293 | # Build function-wide formal and fuzzy signatures by simply 294 | # concatenating the individual function block signatures. 295 | formal = self.sighash(''.join([str(e) 296 | for (e, f, i, c) in blocks])) 297 | fuzzy = self.sighash(''.join([str(f) 298 | for (e, f, i, c) in blocks])) 299 | 300 | # Add this signature to the function dictionary. 301 | start_ea = ida_shims.start_ea(func) 302 | signatures.functions[start_ea] = (ida_shims.get_name(start_ea), 303 | blocks) 304 | 305 | # Check for and remove formal duplicate signatures. 306 | # If no duplicates, add this to the formal signature dict. 307 | if formal in signatures.formal: 308 | del signatures.formal[formal] 309 | signatures.formaldups.add(formal) 310 | elif formal not in signatures.formaldups: 311 | signatures.formal[formal] = ida_shims.start_ea(func) 312 | 313 | # Check for and remove fuzzy duplicate signatures. 314 | # If no duplicates, add this to the fuzzy signature dict. 315 | if fuzzy in signatures.fuzzy: 316 | del signatures.fuzzy[fuzzy] 317 | signatures.fuzzydups.add(fuzzy) 318 | elif fuzzy not in signatures.fuzzydups: 319 | signatures.fuzzy[fuzzy] = ida_shims.start_ea(func) 320 | 321 | # Check for and remove immediate duplicate signatures. 322 | # If no duplicates, add this to the immediate signature dict. 323 | for (e, f, immediates, c) in blocks: 324 | for immediate in immediates: 325 | if immediate in signatures.immediates: 326 | del signatures.immediates[immediate] 327 | signatures.immediatedups.add(immediate) 328 | elif immediate not in signatures.immediatedups: 329 | signatures.immediates[immediate] = \ 330 | ida_shims.start_ea(func) 331 | 332 | # These need not be maintained across function calls, 333 | # and only add to the size of the saved signature file. 334 | signatures.fuzzydups = set() 335 | signatures.formaldups = set() 336 | signatures.stringdups = set() 337 | signatures.immediatedups = set() 338 | 339 | # DEBUG 340 | signatures.show() 341 | 342 | return signatures 343 | 344 | def match(self, extsigs): 345 | fuzzy = {} 346 | formal = {} 347 | strings = {} 348 | immediates = {} 349 | 350 | # Match formal function signatures 351 | start = time.time() 352 | for (extsig, ext_func_ea) in extsigs.formal.items(): 353 | if extsig in self.signatures.formal: 354 | new_fun = RizzoFunctionDescriptor( 355 | extsigs.formal, extsigs.functions, extsig) 356 | curr_fun = RizzoFunctionDescriptor( 357 | self.signatures.formal, self.signatures.functions, extsig) 358 | formal[curr_fun] = new_fun 359 | end = time.time() 360 | print("Found %d formal matches in %.2f seconds." % (len(formal), 361 | (end-start))) 362 | 363 | # Match fuzzy function signatures 364 | start = time.time() 365 | for (extsig, ext_func_ea) in extsigs.fuzzy.items(): 366 | if extsig in self.signatures.fuzzy: 367 | curr_fun = RizzoFunctionDescriptor( 368 | self.signatures.fuzzy, self.signatures.functions, extsig) 369 | new_fun = RizzoFunctionDescriptor( 370 | extsigs.fuzzy, extsigs.functions, extsig) 371 | # Only accept this as a valid match if the functions have the 372 | # same number of basic code blocks 373 | if len(curr_fun.blocks) == len(new_fun.blocks): 374 | fuzzy[curr_fun] = new_fun 375 | end = time.time() 376 | print("Found %d fuzzy matches in %.2f seconds." % (len(fuzzy), 377 | (end-start))) 378 | 379 | # Match string based function signatures 380 | start = time.time() 381 | for (extsig, ext_func_ea) in extsigs.strings.items(): 382 | if extsig in self.signatures.strings: 383 | curr_fun = RizzoFunctionDescriptor( 384 | self.signatures.strings, self.signatures.functions, extsig) 385 | new_fun = RizzoFunctionDescriptor( 386 | extsigs.strings, extsigs.functions, extsig) 387 | strings[curr_fun] = new_fun 388 | end = time.time() 389 | print("Found %d string matches in %.2f seconds." % (len(strings), 390 | (end-start))) 391 | 392 | # Match immediate baesd function signatures 393 | start = time.time() 394 | for (extsig, ext_func_ea) in extsigs.immediates.items(): 395 | if extsig in self.signatures.immediates: 396 | curr_fun = RizzoFunctionDescriptor( 397 | self.signatures.immediates, self.signatures.functions, 398 | extsig) 399 | new_fun = RizzoFunctionDescriptor( 400 | extsigs.immediates, extsigs.functions, extsig) 401 | immediates[curr_fun] = new_fun 402 | end = time.time() 403 | print("Found %d immediate matches in %.2f seconds." % (len(immediates), 404 | (end-start))) 405 | 406 | # Return signature matches in the order we want them applied 407 | # The second tuple of each match is set to True if it is a fuzzy match, 408 | # e.g.: ((match, fuzzy), (match, fuzzy), ...) 409 | return ((formal, False), 410 | (strings, False), 411 | (immediates, False), 412 | (fuzzy, True)) 413 | 414 | def rename(self, ea, name): 415 | # Don't rely on the name in curfunc, it could have already been renamed 416 | curname = ida_shims.get_name(ea) 417 | # Don't rename if the name is a special identifier, or if the ea has 418 | # already been named 419 | if curname.startswith('sub_') and \ 420 | name.split('_')[0] not in \ 421 | ['sub', 'loc', 'unk', 'dword', 'word', 'byte']: 422 | # Don't rename if the name already exists in the IDB 423 | if ida_shims.get_name_ea_simple(name) == idc.BADADDR: 424 | if ida_shims.set_name(ea, name): 425 | ida_shims.set_func_flags( 426 | ea, (ida_shims.get_func_flags(ea) | idc.FUNC_LIB)) 427 | return 1 428 | return 0 429 | 430 | def apply(self, extsigs): 431 | count = 0 432 | 433 | start = time.time() 434 | 435 | # This applies formal matches first, then fuzzy matches 436 | for (match, fuzzy) in self.match(extsigs): 437 | # Keeps track of all function names that we've identified candidate 438 | # functions for 439 | rename = {} 440 | 441 | for (curfunc, newfunc) in match.items(): 442 | if newfunc not in rename: 443 | rename[newfunc.name] = [] 444 | 445 | # Attempt to rename this function 446 | rename[newfunc.name].append(curfunc.ea) 447 | 448 | bm = {} 449 | duplicates = set() 450 | 451 | # Search for unique matching code blocks inside this function 452 | for nblock in newfunc.blocks: 453 | nblock = RizzoBlockDescriptor(nblock) 454 | for cblock in curfunc.blocks: 455 | cblock = RizzoBlockDescriptor(cblock) 456 | 457 | if cblock.match(nblock, fuzzy): 458 | if cblock in bm: 459 | del bm[cblock] 460 | duplicates.add(cblock) 461 | elif cblock not in duplicates: 462 | bm[cblock] = nblock 463 | 464 | # Rename known function calls from each unique code block 465 | for (cblock, nblock) in bm.items(): 466 | for n in range(0, len(cblock.functions)): 467 | ea = ida_shims.get_name_ea_simple(cblock.functions[n]) 468 | if ea != idc.BADADDR: 469 | if nblock.functions[n] in rename: 470 | rename[nblock.functions[n]].append(ea) 471 | else: 472 | rename[nblock.functions[n]] = [ea] 473 | 474 | # Rename the identified functions 475 | for (name, candidates) in rename.items(): 476 | if candidates: 477 | winner = \ 478 | collections.Counter(candidates).most_common(1)[0][0] 479 | count += self.rename(winner, name) 480 | 481 | end = time.time() 482 | print("Renamed %d functions in %.2f seconds." % (count, (end-start))) 483 | 484 | 485 | def RizzoBuild(sigfile=None): 486 | print("Building Rizzo signatures, this may take a few minutes...") 487 | start = time.time() 488 | r = Rizzo(sigfile) 489 | r.save() 490 | end = time.time() 491 | print("Built signatures in %.2f seconds" % (end-start)) 492 | 493 | 494 | def RizzoApply(sigfile=None): 495 | print("Applying Rizzo signatures, this may take a few minutes...") 496 | start = time.time() 497 | r = Rizzo(sigfile) 498 | s = r.load() 499 | r.apply(s) 500 | end = time.time() 501 | print("Signatures applied in %.2f seconds" % (end-start)) 502 | 503 | 504 | def rizzo_produce(arg=None): 505 | fname = ida_shims.ask_file(1, "*.riz", "Save signature file as") 506 | if fname: 507 | if '.' not in fname: 508 | fname += ".riz" 509 | RizzoBuild(fname) 510 | 511 | 512 | def rizzo_load(arg=None): 513 | fname = ida_shims.ask_file(0, "*.riz", "Load signature file") 514 | if fname: 515 | RizzoApply(fname) 516 | 517 | try: 518 | class ProduceRizzoAction(idaapi.action_handler_t): 519 | def __init__(self): 520 | idaapi.action_handler_t.__init__(self) 521 | 522 | def activate(self, ctx): 523 | rizzo_produce() 524 | return 1 525 | 526 | def update(self, ctx): 527 | return idaapi.AST_ENABLE_ALWAYS 528 | 529 | 530 | class LoadRizzoAction(idaapi.action_handler_t): 531 | def __init__(self): 532 | idaapi.action_handler_t.__init__(self) 533 | 534 | def activate(self, ctx): 535 | rizzo_load() 536 | return 1 537 | 538 | def update(self, ctx): 539 | return idaapi.AST_ENABLE_ALWAYS 540 | except AttributeError: 541 | pass 542 | 543 | 544 | class RizzoPlugin(idaapi.plugin_t): 545 | flags = 0 546 | comment = "Function signature" 547 | help = "" 548 | wanted_name = "Rizzo" 549 | wanted_hotkey = "" 550 | produce_action_name = 'producerizzo:action' 551 | load_action_name = 'loadrizzo:action' 552 | menu_name = "Rizzo signature file..." 553 | produce_tooltip = "Produce rizzo signature file." 554 | load_tooltip = "Load rizzo signature file." 555 | menu_tab = 'File/' 556 | menu_context = [] 557 | 558 | def init(self): 559 | if idaapi.IDA_SDK_VERSION >= 700: 560 | produce_desc = idaapi.action_desc_t(self.produce_action_name, 561 | self.menu_name, 562 | ProduceRizzoAction(), 563 | self.wanted_hotkey, 564 | self.produce_tooltip, 565 | 199) 566 | 567 | load_desc = idaapi.action_desc_t(self.load_action_name, 568 | self.menu_name, 569 | LoadRizzoAction(), 570 | self.wanted_hotkey, 571 | self.load_tooltip, 572 | 199) 573 | 574 | idaapi.register_action(produce_desc) 575 | idaapi.register_action(load_desc) 576 | 577 | idaapi.attach_action_to_menu( 578 | os.path.join(self.menu_tab, 'Produce file/'), 579 | self.produce_action_name, 580 | idaapi.SETMENU_APP) 581 | idaapi.attach_action_to_menu( 582 | os.path.join(self.menu_tab, 'Load file/'), 583 | self.load_action_name, 584 | idaapi.SETMENU_APP) 585 | else: 586 | self.menu_context.append( 587 | idaapi.add_menu_item( 588 | os.path.join(self.menu_tab, 'Load file/'), 589 | "Rizzo signature file...", "", 0, rizzo_load, (None,))) 590 | 591 | self.menu_context.append( 592 | idaapi.add_menu_item( 593 | os.path.join(self.menu_tab, 'Produce file/'), 594 | "Rizzo signature file...", "", 0, rizzo_produce, (None,))) 595 | 596 | return idaapi.PLUGIN_KEEP 597 | 598 | def term(self): 599 | if idaapi.IDA_SDK_VERSION >= 700: 600 | idaapi.detach_action_from_menu( 601 | self.menu_tab, self.produce_action_name) 602 | idaapi.detach_action_from_menu( 603 | self.menu_tab, self.load_action_name) 604 | else: 605 | if self.menu_context is not None: 606 | idaapi.del_menu_item(self.menu_context) 607 | return None 608 | 609 | def run(self, arg): 610 | return None 611 | 612 | def rizzo_script(self): 613 | idaapi.IDAPython_ExecScript(self.script, globals()) 614 | 615 | 616 | def PLUGIN_ENTRY(): 617 | return RizzoPlugin() 618 | -------------------------------------------------------------------------------- /plugins/shims/ida_shims.py: -------------------------------------------------------------------------------- 1 | # Shim file to support IDA 6.x-7.3 and 7.5+ 2 | # Documentation provided by Hex-Rays: 3 | # https://hex-rays.com/products/ida/support/ida74_idapython_no_bc695_porting_guide.html 4 | 5 | import idc 6 | import idaapi 7 | 8 | try: 9 | import ida_bytes 10 | except ImportError: 11 | ida_bytes = None 12 | 13 | try: 14 | import ida_name 15 | except ImportError: 16 | ida_name = None 17 | 18 | try: 19 | import ida_kernwin 20 | except ImportError: 21 | ida_kernwin = None 22 | 23 | try: 24 | import ida_nalt 25 | except ImportError: 26 | ida_nalt = None 27 | 28 | try: 29 | import ida_ua 30 | except ImportError: 31 | ida_ua = None 32 | 33 | try: 34 | import ida_funcs 35 | except ImportError: 36 | ida_funcs = None 37 | 38 | 39 | def _get_fn_by_version(lib, curr_fn, archive_fn, archive_lib=None): 40 | ''' 41 | Determine which function should be called based on the version of IDA. 42 | 43 | :param curr_fn: 7.X version of the function. 44 | 45 | :param archive_fn: 6.X version of the function. 46 | 47 | :param archive_lib: If the archive lib is different than the current lib, 48 | set it here. 49 | 50 | :return: Function based on the version of IDA. 51 | ''' 52 | if idaapi.IDA_SDK_VERSION >= 700: 53 | try: 54 | return getattr(lib, curr_fn) 55 | except AttributeError: 56 | raise Exception('%s is not a valid function in %s' % (curr_fn, 57 | lib)) 58 | use_lib = lib if archive_lib is None else archive_lib 59 | try: 60 | return getattr(use_lib, archive_fn) 61 | except AttributeError: 62 | raise Exception('%s is not a valid function in %s' % (archive_fn, 63 | use_lib)) 64 | 65 | 66 | def print_insn_mnem(ea): 67 | ''' 68 | Get instruction mnemonics. 69 | 70 | :param ea: Linear address of the instruction. 71 | :type ea: int 72 | 73 | :return: Instruction mnemonic. "" if not instruction is found. 74 | 75 | :note: *Heavy breath* This function may not return exactly the same 76 | mnemonics as you see on the screen. 77 | ''' 78 | fn = _get_fn_by_version(idc, 'print_insn_mnem', 'GetMnem') 79 | return fn(ea) 80 | 81 | 82 | def print_operand(ea, n): 83 | ''' 84 | Get operand of an instruction or data. 85 | 86 | :param ea: Linear address of the item. 87 | :type ea: int 88 | 89 | :param n: Number of operand: 0 - the first operand 1 - the second operand. 90 | :type n: int 91 | 92 | :return: The current text representation of operand or "". 93 | ''' 94 | fn = _get_fn_by_version(idc, 'print_operand', 'GetOpnd') 95 | return fn(ea, n) 96 | 97 | 98 | def define_local_var(start, end, location, name): 99 | ''' 100 | Create a local variable. 101 | 102 | :param start: Start address range for the local variable. 103 | :type start: int 104 | 105 | :param end: End of address range for the local variable. 106 | :type end: int 107 | 108 | :param location: The variable location in the "[bp+xx]" form where xx is 109 | a number. The location can also be specified as a 110 | register name. 111 | :type location: str 112 | 113 | :param name: Name of the local variable. 114 | :type name: str 115 | 116 | :return: 1-ok, 0-failure 117 | ''' 118 | fn = _get_fn_by_version(idc, 'define_local_var', 'MakeLocal') 119 | return fn(start, end, location, name) 120 | 121 | 122 | def find_func_end(ea): 123 | ''' 124 | Determine a new function boundaries. 125 | 126 | :param ea: Start address of the new function. 127 | :type ea: int 128 | 129 | :return: If a function already exists, then return its end address. If a 130 | function end cannot be determine, the return BADADDR otherwise return the 131 | end address of the new function. 132 | ''' 133 | fn = _get_fn_by_version(idc, 'find_func_end', 'FindFuncEnd') 134 | return fn(ea) 135 | 136 | 137 | def is_code(flag): 138 | ''' 139 | Does flag denote start of an instruction. 140 | :param flag: Flag for an instruction. 141 | :type flag: int 142 | 143 | :return: True if flags indicate code, False otherwise. 144 | ''' 145 | fn = _get_fn_by_version(ida_bytes, 'is_code', 'isCode', idaapi) 146 | return fn(flag) 147 | 148 | 149 | def get_full_flags(ea): 150 | ''' 151 | Get flags value for address 'ea' 152 | 153 | :param ea: Linear address. 154 | :type ea: int 155 | 156 | :return: 0 if flags not present in the program 157 | ''' 158 | fn = _get_fn_by_version(ida_bytes, 'get_full_flags', 'getFlags', idaapi) 159 | return fn(ea) 160 | 161 | 162 | def get_name(ea): 163 | ''' 164 | Get name at the specified address. 165 | 166 | :param ea: Linear address 167 | :type ea: int 168 | 169 | :return: "" - byte has no name. 170 | ''' 171 | fn = _get_fn_by_version(idc, 'get_name', 'Name') 172 | 173 | if idaapi.IDA_SDK_VERSION > 700: 174 | return fn(ea, ida_name.GN_VISIBLE) 175 | return fn(ea) 176 | 177 | 178 | def get_func_off_str(ea): 179 | ''' 180 | Convert address to 'funcname+offset' string. 181 | 182 | :param ea: Address to convert. 183 | :type ea: int 184 | 185 | :return: If the address belongs to a function then return a string formed as 186 | 'name+offset' where 'name' is a function name, 'offset' is offset within 187 | the function else return null string. 188 | ''' 189 | fn = _get_fn_by_version(idc, 'get_func_off_str', 'GetFuncOffset') 190 | return fn(ea) 191 | 192 | 193 | def jumpto(ea, opnum=-1, uijmp_flags=0x0001): 194 | ''' 195 | Jump to the specified address. 196 | 197 | :param ea: Destination 198 | :type ea: int 199 | 200 | :param opnum: -1: don't change the x coord. 201 | :type opnum: int 202 | 203 | :param uijmp_flags: Jump flags. 204 | :type uijmp_flags: int 205 | 206 | :return: success 207 | ''' 208 | fn = _get_fn_by_version(ida_kernwin, 'jumpto', 'Jump', idc) 209 | if idaapi.IDA_SDK_VERSION >= 700: 210 | return fn(ea, opnum, uijmp_flags) 211 | return fn(ea) 212 | 213 | 214 | def ask_yn(default, format_str): 215 | ''' 216 | Display a dialog box and get choice from "Yes", "No", "Cancel". 217 | 218 | :param default: Default choice: one of Button IDs 219 | :type default: int 220 | 221 | :param format_str: The question in printf() style format. 222 | :type format_str: str 223 | 224 | :return: The selected button (one of Button IDs). 225 | ''' 226 | fn = _get_fn_by_version(ida_kernwin, 'ask_yn', 'AskYN', idc) 227 | return fn(default, format_str) 228 | 229 | 230 | def ask_file(for_saving, default, dialog): 231 | ''' 232 | Get file from user. 233 | 234 | :param for_saving: File is for saving. 235 | :type for_saving: int 236 | 237 | :param default: File extension. 238 | :type default: str 239 | 240 | :param dialog: Dialog box to display to the user. 241 | :type dialog: str 242 | 243 | :return: file path. 244 | ''' 245 | fn = _get_fn_by_version(ida_kernwin, 'ask_file', 'AskFile', idc) 246 | return fn(for_saving, default, dialog) 247 | 248 | 249 | def get_func_attr(ea, attr): 250 | ''' 251 | Get a function attribute. 252 | 253 | :param ea: Any address belonging to the function. 254 | :type ea: int 255 | 256 | :param attr: One of FUNCATTR_... constants 257 | 258 | :return: BADADDR - error otherwise returns the attribute value. 259 | ''' 260 | fn = _get_fn_by_version(idc, 'get_func_attr', 'GetFunctionAttr') 261 | return fn(ea, attr) 262 | 263 | 264 | def get_name_ea_simple(name): 265 | ''' 266 | Get linear address of a name. 267 | 268 | :param name: Name of program byte. 269 | :type name: str 270 | 271 | :return: Address of the name or BADADDR - No such name. 272 | ''' 273 | fn = _get_fn_by_version(idc, 'get_name_ea_simple', 'LocByName') 274 | return fn(name) 275 | 276 | 277 | def next_head(ea, maxea=4294967295): 278 | ''' 279 | Get next defined item (instruction or data) in the program. 280 | 281 | :param ea: Linear address to start search from. 282 | :type ea: int 283 | 284 | :param maxea: The search will stop at the address maxea. maxea is not 285 | included in the search range 286 | :type maxea: int 287 | 288 | :return: BADADDR - no (more) defined items 289 | ''' 290 | fn = _get_fn_by_version(idc, 'next_head', 'NextHead') 291 | return fn(ea, maxea) 292 | 293 | 294 | def get_screen_ea(): 295 | ''' 296 | Return the linear address of the current screen location. 297 | 298 | :return: Address of screen focus. 299 | ''' 300 | fn = _get_fn_by_version(idc, 'get_screen_ea', 'ScreenEA') 301 | return fn() 302 | 303 | 304 | def choose_func(title): 305 | ''' 306 | Ask the user to select a function. 307 | 308 | :param title: Title of the dialog box. 309 | :type title: str 310 | 311 | :return: -1 user refused to select a function, otherwise function start addr 312 | ''' 313 | fn = _get_fn_by_version(idc, 'choose_func', 'ChooseFunction') 314 | return fn(title) 315 | 316 | 317 | def ask_ident(default, prompt): 318 | ''' 319 | Ask for a long text. 320 | :param default: The default value. 321 | :type default: str 322 | 323 | :param prompt: The prompt value. 324 | :type prompt: str 325 | 326 | :return: None or the entered string. 327 | ''' 328 | fn = _get_fn_by_version(ida_kernwin, 'ask_str', 'AskIdent', idc) 329 | if idaapi.IDA_SDK_VERSION >= 700: 330 | return fn(default, ida_kernwin.HIST_IDENT, prompt) 331 | return fn(default, prompt) 332 | 333 | 334 | def set_name(ea, name): 335 | ''' 336 | Rename an address. 337 | 338 | :param ea: Linear address. 339 | :type ea: int 340 | 341 | :param name: New name of address. If name == "" then delete old name. 342 | :type name: str 343 | 344 | :return: 1-ok, 0-failure 345 | ''' 346 | fn = _get_fn_by_version(idc, 'set_name', 'MakeName') 347 | if idaapi.IDA_SDK_VERSION >= 700: 348 | return fn(ea, name, ida_name.SN_CHECK) 349 | return fn(ea, name) 350 | 351 | 352 | def get_wide_dword(ea): 353 | ''' 354 | Get one wide word of the program at 'ea' 355 | :param ea: linear address. 356 | :type ea: int 357 | 358 | :return: uint64 359 | ''' 360 | fn = _get_fn_by_version(idc, 'get_wide_dword', 'Dword') 361 | return fn(ea) 362 | 363 | 364 | def get_strlit_contents(ea): 365 | ''' 366 | Get string contents. 367 | 368 | :param ea: Linear address. 369 | :type ea: int 370 | 371 | :return: String contents or empty string. 372 | ''' 373 | fn = _get_fn_by_version(idc, 'get_strlit_contents', 'GetString') 374 | return fn(ea) 375 | 376 | 377 | def get_func_name(ea): 378 | ''' 379 | Retrieve function name. 380 | 381 | :param ea: Any address belonging to the function. 382 | :type ea: int 383 | 384 | :return: Null string if not found, otherwise the functions name. 385 | ''' 386 | fn = _get_fn_by_version(idc, 'get_func_name', 'GetFunctionName') 387 | return fn(ea) 388 | 389 | 390 | def get_first_seg(): 391 | ''' 392 | Get first segment. 393 | 394 | :return: Address of the start of the first segment or BADADDR if no 395 | segments found. 396 | ''' 397 | fn = _get_fn_by_version(idc, 'get_first_seg', 'FirstSeg') 398 | return fn() 399 | 400 | 401 | def get_segm_attr(segea, attr): 402 | ''' 403 | Get segment attribute. 404 | 405 | :param segea: Any address within the segment. 406 | :type segea: int 407 | 408 | :param attr: One of SEGATTR_... constants. 409 | :type attr: int 410 | 411 | :return: Segment attributes. 412 | ''' 413 | fn = _get_fn_by_version(idc, 'get_segm_attr', 'GetSegmentAttr') 414 | return fn(segea, attr) 415 | 416 | 417 | def get_next_seg(ea): 418 | ''' 419 | Get next segment. 420 | 421 | :param ea: Linear address. 422 | :type ea: int 423 | 424 | :return: Start of the next segment or BADADDR 425 | ''' 426 | fn = _get_fn_by_version(idc, 'get_next_seg', 'NextSeg') 427 | return fn(ea) 428 | 429 | 430 | def is_strlit(flags): 431 | ''' 432 | Do flags indicate a string. 433 | 434 | :param flags: Flags for address. 435 | :type flags: int 436 | 437 | :return: bool 438 | ''' 439 | fn = _get_fn_by_version(ida_bytes, 'is_strlit', 'isASCII', idc) 440 | return fn(flags) 441 | 442 | 443 | def create_strlit(start, lenth): 444 | ''' 445 | Convert to string literal and give a meaningful name. 446 | 447 | :param start: Start ea. 448 | :type start: int 449 | 450 | :param lenth: Length of string, or 0 to determine dynamically. 451 | :type lenth: int 452 | 453 | :return: bool 454 | ''' 455 | fn = _get_fn_by_version(ida_bytes, 'create_strlit', 'MakeStr', idc) 456 | if idaapi.IDA_SDK_VERSION >= 700: 457 | return fn(start, lenth, ida_nalt.STRTYPE_C) 458 | return fn(start, idc.BADADDR) 459 | 460 | 461 | def is_unknown(flags): 462 | ''' 463 | Do flags indicate an unknown type. 464 | 465 | :param flags: Flags for address. 466 | :type flags: int 467 | 468 | :return: bool 469 | ''' 470 | fn = _get_fn_by_version(ida_bytes, 'is_unknown', 'isUnknown', idc) 471 | return fn(flags) 472 | 473 | 474 | def is_byte(flags): 475 | ''' 476 | Do flags indicate a byte type. 477 | 478 | :param flags: Flags for address. 479 | :type flags: int 480 | 481 | :return: bool 482 | ''' 483 | fn = _get_fn_by_version(ida_bytes, 'is_byte', 'isByte', idc) 484 | return fn(flags) 485 | 486 | 487 | def create_dword(ea): 488 | ''' 489 | Convert to data. 490 | 491 | :param ea: Linear address . 492 | :type ea: int 493 | 494 | :return: bool 495 | ''' 496 | fn = _get_fn_by_version(ida_bytes, 'create_data', 'MakeDword', idc) 497 | if idaapi.IDA_SDK_VERSION >= 700: 498 | return fn(ea, ida_bytes.FF_DWORD, 4, idaapi.BADADDR) 499 | return fn(ea) 500 | 501 | 502 | def op_plain_offset(ea, n, base): 503 | ''' 504 | Convert operand to an offset. 505 | 506 | :param ea: Linear address. 507 | :type ea: int 508 | 509 | :param n: Number of operands. 510 | :type n: int 511 | 512 | :param base: Base of the offset. 513 | :type base: int 514 | 515 | :return: 516 | ''' 517 | fn = _get_fn_by_version(idc, 'op_plain_offset', 'OpOff') 518 | return fn(ea, n, base) 519 | 520 | 521 | def next_addr(ea): 522 | ''' 523 | Get next address in the program. 524 | 525 | :param ea: Linear address. 526 | :type ea: int 527 | 528 | :return: Next address or BADADDR 529 | ''' 530 | fn = _get_fn_by_version(ida_bytes, 'next_addr', 'NextAddr', idc) 531 | return fn(ea) 532 | 533 | 534 | def can_decode(ea): 535 | ''' 536 | Can the bytes at ea be decoded as an instruction? 537 | 538 | :param ea: Linear address 539 | :type ea: int 540 | 541 | :return: bool 542 | ''' 543 | fn = _get_fn_by_version(ida_ua, 'can_decode', 'decode_insn', idaapi) 544 | return fn(ea) 545 | 546 | 547 | def get_operands(insn): 548 | ''' 549 | Get operands for the current address. 550 | 551 | :return: 552 | ''' 553 | if idaapi.IDA_SDK_VERSION >= 700: 554 | return insn.ops 555 | return idaapi.cmd.Operands 556 | 557 | 558 | def get_canon_feature(insn): 559 | ''' 560 | Get operands for the provided instruction. 561 | 562 | :return: 563 | ''' 564 | if idaapi.IDA_SDK_VERSION >= 700: 565 | return insn.get_canon_feature() 566 | return idaapi.cmd.get_canon_feature() 567 | 568 | 569 | def get_segm_name(ea): 570 | ''' 571 | Get name of a segment. 572 | 573 | :param ea: Any address within the segment. 574 | :type ea: int 575 | 576 | :return: Segement name. 577 | ''' 578 | fn = _get_fn_by_version(idc, 'get_segm_name', 'SegName') 579 | return fn(ea) 580 | 581 | 582 | def add_func(ea): 583 | ''' 584 | Add a new function. 585 | 586 | :param ea: Start address. 587 | :type ea: int 588 | 589 | :return: bool 590 | ''' 591 | fn = _get_fn_by_version(ida_funcs, 'add_func', 'MakeFunction', idc) 592 | return fn(ea) 593 | 594 | 595 | def create_insn(ea): 596 | ''' 597 | Create instruction. 598 | 599 | :param ea: Linear address 600 | :type ea: int 601 | 602 | :return: bool 603 | ''' 604 | fn = _get_fn_by_version(idc, 'create_insn', 'MakeCode') 605 | return fn(ea) 606 | 607 | 608 | def get_segm_end(ea): 609 | ''' 610 | Get end address of a segment. 611 | 612 | :param ea: Linear address 613 | :type ea: int 614 | 615 | :return: Address 616 | ''' 617 | fn = _get_fn_by_version(idc, 'get_segm_end', 'SegEnd') 618 | return fn(ea) 619 | 620 | 621 | def get_segm_start(ea): 622 | ''' 623 | Get start address of a segment. 624 | 625 | :param ea: Linear address 626 | :type ea: int 627 | 628 | :return: Address 629 | ''' 630 | fn = _get_fn_by_version(idc, 'get_segm_start', 'SegStart') 631 | return fn(ea) 632 | 633 | 634 | def decode_insn(ea): 635 | """ 636 | Decode instruction. 637 | :param ea: Linear address. 638 | :type ea: int 639 | 640 | :return: Instruction at ea. 641 | """ 642 | fn = _get_fn_by_version(ida_ua, 'decode_insn', 'decode_insn', idaapi) 643 | if idaapi.IDA_SDK_VERSION >= 700: 644 | insn = ida_ua.insn_t() 645 | fn(insn, ea) 646 | return insn 647 | fn(ea) 648 | return idaapi.cmd 649 | 650 | 651 | def get_bookmark(index): 652 | """ 653 | Get bookmark 654 | 655 | :param index: Index of bookmark 656 | :type index: int 657 | 658 | :return: Address of bookmark 659 | """ 660 | fn = _get_fn_by_version(idc, 'get_bookmark', 'GetMarkedPos') 661 | return fn(index) 662 | 663 | 664 | def get_bookmark_desc(index): 665 | """ 666 | Get bookmark description. 667 | 668 | :param index: Index of bookmark 669 | :type index: int 670 | 671 | :return: 672 | """ 673 | fn = _get_fn_by_version(idc, 'get_bookmark_desc', 'GetMarkComment') 674 | return fn(index) 675 | 676 | 677 | def set_color(ea, what, color): 678 | """ 679 | Set item color. 680 | 681 | :param ea: Linear address. 682 | :type ea: int 683 | 684 | :param what: Type of the item, one of CIC_... contstants 685 | :type what: int 686 | 687 | :param color: New color code in RGB. 688 | :type color: int 689 | 690 | :return: bool 691 | """ 692 | fn = _get_fn_by_version(idc, 'set_color', 'SetColor') 693 | return fn(ea, what, color) 694 | 695 | 696 | def msg(message): 697 | """ 698 | Display a UTF-8 string in the message window. 699 | 700 | :param message: Message to print. 701 | :type message: str 702 | 703 | :return: PyObject * (what?) 704 | """ 705 | fn = _get_fn_by_version(ida_kernwin, 'msg', 'Message', idc) 706 | return fn(message) 707 | 708 | 709 | def get_highlighted_identifier(): 710 | """ 711 | Get currently highlighted text. 712 | 713 | :return: Highlighted text or "" 714 | """ 715 | fn = _get_fn_by_version(ida_kernwin, 'get_highlight', 716 | 'get_highlighted_identifier', idaapi) 717 | 718 | if idaapi.IDA_SDK_VERSION >= 700: 719 | viewer = ida_kernwin.get_current_viewer() 720 | highlight = fn(viewer) 721 | if highlight and highlight[1]: 722 | return highlight[0] 723 | return fn() 724 | 725 | 726 | def start_ea(obj): 727 | """ 728 | Return start ea for supplied object. 729 | 730 | :param obj: Object to retrieve start ea. 731 | 732 | :return: start ea. 733 | """ 734 | if not obj: 735 | return None 736 | 737 | try: 738 | return obj.startEA 739 | except AttributeError: 740 | return obj.start_ea 741 | 742 | 743 | def end_ea(obj): 744 | """ 745 | Return end ea for supplied object. 746 | 747 | :param obj: Object to retrieve end ea. 748 | 749 | :return: end ea. 750 | """ 751 | if not obj: 752 | return None 753 | 754 | try: 755 | return obj.endEA 756 | except AttributeError: 757 | return obj.end_ea 758 | 759 | 760 | def set_func_flags(ea, flags): 761 | """ 762 | Change function flags. 763 | 764 | :param ea: Any address belonging to the function. 765 | :type ea: int 766 | 767 | :param flags: Flags to set. 768 | :type flags: int 769 | 770 | :return: 0 - ok 771 | """ 772 | fn = _get_fn_by_version(idc, 'set_func_attr', 'SetFunctionFlags') 773 | if idaapi.IDA_SDK_VERSION >= 700: 774 | return fn(ea, idc.FUNCATTR_FLAGS, flags) 775 | return fn(ea, flags) 776 | 777 | 778 | def get_func_flags(ea): 779 | """ 780 | Get function flags. 781 | 782 | :param ea: Any address belonging to the function. 783 | :type ea: int 784 | 785 | :return: Flags 786 | """ 787 | fn = _get_fn_by_version(idc, 'get_func_attr', 'GetFunctionFlags') 788 | if idaapi.IDA_SDK_VERSION >= 700: 789 | return fn(ea, idc.FUNCATTR_FLAGS) 790 | return fn(ea) 791 | -------------------------------------------------------------------------------- /scripts/wpsearch.py: -------------------------------------------------------------------------------- 1 | import idc 2 | import idaapi 3 | import idautils 4 | 5 | class WPSearch(object): 6 | ''' 7 | Searches for immediate values commonly founds in MIPS WPS checksum implementations. 8 | May be applicable to other architectures as well. 9 | ''' 10 | 11 | IMMEDIATES = { 12 | 0x6B5FCA6B : set(), 13 | 0x431BDE83 : set(), 14 | 0x0A7C5AC5 : set(), 15 | 0x10624DD3 : set(), 16 | 0x51EB851F : set(), 17 | 0xCCCCCCCD : set(), 18 | 0xD1B71759 : set(), 19 | } 20 | 21 | def __init__(self): 22 | self.cksums = set() 23 | 24 | def checksums(self): 25 | ''' 26 | Search for WPS checksum functions. 27 | 28 | Returns a set of function EAs. 29 | ''' 30 | self._search_for_immediates() 31 | 32 | self.cksums = self.IMMEDIATES.values()[0] 33 | for i in range(1, len(self.IMMEDIATES.values())): 34 | self.cksums = self.cksums & self.IMMEDIATES.values()[i] 35 | 36 | return self.cksums 37 | 38 | def xrefs(self): 39 | ''' 40 | Identify functions that reference the WPS checksum functions and resolve their string xrefs. 41 | 42 | Returns a dictionary of function EAs and a list of their string xrefs. 43 | ''' 44 | self._generate_checksum_xrefs_table() 45 | 46 | for string in idautils.Strings(): 47 | for xref in idautils.XrefsTo(string.ea): 48 | func = idaapi.get_func(xref.frm) 49 | if func and self.funcs.has_key(func.startEA): 50 | self.funcs[func.startEA].add(str(string)) 51 | 52 | return self.funcs 53 | 54 | def _search_for_immediates(self): 55 | for immediate in self.IMMEDIATES.keys(): 56 | ea = 0 57 | while ea != idc.BADADDR: 58 | (ea, n) = idc.FindImmediate(ea, idc.SEARCH_DOWN, self._twos_compliment(immediate)) 59 | if ea != idc.BADADDR: 60 | func = idaapi.get_func(ea) 61 | if func: 62 | self.IMMEDIATES[immediate].add(func.startEA) 63 | 64 | def _twos_compliment(self, val): 65 | if idaapi.BADADDR == 0xFFFFFFFFFFFFFFFFL: 66 | tv = self.__twos_compliment(val, 64) 67 | else: 68 | tv = self.__twos_compliment(val, 32) 69 | return tv 70 | 71 | def __twos_compliment(self, val, bits): 72 | ''' 73 | Python converts values larger than 0x7FFFFFFF into longs, which 74 | aren't converted properly in the swig translation. Use 2's compliment 75 | for large values instead. 76 | ''' 77 | if (val & (1 << (bits - 1))) != 0: 78 | val = val - (1 << bits) 79 | return val 80 | 81 | def _generate_checksum_xrefs_table(self): 82 | self.funcs = {} 83 | 84 | if not self.cksums: 85 | self.checksums() 86 | 87 | for cksum in self.cksums: 88 | func = idaapi.get_func(cksum) 89 | if func: 90 | self.funcs[func.startEA] = set() 91 | 92 | for xref in idautils.XrefsTo(cksum): 93 | func = idaapi.get_func(xref.frm) 94 | if func and not self.funcs.has_key(func.startEA): 95 | self.funcs[func.startEA] = set() 96 | 97 | class WPSearchFunctionChooser(idaapi.Choose2): 98 | 99 | DELIM_COL_1 = '-' * 50 100 | DELIM_COL_2 = '-' * 20 101 | DELIM_COL_3 = '-' * 125 102 | 103 | def __init__(self): 104 | idaapi.Choose2.__init__(self, 105 | "WPS Function Profiles", 106 | [ 107 | ["Function", 15 | idaapi.Choose2.CHCOL_PLAIN], 108 | ["Contains checksum algorithm", 15 | idaapi.Choose2.CHCOL_PLAIN], 109 | ["String(s)", 75 | idaapi.Choose2.CHCOL_PLAIN], 110 | ]) 111 | 112 | self.icon = 41 113 | self.wps = WPSearch() 114 | 115 | self.run_scans() 116 | self.populate_items() 117 | 118 | def OnSelectLine(self, n): 119 | idc.Jump(self.items[n][-1]) 120 | 121 | def OnGetSize(self): 122 | return len(self.items) 123 | 124 | def OnGetLine(self, n): 125 | return self.items[n] 126 | 127 | def OnClose(self): 128 | pass 129 | 130 | def run_scans(self): 131 | self.checksum_functions = self.wps.checksums() 132 | self.checksum_string_xrefs = self.wps.xrefs() 133 | 134 | def populate_items(self): 135 | self.items = [] 136 | 137 | for (func_ea, strings) in self.checksum_string_xrefs.iteritems(): 138 | 139 | is_checksum_function = str(func_ea in self.checksum_functions) 140 | 141 | if not strings: 142 | self.items.append([idc.Name(func_ea), is_checksum_function, "", func_ea]) 143 | else: 144 | for string in strings: 145 | self.items.append([idc.Name(func_ea), is_checksum_function, string, func_ea]) 146 | 147 | self.items.append([self.DELIM_COL_1, self.DELIM_COL_2, self.DELIM_COL_3, idc.BADADDR]) 148 | 149 | # Remove the last delimiter column 150 | if self.items and self.items[-1][-1] == idc.BADADDR: 151 | self.items.pop(-1) 152 | 153 | def show(self): 154 | if self.Show(modal=False) < 0: 155 | return False 156 | return True 157 | 158 | 159 | 160 | if __name__ == '__main__': 161 | WPSearchFunctionChooser().show() 162 | 163 | --------------------------------------------------------------------------------