├── README.md └── ropstone.py /README.md: -------------------------------------------------------------------------------- 1 | ropstone 2 | ==================== 3 | 4 | A basic ROP/gadget finder. 5 | 6 | Dependencies 7 | ==================== 8 | 9 | ropstone depends on: 10 | 11 | * [capstone](http://www.capstone-engine.org/) 12 | * [keystone](http://www.keystone-engine.org/) 13 | * [elftools](https://github.com/eliben/pyelftools) 14 | * [docopt](http://docopt.org/) 15 | 16 | Installation 17 | ==================== 18 | 19 | .. TODO: write me 20 | 21 | Usage 22 | ==================== 23 | 24 | See ./ropstone.py -h 25 | -------------------------------------------------------------------------------- /ropstone.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ropstone 4 | 5 | Usage: 6 | ropstone.py [-f] [-s] [-S SECTIONS] [-b ADDR] [-a ARCH] [-m MODE(s)] 7 | ropstone.py -A 8 | ropstone.py -a -M 9 | 10 | Finds code gadgets in a given ELF/binary file 11 | 12 | Arguments: 13 | FILE input ELF/binary file 14 | PATTERN hex pattern ('?' is nibble wildcard) or assembly code 15 | 16 | options: 17 | -h, --help display help output 18 | -a, --arch ARCH specify architecture 19 | -A, --list-arch list all available architectures 20 | -m, --mode MODE specify architecture mode parameters (comma separated) 21 | -M, --list-mode list all architecture mode parameters 22 | -b, --base ADDR specify base address 23 | -s, --single only display unique gadgets 24 | -S, --section SECTIONS only display gadgets from sections (comma separated) 25 | -f, --fancy fancy (colorized) output 26 | """ 27 | 28 | import sys 29 | import re 30 | 31 | from capstone import * 32 | from keystone import * 33 | from elftools.elf.elffile import ELFFile 34 | from docopt import docopt 35 | 36 | class ropstone(): 37 | BANNER_STR = ">> ropstone v0.1 by blasty " 38 | bin_chunks = [] 39 | 40 | arch = None 41 | modes = [] 42 | base_addr = 0 43 | unique_only = False 44 | unique_patterns = [] 45 | section_filter = None 46 | fancy = False 47 | 48 | archs = [ 49 | { 50 | "name" : "arm", 51 | "cs_const" : CS_ARCH_ARM, 52 | "ks_const" : KS_ARCH_ARM, 53 | "boundary" : 4, 54 | "modes" : [ 55 | { 56 | "name" : "thumb", 57 | "desc" : "THUMB processor mode", 58 | "cs_const" : CS_MODE_THUMB, 59 | "ks_const" : KS_MODE_THUMB, 60 | # this overrides the boundary of the parent architecture 61 | "boundary" : 2, 62 | # this adds a shift offset to the output addr to force THUMB mode 63 | "retshift" : 1 64 | }, 65 | { 66 | "name" : "le", 67 | "desc" : "Little endian", 68 | "cs_const" : CS_MODE_LITTLE_ENDIAN, 69 | "ks_const" : KS_MODE_LITTLE_ENDIAN, 70 | }, 71 | { 72 | "name" : "be", 73 | "desc" : "Big endian", 74 | "cs_const" : CS_MODE_BIG_ENDIAN, 75 | "ks_const" : KS_MODE_BIG_ENDIAN, 76 | } 77 | ] 78 | }, 79 | { 80 | "name" : "arm64", 81 | "cs_const" : CS_ARCH_ARM64, 82 | "ks_const" : KS_ARCH_ARM64, 83 | "boundary" : 4, 84 | "modes" : [ 85 | { 86 | "name" : "le", 87 | "desc" : "Little Endian", 88 | "cs_const" : CS_MODE_LITTLE_ENDIAN, 89 | "ks_const" : KS_MODE_LITTLE_ENDIAN 90 | } 91 | ] 92 | }, 93 | { 94 | "name" : "mips", 95 | "cs_const" : CS_ARCH_MIPS, 96 | "ks_const" : KS_ARCH_MIPS, 97 | "boundary" : 4, 98 | "modes" : [ 99 | { 100 | "name" : "32b", 101 | "desc" : "MIPS32", 102 | "cs_const" : CS_MODE_MIPS32, 103 | "ks_const" : KS_MODE_MIPS32 104 | }, 105 | { 106 | "name" : "64b", 107 | "desc" : "MIPS64", 108 | "cs_const" : CS_MODE_MIPS64, 109 | "ks_const" : KS_MODE_MIPS64 110 | }, 111 | { 112 | "name" : "le", 113 | "desc" : "Little endian", 114 | "cs_const" : CS_MODE_LITTLE_ENDIAN, 115 | "ks_const" : KS_MODE_LITTLE_ENDIAN, 116 | }, 117 | { 118 | "name" : "be", 119 | "desc" : "Big endian", 120 | "cs_const" : CS_MODE_BIG_ENDIAN, 121 | "ks_const" : KS_MODE_BIG_ENDIAN, 122 | } 123 | ] 124 | }, 125 | { 126 | "name" : "x86", 127 | "cs_const" : CS_ARCH_X86, 128 | "ks_const" : KS_ARCH_X86, 129 | "boundary" : 1, 130 | "modes" : [ 131 | { 132 | "name" : "32b", 133 | "desc" : "x86 32bit", 134 | "cs_const" : CS_MODE_32, 135 | "ks_const" : KS_MODE_32 136 | }, 137 | { 138 | "name" : "64b", 139 | "desc" : "x86_64 64bit", 140 | "cs_const" : CS_MODE_64, 141 | "ks_const" : KS_MODE_64 142 | } 143 | ] 144 | } 145 | ] 146 | 147 | color_tbl = [ 148 | 'black', 'red', 'green', 'yellow', 149 | 'blue', 'magenta', 'cyan', 'white' 150 | ] 151 | 152 | def col_fg(self, col): 153 | return "\x1b[%dm" % (30 + self.color_tbl.index(col)) 154 | 155 | def col_bold(self): 156 | return "\x1b[1m" 157 | 158 | def col_end(self): 159 | return "\x1b[0m" 160 | 161 | def error(self, errstr): 162 | if self.fancy: 163 | o = self.col_fg('red') + self.col_bold() 164 | o += "ERROR: " + self.col_end() + errstr + "\n" 165 | sys.stderr.write(o) 166 | else: 167 | sys.stderr.write("ERROR: " + errstr + "\n") 168 | 169 | exit(-1) 170 | 171 | def get_arch_by_name(self, name): 172 | for arch in self.archs: 173 | if arch['name'] == name: 174 | return arch 175 | 176 | return None 177 | 178 | def get_mode_by_name(self, arch, name): 179 | for mode in arch['modes']: 180 | if mode['name'] == name: 181 | return mode 182 | 183 | return None 184 | 185 | def list_archs(self): 186 | print(" > Architecture list:") 187 | for arch in self.archs: 188 | print (" - " + arch['name']) 189 | print ("") 190 | 191 | def list_modes(self): 192 | print (" > Mode parameters for architecture '%s':" % (self.arch['name'])) 193 | for mode in self.arch['modes']: 194 | print (" - %s : %s" % (mode['name'], mode['desc'])) 195 | print ("") 196 | 197 | def is_elf(self, filename): 198 | magic = str(open(filename, "rb").read(4)) 199 | 200 | if magic == chr(0x7f)+"ELF": 201 | return True 202 | 203 | return False 204 | 205 | def assemble_str(self, asm_code): 206 | ks_mode = 0 207 | 208 | for mode in self.modes: 209 | ks_mode = ks_mode | mode['ks_const'] 210 | 211 | ks = Ks(self.arch['ks_const'], ks_mode) 212 | encoding, count = ks.asm(asm_code) 213 | 214 | o="" 215 | for v in encoding: 216 | o += chr(v) 217 | 218 | return o 219 | 220 | def find_pattern(self, data, matchstring): 221 | matches = [] 222 | 223 | # convert match string to valid regex 224 | matchstring = matchstring.replace("?", ".") 225 | 226 | regex = re.compile(matchstring) 227 | for match in regex.finditer(data.encode("hex")): 228 | # skip non-byte aligned matches 229 | if (match.start() % 2) != 0: 230 | continue 231 | 232 | addr = match.start() / 2 233 | 234 | # keep architecture alignment in mind 235 | if (addr % self.arch['boundary']) != 0: 236 | continue 237 | 238 | matches.append({ "addr" : addr, "pattern" : match.group() }) 239 | 240 | return matches 241 | 242 | def __init__(self): 243 | print ("\n" + self.BANNER_STR + "\n") 244 | 245 | arguments = docopt(__doc__) 246 | 247 | # docopt beats argparse, but I don't like crap in my dicts 248 | self.arguments = {k.strip('-<>'): v for k, v in arguments.items()} 249 | 250 | if self.arguments['base'] is not None: 251 | self.base_addr = int(self.arguments['base'], 0) 252 | 253 | self.unique_only = self.arguments['single'] 254 | self.fancy = self.arguments['fancy'] 255 | 256 | # check if input file is ELF, if so: determine ARCH and MODE 257 | if self.arguments['FILE'] is not None: 258 | if self.is_elf(self.arguments['FILE']): 259 | f = open(self.arguments['FILE'], "rb") 260 | 261 | elffile = ELFFile(f) 262 | 263 | march = elffile.get_machine_arch() 264 | 265 | if march == "x64": 266 | self.arch = self.get_arch_by_name("x86") 267 | self.modes.append(self.get_mode_by_name(self.arch, "64b")) 268 | elif march == "x86": 269 | self.arch = self.get_arch_by_name("x86") 270 | self.modes.append(self.get_mode_by_name(self.arch, "32b")) 271 | elif march == "ARM": 272 | self.arch = self.get_arch_by_name("arm") 273 | if elffile.little_endian: 274 | self.modes.append(self.get_mode_by_name(self.arch, "le")) 275 | else: 276 | self.modes.append(self.get_mode_by_name(self.arch, "be")) 277 | elif march == "MIPS": 278 | self.arch = self.get_arch_by_name("mips") 279 | self.modes.append(self.get_mode_by_name(self.arch, "32b")) 280 | 281 | if elffile.little_endian: 282 | self.modes.append(self.get_mode_by_name(self.arch, "le")) 283 | else: 284 | self.modes.append(self.get_mode_by_name(self.arch, "be")) 285 | else: 286 | self.error("ELF has unsupported machine type. (%s)" % elffile['e_machine']) 287 | 288 | for i in xrange(elffile.num_sections()): 289 | section = elffile.get_section(i) 290 | 291 | if section['sh_type'] != "SHT_PROGBITS": 292 | continue 293 | 294 | if not (section['sh_flags'] & 0x04): 295 | continue 296 | 297 | self.bin_chunks.append({ 298 | 'name' : section.name, 299 | 'addr' : section['sh_addr'], 300 | 'data' : section.data() 301 | }) 302 | 303 | if self.arguments['section'] is not None: 304 | self.section_filter = self.arguments['section'].split(",") 305 | 306 | else: 307 | self.bin_chunks.append({ 308 | 'name' : "RAW", 309 | 'addr' : self.base_addr, 310 | 'data' : open(self.arguments['FILE'], "rb").read() 311 | }) 312 | 313 | if self.arguments['arch']: 314 | self.arch = self.get_arch_by_name(self.arguments['arch']) 315 | 316 | if self.arch is None: 317 | self.error("Invalid architecture specified! (use -A for architecture list)") 318 | 319 | if self.arguments['mode']: 320 | if self.arch is None: 321 | self.error("No architecture specified! (use -A for architecture list)") 322 | 323 | s_modes = self.arguments['mode'].split(",") 324 | 325 | for i in xrange(len(s_modes)): 326 | for mode in self.arch['modes']: 327 | if mode['name'] == s_modes[i]: 328 | self.modes.append(mode) 329 | 330 | if self.arguments['list-arch']: 331 | self.list_archs() 332 | elif self.arguments['list-mode']: 333 | if self.arch is None: 334 | self.error("Invalid (or no) architecture specified! (use -A for architecture list)") 335 | 336 | self.list_modes() 337 | else: 338 | # figure out if parameter is a hex pattern or asm code 339 | if re.search("^[0-9a-f\?]+$", self.arguments['PATTERN']) != None: 340 | pattern = self.arguments['PATTERN'] 341 | else: 342 | pattern = self.assemble_str(self.arguments['PATTERN']).encode('hex') 343 | 344 | num_hits = 0 345 | 346 | if self.fancy: 347 | print ("> searching for pattern '%s%s%s'\n" % ( 348 | self.col_fg('green')+self.col_bold(), pattern, self.col_end() 349 | )) 350 | else: 351 | print ("> searching for pattern '%s'\n" % (pattern)) 352 | 353 | cs_mode = 0 354 | retshift = 0 355 | 356 | for mode in self.modes: 357 | cs_mode = cs_mode | mode['cs_const'] 358 | 359 | # check for mode specific overrides (only needed for THUMB atm) 360 | if "boundary" in mode: 361 | self.arch['boundary'] = mode['boundary'] 362 | if "retshift" in mode: 363 | retshift = mode['retshift'] 364 | 365 | md = Cs(self.arch['cs_const'], cs_mode) 366 | 367 | for chunk in self.bin_chunks: 368 | if self.section_filter is not None: 369 | if chunk['name'] not in self.section_filter: 370 | continue 371 | 372 | matches = self.find_pattern(chunk['data'], pattern) 373 | 374 | num_hits = num_hits + len(matches) 375 | matches_printed = 0 376 | 377 | for match in matches: 378 | if self.unique_only: 379 | if match['pattern'] in self.unique_patterns: 380 | continue 381 | 382 | if matches_printed == 0: 383 | if self.fancy: 384 | print ("> hits in '%s%s%s':" % ( 385 | self.col_bold(), chunk['name'], self.col_end() 386 | )) 387 | else: 388 | print ("> hits in '%s':" % (chunk['name'])) 389 | 390 | matches_printed = matches_printed + 1 391 | 392 | bytecode = match['pattern'].decode('hex') 393 | disas = [] 394 | for ins in md.disasm(bytecode, chunk['addr']+match['addr']): 395 | disas.append("%s %s" % (ins.mnemonic, ins.op_str)) 396 | 397 | if len(disas) == 0: 398 | disas = [ "" ] 399 | 400 | if self.fancy: 401 | print (" + %s%08x%s | %s%s%s | %s%s%s" % ( 402 | self.col_fg('cyan'), chunk['addr']+match['addr']+retshift, self.col_end(), 403 | self.col_fg('red'), match['pattern'], self.col_end(), 404 | self.col_fg('green'), " ; ".join(disas), self.col_end() 405 | )) 406 | else: 407 | print (" + %08x | %s | %s" % ( 408 | chunk['addr']+match['addr']+retshift, 409 | match['pattern'], 410 | " ; ".join(disas) 411 | )) 412 | 413 | if self.unique_only: 414 | self.unique_patterns.append(match['pattern']) 415 | 416 | if matches_printed > 0: 417 | print ("") 418 | 419 | if num_hits == 0: 420 | self.error("not a single match found, better luck next time! :(") 421 | else: 422 | if self.fancy: 423 | print ("> %s%d hits%s found!" % (self.col_bold(), num_hits, self.col_end())) 424 | else: 425 | print ("> %d hits found!" % (num_hits)) 426 | 427 | print ("") 428 | 429 | rs = ropstone() 430 | --------------------------------------------------------------------------------