├── .gitignore ├── CHANGELOG ├── CLI ├── __init__.py ├── bp_trace.py ├── general.py ├── hook.py ├── memory.py └── store_load_state.py ├── CONTRIBUTING.md ├── LICENSE ├── README.gdbcov.md ├── README.md ├── TODO ├── config.py ├── core ├── GOT.py ├── __init__.py ├── compiler.py ├── constants.py ├── disasm │ ├── __init__.py │ ├── constants.py │ ├── disasm.py │ ├── disasm_strategy.py │ └── objects.py ├── march.py ├── memory.py ├── parser │ ├── __init__.py │ ├── _constants.py │ ├── _exception.py │ ├── graph.py │ ├── objects.py │ ├── parse_declaration.py │ ├── parse_file.py │ ├── parser.py │ ├── wrap_objects.py │ └── wrap_parser.py └── sections.py ├── docs ├── commands │ └── readme.md ├── img │ ├── favicon.ico │ ├── gdbcov_demo.gif │ ├── hook_example.gif │ ├── start │ │ ├── t1.jpg │ │ └── t2.jpg │ ├── t1.jpg │ └── t2.jpg ├── index.md ├── settings │ └── readme.md ├── start.md └── strategy │ ├── pre_func.md │ └── strategy.md ├── example ├── bleed_example │ ├── declare_static_data.c.bleed │ ├── inspect_status.c.bleed │ ├── inspect_status[x86_64].c.bleed │ ├── internal_func.c.bleed │ └── readme.c.bleed └── main.c ├── gdbleed.py ├── hook ├── __init__.py ├── _constants.py ├── default_hooks.py ├── examples.py ├── inline_hooks.py ├── inline_objects.py └── poor_ltrace.py ├── mkdocs.yml ├── plugins └── code_cov │ ├── gagent_data.c.bleed │ ├── gdbcov_dichotomic.c.bleed │ ├── gdbcov_entrypoint.c.bleed │ ├── gdbcov_init.c.bleed │ └── readme.md ├── proc_dump ├── proc_dump.c ├── proc_dump.h ├── proc_dump.py └── readme.md ├── setup.sh ├── tests ├── gdb_scripts │ ├── test1.gdb │ ├── test2.gdb │ └── test3.gdb ├── gdbinit-gef.py ├── test1.sh ├── test2.sh └── test3.sh ├── tracer ├── __init__.py ├── extensions.py ├── lambda_rules.py └── trace_all.py └── utils ├── __init__.py ├── colorsX.py ├── ctypes_stuff.py ├── gdb_utils.py ├── hexdump.py └── utilsX.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | keystone_repo 3 | keystone 4 | lief.so 5 | LIEF 6 | mini_httpd 7 | script_gdb.gdb 8 | dump_dir/* 9 | example/main 10 | log_exec.txt 11 | r2pipe 12 | site/ 13 | site.zip 14 | *.pyc 15 | *.swp 16 | /out.asm 17 | .gdbinit-gef.py 18 | cscope.files 19 | cscope.in.out 20 | cscope.out 21 | cscope.po.out 22 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | 2 | 3 | 08/18/2022 : 4 | - v0.1 (beta) 5 | 6 | 7 | -------------------------------------------------------------------------------- /CLI/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | The module CLI will expose to GDB the new commands 5 | """ 6 | 7 | from CLI import general as general_mod 8 | from CLI import memory as memory_mod 9 | from CLI import hook as hook_mod 10 | from CLI import bp_trace as bp_trace_mod 11 | from CLI import store_load_state as store_load_state_mod 12 | 13 | 14 | 15 | general = general_mod 16 | memory = memory_mod 17 | hook = hook_mod 18 | bp_trace = bp_trace_mod 19 | store_load_state = store_load_state_mod 20 | 21 | 22 | def init(*args, **kwargs) : 23 | general.init(*args, **kwargs) 24 | memory.init(*args, **kwargs) 25 | hook.init(*args, **kwargs) 26 | bp_trace.init(*args, **kwargs) 27 | store_load_state.init(*args, **kwargs) 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /CLI/bp_trace.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Trace stuff using breakpoints 5 | """ 6 | 7 | import gdb 8 | import re 9 | import lief 10 | 11 | from CLI.memory import MemCommand 12 | 13 | from utils.gdb_utils import find_function_addr 14 | 15 | from tracer.trace_all import NetBreakpoint 16 | 17 | 18 | 19 | class TraceBP(MemCommand): 20 | """ 21 | Trace using breakpoints 22 | """ 23 | 24 | cmd_default = "trace-bp" 25 | 26 | def __init__(self, name, details, details_mem, details_data): 27 | super(TraceBP, self).__init__(name, details, details_mem) 28 | self.details_data = details_data 29 | self.bp_list = [] 30 | 31 | 32 | def do_reset(self, argv, reset_all=False): 33 | """ 34 | Clear breakpoints 35 | """ 36 | for bp in self.bp_list : 37 | bp.delete() 38 | self.bp_list = [] 39 | 40 | 41 | def invoke(self, argv, from_tty): 42 | argv = [x.strip() for x in argv.split(" ") if x.strip() != "" ] 43 | 44 | try: 45 | syscall = argv[0] 46 | break_on_symbol = False 47 | break_on_addr = False 48 | 49 | if syscall == "--help" : 50 | raise Exception("help invoked") 51 | 52 | elif syscall == "--reset" : 53 | return self.do_reset(argv[1:]) 54 | 55 | elif syscall == "--internal" : 56 | print("#TODO") 57 | return 58 | 59 | elif syscall == "--address" : 60 | break_on_addr = True 61 | syscall = argv[1] 62 | 63 | elif syscall == "--symbol" : 64 | break_on_symbol = True 65 | syscall = argv[1] 66 | 67 | if syscall == "--trace-all" : 68 | for syscall, wrap_pltgot in self.details_data["got_entries"].items(): 69 | addr = wrap_pltgot.address_resolv 70 | if addr != None : 71 | spec = "*0x{:x}".format(addr) 72 | self.bp_list.append(NetBreakpoint(spec, addr, syscall, self.details)) 73 | else : 74 | print("[!] skipping '{}' trace".format(syscall)) 75 | 76 | else : 77 | 78 | if not break_on_addr : 79 | if syscall not in self.details_data["got_entries"]: 80 | raise Exception("Can't find '{}' in GOT entries".format(syscall)) 81 | wrap_pltgot = self.details_data["got_entries"][syscall] 82 | addr = wrap_pltgot.address_resolv 83 | else : 84 | base = 16 if syscall.startswith("0x") else 10 85 | addr = int(syscall,base) 86 | 87 | if break_on_symbol : 88 | spec = syscall 89 | addr = 0 90 | 91 | elif addr != None : 92 | spec = "*0x{:x}".format(addr) 93 | 94 | else: 95 | raise Exception("Can't find '{}'".format(syscall)) 96 | 97 | self.bp_list.append(NetBreakpoint(spec, addr, syscall, self.details)) 98 | 99 | except Exception as ex : 100 | self.details["slog"].append(str(ex)) 101 | self.details["slog"].append( 102 | "Usage: {} [--reset|--trace-all|--symbol] ".format(TraceBP.cmd_default) + "\n" +\ 103 | " --trace-all : do the ltrace similar function to each function imported by binary (don't traverse shared libraries)\n" +\ 104 | " --reset : delete breakpoints\n" +\ 105 | " --symbol : do breakpoint on symbol instead of address, breaking on each call of that function and not only inside PLT section\n" +\ 106 | " --address : do breakpoint on specific address\n" +\ 107 | " --help : this message\n" 108 | ) 109 | 110 | 111 | 112 | def init(details, details_mem, details_data, extra=dict()) : 113 | """ 114 | Initialize trace methods 115 | """ 116 | TraceBP(TraceBP.cmd_default, details, details_mem, details_data) 117 | 118 | 119 | -------------------------------------------------------------------------------- /CLI/general.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import gdb 3 | 4 | class GetUtil(gdb.Command): 5 | """ 6 | Get python variables from gdb UI 7 | """ 8 | 9 | def __init__(self, name, var_name, details, details_data, cast_to=lambda x, y: x): 10 | super(GetUtil, self).__init__(name, gdb.COMMAND_NONE) 11 | self.var_name = var_name 12 | self.cast_to = cast_to 13 | self.__details_data = details_data 14 | self.details = details 15 | 16 | def invoke(self, argv, from_tty): 17 | self.details["slog"].append(self.cast_to(self.__details_data[self.var_name], argv)) 18 | 19 | 20 | 21 | def cast_got_entries(got_entries, argv) : 22 | output = [] 23 | output_list = [] 24 | output_dict = {} 25 | 26 | argv = [ x.strip() for x in argv.split(" ") if x.strip() != ""] 27 | if len(argv) > 0 : 28 | 29 | rets = "Can't find got-symbol '{}'".format(argv[0]) 30 | 31 | if argv[0] == "--count" : 32 | rets = str(len(got_entries.items())) 33 | 34 | else : 35 | try : 36 | addr = got_entries[argv[0]].address_dyn 37 | rets = "[0x{:x}] ---> {}".format(addr, argv[0]) 38 | except : 39 | pass 40 | 41 | return rets 42 | 43 | for k,x in got_entries.items() : 44 | addr = x.address_dyn 45 | fname = x.fname 46 | output_list.append(addr) 47 | output_dict.update({addr:fname}) 48 | 49 | output_list.sort() 50 | 51 | for addr in output_list : 52 | output.append("[0x{:x}] ---> {}".format(addr, output_dict[addr])) 53 | 54 | return "\n".join(output) 55 | 56 | 57 | def init(details, details_mem, details_data, extra=dict()) : 58 | """ 59 | Initialize general CLI methods 60 | """ 61 | GetUtil("base-address", "base_address", details, details_data, lambda x, y : hex(x)) 62 | GetUtil("binary-name", "binary_name", details, details_data) 63 | GetUtil("binary-name-local", "binary_name_local", details, details_data) 64 | GetUtil("got-entries", "got_entries", details, details_data, cast_got_entries) 65 | 66 | 67 | -------------------------------------------------------------------------------- /CLI/memory.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Allocate memory using vmmap syscall, 5 | and malloc. 6 | 7 | If vmmap call fails then use malloc, 8 | or select to call directly malloc. 9 | 10 | Also keep track of memory region areas. 11 | """ 12 | 13 | import gdb 14 | import re 15 | import mmap 16 | 17 | import os 18 | from os.path import exists 19 | from os import listdir, mkdir 20 | import shutil 21 | 22 | 23 | from core import memory 24 | from utils.gdb_utils import make_executable 25 | import config 26 | 27 | 28 | tmp_folder = config.tmp_folder 29 | DUMP_DIR = "{}/dump_dir".format(tmp_folder) 30 | 31 | 32 | class MemCommand(gdb.Command): 33 | 34 | def __init__(self, name, details, details_mem): 35 | super(MemCommand, self).__init__(name, gdb.COMMAND_NONE) 36 | self.details_mem = details_mem 37 | self.details = details 38 | 39 | def __get_details_mem(self, k) : 40 | return self.details_mem[k] 41 | 42 | @property 43 | def mm_regions(self) : 44 | return self.__get_details_mem("mm_regions") 45 | 46 | @property 47 | def mm_addresses(self) : 48 | return self.__get_details_mem("mm_addresses") 49 | 50 | @property 51 | def mm_regions_ctrl(self) : 52 | return self.__get_details_mem("mm_regions_ctrl") 53 | 54 | @property 55 | def mm_addresses_ctrl(self) : 56 | return self.__get_details_mem("mm_addresses_ctrl") 57 | 58 | def invoke(self, argv, from_tty): 59 | """ 60 | Abstract method 61 | """ 62 | pass 63 | 64 | 65 | class DumpAll(MemCommand): 66 | """ 67 | Dump all process memory area and save output into /dump_dir folder 68 | """ 69 | 70 | cmd_default = "dump-all" 71 | 72 | def __init__(self, name, details, details_mem): 73 | super(DumpAll, self).__init__(name, details, details_mem) 74 | 75 | def invoke(self, argv, from_tty): 76 | 77 | if exists(DUMP_DIR) : 78 | if listdir(DUMP_DIR) : 79 | self.details["slog"].append( 80 | "[{}] Found some files inside folder '{}'\n".format(DumpAll.cmd_default, DUMP_DIR) +\ 81 | " \---> Do you want to delete them? (y/Y/-)" 82 | ) 83 | if input().strip().upper() != "Y" : 84 | shutil.rmtree(DUMP_DIR) 85 | os.mkdir(DUMP_DIR) 86 | 87 | else : 88 | os.mkdir(DUMP_DIR) 89 | 90 | for addr in self.mm_addresses : 91 | mm = self.mm_regions[addr] 92 | addr = hex(addr) 93 | eaddr = hex(mm.eaddr) 94 | addr_name = "{}.bin".format(addr) 95 | cmd = "dump binary memory {}/{} {} {}".format(DUMP_DIR, addr_name, addr, eaddr) 96 | try : 97 | gdb.execute(cmd) 98 | except Exception as ex : 99 | self.details["slog"].append("[{}] exeception on entry '{}' ..ignoring".format(DumpAll.cmd_default, cmd)) 100 | self.details["slog"].append(str(ex)) 101 | self.details["slog"].append("") 102 | 103 | 104 | class MemMap(MemCommand): 105 | """ 106 | Map memory using mmap and/or malloc 107 | """ 108 | 109 | cmd_default = "mem-map" 110 | match_with = r"\$[0-9]+ = (\(.*\))[ ]+(0x[A-f0-9]+)[ ]+.*" 111 | match_with_2 = r"\$[0-9]+ = (\(.*\))[ ]+(0x[A-f0-9]+)" 112 | match_without_decl = r"\$[0-9]+ = .*(0x[A-f0-9]+)" 113 | 114 | def __init__(self, name, details, details_mem): 115 | super(MemMap, self).__init__(name, details, details_mem) 116 | rets = gdb.execute("p &mmap", to_string=True).strip() 117 | rets_2 = re.search(MemMap.match_with, rets) 118 | 119 | if not rets_2 : 120 | self.details["slog"].append("[!] can't find mmap declaration ..ignoring it") 121 | rets_2 = re.search(MemMap.match_without_decl, rets) 122 | rets = rets_2.groups() 123 | rets = ["", rets[0]] 124 | else : 125 | rets = rets_2.groups() 126 | 127 | self.mmap_decl = rets[0] 128 | self.mmap_addr = int(rets[1], 16) 129 | 130 | 131 | def do_mmap(self, addr, size, permissions, flags=None, do_adjust_addr=True) : 132 | 133 | perm = {"-":0, "r":mmap.PROT_READ, "w":mmap.PROT_WRITE, "x":mmap.PROT_EXEC} 134 | flags = mmap.MAP_ANONYMOUS | mmap.MAP_PRIVATE if flags == None else flags 135 | 136 | # adjust permissions 137 | perm_tmp = 0 138 | for x in list(permissions) : 139 | perm_tmp |= perm[x] 140 | permissions = perm_tmp 141 | 142 | # adjust address 143 | adjust_addr = False 144 | if addr in self.mm_addresses : 145 | adjust_addr = True 146 | self.details["slog"].append("Address '{}' already mapped ..changing".format(hex(addr))) 147 | # size mu be updated here, for now is fine 148 | # 149 | if addr == 0 and do_adjust_addr : 150 | adjust_addr = True 151 | 152 | if adjust_addr : 153 | if self.mm_addresses[0] != 0 : 154 | addr = self.mm_addresses[0] - size 155 | 156 | cmd = "call (void *) mmap({}, {}, {}, {}, -1, 0)".format(hex(addr), hex(size), permissions, flags) 157 | try : 158 | rets = gdb.execute(cmd, to_string=True).strip() 159 | except : 160 | rets = "Command aborted." 161 | 162 | if "Command aborted." not in rets : 163 | rets_2 = re.search(MemMap.match_with_2, rets) 164 | rets = rets_2.groups() 165 | else : 166 | rets = [-1,-1] 167 | 168 | if rets[1] != hex(addr) : 169 | return None 170 | 171 | addr = int(rets[1], 16) 172 | return addr 173 | 174 | 175 | def do_malloc(self, size) : 176 | cmd = "call (void *) malloc({})".format(hex(size)) 177 | rets = gdb.execute(cmd, to_string=True).strip() 178 | rets_2 = re.search(MemMap.match_with_2, rets) 179 | rets = rets_2.groups() 180 | addr = int(rets[1], 16) 181 | return addr 182 | 183 | 184 | def invoke(self, argv, from_tty): 185 | argv = [x.strip() for x in argv.split(" ") if x.strip() != "" ] 186 | permissions = "rw" 187 | allocated_with_malloc = False 188 | 189 | try_malloc = False 190 | malloc = False 191 | do_adjust_addr = True 192 | 193 | if "--help" == argv[0] : 194 | raise Exception("--help") 195 | 196 | if "--try-malloc" == argv[0] : 197 | try_malloc = True 198 | argv = argv[1:] 199 | 200 | elif "--malloc" == argv[0] : 201 | malloc = True 202 | argv = argv[1:] 203 | 204 | if not malloc : 205 | if "--no-adjust-addr" == argv[0] : 206 | argv = argv[1:] 207 | do_adjust_addr = False 208 | 209 | try : 210 | size = int(argv[0], 16 if argv[0].startswith("0x") else 10) 211 | 212 | if not malloc : 213 | 214 | addr = size 215 | size = 0x2000 216 | 217 | if len(argv) > 1 : 218 | size = int(argv[1], 16 if argv[1].startswith("0x") else 10) 219 | 220 | permissions = "rwx" if len(argv) < 3 else argv[2] 221 | 222 | flags = None if len(argv) < 4 else int(argv[3], 16 if argv[3].startswith("0x") else 10) 223 | 224 | rets = self.do_mmap(addr, size, permissions, flags, do_adjust_addr) 225 | 226 | if rets == None : 227 | tmp_str = "[!] mmap call Fail, address given:'{}', returned value: '{}'".format(hex(addr), rets) 228 | if not try_malloc : 229 | raise Exception(tmp_str) 230 | 231 | self.details["slog"].append( 232 | tmp_str + "\n" +\ 233 | " \---> calling malloc instead" 234 | ) 235 | 236 | else : 237 | # if we here all went fine 238 | try_malloc = False 239 | 240 | if malloc or try_malloc : 241 | rets = self.do_malloc(size) 242 | allocated_with_malloc = True 243 | if rets == None : 244 | raise Exception("[x] calling malloc failed!") 245 | 246 | addr = rets 247 | 248 | # update mm_regions and mm_addresses vars 249 | mm = memory.MemoryRegion( 250 | addr, 251 | addr+size, 252 | size, 253 | 0, 254 | "", 255 | perm=permissions, 256 | allocated_with_malloc = allocated_with_malloc 257 | ) 258 | 259 | if allocated_with_malloc and "x" in permissions : 260 | make_executable(self.details, addr, size) 261 | 262 | self.details_mem["mm_regions"].update({addr:mm}) 263 | self.details_mem["mm_addresses"].append(addr) 264 | self.details_mem["mm_addresses"].sort() 265 | self.details_mem["mm_regions_ctrl"].update({addr:mm}) 266 | self.details_mem["mm_addresses_ctrl"].append(addr) 267 | self.details_mem["mm_addresses_ctrl"].sort() 268 | 269 | print(hex(addr)) 270 | 271 | except Exception as ex : 272 | self.details["slog"].append( 273 | "Usage: {} [--help] [--try-malloc] [--no-adjust-addr]
[size] [permissions] [flags]".format(MemMap.cmd_default) + "\n" +\ 274 | ": {} [--malloc] ".format(MemMap.cmd_default) + "\n" +\ 275 | " --help : This message\n" +\ 276 | " --try-malloc : if mmap fails try malloc instead\n" +\ 277 | " --no-adjust-addr : Don't try to map memory near to previous allocated memory\n" +\ 278 | " address : choose the address to map (insert '0' to let gdb choose)\n" +\ 279 | " size : size to map (default: '0x2000')\n" +\ 280 | " permissions : memory region permissions, e.g 'rx' (default: 'rwx')\n" +\ 281 | " flags : flags\n" +\ 282 | "\n" +\ 283 | " --malloc : allocate memory using malloc only\n" +\ 284 | " size : size\n" 285 | ) 286 | 287 | 288 | class MemUnmap(MemCommand): 289 | """ 290 | Unmap memory using munmap 291 | """ 292 | 293 | cmd_default = "mem-unmap" 294 | match_with = r"\$[0-9]+ = (\(.*\))[ ]+(0x[A-f0-9]+)[ ]+.*" 295 | match_without_decl = r"\$[0-9]+ = .*(0x[A-f0-9]+)" 296 | 297 | def __init__(self, name, details, details_mem): 298 | super(MemUnmap, self).__init__(name, details, details_mem) 299 | rets = gdb.execute("p &munmap", to_string=True).strip() 300 | rets_2 = re.search(MemUnmap.match_with, rets) 301 | 302 | if not rets_2 : 303 | self.details["slog"].append("[!] can't find munmap declaration ..ignoring it") 304 | rets_2 = re.search(MemUnmap.match_without_decl, rets) 305 | rets = rets_2.groups() 306 | rets = ["", rets[0]] 307 | else : 308 | rets = rets_2.groups() 309 | 310 | self.munmap_decl = rets[0] 311 | self.munmap_addr = int(rets[1], 16) 312 | 313 | 314 | def invoke(self, argv, from_tty): 315 | argv = [x.strip() for x in argv.split(" ") if x.strip() != "" ] 316 | 317 | try : 318 | 319 | if argv[0].startswith("0x") : 320 | addr = int(argv[0], 16) 321 | else : 322 | addr = int(argv[0]) 323 | 324 | if argv[1].startswith("0x") : 325 | size = int(argv[1], 16) 326 | else : 327 | size = int(argv[1]) 328 | 329 | if addr not in self.mm_addresses : 330 | raise Exception("Address '0x{:x}' sems to not be mapped".format(addr)) 331 | 332 | mm = self.mm_regions[addr] 333 | allocated_with_malloc = mm.allocated_with_malloc 334 | 335 | cmd = "call (int) munmap({}, {})".format(hex(addr), hex(size)) 336 | if allocated_with_malloc : 337 | cmd = "call (long long) free({})".format(hex(addr)) 338 | 339 | rets = gdb.execute(cmd, to_string=True).strip() 340 | 341 | match_with = r"\$[0-9]+ = (0x[A-f0-9]+)" 342 | rets_2 = re.search(MemUnmap.match_without_decl, rets) 343 | rets = rets_2.groups() 344 | 345 | ret_addr = int(rets[0].strip(), 16) 346 | 347 | if allocated_with_malloc : 348 | if ret_addr == 0 : 349 | raise Exception("[!] free call Fail, returned value: '{}'".format(rets)) 350 | 351 | else : 352 | if ret_addr != 0 : 353 | raise Exception("[!] munmap call Fail, returned value: '{}'".format(rets)) 354 | 355 | # update mm_regions and mm_addresses vars 356 | del self.mm_regions[addr] 357 | del self.mm_addresses[self.mm_addresses.index(addr)] 358 | if addr in self.mm_addresses_ctrl : 359 | del self.mm_regions_ctrl[addr] 360 | del self.mm_addresses_ctrl[self.mm_addresses_ctrl.index(addr)] 361 | print(hex(addr)) 362 | 363 | except Exception as ex : 364 | self.details["slog"].append(str(ex)) 365 | self.details["slog"].append( 366 | "Usage: {}
".format(MemUnmap.cmd_default) + "\n" +\ 367 | " address : choose the address to unmap/free\n" +\ 368 | " size : size of the memory region to unmap\n" 369 | ) 370 | 371 | 372 | def init(details, details_mem, details_data, extra=dict()) : 373 | DumpAll(DumpAll.cmd_default, details, details_mem) 374 | MemMap(MemMap.cmd_default, details, details_mem) 375 | MemUnmap(MemUnmap.cmd_default, details, details_mem) 376 | 377 | -------------------------------------------------------------------------------- /CLI/store_load_state.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import gdb 3 | import pickle 4 | from os.path import exists 5 | 6 | from config import tmp_folder 7 | 8 | 9 | default_store_file = "{}/state.bin".format(tmp_folder) 10 | 11 | 12 | class StoreState(gdb.Command): 13 | 14 | """ 15 | Save gdbleed session, which are stored in variables: 16 | - details 17 | - details_mem 18 | - details_data 19 | 20 | """ 21 | 22 | default_cmd = "store-state" 23 | 24 | def __init__(self, name, details, details_mem, details_data, save_to=default_store_file): 25 | super(StoreState, self).__init__(name, gdb.COMMAND_NONE) 26 | self.details = details 27 | self.details_mem = details_mem 28 | self.details_data = details_data 29 | self.save_to = save_to 30 | 31 | def invoke(self, argv, from_tty): 32 | print("#TODO. Sorry not support right now, some issue with pickle and lief modules") 33 | return 34 | 35 | try : 36 | fp = open(self.save_to, "wb") 37 | old_val = self.details["session_loaded"] 38 | self.details["session_loaded"] = True 39 | 40 | save_data = [self.details, self.details_mem, self.details_data] 41 | pickle.dump(save_data, fp) 42 | fp.close() 43 | 44 | self.details["session_loaded"] = old_val 45 | 46 | except Exception as ex : 47 | self.details["slog"].append(str(ex)) 48 | self.details["slog"].append( 49 | "[x] Can't save session into file '{}'".format(self.save_to) 50 | ) 51 | 52 | 53 | 54 | class LoadState(gdb.Command): 55 | 56 | """ 57 | Load gdbleed session, which are stored in variables: 58 | - details 59 | - details_mem 60 | - details_data 61 | 62 | """ 63 | 64 | default_cmd = "load-state" 65 | 66 | def __init__(self, name, details, details_mem, details_data, save_to=default_store_file): 67 | super(LoadState, self).__init__(name, gdb.COMMAND_NONE) 68 | self.details = details 69 | self.details_mem = details_mem 70 | self.details_data = details_data 71 | self.save_to = save_to 72 | self.skip_pid_check = False 73 | 74 | def invoke(self, argv, from_tty): 75 | print("#TODO. Sorry not support right now, some issue with pickle and lief modules") 76 | return 77 | 78 | try : 79 | fp = fopen(self.save_to, "rb") 80 | save_data = pickle.load(fp) 81 | fp.close() 82 | self.update_state(save_data) 83 | except : 84 | self.details["slog"].append( 85 | "[x] Can't load session from file '{}'".format(self.save_to) 86 | ) 87 | 88 | def update_state(self, save_data) : 89 | 90 | current_pid = self.details["pid"] 91 | new_pid = save_data[0]["pid"] 92 | 93 | if current_pid != new_pid : 94 | 95 | if self.skip_pid_check : 96 | return 97 | 98 | self.details["slog"].append( 99 | "[!] The debugged process has '{}' PID, meanwhile the saved session has '{}' PID\n".format(current_pid, new_pid) +\ 100 | " \---> Do you still want to load the old session? (y/Y/-)" 101 | ) 102 | if input().strip().upper() != "Y" : 103 | return 104 | 105 | save_data_now = [self.details, self.details_mem, self.details_data] 106 | for i, dict_now in enumerate(save_data_now) : 107 | dict_new = save_data[i] 108 | for k in list(dict_now.keys()) : 109 | dict_now[k] = dict_new[k] 110 | 111 | 112 | def init(details, details_mem, details_data, extra=dict()) : 113 | StoreState(StoreState.default_cmd, details, details_mem, details_data) 114 | loadstate_obj = LoadState(LoadState.default_cmd, details, details_mem, details_data) 115 | 116 | if exists(loadstate_obj.save_to) : 117 | old_val = loadstate_obj.skip_pid_check 118 | loadstate_obj.skip_pid_check = True 119 | loadstate_obj.invoke(None, None) 120 | loadstate_obj.skip_pid_check = old_val 121 | 122 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | 2 | ## Welcome to GDBleed docs contributing guide ## 3 | 4 | ### TODO 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2022, Altin (tin-z) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.gdbcov.md: -------------------------------------------------------------------------------- 1 | # GDBcov 2 | 3 | [DEMO] 4 | 5 | GDBcov - Hooking and instrumentation tool built on top of GDBleed 6 | 7 | GDBcov is'an extension of GDBleed that permits to hook conditional branch instructions and thus to reach some level of instrumentation. Currently only x86-64 architecture is supported and only for JCC-type instructions occupying at least 5 bytes. As it's stated on GDBleed's README file, we do not want to do heavily changes on the control flow of the program, so instead of using well-known techniques of binary instrumentation then we prefer to hook conditional branches by converting them into call instructions and then retrieving the visited basic block by comparing the return addresses. 8 | 9 | Please note, the tool is not complete and this is just a demo, refer to [README](./README.md) for more information about the GDBleed architecture in general 10 | 11 |
12 | 13 | ### Requirements and limitations 14 | 15 | - GDBleed must be installed and GDBLEED_HOME env must be set (follow the [readme](https://github.com/tin-z/GDBleed#req)) 16 | 17 | - tested on docker image ubuntu 20.04 18 | - Capability of mapping the 0 address on userspace is required 19 | - Hooking of short JCC-type (2 bytes) on x86-64 is not supported 20 | - Thread support is still ongoing 21 | 22 | 23 |
24 | 25 | ### Demo 26 | 27 | 28 | - Setup: 29 | 30 | ``` 31 | # 1. spawn gdbserver and attach it to a process that can map the 0-address (required only for kernel 4.+ or 5.+, something like so) 32 | # gdbserver --attach : 33 | # e.g. 34 | gdbserver --attach 127.0.0.1:12345 554449 35 | 36 | 37 | # 2. delete old gdbleed session 38 | rm -rf /tmp/gdbleed/ 39 | 40 | # 3. spawn gdb from gdbleed working directory 41 | cd $GDBLEED_HOME 42 | 43 | gdb /bin/bash -ex "source ./.gdbinit-gef.py" -ex "target remote 127.0.0.1:12345" -ex "source gdbleed.py" -ex "hook-got-inline --gdbcov --init-data" -ex "hook-got-inline --gdbcov --init-trampoline fork" -ex "hook-got-inline --create ./plugins/code_cov/gdbcov_dichotomic.c.bleed" -ex "hook-got-inline --create ./plugins/code_cov/gdbcov_entrypoint.c.bleed" -ex "hook-got-inline --list gdbcov.entry" -ex "hook-got-inline --gdbcov --trace" -ex "b *0" 44 | ``` 45 | 46 | - Poc: 47 | 48 | ![gdbcov_demo.gif](./docs/img/gdbcov_demo.gif) 49 | 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GDBleed 2 | 3 | [![Docs](https://img.shields.io/badge/Documentation-blue.svg)](https://tin-z.github.io/gdbleed/) [![MIT](https://img.shields.io/packagist/l/doctrine/orm.svg?maxAge=2592000?style=plastic)](https://github.com/tin-z/GDBleed/blob/main/LICENSE) 4 | 5 | :warning: Please note the binary instrumentation part is implemented by [GDBcov](README.gdbcov.md), which is still a work in progress. 6 | 7 | 8 | GDBleed - Dynamic-Static binary instrumentation framework on top of GDB 9 | 10 | `GDBleed` is a gdb wrapper exposing a set of commands for x86-64, ARM and MIPS 11 | (x86 and ARM thumb-mode in progress) architectures to perform binary 12 | instrumentation. The objective was to exploit the hackish features of GDB 13 | python API, while ignoring the software performance attribute (for now). And in 14 | the end to have a user-friendly framework. GDBleed focus is applicability, then 15 | we have efficiency. The more CPU archs it does suport the better it is. 16 | 17 | 18 | 19 | ### Why? 20 | 21 | - "easy"-fast minimal static-dynamic code instrumentation supporting all main CPU archs 22 | 23 | - Framework based on tools with a strong community support: GDB, gcc, r2, keystone, LIEF, etc. 24 | 25 | - No control flow information is needed 26 | 27 | - ideal for IoT devices, why? 28 | 29 | * no binary instrumentation for MIPS 30 | 31 | * cross-compilation is boring (and if it works then it will break somewhere during the execution) 32 | 33 | * A lot of the new IoT devices still using old linux kernel versions not supporting EBPF 34 | 35 | 36 | 37 | ### Usage 38 | 39 | - run gdb from the current folder 40 | 41 | - Start the process using `start` command or attach gdb to the debugged process 42 | 43 | - Run the command 44 | 45 | ``` 46 | source gdbleed.py 47 | ``` 48 | 49 | - For more info take a look at the `tests` folder 50 | 51 | --- 52 | 53 | ### Usage of the hooking functionalities ### 54 | 55 | - [Start guide](https://tin-z.github.io/gdbleed/start/) 56 | 57 | * For more clearance on how hooking/instrument stuff is working take a look at [strategy doc section](https://tin-z.github.io/gdbleed/strategy/strategy/) 58 | 59 | --- 60 | 61 | ### Req 62 | 63 | - Tested on ubuntu 20.04 64 | 65 | - Dep: keystone, LIEF 66 | 67 | 68 | ### Installation 69 | 70 | - Install 71 | ``` 72 | # GEF gdb extension, ref https://github.com/hugsy/gef 73 | sudo apt-get -y install unzip cmake binutils 74 | ``` 75 | 76 | - Declare env vars 77 | ``` 78 | # python's version which your gdb intalled supports 79 | export PYTHON_VER="python3" 80 | sudo apt-get install ${PYTHON_VER}-distutils ${PYTHON_VER}-setuptools 81 | 82 | # Choose module versions (i suggest not changing the major number version) 83 | export KEYSTONE_VER="0.9.2" 84 | export LIEF_VER="0.12.3" 85 | ``` 86 | 87 | - From current folder run: 88 | ``` 89 | ./setup.sh 90 | 91 | ``` 92 | 93 | 94 | **Required for hooking/instrumentation also aka Inline GOT hooking** 95 | 96 | - Install 97 | ```sh 98 | 99 | export TARGET=arm-linux-gnueabi 100 | sudo apt-get install -y binutils-${TARGET} gcc-${TARGET} 101 | 102 | export TARGET=mips-linux-gnu 103 | sudo apt-get install -y binutils-${TARGET} gcc-${TARGET} 104 | ``` 105 | 106 | - add vim highlighting 107 | 108 | ```vim 109 | augroup filetypedetect 110 | au! BufRead,BufNewFile *.c.bleed setfiletype c 111 | augroup END 112 | ``` 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | 2 | ## TODO list 3 | 4 | - instrumentation e.g. trace basic blocks 5 | - add ctypes support 6 | - add 'perfom assembly inline in GDB' by using keystone 7 | 8 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Configurable constants 4 | 5 | hexdump_max_length = 200 6 | 7 | 8 | 9 | slog_path = None 10 | """ 11 | Save output to 'slog_path', default stdout 12 | """ 13 | 14 | tmp_folder = "/tmp/gdbleed" 15 | log_file="{}/log_exec.txt".format(tmp_folder) 16 | 17 | LITTLE_ENDIAN=0 18 | BIG_ENDIAN=1 19 | 20 | LE = LITTLE_ENDIAN 21 | BE = BIG_ENDIAN 22 | 23 | 24 | compiler_path = { 25 | "mips" : {LE:"mipsel-linux-gnu-gcc", BE:"mips-linux-gnu-gcc"} ,\ 26 | "x86-64" : {LE:"/usr/bin/gcc", BE:None} ,\ 27 | "arm": {LE:"/usr/bin/arm-linux-gnueabi-gcc", BE:"/usr/bin/arm-linux-gnueabi-gcc"} 28 | } 29 | """ 30 | Cross-Compiler paths 31 | """ 32 | 33 | default_flag = dflg = "-g -fPIC -c" 34 | 35 | compiler_flags = { 36 | "mips" : {LE:dflg, BE:dflg} ,\ 37 | "x86-64" : {LE:dflg, BE:dflg} ,\ 38 | "arm" : {LE:dflg, BE:dflg} 39 | } 40 | """ 41 | Cross-Compiler flags 42 | """ 43 | 44 | 45 | ARCH_supported = ["mips", "x86-64", "arm"] 46 | """ 47 | Archs supported 48 | """ 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /core/GOT.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | ELF GOT entries 5 | """ 6 | 7 | import lief 8 | 9 | from utils.gdb_utils import get_data_memory, find_function_addr 10 | 11 | 12 | 13 | class WrapPLTGOT : 14 | """ 15 | Wrapper around plt-got LIEF object 16 | """ 17 | 18 | def __init__(self, pltgot_lief, fname, address, address_dyn, decl, address_resolv) : 19 | """ 20 | pltgot_lief : LIEF proxy object 21 | fname : function name 22 | address : got entry's offset 23 | address_dyn : got entry's virtual address 24 | decl : call arguments types 25 | address_resolv : address of the function 26 | """ 27 | self.pltgot_lief = pltgot_lief 28 | self.fname = fname 29 | self.address = address 30 | self.address_dyn = address_dyn 31 | self.decl = decl 32 | self.address_resolv = address_resolv 33 | self.address_hooking = -1 34 | 35 | def __str__(self): 36 | return self.pltgot_lief.__str__() 37 | 38 | def is_hooked(self): 39 | return self.address_hooking != -1 40 | 41 | 42 | ## Runtime methods 43 | 44 | def got_symbols(binary_name, binary_name_local, base_address, size_base_address, details) : 45 | """ 46 | The method extract got entries (only for the main binary, which is 47 | the debugged process) 48 | 49 | Notes: 50 | - Simple plt.got support (MIPS' type of got is not supported) 51 | - Skip ordinal import 52 | 53 | """ 54 | got_entries = dict() 55 | binary = lief.parse(binary_name_local) 56 | pltgot_list = binary.pltgot_relocations 57 | details["is_pie"] = binary.is_pie 58 | 59 | for x in pltgot_list : 60 | 61 | if x.has_symbol : 62 | address = x.address 63 | address_dyn = address 64 | if details["is_pie"] : 65 | address_dyn += base_address 66 | 67 | fname = x.symbol.name 68 | decl, address_resolv = find_function_addr(fname, details) 69 | 70 | # if dyn resolver already found function, then use that one 71 | rets = get_data_memory(details["word"], address_dyn) 72 | if rets < base_address or rets > (base_address + size_base_address) : 73 | address_resolv = rets 74 | 75 | y = WrapPLTGOT(x, fname, address, address_dyn, decl, address_resolv) 76 | got_entries.update({fname:y}) 77 | 78 | return got_entries 79 | 80 | 81 | -------------------------------------------------------------------------------- /core/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Core classes (memory mapping, GOT, cpu, etc.) 3 | """ 4 | -------------------------------------------------------------------------------- /core/compiler.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Simple compiler strategies. 5 | 6 | """ 7 | 8 | import os 9 | import config 10 | 11 | tmp_folder = config.tmp_folder 12 | LE = config.LITTLE_ENDIAN 13 | BE = config.BIG_ENDIAN 14 | compiler_path = config.compiler_path 15 | compiler_flags = config.compiler_flags 16 | 17 | 18 | class Compiler : 19 | """ 20 | Compiler class 21 | 22 | """ 23 | 24 | def __init__(self, details, details_mem, details_data) : 25 | self.details = details 26 | self.details_mem = details_mem 27 | self.details_data = details_data 28 | self.reset() 29 | 30 | def reset(self) : 31 | self.flags = compiler_flags[self.details["arch"]][self.details["endian"]] 32 | self.comp = compiler_path[self.details["arch"]][self.details["endian"]] 33 | 34 | def compile(self, file_name, code) : 35 | """ 36 | - file_name : temporary file created 37 | - code : C code 38 | 39 | Return object file path. User should check the output file 40 | """ 41 | object_file_name = file_name + ".o" 42 | in_file = "{}/{}".format(tmp_folder, file_name) 43 | out_file = "{}/{}".format(tmp_folder, object_file_name) 44 | 45 | with open(in_file, "w") as fp : 46 | fp.write(code) 47 | 48 | while True : 49 | cmd = "{} {} {} -o {}".format(self.comp, self.flags, in_file, out_file) 50 | os.system(cmd) 51 | 52 | self.details["slog"].append( 53 | "Code compiled or maybe not, you should check that and change stuff (folder '{}').\n".format(out_file) +\ 54 | "Do you want to retry compilation? (y/Y/-)" 55 | ) 56 | 57 | retry = input().strip() 58 | if retry.upper() != "Y" : 59 | break 60 | 61 | return out_file 62 | 63 | -------------------------------------------------------------------------------- /core/constants.py: -------------------------------------------------------------------------------- 1 | """ 2 | CPU Architectural Constants 3 | """ 4 | import config 5 | import copy 6 | 7 | 8 | 9 | LITTLE_ENDIAN=config.LITTLE_ENDIAN 10 | BIG_ENDIAN=config.BIG_ENDIAN 11 | 12 | 13 | CALL_CONVENTION = { 14 | "arm" : ["R0", "R1", "R2", "R3", "@@ R13"] ,\ 15 | "x86-64" : ["RDI", "RSI", "RDX", "RCX", "R8", "R9", "@@ RSP"] ,\ 16 | "mips" : ["$a0","$a1","$a2","$a3", "@@ $sp"] 17 | } 18 | """ 19 | Calling convention supported 20 | """ 21 | 22 | 23 | NOP_ins = { 24 | "arm" : "NOP" ,\ 25 | "x86-64" : "NOP" ,\ 26 | "mips" : "add $t0, $t0, 0" 27 | } 28 | 29 | 30 | 31 | CALL_ins = { 32 | "arm" : ["blx", "bl"] ,\ 33 | "x86-64" : ["call", "callq"] ,\ 34 | "mips" : ["jal"] 35 | } 36 | 37 | 38 | special_regs = { 39 | "mips_gp" : "$t9" 40 | } 41 | 42 | 43 | 44 | 45 | 46 | # ref https://ablconnect.harvard.edu/files/ablconnect/files/mips_instruction_set.pdf 47 | mips_branch_type = [ 48 | "beq" ,\ 49 | "bne" ,\ 50 | "blez" ,\ 51 | "bgtz" ,\ 52 | "bltz" ,\ 53 | "bgez" ,\ 54 | "bltzal" ,\ 55 | "bgezal" ,\ 56 | ] 57 | 58 | 59 | # ref https://www.slideserve.com/elina/68000-addressing-modes 60 | # https://www.intel.com/content/www/us/en/docs/programmable/683620/current/bgtu.html 61 | arm32_branch_type = [ 62 | "beq" ,\ 63 | "bne" ,\ 64 | "bpl" ,\ 65 | "bmi" ,\ 66 | "bcc" ,\ 67 | "blo" ,\ 68 | "bcs" ,\ 69 | "bhs" ,\ 70 | "bvc" ,\ 71 | "bvs" ,\ 72 | "bge" ,\ 73 | "bgt" ,\ 74 | "ble" ,\ 75 | "blt" ,\ 76 | "bhi" ,\ 77 | "bls" ,\ 78 | "bgtu" ,\ 79 | "bltu" ,\ 80 | "bgeu" ,\ 81 | "bleu" ,\ 82 | "beqz" ,\ 83 | "bnez" ,\ 84 | "blez" ,\ 85 | "bgez" ,\ 86 | "bltz" ,\ 87 | "bgtz" ,\ 88 | ] 89 | 90 | 91 | # https://www.felixcloutier.com/x86/jcc 92 | # - last instr occupies 3 bytes 93 | # - The JRCXZ, JECXZ, and JCXZ instructions differ from other Jcc instructions because they do not check status flags. Instead, they check RCX, ECX or CX for 0. The register checked is determined by the address-size attribute. 94 | 95 | x86_64_branch_type = [ 96 | ["jo"] ,\ 97 | ["jno"] ,\ 98 | ["js"] ,\ 99 | ["jns"] ,\ 100 | ["je", "jz"] ,\ 101 | ["jne", "jnz"] ,\ 102 | ["jb", "jnae", "jc"] ,\ 103 | ["jnb", "jae", "jnc"] ,\ 104 | ["jbe", "jna"] ,\ 105 | ["ja", "jnbe"] ,\ 106 | ["jl", "jnge"] ,\ 107 | ["jge", "jnl"] ,\ 108 | ["jle", "jng"] ,\ 109 | ["jg", "jnle"] ,\ 110 | ["jp", "jpe"] ,\ 111 | ["jnp", "jpo"] ,\ 112 | ["jrcxz"] ,\ 113 | ["jecxz"] ,\ 114 | ] 115 | 116 | x86_branch_type = copy.deepcopy(x86_64_branch_type) 117 | x86_branch_type[-2] = ["jecxz"] 118 | x86_branch_type[-1] = ["jcxz"] 119 | 120 | 121 | JCC_types = { 122 | "arm" : arm32_branch_type ,\ 123 | "x86-64" : x86_64_branch_type ,\ 124 | "mips" : mips_branch_type ,\ 125 | } 126 | 127 | -------------------------------------------------------------------------------- /core/disasm/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Disassembler module class 3 | 4 | In particular radare2 (r2pipe) is used 5 | """ 6 | -------------------------------------------------------------------------------- /core/disasm/constants.py: -------------------------------------------------------------------------------- 1 | 2 | ## Fb types 3 | TYP_FUNC = 0 4 | TYP_FUNC_IMP = 1 # function inside plt or mips stub section 5 | 6 | ## Bb types 7 | TYP_END_FUNCTION = 0 8 | TYP_RETURN = 1 9 | TYP_BRANCH = 2 10 | TYP_CONDITIONAL_BRANCH = 4 11 | TYP_UNKNOWN_TYPE_BRANCH = 8 12 | 13 | type_blocks = [ 14 | TYP_END_FUNCTION ,\ 15 | TYP_RETURN ,\ 16 | TYP_BRANCH ,\ 17 | TYP_CONDITIONAL_BRANCH ,\ 18 | TYP_UNKNOWN_TYPE_BRANCH ,\ 19 | ] 20 | 21 | 22 | ## Cb types 23 | TYP_CALL = 0 24 | TYP_CALL_IMP = 1 # call to imported section (plt, or mips stub section) 25 | 26 | -------------------------------------------------------------------------------- /core/disasm/disasm.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from core.disasm.constants import TYP_CONDITIONAL_BRANCH 3 | from core.disasm.disasm_strategy import R2Disasm 4 | 5 | 6 | class WrapDisasm : 7 | 8 | supported_strategy = { 9 | "r2": R2Disasm 10 | } 11 | 12 | def __init__(self, details, details_mem, strategy="r2"): 13 | self.details = details 14 | self.details_mem = details_mem 15 | 16 | assert(strategy in WrapDisasm.supported_strategy) 17 | 18 | self.disasm = WrapDisasm.supported_strategy[strategy]( 19 | self.details["binary_path"], 20 | self.details["is_pie"], 21 | self.details 22 | ) 23 | self.baddr = self.disasm.baddr 24 | self.is_init = False 25 | 26 | 27 | def init(self): 28 | if self.is_init : 29 | return 30 | self.disasm.init() 31 | self.is_init = True 32 | 33 | def get_conditional_branches(self, jump_offset=None): 34 | bb_offsets = self.disasm.get_bbs(TYP_CONDITIONAL_BRANCH) 35 | bb_list = [] 36 | 37 | for bb_offset in bb_offsets : 38 | bb_list.append(self.disasm.get_bb(bb_offset)) 39 | 40 | rets = bb_list 41 | if jump_offset : 42 | for bb in bb_list : 43 | if bb.arg_offset == jump_offset : 44 | rets = [bb] 45 | break 46 | 47 | return rets 48 | 49 | 50 | -------------------------------------------------------------------------------- /core/disasm/disasm_strategy.py: -------------------------------------------------------------------------------- 1 | import r2pipe 2 | import re 3 | import time 4 | 5 | from core.disasm.objects import * 6 | from core.disasm.constants import * 7 | 8 | 9 | ## Interface 10 | class StrategyDisasm: 11 | """ 12 | Disasm strategy interface 13 | 14 | """ 15 | 16 | def init(self): 17 | """ 18 | Init function defined by sub classes 19 | 20 | """ 21 | pass 22 | 23 | 24 | def get_fbs(self, filter_type=-1): 25 | """ 26 | Return list of function block having type equals to 'filter_type' 27 | (Default filter_type=-1 for returning every Fb) 28 | 29 | """ 30 | if filter_type == -1 : 31 | return list(self.fb_offset_dict.keys()) 32 | output = [] 33 | for x,i in self.fb_offset_dict.items() : 34 | if self.fb_list[i].is_type(filter_type) : 35 | output.append(x) 36 | return output 37 | 38 | def get_bbs(self, filter_type=-1): 39 | """ 40 | Return list of basic block having type equals to 'filter_type' 41 | (Default filter_type=-1 for returning every Bb) 42 | 43 | """ 44 | if filter_type == -1 : 45 | return list(self.bb_offset_dict.keys()) 46 | output = [] 47 | for x,i in self.bb_offset_dict.items() : 48 | if self.bb_list[i].is_type(filter_type) : 49 | output.append(x) 50 | return output 51 | 52 | def get_cbs(self, filter_type=-1): 53 | """ 54 | Return list of instruction calls (Call block) having type equals to 'filter_type' 55 | (Default filter_type=-1 for returning every Cb) 56 | 57 | """ 58 | if filter_type == -1 : 59 | return list(self.cb_offset_dict.keys()) 60 | output = [] 61 | for x,i in self.cb_offset_dict.items() : 62 | if self.cb_list[i].is_type(filter_type) : 63 | output.append(x) 64 | return output 65 | 66 | 67 | 68 | class R2Disasm(r2pipe.open_sync.open, StrategyDisasm): 69 | 70 | def __init__(self, binary_name, is_pie, details): 71 | super(R2Disasm, self).__init__(binary_name, ['-B', str(0)]) 72 | self.binary_name = binary_name 73 | self.is_pie = is_pie 74 | self.details = details 75 | self.baddr = 0 76 | self.arch = self.details["arch"] 77 | self.import_section_offset = 0 78 | self.import_section_offset_end = 0 79 | 80 | if self.arch == "x86-64" : 81 | self.cmd_search_call = "/adj call" 82 | self.import_section_name = ".plt" 83 | else : 84 | # note for mips we need to use import_section_name = ".MIPS.stubs" 85 | raise Exception(f"Arch '{self.arch}' TODO") 86 | 87 | self.is_init = False 88 | 89 | 90 | def init(self): 91 | if self.is_init : 92 | return 93 | self.cmd('aaa') 94 | self.__init_fb() 95 | self.__init_cb() 96 | self.is_init = True 97 | 98 | def exec_cmdj_retry(self, cmd, raise_ex=False, retry=2, t_sleep=0.1) : 99 | for _ in range(retry) : 100 | rets = self.cmdj(cmd) 101 | if rets != {} : 102 | return rets 103 | time.sleep(t_sleep) 104 | if raise_ex : 105 | raise Exception(f"No return data from '{cmd}'") 106 | 107 | def __init_fb(self): 108 | self.fb_offset_dict = {} 109 | self.fb_list = [] 110 | self.bb_offset_dict = {} 111 | self.bb_list = [] 112 | 113 | functions = self.exec_cmdj_retry('aflj', True) 114 | for x in functions : 115 | if x['type'] == "fcn" : 116 | offset = x['minbound'] 117 | fb_arg = [ 118 | offset ,\ 119 | x['maxbound'],\ 120 | x ,\ 121 | x['name'] ,\ 122 | TYP_FUNC ,\ 123 | set() ,\ 124 | ] 125 | 126 | if fb_arg[-3].startswith("sym.imp.") : 127 | fb_arg[-2] = TYP_FUNC_IMP 128 | 129 | # TODO: add edge_in and edge_out ('callrefs' for edge_out and 'callxrefs' for edge_in) 130 | basic_blocks = self.exec_cmdj_retry(f'afbj {offset}') 131 | for y in basic_blocks : 132 | bb_offset = y['addr'] 133 | bb_arg = [ 134 | bb_offset ,\ 135 | bb_offset + y['size'] ,\ 136 | y ,\ 137 | offset ,\ 138 | TYP_END_FUNCTION ,\ 139 | ] 140 | 141 | bb_extra_arg = {} 142 | if 'jump' in y : 143 | bb_arg[-1], bb_extra_arg = self.return_type_branch(bb_offset) 144 | 145 | self.bb_offset_dict.update( 146 | {bb_offset : len(self.bb_list)} 147 | ) 148 | 149 | bb_extra_arg["arch"] = self.details["arch"] 150 | 151 | self.bb_list.append( 152 | Bb(*bb_arg, **bb_extra_arg) 153 | ) 154 | 155 | fb_arg[-1].add(bb_offset) 156 | 157 | self.fb_list.append( 158 | Fb( 159 | *fb_arg 160 | ) 161 | ) 162 | self.fb_offset_dict.update( 163 | {offset : len(self.fb_list)} 164 | ) 165 | 166 | 167 | def __set_imp_section(self): 168 | sections = self.exec_cmdj_retry("iSj") 169 | for s in sections : 170 | if s['name'] == self.import_section_name : 171 | self.import_section_offset = s['vaddr'] 172 | self.import_section_offset_end = s['vaddr'] + s['vsize'] 173 | 174 | def offset_is_in_imp_section(self, offset): 175 | return (\ 176 | offset >= self.import_section_offset and \ 177 | offset < self.import_section_offset_end \ 178 | ) 179 | 180 | 181 | def __init_cb(self): 182 | self.cb_offset_dict = {} 183 | self.cb_list = [] 184 | 185 | if not self.import_section_offset : 186 | self.__set_imp_section() 187 | assert(self.import_section_offset != 0) 188 | 189 | assert( 190 | self.import_section_offset <= \ 191 | self.import_section_offset_end 192 | ) 193 | 194 | call_blocks = self.exec_cmdj_retry(self.cmd_search_call) 195 | for x in call_blocks : 196 | cb_offset = x['offset'] 197 | cb_arg = [ 198 | cb_offset ,\ 199 | cb_offset + x['len'] ,\ 200 | x ,\ 201 | -1 ,\ 202 | TYP_CALL ,\ 203 | ] 204 | 205 | code = x['code'] 206 | 207 | if code.lower() != "syscall" : 208 | 209 | target = code.split(" ")[1] 210 | 211 | if target.startswith("0x") : 212 | cb_arg[-2] = int(target, 16) 213 | 214 | if self.offset_is_in_imp_section(cb_arg[-2]) : 215 | cb_arg[-1] = TYP_CALL_IMP 216 | 217 | self.cb_list.append( 218 | Cb( 219 | *cb_arg 220 | ) 221 | ) 222 | self.cb_offset_dict.update( 223 | {cb_offset : len(self.cb_list)} 224 | ) 225 | 226 | 227 | def return_type_branch(self, bb_offset): 228 | asm = self.exec_cmdj_retry(f'pdbj @ {bb_offset}') 229 | # NOTE: last instruction should be the branch one (for mips is the second last one) 230 | asm = asm[-1] 231 | 232 | ret_type = TYP_UNKNOWN_TYPE_BRANCH 233 | ret_dict = { 234 | "arg_offset" : asm["offset"] ,\ 235 | "arg_size" : asm["size"] ,\ 236 | "arg_opcode" : asm["opcode"] ,\ 237 | "arg_bytes" : asm["bytes"] ,\ 238 | } 239 | 240 | if asm['type'] in ['jmp', 'cjmp', 'nop', 'cmov', 'call', 'mov'] : 241 | # known type is fine 242 | 243 | if asm['type'] in ['cjmp', 'jmp'] : 244 | ret_type = TYP_BRANCH 245 | ret_dict.update( 246 | {"arg_jump" : asm["jump"]} 247 | ) 248 | 249 | if asm["type"] == "cjmp" : 250 | ret_type = TYP_CONDITIONAL_BRANCH 251 | ret_dict.update( 252 | {"arg_fail" : asm["offset"] + asm["size"]} 253 | ) 254 | 255 | asm = self.exec_cmdj_retry(f"pdj 1 @ {ret_dict['arg_fail']}") 256 | asm = asm[-1] 257 | 258 | if asm["type"] == "invalid" : 259 | raise Exception( 260 | f"r2disasm: Can't disasseble instruction at offset {hex(ret_dict['arg_fail'])}, upgrade radare2" 261 | ) 262 | 263 | ret_dict.update( 264 | { 265 | "arg_fail_type" : asm["type"] ,\ 266 | "arg_fail_opcode" : asm["opcode"] ,\ 267 | "arg_fail_size" : asm["size"] 268 | } 269 | ) 270 | 271 | else : 272 | self.details["slog"].append( 273 | print( 274 | f"[!] Found a basic block with type unknown at offset '0x{asm['offset']:x}' '{asm['type']}'" 275 | ) 276 | ) 277 | 278 | return ret_type, ret_dict 279 | 280 | 281 | def get_fb(self, offset): 282 | return self.fb_list[ \ 283 | self.fb_offset_dict[offset] 284 | ] 285 | 286 | def get_bb(self, offset): 287 | return self.bb_list[ \ 288 | self.bb_offset_dict[offset] 289 | ] 290 | 291 | def get_cb(self, offset): 292 | return self.cb_list[ \ 293 | self.cb_offset_dict[offset] 294 | ] 295 | 296 | 297 | -------------------------------------------------------------------------------- /core/disasm/objects.py: -------------------------------------------------------------------------------- 1 | from core.disasm.constants import * 2 | 3 | 4 | class Node : 5 | def __init__(self, offset, offset_end, obj_ptr, type): 6 | self.id_ = offset 7 | self.offset = offset 8 | self.offset_end = offset_end 9 | self.obj_ptr = obj_ptr 10 | self.type = type 11 | 12 | def is_type(self, type) : 13 | return self.type == type 14 | 15 | 16 | 17 | class Fb(Node): 18 | """ 19 | Function block 20 | 21 | """ 22 | 23 | def __init__(self, offset, offset_end, r2_object, fname, type=TYP_FUNC, bb=set(), edge_in=set(), edge_out=set()): 24 | """ 25 | offset : Function block offset 26 | offset_end : Next function block offset 27 | r2_object : Dictionary returned by r2 for future use 28 | fname : function name 29 | type : Function type 30 | bb : basic block set 31 | edge_in : functions calling this function set 32 | edge_out : functions being called by this function set 33 | 34 | """ 35 | self.fname = fname 36 | self.edge_in = edge_in.copy() 37 | self.edge_out = edge_out.copy() 38 | self.bb = bb.copy() 39 | super(Fb, self).__init__(offset, offset_end, r2_object, type) 40 | 41 | def add_bb(self, bb) : 42 | self.bb.add(bb) 43 | 44 | def add_edge_in(self, offset): 45 | self.edge_in.add(offset) 46 | 47 | def add_edge_out(self, offset): 48 | self.edge_out.add(offset) 49 | 50 | 51 | class Bb(Node): 52 | """ 53 | Basic block 54 | """ 55 | 56 | def __init__(self, offset, offset_end, r2_object, fb_offset, type=TYP_END_FUNCTION, **kwargs) : 57 | """ 58 | offset : Basic block offset 59 | offset_end : Next basic block offset 60 | r2_object : Dictionary returned by r2 for future use 61 | fb_offset : Function block offset 62 | type : Basic block type 63 | 64 | """ 65 | assert(type in type_blocks) 66 | super(Bb, self).__init__(offset, offset_end, r2_object, type) 67 | self.fb_offset = fb_offset 68 | 69 | if self.type & (TYP_BRANCH | TYP_CONDITIONAL_BRANCH) : 70 | k = ["arg_offset", "arg_size", "arg_opcode", "arg_bytes", "arg_jump"] 71 | if self.type & TYP_CONDITIONAL_BRANCH : 72 | k.append("arg_fail") 73 | k.append("arg_fail_type") 74 | k.append("arg_fail_opcode") 75 | k.append("arg_fail_size") 76 | 77 | for x in k : 78 | setattr(self, x, kwargs[x]) 79 | if self.type & TYP_CONDITIONAL_BRANCH : 80 | setattr(self, "arg_jump_type", self.arg_opcode.split(" ")[0]) 81 | 82 | def __str__(self): 83 | rets = f"Bb[{hex(self.offset)}:{hex(self.offset_end)}, {self.type})]" 84 | 85 | if self.type & (TYP_BRANCH | TYP_CONDITIONAL_BRANCH) : 86 | rets += f"({hex(self.arg_offset)}, {hex(self.arg_size)}, {self.arg_opcode}, 0x{self.arg_bytes}, {hex(self.arg_jump)}" 87 | 88 | if self.type & TYP_CONDITIONAL_BRANCH : 89 | rets += f", {self.arg_jump_type}" 90 | rets += f", {hex(self.arg_fail)}: {self.arg_fail_opcode}" 91 | rets += f", {self.arg_fail_size}" 92 | rets += f", {self.arg_fail_type}" 93 | 94 | rets += ")" 95 | 96 | return rets 97 | 98 | 99 | def __repr__(self): 100 | return self.__str__() 101 | 102 | 103 | 104 | class Cb(Node): 105 | """ 106 | Call block (instruction) 107 | """ 108 | 109 | def __init__(self, offset, offset_end, r2_object, target=-1, type=TYP_CALL) : 110 | """ 111 | offset : Call instruction offset 112 | offset_end : Next instruction offset (for mips we need the next's next instruction) 113 | r2_object : Dictionary returned by r2 for future use 114 | target : Call destination offset, only available for numerical operand 115 | type : Call block type 116 | 117 | """ 118 | super(Cb, self).__init__(offset, offset_end, r2_object, type) 119 | self.target = target 120 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /core/march.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Retrieve CPU architectural values and specific process values 5 | """ 6 | 7 | import gdb 8 | import re 9 | from core.constants import * 10 | 11 | import config 12 | 13 | 14 | ARCH_supported = config.ARCH_supported 15 | 16 | 17 | x86_64_convention = { 18 | "args" : ["$rdi","$rsi","$rdx","$rcx","$r8","$r9","@@ $rsp+"] ,\ 19 | "ret_val" : ["$rax"] ,\ 20 | "ret" : ["@@ $rbp+"] 21 | } 22 | 23 | mips_convetion = { 24 | "args" : ["$a0","$a1","$a2","$a3", "@@ $sp+"] ,\ 25 | "ret_val" : ["$v0","$v1"] ,\ 26 | "ret" : ["$ra","@@ $sp+"] 27 | } 28 | 29 | arm_convention = { 30 | "args" : ["$r0","$r1","$r2","$r3","@@ $r13+"] ,\ 31 | "ret_val" : ["$r0"] ,\ 32 | "ret" : ["$r14","@@ $r13+"] 33 | } 34 | 35 | archs = { "mips":mips_convetion, "x86-64":x86_64_convention, "arm":arm_convention } 36 | 37 | 38 | """ 39 | Number Name Purpose 40 | $0 $0 Always 0 41 | $1 $at The Assembler Temporary used by the assembler in expanding pseudo-ops. 42 | $2-$3 $v0-$v1 These registers contain the Returned Value of a subroutine; if the value is 1 word only $v0 is significant. 43 | $4-$7 $a0-$a3 The Argument registers, these registers contain the first 4 argument values for a subroutine call. 44 | $8-$15,$24,$25 $t0-$t9 The Temporary Registers. 45 | $16-$23 $s0-$s7 The Saved Registers. 46 | $26-$27 $k0-$k1 The Kernel Reserved registers. DO NOT USE. 47 | $28 $gp The Globals Pointer used for addressing static global variables. For now, ignore this. 48 | $29 $sp The Stack Pointer. 49 | 50 | $30 $fp (or $s8) The Frame Pointer, if needed (this was discussed briefly in lecture). Programs that do not use an explicit frame pointer 51 | (e.g., everything assigned in ECE314) can use register $30 as another saved register. Not recommended however. 52 | 53 | $31 $ra The Return Address in a subroutine call. 54 | 55 | 56 | - ref, https://courses.cs.washington.edu/courses/cse378/10sp/lectures/lec05-new.pdf 57 | The caller is responsible for saving and restoring any of the following caller-saved registers that it cares about. 58 | $t0-t9 $a0-$a3 $v0-$v1 59 | 60 | The callee is responsible for saving and restoring any of the following callee-saved registers that it uses 61 | $s0-$s7 $ra (in our case we'll save also $ra) 62 | 63 | ---- 64 | 65 | ## mips calling conventions, refs: 66 | # - https://courses.cs.washington.edu/courses/cse410/09sp/examples/MIPSCallingConventionsSummary.pdf 67 | # 68 | ## arm calling convention, refs: 69 | # - https://www.ele.uva.es/~jesus/hardware_empotrado/ARM_calling.pdf 70 | # - https://azeria-labs.com/arm-data-types-and-registers-part-2/ 71 | """ 72 | 73 | 74 | class GDBExceptionBase(Exception) : 75 | pass 76 | 77 | class UnsupportedArch(GDBExceptionBase) : 78 | def __init__(self, msg): 79 | super().__init__("Unsupported arch: '{}'".format(msg)) 80 | 81 | 82 | 83 | def getarch(details): 84 | """ 85 | Mostly copied and pasted from: 86 | - https://www.programcreek.com/python/?code=scwuaptx%2FPwngdb%2FPwngdb-master%2Fangelheap%2Fangelheap.py 87 | """ 88 | value_to_set = ["capsize", "word", "arch", "isa"] 89 | capsize = None 90 | word = None 91 | arch = None 92 | isa = None 93 | 94 | rets = None 95 | 96 | data = gdb.execute('show arch',to_string = True) 97 | tmp = re.search("currently.*",data) 98 | if tmp : 99 | info = tmp.group() 100 | if "x86-64" in info: 101 | capsize = 8 102 | word = "gx " 103 | arch = "x86-64" 104 | rets = "x86-64" 105 | 106 | elif "mips" in info and "isa64" not in info : 107 | capsize = 4 108 | word = "wx " 109 | arch = "mips" 110 | isa = info.split(":")[1] 111 | rets = "mips" 112 | 113 | elif "arm" in info : 114 | capsize = 4 115 | word = "wx " 116 | arch = "arm" 117 | isa = "armv7" if "v7" in info else "arm" 118 | rets = "arm" 119 | 120 | else : 121 | raise UnsupportedArch(info) 122 | 123 | #elif "aarch64" in info : 124 | # capsize = 8 125 | # word = "gx " 126 | # arch = "aarch64" 127 | # return "aarch64" 128 | 129 | #else : 130 | # word = "wx " 131 | # capsize = 4 132 | # arch = "i386" 133 | # return "i386" 134 | else : 135 | pass 136 | 137 | for x in value_to_set : 138 | details[x] = locals()[x] 139 | 140 | return rets 141 | 142 | 143 | def getendianess(details): 144 | data = gdb.execute('show endian',to_string = True) 145 | if "little endian" in data : 146 | details["endian"] = LITTLE_ENDIAN 147 | elif "big endian" in data : 148 | details["endian"] = BIG_ENDIAN 149 | else : 150 | return False 151 | return True 152 | 153 | 154 | def infoprocmap(): 155 | """ Use gdb command 'info proc map' to get the memory mapping """ 156 | """ Notice: No permission info """ 157 | resp = gdb.execute("info proc map", to_string=True).split("\n") 158 | resp = '\n'.join(resp[i] for i in range(4, len(resp))).strip().split("\n") 159 | infomap = "" 160 | for l in resp: 161 | line = "" 162 | first = True 163 | for sep in l.split(" "): 164 | if len(sep) != 0: 165 | if first: # start address 166 | line += sep + "-" 167 | first = False 168 | else: 169 | line += sep + " " 170 | line = line.strip() + "\n" 171 | infomap += line 172 | return infomap 173 | 174 | 175 | def procmap(): 176 | data = gdb.execute('info proc exe',to_string = True) 177 | pid = re.search('process.*',data) 178 | if pid : 179 | pid = pid.group() 180 | pid = pid.split()[1] 181 | fpath = "/proc/" + pid + "/maps" 182 | if os.path.isfile(fpath): # if file exist, read memory mapping directly from file 183 | maps = open(fpath) 184 | infomap = maps.read() 185 | maps.close() 186 | return infomap 187 | else: # if file doesn't exist, use 'info proc map' to get the memory mapping 188 | return infoprocmap() 189 | else : 190 | return None 191 | 192 | 193 | def libcbase(): 194 | infomap = procmap() 195 | data = re.search(".*libc.*\.so",infomap) 196 | if data : 197 | libcaddr = data.group().split("-")[0] 198 | libcbaddr = int(libcaddr,16) 199 | return libcbaddr 200 | else : 201 | return None 202 | 203 | 204 | def getoff(sym): 205 | libcbaddr = libcbase() 206 | if type(sym) is int : 207 | return sym-libcbaddr 208 | else : 209 | try : 210 | data = gdb.execute("x/x " + sym ,to_string=True) 211 | if "No symbol" in data: 212 | return None 213 | 214 | else : 215 | data = re.search("0x.*[0-9a-f] ",data) 216 | data = data.group() 217 | symaddr = int(data[:-1] ,16) 218 | return symaddr-libcbaddr 219 | 220 | except : 221 | return None 222 | 223 | 224 | 225 | 226 | -------------------------------------------------------------------------------- /core/memory.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Memory area regions of the process 5 | 6 | """ 7 | 8 | import re 9 | import gdb 10 | import mmap 11 | 12 | import lief 13 | 14 | 15 | ## Utils 16 | match_entry = r"^(0x[A-f0-9]+)[ ]+(0x[A-f0-9]+)[ ]+(0x[A-f0-9]+)[ ]+(0x[A-f0-9]+)[ ]*(.*)" 17 | 18 | permissions_table = {"-":0, "r":mmap.PROT_READ, "w":mmap.PROT_WRITE, "x":mmap.PROT_EXEC} 19 | permissions_table_l = ["r","w","x"] 20 | 21 | 22 | ## Classes 23 | 24 | class MemoryRegion: 25 | """ 26 | MemoryRegion class 27 | """ 28 | def __init__(self, 29 | addr_from, 30 | addr_to, 31 | addr_size, 32 | addr_offset, 33 | addr_name, 34 | perm=0, 35 | allocated_with_malloc = False, 36 | is_main_proc = False 37 | ) : 38 | """ 39 | addr_from : memory region start 40 | addr_to : memory region end 41 | addr_size : size memory region 42 | addr_offset : offset inside memory region 43 | addr_name : memory region name 44 | perm : memory region permission 45 | """ 46 | self.addr = addr_from 47 | self.eaddr = addr_to 48 | self.size = addr_size 49 | self.offset = addr_offset 50 | self.name = addr_name 51 | self.perm = perm 52 | self.allocated_with_malloc = allocated_with_malloc 53 | self.is_main_proc = is_main_proc 54 | 55 | def print_permissions(self): 56 | output = "" 57 | for k in permissions_table_l : 58 | if self.perm & permissions_table[k] : 59 | output += k 60 | else : 61 | output += "-" 62 | return output 63 | 64 | def __check_perm(self, perm): 65 | return (self.perm & permissions_table[perm]) != 0 66 | 67 | def is_readable(self): 68 | return self.__check_perm("r") 69 | 70 | def is_writable(self): 71 | return self.__check_perm("w") 72 | 73 | def is_executable(self): 74 | return self.__check_perm("x") 75 | 76 | def is_main_proc(self): 77 | return self.is_main_proc 78 | 79 | def __str__(self) : 80 | return "MemoryRegion 0x{:x} 0x{:x} 0x{:x} 0x{:x} {} {}".format( 81 | self.addr, 82 | self.eaddr, 83 | self.size, 84 | self.offset, 85 | self.print_permissions(), 86 | self.name 87 | ) 88 | 89 | 90 | 91 | ## Runtime methods 92 | 93 | def map_memory(details): 94 | """ 95 | Create memory mapping view 96 | """ 97 | output_list = list() 98 | output_dict = dict() 99 | 100 | # check if qemu user-mode 101 | rets = gdb.execute("vmmap", to_string=True) 102 | if "0x00000000 0xffffffff 0x00000000 rwx" in rets : 103 | details["qemu_usermode"] = True 104 | 105 | rets = gdb.execute("info proc mappings", to_string=True) 106 | if "unable to open" in rets : 107 | details["qemu_usermode"] = True 108 | 109 | if details["qemu_usermode"] : 110 | return map_memory_qemu(details) 111 | 112 | for x in rets.split("\n") : 113 | x = x.strip() 114 | if not x.startswith("0x") : 115 | continue 116 | 117 | rets = re.search(match_entry, x) 118 | if not rets : 119 | details["slog"].append("[!] can't parse entry '{}' .. ignoring".format(x)) 120 | continue 121 | 122 | rets = rets.groups() 123 | addr_from = int(rets[0].strip(), 16) 124 | addr_to = int(rets[1].strip(), 16) 125 | addr_size = int(rets[2].strip(), 16) 126 | addr_offset = int(rets[3].strip(), 16) 127 | addr_name = rets[4].strip() 128 | 129 | mm = MemoryRegion(addr_from, addr_to, addr_size, addr_offset, addr_name) 130 | output_dict.update({mm.addr : mm}) 131 | output_list.append(mm.addr) 132 | 133 | output_list.sort() 134 | update_memory_perm(output_dict, details) 135 | return output_dict, output_list 136 | 137 | 138 | 139 | def map_memory_qemu(details): 140 | details["slog"].append( 141 | "[!] Unable to open /proc//maps files .. trying the other method" 142 | ) 143 | 144 | # NOTE: in future if we want to remove vmmap, we should modify this code, e.g: 145 | # - "are you running qemu-user-mode?" etc. 146 | 147 | cmd = "show solib-search-path".strip() 148 | rets = gdb.execute(cmd, to_string=True) 149 | match_now = r"The search path for loading non-absolute shared library symbol files is(.*)\." 150 | 151 | rets = re.search(match_now, rets).groups()[0].strip() 152 | if rets == "" : 153 | raise Exception( 154 | "[X] run gdb command `set solib-search-path /lib:/usr/lib` ..quit" 155 | ) 156 | 157 | lib_paths = rets.split(":") 158 | lib_files = list_files(lib_paths) 159 | 160 | rets = gdb.execute("info files", to_string=True).split("\n") 161 | 162 | match_hex = r"(0x[A-f0-9]+)" 163 | match_lib = r"^[ \t]+{0} - {0} is (\..*) in (.*)$".format(match_hex) 164 | match_bin = r"^[ \t]+{0} - {0} is (\..*)$".format(match_hex) 165 | 166 | index = rets.index("Local exec file:") 167 | binary_name = rets[index+1].strip().split("`")[1].split("'")[0] 168 | prev_fname = binary_name 169 | 170 | output = {binary_name:{}} 171 | 172 | for x in rets[index+2:] : 173 | rets = re.search(match_lib, x) 174 | is_bin = False 175 | if not rets : 176 | rets = re.search(match_bin, x) 177 | is_bin = True 178 | if not rets : 179 | details["slog"].append("[!] Skipping entry '{}'".format(x)) 180 | continue 181 | 182 | rets = rets.groups() 183 | addr = int(rets[0], 16) 184 | eaddr = int(rets[1], 16) 185 | section = rets[2].strip() 186 | fname = binary_name 187 | 188 | if not is_bin : 189 | fname = rets[3].strip() 190 | 191 | if prev_fname != fname : 192 | prev_fname = fname 193 | output.update({fname:{}}) 194 | 195 | output[fname].update({section : {"addr":addr, "eaddr":eaddr, "size":None}}) 196 | 197 | return map_memory_qemu_2(details, output, binary_name, lib_files) 198 | 199 | 200 | 201 | def map_memory_qemu_2(details, output, binary_name, lib_files) : 202 | 203 | section_order = [ 204 | [".gnu.hash", permissions_table["r"]],\ 205 | [".text", permissions_table["r"] | permissions_table["x"]],\ 206 | [".rodata", permissions_table["r"]],\ 207 | [".data", permissions_table["r"] | permissions_table["w"]],\ 208 | [".bss", permissions_table["r"]] 209 | ] 210 | 211 | output_list = list() 212 | output_dict = dict() 213 | 214 | for x,v in output.items() : 215 | 216 | is_main_proc = True 217 | if x != binary_name : 218 | is_main_proc = False 219 | if x not in lib_files : 220 | raise Exception("[x] Can't find library '{}'".format(x)) 221 | 222 | path_lib_now = x 223 | binary_path_lib_now = lief.parse(path_lib_now) 224 | section_now = {x[0]:None for x in section_order} 225 | 226 | for section in binary_path_lib_now.sections : 227 | if section.name in section_now : 228 | section_now[section.name] = section.file_offset 229 | 230 | baddr = v[".gnu.hash"]["addr"] - section_now[".gnu.hash"] 231 | prev_addr = baddr 232 | prev_perm = section_order[0][1] 233 | 234 | for sname, perm in section_order : 235 | if sname == ".gnu.hash" : 236 | continue 237 | 238 | eaddr = v[sname]["addr"] >> 12 << 12 239 | size = eaddr - prev_addr 240 | mm = MemoryRegion(prev_addr, eaddr, size, 0, x, is_main_proc=is_main_proc) 241 | mm.perm = prev_perm 242 | 243 | output_dict.update({mm.addr : mm}) 244 | output_list.append(mm.addr) 245 | 246 | prev_addr = eaddr 247 | prev_perm = perm 248 | 249 | eaddr = (v[sname]["eaddr"] + 0x1000) >> 12 << 12 250 | size = eaddr - prev_addr 251 | mm = MemoryRegion(prev_addr, eaddr, size, 0, x, is_main_proc=is_main_proc) 252 | mm.perm = prev_perm 253 | 254 | output_dict.update({mm.addr : mm}) 255 | output_list.append(mm.addr) 256 | 257 | output_list.sort() 258 | return output_dict, output_list 259 | 260 | 261 | 262 | def list_files(lib_paths): 263 | import os 264 | output = [] 265 | for x in lib_paths : 266 | output += [ "{}/{}".format(x,y) for y in os.listdir(x) if os.path.isfile("{}/{}".format(x,y)) ] 267 | return output 268 | 269 | 270 | 271 | def update_memory_perm(output_dict, details) : 272 | """ 273 | Update memory regions permission 274 | (GEF extension required) 275 | """ 276 | try : 277 | rets = gdb.execute("vmmap", to_string=True) 278 | except : 279 | details["slog"].append("Cannot find memory region area permissions.. check if GEF interface is loaded by GDB") 280 | return 281 | 282 | for x in rets.split("\n")[2:] : 283 | x = x.strip() 284 | 285 | # remove unicode colors 286 | x = x.replace("\x1b[0m", "") 287 | for y in range(101) : 288 | x = x.replace("\x1b[{}m".format(y), "") 289 | 290 | if not x.startswith("0x") : 291 | continue 292 | 293 | rets = x.split(" ") 294 | if not rets : 295 | details["slog"].append("[!] [vmmap] can't parse entry '{}' .. ignoring".format(x)) 296 | continue 297 | 298 | addr_from = int(re.search("^(0x[A-f0-9]+).*", rets[0].strip() ).groups()[0], 16) 299 | addr_perm = re.search("^([rwx-]{3})(.*)", rets[3].strip()).groups()[0] 300 | addr_name = rets[4].strip() 301 | 302 | # adjust permissions 303 | perm_tmp = 0 304 | for x in list(addr_perm) : 305 | perm_tmp |= permissions_table[x] 306 | addr_perm = perm_tmp 307 | 308 | output_dict[addr_from].perm = addr_perm 309 | output_dict[addr_from].name = addr_name 310 | 311 | 312 | -------------------------------------------------------------------------------- /core/parser/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | bleed Parser class 3 | """ 4 | -------------------------------------------------------------------------------- /core/parser/_constants.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | types_list = ["t_FUNC", "t_BYTE", "t_SHORT", "t_INT", "t_LONG", "t_LONG_2", "t_POINTER", "t_STRING", "t_STRUCT"] 4 | types_dict = { i:x for i,x in enumerate(types_list) } 5 | 6 | for i,x in enumerate(types_list) : 7 | locals()[x]=i 8 | # 9 | # 10 | 11 | default_size = { 12 | "hw" : [8] ,\ 13 | "w" : [16] ,\ 14 | "dw" : [32] ,\ 15 | "qw" : [64] 16 | } 17 | 18 | 19 | default_types_size = { 20 | t_BYTE : default_size["hw"] ,\ 21 | t_SHORT : default_size["w"] ,\ 22 | t_INT : default_size["dw"] ,\ 23 | t_LONG : default_size["dw"] ,\ 24 | t_LONG_2 : default_size["qw"] ,\ 25 | t_POINTER : default_size["qw"] ,\ 26 | } 27 | 28 | 29 | 30 | ######################################## 31 | ### parse sections .bleed script 32 | ## Match declaration 33 | re_match_declare = r"^[ ]*([A-z][A-z0-9]*)[ ]+([A-z_][A-z0-9_]*)[ ]*[=]*[ ]*(.*);$" 34 | re_match_declare_global = r"^[ ]*__static__[ ]+([A-z_][A-z0-9_]*)[ ]*[=]*[ ]*(.*);$" 35 | 36 | 37 | ### particular types 38 | re_match_long_long = r"^[ ]*(long[ ]+long)[ ]+([A-z_][A-z0-9_]*)[ ]*[=]*[ ]*(.*);$" 39 | re_match_long_long_unsigned = r"^[ ]*(unsigned[ ]+long[ ]+long)[ ]+([A-z_][A-z0-9_]*)[ ]*[=]*[ ]*(.*);$" 40 | 41 | re_match_unsigned_declare = r"^[ ]*(unsigned[ ]+[A-z][A-z0-9]*)[ ]+([A-z_][A-z0-9_]*)[ ]*[=]*[ ]*(.*);$" 42 | re_match_string_declare = r"^[ ]*(char \*)[ ]+([A-z_][A-z0-9_]*)[ ]*[=]*[ ]*(.*);$" 43 | re_match_pointer_declare = r"^[ ]*(void \*)[ ]+([A-z_][A-z0-9_]*)[ ]*[=]*[ ]*(.*);$" 44 | re_match_create_struct = r"^[ ]*struct ([A-z_][A-z0-9_]+)[ ]*{$" 45 | 46 | 47 | ## Match external-functions 48 | re_match_ext_function = r"^[ ]*([A-z][A-z0-9]+)\(([012])\)[ ]*;$" 49 | # match (namespace).(name) 50 | re_match_ext_internal_function = r"^[ ]*__static__[ ]+([A-z][A-z0-9]+)\.([A-z][A-z0-9]+)\(([012])\)[ ]*;$" 51 | 52 | 53 | 54 | ## Match code section 55 | re_match_function_ret_val = r"^[ ]*([A-z][A-z0-9]+)[ ]*=[ ]*([A-z][A-z0-9]+)\((.*)\)[ ]*;$" 56 | re_match_function = r"^[ ]*([A-z][A-z0-9]+)\((.*)\)[ ]*;$" 57 | re_match_function_return = r"^[ ]*(return|RETURN)[ ]+(.*);$" 58 | 59 | 60 | ## internal_func settings 61 | re_match_internal_func_decl = "^[ ]*(.*)[ ](.*)\(.*" 62 | 63 | 64 | 65 | ## Match other 66 | re_match_comment = r"^[ ]*(//)(.*)$" 67 | re_match_empty = r"^[ \t]*$" 68 | 69 | 70 | 71 | 72 | ######################################## 73 | ### .bleed file parsing constants 74 | sep_section = "--" 75 | sep_sub_section = "@@" 76 | sep_special_char = "__" 77 | 78 | sect_skip_id = "{0}skip{0}".format(sep_section) 79 | sect_declare_id = "{0}declare{0}".format(sep_section) 80 | sect_code_id = "{0}code{0}".format(sep_section) 81 | 82 | 83 | tags = { 84 | sect_skip_id : [] ,\ 85 | sect_declare_id : ["{0}{1}{0}".format(sep_sub_section, x) for x in ["types", "vars", "external-functions"]] ,\ 86 | sect_code_id : ["{0}{1}{0}".format(sep_sub_section, x) for x in ["function", "pre_func", "post_func"]] 87 | } 88 | 89 | # this array also represents the order in which pre_func/post_func should declare them as the function arguments 90 | special_symbols = [ 91 | # arguments till 4th argument for now 92 | "{0}arg1{0}".format(sep_special_char) ,\ 93 | "{0}arg2{0}".format(sep_special_char) ,\ 94 | "{0}arg3{0}".format(sep_special_char) ,\ 95 | "{0}arg4{0}".format(sep_special_char) ,\ 96 | "{0}arg5{0}".format(sep_special_char) ,\ 97 | "{0}arg6{0}".format(sep_special_char) ,\ 98 | # name, name length, address of the hooked function 99 | "{0}fname_length{0}".format(sep_special_char) ,\ 100 | "{0}fname{0}".format(sep_special_char) ,\ 101 | "{0}fname_addr{0}".format(sep_special_char) ,\ 102 | "{0}ret_addr{0}".format(sep_special_char) ,\ 103 | "{0}num_arg{0}".format(sep_special_char) ,\ 104 | "{0}sp_arg{0}".format(sep_special_char) ,\ 105 | # return value of the hooked function (only available in post_func function) 106 | "{0}rets{0}".format(sep_special_char) 107 | ] 108 | 109 | 110 | special_symbols_default_type = "void *" 111 | special_symbols_types = { x:special_symbols_default_type for x in special_symbols } 112 | 113 | special_symbols_types["{0}fname_length{0}".format(sep_special_char)] = "unsigned long" 114 | special_symbols_types["{0}fname{0}".format(sep_special_char)] = "char *" 115 | special_symbols_types["{0}num_arg{0}".format(sep_special_char)] = "unsigned long" 116 | 117 | 118 | 119 | ## pre_func settings 120 | 121 | ### pre_func decl 122 | pre_func_decl = "void * pre_func(" 123 | pre_func_return = "return 0;\n}" 124 | pre_func_decl += ", ".join("{} {}".format(special_symbols_types[x],x) for x in special_symbols[:4]) 125 | 126 | pre_func_space = " " * 4 127 | 128 | pre_func_decl_x86_64 = pre_func_decl 129 | pre_func_decl_x86_64 += ", " + ", ".join("{} {}".format(special_symbols_types[x],x) for x in special_symbols[4:-1]) + ")" 130 | 131 | pre_func_decl += ", " + ", ".join("{} {}".format(special_symbols_types[x],x) for x in special_symbols[6:-1]) + ")" 132 | 133 | pre_func_decl += "{" 134 | pre_func_decl_x86_64 += "{" 135 | 136 | 137 | ## function type enum 138 | PRE_FUNC = 1 139 | POST_FUNC = 2 140 | INTERNAL_FUNC = 4 141 | 142 | 143 | 144 | -------------------------------------------------------------------------------- /core/parser/_exception.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | class ParserException(Exception) : 4 | def __init__(self, msg, line_num, line) : 5 | super(ParserException, self).__init__(msg) 6 | self.pre_msg = "[X] [parser]" 7 | self.msg = msg 8 | self.line_num = line_num 9 | self.line = line 10 | 11 | def __str__(self) : 12 | return "{} - {} at line '{}:{}'".format(self.pre_msg, self.msg, self.line_num, self.line) 13 | 14 | def __repr__(self) : 15 | return "{}('{}','{}','{}')".format(self.__class__.__name__, self.msg, self.line_num, self.line) 16 | 17 | 18 | class WrongDeclarationParser(ParserException) : 19 | def __init__(self, msg, line_num, line) : 20 | super(WrongDeclarationParser, self).__init__(msg, line_num, line) 21 | self.pre_msg += " [declaration]" 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /core/parser/graph.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | class Node: 4 | 5 | def __init__(self, Id, val, decl, body): 6 | self.Id = Id 7 | self.val = val 8 | self.decl = decl 9 | self.body = body 10 | self.edge_in = {} 11 | self.edge_out = {} 12 | self.edge_out_fine = {} 13 | self.func_object = None 14 | 15 | 16 | def get_f_object(self): 17 | return self.func_object 18 | 19 | def set_f_object(self, func_object): 20 | self.func_object = func_object 21 | 22 | def add_in(self, node): 23 | self.edge_in.update({node.Id:node}) 24 | 25 | def del_in(self, node): 26 | del self.edge_in[node.Id] 27 | 28 | def add_out(self, node): 29 | self.edge_out.update({node.Id:node}) 30 | 31 | def del_out(self, node): 32 | del self.edge_out[node.Id] 33 | 34 | def mov_out(self, node): 35 | self.edge_out_fine.update( 36 | {node.Id : node} 37 | ) 38 | self.del_out(node) 39 | 40 | def is_fine(self) : 41 | return self.edge_out == {} 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /core/parser/parse_declaration.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from core.parser._constants import re_match_declare 3 | 4 | 5 | 6 | def parse_types(data) : 7 | #TODO 8 | pass 9 | 10 | def parse_vars(data) : 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /core/parser/parse_file.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from core.parser.objects import Section, Tag 3 | 4 | from core.parser._constants import tags, sep_section, sep_sub_section ,\ 5 | sect_skip_id, sect_declare_id, sect_code_id 6 | 7 | 8 | 9 | def parse_blade(lines) : 10 | global tags 11 | sections = { k: Section(k.replace(sep_section,""), 0) for k in tags } 12 | 13 | prev_section = None 14 | section_now = None 15 | 16 | for i, line in enumerate(lines) : 17 | if line.startswith(sep_section) : 18 | for k in tags : 19 | if line.startswith(k) : 20 | prev_section = section_now 21 | section_now = sections[k] 22 | section_now.set_start(i) 23 | tag_list_now = tags[k] 24 | if prev_section : 25 | prev_section.set_end(i) 26 | break 27 | 28 | elif line.startswith(sep_sub_section): 29 | for k in tag_list_now : 30 | if line.startswith(k) : 31 | section_now.set_end_prev_tag(i) 32 | section_now.add_tag(Tag(k.replace(sep_sub_section,""), i)) 33 | break 34 | 35 | if section_now : 36 | section_now.set_end(i) 37 | 38 | for sect in sections.values() : 39 | sect.set_data(lines) 40 | sect.update_line_number() 41 | 42 | return sections[sect_skip_id], sections[sect_declare_id], sections[sect_code_id] 43 | 44 | 45 | 46 | if __name__ == "__main__" : 47 | import sys 48 | 49 | if len(sys.argv) < 2 : 50 | print("Usage: {} ".format(sys.argv[0])) 51 | sys.exit(1) 52 | 53 | file_path = sys.argv[-1] 54 | fp = open(file_path) 55 | lines = fp.read().split("\n") 56 | fp.close() 57 | print(parse_blade(lines)) 58 | 59 | 60 | -------------------------------------------------------------------------------- /core/parser/wrap_objects.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import re 3 | 4 | from core.parser._constants import PRE_FUNC ,\ 5 | POST_FUNC ,\ 6 | INTERNAL_FUNC 7 | 8 | 9 | class WrapFunction: 10 | 11 | def __init__( 12 | self, Id, func_type, 13 | name, namespace, description, 14 | declaration, 15 | addr=None, used_by_function=[], hooking_function=[] 16 | ): 17 | 18 | self.Id = Id 19 | self.func_type = func_type 20 | self.name = name 21 | self.namespace = namespace 22 | self.description = description 23 | self.declaration = declaration 24 | self.addr = addr 25 | self.used_by_function = used_by_function 26 | self.hooking_function = hooking_function 27 | self.source_code = None 28 | self.full_name = "{}.{}".format(self.namespace, self.name) 29 | self.code = [] 30 | self.header = [] 31 | 32 | 33 | def update_declare_lists( 34 | self, func_declare_list,\ 35 | vars_list, function_calls_list,\ 36 | func_return 37 | ): 38 | 39 | self.func_declare_list = func_declare_list 40 | self.vars_list = vars_list 41 | self.function_calls_list = function_calls_list 42 | self.func_return = func_return 43 | 44 | 45 | def update_header(self, define_section_data): 46 | self.header = define_section_data 47 | 48 | 49 | def update_addr_func(self, addr, code): 50 | self.addr = addr 51 | self.code = code 52 | 53 | 54 | def is_ready(self): 55 | return self.addr != None and self.code != [] 56 | 57 | 58 | def _make_source_code(self): 59 | output = [] 60 | if self.header : 61 | output.append("\n".join(self.header)) 62 | output.append(self.declaration) 63 | output.append("\n".join( [ str(x) for x in self.func_declare_list ])) 64 | output.append("\n".join( [ str(x) for x in self.vars_list ])) 65 | output.append("// ## code starts from here:") 66 | output.append("\n".join( [ str(x) for x in self.function_calls_list ])) 67 | output.append(self.func_return) 68 | self.source_code = "\n".join(output) 69 | 70 | 71 | def get_source_code(self, repeat=False): 72 | if repeat or self.source_code == None : 73 | self._make_source_code() 74 | return self.source_code 75 | 76 | 77 | class PreFunction(WrapFunction): 78 | def __init__( 79 | self, Id, namespace, description, 80 | declaration, 81 | addr=None, used_by_function=[], hooking_function=[] 82 | ): 83 | 84 | super(PreFunction, self).__init__( 85 | Id, PRE_FUNC, 86 | "pre_func", namespace, description, 87 | declaration, 88 | addr, used_by_function, hooking_function 89 | ) 90 | 91 | class PostFunction(WrapFunction): 92 | def __init__( 93 | self, Id, namespace, description, 94 | declaration, 95 | addr=None, used_by_function=[], hooking_function=[] 96 | ): 97 | 98 | super(PostFunction, self).__init__( 99 | Id, POST_FUNC, 100 | "post_func", namespace, description, 101 | declaration, 102 | addr, used_by_function, hooking_function 103 | ) 104 | 105 | class InternalFunction(WrapFunction): 106 | def __init__( 107 | self, Id, func_name, namespace, description, 108 | declaration, 109 | addr=None, used_by_function=[], using_function=[] 110 | ): 111 | """ 112 | used_by_function : var used by pre_func and post_func functions to declare 113 | if they use this internal function 114 | using_function : var used to declare only internal functions used by this function 115 | """ 116 | super(InternalFunction, self).__init__( 117 | Id, INTERNAL_FUNC, 118 | func_name, namespace, description, 119 | declaration, 120 | addr, used_by_function, using_function 121 | ) 122 | self.decl_as_ptr = None 123 | self.declaration_as_pointer() 124 | 125 | 126 | def declaration_as_pointer(self): 127 | if not self.decl_as_ptr : 128 | 129 | rets = re.split( 130 | "(?/dump_dir` folder 38 | 39 | `mem-map` : allocate memory using mmap, then malloc if mmap call failed 40 | 41 | `mem-unmap` : unallocate memory 42 | 43 | --- 44 | 45 | ### Save session (#TODO) 46 | 47 | `store-state` : use serialization for saving current gdbleed session to `/state.bin` 48 | 49 | `load-state` : load previous gdbleed session saved 50 | 51 | 52 | -------------------------------------------------------------------------------- /docs/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tin-z/GDBleed/180cfea4a15cb4f2eb84c0255c11ac2a932601ff/docs/img/favicon.ico -------------------------------------------------------------------------------- /docs/img/gdbcov_demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tin-z/GDBleed/180cfea4a15cb4f2eb84c0255c11ac2a932601ff/docs/img/gdbcov_demo.gif -------------------------------------------------------------------------------- /docs/img/hook_example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tin-z/GDBleed/180cfea4a15cb4f2eb84c0255c11ac2a932601ff/docs/img/hook_example.gif -------------------------------------------------------------------------------- /docs/img/start/t1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tin-z/GDBleed/180cfea4a15cb4f2eb84c0255c11ac2a932601ff/docs/img/start/t1.jpg -------------------------------------------------------------------------------- /docs/img/start/t2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tin-z/GDBleed/180cfea4a15cb4f2eb84c0255c11ac2a932601ff/docs/img/start/t2.jpg -------------------------------------------------------------------------------- /docs/img/t1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tin-z/GDBleed/180cfea4a15cb4f2eb84c0255c11ac2a932601ff/docs/img/t1.jpg -------------------------------------------------------------------------------- /docs/img/t2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tin-z/GDBleed/180cfea4a15cb4f2eb84c0255c11ac2a932601ff/docs/img/t2.jpg -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # GDBleed 2 | 3 | [![Docs](https://img.shields.io/badge/Documentation-blue.svg)](https://tin-z.github.io/gdbleed/) [![MIT](https://img.shields.io/packagist/l/doctrine/orm.svg?maxAge=2592000?style=plastic)](https://github.com/tin-z/GDBleed/blob/main/LICENSE) 4 | 5 | GDBleed - Dynamic-Static binary instrumentation framework on top of GDB 6 | 7 | `GDBleed` is a gdb wrapper exposing a set of commands for x86-64, ARM and MIPS 8 | (x86 and ARM thumb-mode in progress) architectures to perform binary 9 | instrumentation. The objective was to exploit the hackish features of GDB 10 | python API, while ignoring the software performance attribute (for now). And in 11 | the end to have a user-friendly framework. GDBleed focus is applicability, then 12 | we have efficiency. The more CPU archs it does suport the better it is. 13 | 14 | 15 | 16 | ### Why? 17 | 18 | - "easy"-fast minimal static-dynamic code instrumentation supporting all main CPU archs 19 | 20 | - Framework based on tools with a strong community support: GDB, gcc, r2, keystone, LIEF, etc. 21 | 22 | - No control flow information is needed 23 | 24 | - ideal for IoT devices, why? 25 | 26 | * no binary instrumentation for MIPS 27 | 28 | * cross-compilation is boring (and if it works then it will break somewhere during the execution) 29 | 30 | * A lot of the new IoT devices still using old linux kernel versions not supporting EBPF 31 | 32 | 33 | 34 | ### Usage 35 | 36 | - run gdb from the current folder 37 | 38 | - Start the process using `start` command or attach gdb to the debugged process 39 | 40 | - Run the command 41 | 42 | ``` 43 | source gdbleed.py 44 | ``` 45 | 46 | - For more info take a look at the `tests` folder 47 | 48 | 49 | ### Req 50 | 51 | - Tested on ubuntu 20.04 52 | 53 | - Dep: keystone, LIEF 54 | 55 | 56 | ### Installation 57 | 58 | - Install 59 | ``` 60 | # GEF gdb extension, ref https://github.com/hugsy/gef 61 | sudo apt-get -y install unzip cmake binutils 62 | ``` 63 | 64 | - Declare env vars 65 | ``` 66 | # python's version which your gdb intalled supports 67 | export PYTHON_VER="python3" 68 | sudo apt-get install ${PYTHON_VER}-distutils ${PYTHON_VER}-setuptools 69 | 70 | # don't change these values 71 | export KEYSTONE_VER="0.9.2" 72 | export LIEF_VER="0.12.1" 73 | ``` 74 | 75 | - From current folder run: 76 | ``` 77 | ./setup.sh 78 | 79 | ``` 80 | 81 | 82 | **Required for hooking/instrumentation also aka Inline GOT hooking** 83 | 84 | - Install 85 | ```sh 86 | 87 | export TARGET=arm-linux-gnueabi 88 | sudo apt-get install -y binutils-${TARGET} gcc-${TARGET} 89 | 90 | export TARGET=mips-linux-gnu 91 | sudo apt-get install -y binutils-${TARGET} gcc-${TARGET} 92 | ``` 93 | 94 | - add vim highlighting 95 | 96 | ```vim 97 | augroup filetypedetect 98 | au! BufRead,BufNewFile *.c.bleed setfiletype c 99 | augroup END 100 | ``` 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /docs/settings/readme.md: -------------------------------------------------------------------------------- 1 | # Settings # 2 | 3 | 4 | ### './config.py' ### 5 | 6 | 7 | - `tmp_folder` : temporary folder used by gdbleed to save the current session 8 | 9 | - `log_file` : file where gdbleed currently save session (for now binary and libc location) 10 | 11 | - `ARCH_supported` : ARCH_supported 12 | 13 | - `compiler_path` : Cross-Compiler paths 14 | 15 | - `compiler_flags` : Cross-Compiler flags 16 | 17 | - `slog_path` : save output to 'slog_path' file (default: stdout) 18 | 19 | --- 20 | 21 | ### './gdbleed.py' ### 22 | 23 | 24 | - `details` : Dictionary passed through gdbleed classes containing data about the CPU architectures currently used 25 | 26 | * "capsize" : Size of pointers 27 | 28 | * "word" : format string for printable pointers in gdb using 'x' command notation 29 | 30 | * "arch" : arch currently used 31 | 32 | * "isa" : isa currently used 33 | 34 | * "running" : process is running 35 | 36 | * "slog" : slog class object 37 | 38 | * "endian" : endianess 39 | 40 | * "is_pie" : True if binary is PIE 41 | 42 | * "binary_path" : binary path 43 | 44 | * "libc_path" : libc path 45 | 46 | * "pid" : PID 47 | 48 | * "session_loaded" : True if session was loaded (TODO) 49 | 50 | * "qemu_usermode" : True if qemu user-mode is used 51 | 52 | 53 | 54 | - `details_mem` : Memory status info 55 | 56 | * "mm_regions" : Dict containing memory mapped by the process saved as `MemoryRegion` objects 57 | 58 | * "mm_addresses" : List containing LSB addresses of the memory mapped by the process 59 | 60 | * "mm_regions_ctrl" : Dict containing memory mapped by gdbleed commands saved as `MemoryRegion` objects 61 | 62 | * "mm_addresses_ctrl" : List containing LSB addresses of the memory mapped by gdbleed 63 | 64 | 65 | 66 | - `details_data` : Binary related info (GOT, sections, etc.) 67 | 68 | * "binary_name" : binary file name 69 | 70 | * "binary_name_local" : binary file name (the one saved in local) 71 | 72 | * "base_address" : base address given by the loader to the binary 73 | 74 | * "size_base_address" : size base address 75 | 76 | * "libc_base_address" : libc base address 77 | 78 | * "libc_size_base_address" : size libc base address 79 | 80 | * "got_entries" : GOT entries saved as `WrapPLTGOT` objects 81 | 82 | * "section_entries" : ELF sections saved as `WrapSection` objects 83 | 84 | * "parser" : `WrapParser` singleton object 85 | 86 | * "compiler" : `Compiler` singleton object 87 | 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /docs/start.md: -------------------------------------------------------------------------------- 1 | # start guide # 2 | 3 | 4 | - [video](./) 5 | 6 | 7 | 1. Delete old gdbleed session which is saved into folder `/tmp/gdbleed` 8 | 9 | ``` 10 | rm -rf /tmp/gdbleed 11 | ``` 12 | 13 | 14 | 2. Attach gdb to gdbserver session 15 | 16 | ``` 17 | # e.g. we have /bin/bash with pid 258475, then we launch gdbserver as follows 18 | gdbserver --attach 127.0.0.1:12345 258475 19 | 20 | # on gdb session 21 | cd 22 | gdb /bin/bash -ex "source ~/.gdbinit-gef.py" -ex "target remote 127.0.0.1:12345" -ex "source gdbleed.py" 23 | 24 | ``` 25 | 26 | 27 | 3. Because gdbleed does not know if the debuggee program is running in local or in remote it will ask you to choose on which path the binary is present. In any case if we are using gdbserver the 2nd options should be choosed. This information is later saved in gdbleed session (folder `/tmp/gdbleed`) 28 | 29 | 30 | ![t1.jpg](./img/start/t1.jpg) 31 | 32 | 33 | 34 | 4. Inspect got entries 35 | ``` 36 | gef➤ got-entries 37 | ... 38 | [0x563275d6ced8] ---> socket 39 | [0x563275d6cee0] ---> mktemp 40 | 41 | gef➤ got-entries fork 42 | [0x563275d6ce90] ---> fork 43 | ``` 44 | 45 | 46 | 5. Create static variables 47 | 48 | ``` 49 | gef➤ hook-got-inline --help 50 | Help 51 | Usage: hook-got-inline [options] 52 | 53 | Options: 54 | --help : This message 55 | --create : insert gdbleed script from STDIN or by file 56 | --data : Define or list global/static vars 57 | --list : print declared functions nformation 58 | --source-code : print function's source code 59 | --remove : delete function 60 | --compile : Compile function 61 | --inject : inject mode 62 | --inject-ret : inject-ret mode 63 | --inject-post : inject-post mode 64 | --inject-post-ret : inject-post-ret mode 65 | --inject-full : inject-full mode 66 | 67 | Notes: 68 | --inject : call pre_func, jump to function-hooked 69 | --inject-ret : jump directly to pre_func and return its return value 70 | --inject-post : call function-hooked, post_func, then return function-hooked's return values 71 | --inject-post-ret : call function-hooked, post_func and return ist return value 72 | --inject-full : call pre_func, function-hooked, post_func, then return function-hooked's return value 73 | 74 | ``` 75 | 76 | ``` 77 | gef➤ hook-got-inline --data --create 78 | Insert gdbleed script (Insert 'EOF' line to terminate reading input) 79 | int x = 10; 80 | char * pino = "Hello pino\n"; 81 | blob data1 = 128; 82 | EOF 83 | 84 | 85 | gef➤ hook-got-inline --data --create ./example/bleed_example/declare_static_data.c.bleed 86 | Var 'x' already defined 87 | Usage: hook-got-inline --data [--list |--create] 88 | --help : this message 89 | --list : list global vars 90 | --create : create new global vars 91 | : list only variable 92 | 93 | 94 | gef➤ hook-got-inline --data --list 95 | Id declaration 96 | 0 int * x = 0x25001f; // (initially declared value was: 10) 97 | 1 char * pino = 0x250023; // size=0xd 98 | 2 void * data1 = 0x25002f; // size=0x80 99 | 100 | ``` 101 | 102 | 6. Create internal functions 103 | 104 | ``` 105 | gef➤ hook-got-inline --create ./example/bleed_example/internal_func.c.bleed 106 | /tmp/gdbleed/inspect_status.print_pino.c: In function ‘print_pino’: 107 | 108 | [...] 109 | 110 | Code compiled or maybe not, you should check that and change stuff (folder '/tmp/gdbleed/inspect_status.print_pino.c.o'). 111 | Do you want to retry compilation? (y/Y/-) 112 | 113 | 114 | /tmp/gdbleed/inspect_status.call_print_pino.c: In function ‘call_print_pino’: 115 | 116 | [...] 117 | 118 | Code compiled or maybe not, you should check that and change stuff (folder '/tmp/gdbleed/inspect_status.call_print_pino.c.o'). 119 | Do you want to retry compilation? (y/Y/-) 120 | 121 | gef➤ hook-got-inline --list 122 | Id namespace full_name addr 123 | 0x0 inspect_status inspect_status.call_print_pino 0x20018e 124 | 0x1 inspect_status inspect_status.print_pino 0x2000ed 125 | 126 | ``` 127 | 128 | - print inspect_status.call_print_pino's details 129 | 130 | ``` 131 | gef➤ hook-got-inline --list inspect_status.call_print_pino 132 | Id namespace full_name addr 133 | 0x0 inspect_status inspect_status.call_print_pino 0x20018e 134 | \---> description: 135 | Inspect arguments given to the hooking functions 'pre_func' and 'post_func' 136 | 137 | \---> declaration: 138 | int call_print_pino(int j){ 139 | 140 | \---> used_by_function: 141 | [] 142 | 143 | \---> hooking_function: 144 | {'inspect_status.print_pino'} 145 | ``` 146 | 147 | ``` 148 | gef➤ hook-got-inline --source-code inspect_status.call_print_pino 149 | 'inspect_status.call_print_pino' source code: 150 | 151 | // here we insert includes and struct typedf etc. 152 | // - we can have only one "define" sub-section 153 | #include 154 | #include 155 | 156 | 157 | int call_print_pino(int j){ 158 | void * (*getpid)() = 0x7ffff7e640f0; 159 | void * (*printf)(void *, ...) = 0x7ffff7de1cc0; 160 | int * (*print_pino) (int i) = 0x2000ed; 161 | char * p = 0x2500ab; // size=0x17 162 | char * p2 = 0x2500c1; // size=0xa 163 | char * pino = 0x25001f; // size=0xd 164 | // ## code starts from here: 165 | 166 | return print_pino(j); 167 | } 168 | 169 | ``` 170 | 171 | 172 | 7. Create pre_func which does call static variables and internal functions declared before 173 | 174 | ``` 175 | gef➤ hook-got-inline --create ./example/bleed_example/inspect_status[x86_64].c.bleed 176 | gef➤ hook-got-inline --list 177 | Id namespace full_name addr 178 | 0x0 inspect_status inspect_status.call_print_pino 0x20018e 179 | 0x1 inspect_status inspect_status.print_pino 0x2000ed 180 | 0x2 inspect_status inspect_status.pre_func None 181 | 182 | 183 | gef➤ hook-got-inline --source-code inspect_status.pre_func 184 | 'inspect_status.pre_func' source code: 185 | void * pre_func(void * __arg1__, void * __arg2__, void * __arg3__, void * __arg4__, void * __arg5__, void * __arg6__, unsigned long __fname_length__, char * __fname__, void * __fname_addr__, void * __ret_addr__, unsigned long __num_arg__, void * __sp_arg__){ 186 | void * (*printf)(void *, ...) = 0x7ffff7de1cc0; 187 | int * (*call_print_pino) (int j) = 0x20018e; 188 | char * p = 0x2500ca; // size=0xc1 189 | char * pino = 0x25001f; // size=0xd 190 | int i = 0x1337; 191 | // ## code starts from here: 192 | printf(p, __fname__, __arg1__, __arg2__, __arg3__, __arg4__, __arg5__, __arg6__, __fname_length__, __fname__, __fname_addr__, __ret_addr__, __num_arg__, __sp_arg__); 193 | printf(pino); 194 | call_print_pino(i); 195 | return 1111; 196 | return 0; 197 | } 198 | 199 | ``` 200 | 201 | 202 | 8. Compile it and then hook 'fork' calls 203 | 204 | ``` 205 | gef➤ hook-got-inline --compile inspect_status.pre_func 206 | /tmp/gdbleed/inspect_status.pre_func.c: In function ‘pre_func’: 207 | 208 | [...] 209 | 210 | Code compiled or maybe not, you should check that and change stuff (folder '/tmp/gdbleed/inspect_status.pre_func.c.o'). 211 | Do you want to retry compilation? (y/Y/-) 212 | 213 | gef➤ hook-got-inline --inject inspect_status.pre_func fork 214 | [TraceHook] Can't find symbol on '.dynstr', is imported by ordinale number.. searching it on LIBC memory space 215 | [TraceHook] Can't find symbol 'fork' on libc, inserting it by hand at addr '0x25018a' 216 | 217 | gef➤ continue 218 | ``` 219 | 220 | 221 | Result: 222 | 223 | ![t2.jpg](./img/start/t2.jpg) 224 | 225 | 226 | 227 | 228 | 9. Trace each external call passing from plt.got section 229 | 230 | ``` 231 | gef➤ hook-got-inline --inject --trace-all inspect_status.pre_func 232 | 233 | ``` 234 | 235 | 236 | -------------------------------------------------------------------------------- /docs/strategy/pre_func.md: -------------------------------------------------------------------------------- 1 | # Pre_func # 2 | 3 | 4 | The trampoline point `pre_func` is a set of assembly instructions which does expect the ".stack" shadow memory to contain 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/strategy/strategy.md: -------------------------------------------------------------------------------- 1 | # Strategy # 2 | 3 | The following doc illustrates the logic behind `hook-got-inline` set of commands. Which for now does only hooking and basic instrumentation, but in future it will be true binary instrumentation stuff. 4 | 5 | 6 | ### Terms 7 | 8 | - function-hooked : function to be hooked 9 | - function-hooking : new generated code hooking/instrumenting the function-hooked 10 | - shadow memory : new memory region added from gdbleed 11 | 12 | 13 | ### General ideas 14 | - We map three new region of memory called shadow memory 15 | * text : where new generated assembly code and trampoline points are saved 16 | * data : data 17 | * stack : here we save transitory data (no pthread support) 18 | 19 | - To hook/instrument functions we have only 2 type of function: 20 | * pre_func : observe a function-hooked before executing it 21 | * post_func : observe a function-hooked after executing it 22 | 23 | - pre_func is declared as: 24 | ``` 25 | # for intel x64 26 | void * pre_func(void * __arg1__, void * __arg2__, void * __arg3__, void * __arg4__, void * __arg5__, void * __arg6__, unsigned long __fname_length__, char * __fname__, void * __fname_addr__, void * __ret_addr__, unsigned long __num_arg__, void * __sp_arg__); 27 | 28 | # for the other archs 29 | void * pre_func(void * __arg1__, void * __arg2__, void * __arg3__, void * __arg4__, unsigned long __fname_length__, char * __fname__, void * __fname_addr__, void * __ret_addr__, unsigned long __num_arg__, void * __sp_arg__); 30 | ``` 31 | 32 | - post_func is declared as: 33 | 34 | ``` 35 | # for intel x64 36 | void * pre_func(void * __arg1__, void * __arg2__, void * __arg3__, void * __arg4__, void * __arg5__, void * __arg6__, void * __rets__, unsigned long __fname_length__, char * __fname__, void * __fname_addr__, void * __ret_addr__, unsigned long __num_arg__, void * __sp_arg__); 37 | 38 | # for the other archs 39 | void * pre_func(void * __arg1__, void * __arg2__, void * __arg3__, void * __arg4__, void * __rets__, unsigned long __fname_length__, char * __fname__, void * __fname_addr__, void * __ret_addr__, unsigned long __num_arg__, void * __sp_arg__); 40 | ``` 41 | 42 | 43 | - Based on these two functions, gdbleed builds 5 fixed types of trampoline points: 44 | * ONLY_PRE_FUNC : Call pre_func, then jump to function-hooked 45 | * RET_PRE_FUNC : Don't call function-hooked, call pre_func and return its return value 46 | * ONLY_POST_FUNC : Call function-hooked, post_func, then return function-hooked's return value 47 | * RET_POST_FUNC : Call function-hooked, then return post_func return value 48 | * ALL_FUNC : Call pre_func, function-hooked, post_func and then return function-hooked's return value 49 | 50 | 51 | - Before calling a trampoline point, the user needs to create its pre_func function, then gdbleed will create an injection point. The injection point is assembly code which will save function-hooked's arguments and stuff into stack shadow-memory, then will call the right trampoline point. Injection points are univoke for each function-hooked. Instead, trampoline points are saved in fixed memory areas. 52 | 53 | * The trampoline point will prepare the stack before calling the custom pre_func/function-hooked/post_func function. 54 | 55 | 56 | 57 | #### The trampoline points approaches 58 | 59 | - `ONLY_PRE_FUNC` trampoline control flow: 60 | ``` 61 | : caller 62 | 0:''@GOT 63 | \---> 1: Injection-point 64 | | 1.1: Prepare stack shadow memory 65 | \ 66 | \---> 2: trampoline_point_ with i in [1..5] 67 | | 2.1: save registers 68 | | 2.2: prepare new stack frame and arguments 69 | \ 70 | \---> 71 | 3: CALL `pre_func` code 72 | / 73 | <---/ 74 | 2: trampoline_point_ (2) 75 | | 2.3: restore registers and old stack frame 76 | \ 77 | \---> 78 | 4: JMP to `` 79 | / 80 | : caller <---/ 81 | ``` 82 | 83 | 84 | - `RET_PRE_FUNC` control flow: 85 | ``` 86 | : caller 87 | 0:''@GOT 88 | \---> 1: Injection-point 89 | | 1.1: Prepare stack shadow memory 90 | \ 91 | \---> 2: trampoline_point_ with i in [1..5] 92 | | 2.1: save registers 93 | | 2.2: prepare new stack frame and arguments 94 | \ 95 | \---> 96 | 3: CALL `pre_func` code 97 | / 98 | <---/ 99 | 2: trampoline_point_ (2) 100 | | 2.3: restore registers and old stack frame 101 | | 2.4: set `pre_func`'s return value 102 | \ 103 | \---> 104 | 4: jump to return address 105 | / 106 | : caller <---/ 107 | ``` 108 | 109 | 110 | - `ONLY_POST_FUNC` control flow : 111 | ``` 112 | : caller 113 | 0:''@GOT 114 | \---> 1: Injection-point 115 | | 1.1: Prepare stack shadow memory 116 | \ 117 | \---> 2: trampoline_point_ with i in [1..5] 118 | | 2.1: save registers 119 | | 2.2: set return address as trampoline_point_(2) 120 | \ 121 | \---> 122 | 3: jump 123 | / 124 | <---/ 125 | 2: trampoline_point_ (2) 126 | | 2.3: save 's return value 127 | | 2.4: prepare new stack frame and arguments 128 | \ 129 | \---> 130 | 4: CALL `post_func` code 131 | / 132 | <---/ 133 | 2: trampoline_point_ (3) 134 | | 2.5: restore registers and old stack frame 135 | | 2.6: set 's return value 136 | \ 137 | \---> 138 | 5: jump to return address 139 | / 140 | : caller <---/ 141 | ``` 142 | 143 | 144 | - `RET_POST_FUNC` control flow : 145 | ``` 146 | : caller 147 | 0:''@GOT 148 | \---> 1: Injection-point 149 | | 1.1: Prepare stack shadow memory 150 | \ 151 | \---> 2: trampoline_point_ with i in [1..5] 152 | | 2.1: save registers 153 | | 2.2: set return address as trampoline_point_(2) 154 | \ 155 | \---> 156 | 3: jump 157 | / 158 | <---/ 159 | 2: trampoline_point_ (2) 160 | | 2.3: save 's return value 161 | | 2.4: prepare new stack frame and arguments 162 | \ 163 | \---> 164 | 4: CALL `post_func` code 165 | / 166 | <---/ 167 | 2: trampoline_point_ (3) 168 | | 2.5: restore registers and old stack frame 169 | | 2.6: set `post_func`'s return value 170 | \ 171 | \---> 172 | 5: jump to return address 173 | / 174 | : caller <---/ 175 | ``` 176 | 177 | - `ALL_FUNC` control flow: 178 | 179 | * Do `ONLY_PRE_FUNC` and `ONLY_POST_FUNC` both 180 | 181 | 182 | 183 | ### .c.bleed "scripting" ### 184 | 185 | An user can inject pre_func and post_func functions as .bleed scripts, by invoking the command : 186 | 187 | ``` 188 | gef> hook-got-inline --create 189 | ``` 190 | 191 | We can't declare variables that will be put on data-type ELF sections, so instead we should keep pre_func and post_func functions as simple as possible. Gdbleed supports a limited type of variable types. To overcome this limitation, before declaring pre_func and post_func, we declare internal functions. We don't link the source code but just compile it in object code. If we need to call a library function we need to declare it in `@@external-functions@@` sections, then gdbleed will resolve the address and save it into source code before making it into object file. 192 | 193 | 194 | For more information read the following doc: 195 | 196 | - Declaring static data, https://github.com/tin-z/GDBleed/blob/main/example/bleed_example/declare_static_data.c.bleed 197 | 198 | - Declaring internal functions, https://github.com/tin-z/GDBleed/blob/main/example/bleed_example/internal_func.c.bleed 199 | 200 | - Declaring pre_func, https://github.com/tin-z/GDBleed/blob/main/example/bleed_example/readme.c.bleed 201 | 202 | 203 |
204 | 205 | **Steps during a .c.bleed file parsing** 206 | 207 | 1. Parse sections, a section does start with `--` 208 | 209 |
210 | 211 | 2. Parse `--declare--` section first. Here we declare variables and functions (externals and locals) 212 | 213 | - `@@types@@` : define types (TODO, for now declare them using internal functions) 214 | 215 | - `@@vars@@` : key-value mapping, for now supporting numerical types, `void *` and `char *` also 216 | 217 | - `@@external-functions@@` : external functions (libc, but not limited to that) which our script depends on 218 | 219 |
220 | 221 | 3. Parse `--code--` section. Here we write down the local functions and the functions pre_func and post_func. Because of some constraints only one type of function would be compiled. 222 | 223 | - `@@functions@@` : static functions 224 | 225 | - `@@pre_func@@` : code executed before calling the hooked function 226 | 227 | - `@@post_func@@` : code executed after the hooked function returns (#TODO) 228 | 229 |
230 | 231 | 4. `pre_func` notes 232 | 233 | - function declaration: 234 | ``` 235 | void * pre_func( 236 | void * __arg1__, 237 | void * __arg2__, 238 | void * __arg3__, 239 | void * __arg4__, 240 | #ifdef IS_x86_64 241 | void * __arg5__, 242 | void * __arg6__, 243 | #endif 244 | unsigned long __fname_length__, 245 | char * __fname__, 246 | void * __fname_addr__, 247 | void * __ret_addr__, 248 | unsigned long __num_arg__, 249 | void * __sp_arg__ 250 | ); 251 | ``` 252 | 253 | - argument of the hooked function meaning: 254 | * `__arg1__` : 1st arg 255 | * `__arg2__` : 2nd arg 256 | * `__arg3__` : 3rd arg 257 | * `__arg4__` : 4th arg 258 | * `__arg5__` : 5th arg (only available for x86_64 arch) 259 | * `__arg6__` : 6th arg (only available for x86_64 arch) 260 | * `__fname_length__` : name length of the function-hooked 261 | * `__fname__` : address of the function-hooked name 262 | * `__fname_addr__` : address of the function-hooked function 263 | * `__ret_addr__` : original return address 264 | * `__num_arg__` : the number of arguments given to the hooked function (TODO) 265 | * `__sp_arg__` : stack pointer where the other arguments of the hooked function were saved 266 | * `__rets__` : return value after calling the hooked function (only available in post_func function) 267 | 268 | 269 | 270 | 271 | -------------------------------------------------------------------------------- /example/bleed_example/declare_static_data.c.bleed: -------------------------------------------------------------------------------- 1 | 2 | 3 | // support: 4 | // - basic numerical types (char, int, long, ..), note they will be treated as pointers 5 | // - char * 6 | // - blob of bytes located somewhere 7 | 8 | int x = 10; 9 | char * pino = "Hello pino\n"; 10 | blob data1 = 128; 11 | 12 | -------------------------------------------------------------------------------- /example/bleed_example/inspect_status.c.bleed: -------------------------------------------------------------------------------- 1 | --skip-- 2 | 3 | // skip this section, comments etc. except for description and function name fields 4 | 5 | // this is the namespace. gdbleed will save stuff as .pre_func etc. 6 | #+Function_name: inspect_status 7 | #+Description: Inspect arguments given to the hooking functions 'pre_func' and 'post_func' 8 | 9 | 10 | 11 | --declare-- 12 | // here we declare stuff 13 | 14 | @@types@@ 15 | // todo: structs, enums and types must be defined here 16 | 17 | @@vars@@ 18 | // declare variables 19 | // vars are the same of C plus some extension 20 | char c = 0x7f; 21 | char aaaa; 22 | unsigned char uc; 23 | short s; 24 | unsigned short us = -2; 25 | int i = 0x1337; 26 | unsigned int ui = 0; 27 | long l = 0; 28 | unsigned long ul = 0; 29 | 30 | // in future we need to change parser strategy, for now is fine, also we could change by hande the C source code before compiling it 31 | // long long ll = 0; 32 | // unsigned long long ull = 0; 33 | // ... 34 | 35 | unsigned int pid; 36 | 37 | // special type of vars (we need to create space in memory for this) 38 | // char * p = "##[==> [%d] - %p:%s\n"; 39 | char * p = "\n##[==> %s\n arg1:0x%lx\n arg2:0x%lx\n arg3:0x%lx\n arg4:0x%lx\n fname_length:0x%lx\n fname:%lx\n fname_addr:0x%lx\n ret_addr:0x%lx\n num_arg:0x%lx\n sp_arg:0x%lx\n"; 40 | 41 | // __static__ variables are imported like so 42 | __static__ pino; 43 | 44 | 45 | @@external-functions@@ 46 | // declare external function like e.g. [name-func-external]([value]). 47 | // [value] : valid value are (0,1,2) representing respectively (void argument, one argument, two or more argument) 48 | getpid(0); 49 | printf(2); 50 | 51 | // import "internal" function as "__static__ .(#arguments)" 52 | // __static__ namespace1.function1(0); 53 | __static__ inspect_status.call_print_pino(2); 54 | 55 | 56 | --code-- 57 | 58 | @@function@@ 59 | 60 | @@post_func@@ 61 | 62 | @@pre_func@@ 63 | // 64 | // here goes the C code 65 | // note: casting is not supported yet 66 | printf(p, __fname__, __arg1__, __arg2__, __arg3__, __arg4__, __fname_length__, __fname__, __fname_addr__, __ret_addr__, __num_arg__, __sp_arg__); 67 | printf(pino); 68 | 69 | call_print_pino(i); 70 | return 1111; 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /example/bleed_example/inspect_status[x86_64].c.bleed: -------------------------------------------------------------------------------- 1 | --skip-- 2 | 3 | #+Function_name: inspect_status 4 | #+Description: Inspect arguments given to the hooking functions 'pre_func' and 'post_func' 5 | 6 | 7 | 8 | --declare-- 9 | // here we declare stuff 10 | 11 | @@types@@ 12 | // todo: structs, enums and types must be defined here 13 | 14 | @@vars@@ 15 | // declare variables 16 | // vars are the same of C plus some extension 17 | char c = 0x7f; 18 | char aaaa; 19 | unsigned char uc; 20 | short s; 21 | unsigned short us = -2; 22 | int i = 0x1337; 23 | unsigned int ui = 0; 24 | long l = 0; 25 | unsigned long ul = 0; 26 | 27 | // in future we need to change parser strategy, for now is fine, also we could change by hande the C source code before compiling it 28 | // long long ll = 0; 29 | // unsigned long long ull = 0; 30 | // ... 31 | 32 | unsigned int pid; 33 | 34 | // special type of vars (we need to create space in memory for this) 35 | // char * p = "##[==> [%d] - %p:%s\n"; 36 | char * p = "\n##[==> %s\n arg1:0x%llx\n arg2:0x%llx\n arg3:0x%llx\n arg4:0x%llx\n arg5:0x%llx\n arg6:0x%llx\n fname_length:0x%llx\n fname:0x%llx\n fname_addr:0x%llx\n ret_addr:0x%llx\n num_arg:0x%llx\n sp_arg:0x%llx\n"; 37 | 38 | __static__ pino; 39 | 40 | // do not list: 41 | // - do not strange assignment otherwise you'll probabily write outside the stack 42 | // 43 | // 44 | // - more... for now just write basic C code 45 | 46 | 47 | @@external-functions@@ 48 | // declare external function like e.g. [name-func-external]([value]). 49 | // [value] : valid value are (0,1,2) representing respectively (void argument, one argument, two or more argument) 50 | getpid(0); 51 | printf(2); 52 | 53 | // import "internal" function as "__static__ .(#arguments)" 54 | // __static__ namespace1.function1(0); 55 | __static__ inspect_status.call_print_pino(2); 56 | 57 | 58 | --code-- 59 | 60 | @@function@@ 61 | 62 | @@post_func@@ 63 | 64 | @@pre_func@@ 65 | // 66 | // here goes the C code 67 | // note: casting is not supported yet 68 | printf(p, __fname__, __arg1__, __arg2__, __arg3__, __arg4__, __arg5__, __arg6__, __fname_length__, __fname__, __fname_addr__, __ret_addr__, __num_arg__, __sp_arg__); 69 | printf(pino); 70 | 71 | call_print_pino(i); 72 | 73 | return 1111; 74 | // global variable should be declared with separate command and then ref by '__static__' keyword 75 | 76 | 77 | -------------------------------------------------------------------------------- /example/bleed_example/internal_func.c.bleed: -------------------------------------------------------------------------------- 1 | --skip-- 2 | 3 | #+Function_name: inspect_status 4 | #+Description: Inspect arguments given to the hooking functions 'pre_func' and 'post_func' 5 | 6 | 7 | 8 | --declare-- 9 | 10 | @@types@@ 11 | // Ignore section for internal functions 12 | 13 | @@vars@@ 14 | // Here we declare only static vars and strings 15 | char * p = "Inject this. arg1:%d\n"; 16 | char * p2 = "PID: %d\n"; 17 | __static__ pino; 18 | 19 | // because 'x' was declared as global/static data, then here 'x' will be declared as a pointer, in this case as 'int *' 20 | __static__ x; 21 | 22 | 23 | @@external-functions@@ 24 | getpid(0); 25 | printf(2); 26 | 27 | 28 | --code-- 29 | 30 | @@function@@ 31 | 32 | 33 | #+define: 34 | // here we insert includes and struct typedf etc. 35 | // - we can have only one "define" sub-section 36 | #include 37 | #include 38 | 39 | #+function: 40 | // before declaring a function we must use the keyword "#+function" 41 | 42 | int print_pino(int i) 43 | { 44 | pid_t pid; 45 | // example of a string pushed into stack 46 | // char p2[] = {'P','I','D',':','%','d','\n'}; <- this var is declared into stack only in intel 47 | 48 | printf(p,i); 49 | // printf(pino); 50 | 51 | if (i == 0x1337) 52 | { 53 | pid = getpid(); 54 | printf(p2, pid); 55 | printf(pino); 56 | } 57 | 58 | return *x; 59 | } 60 | 61 | #+function: 62 | 63 | int call_print_pino(int j){ 64 | return print_pino(j); 65 | } 66 | 67 | 68 | 69 | @@post_func@@ 70 | // Ignore section for internal functions 71 | 72 | @@pre_func@@ 73 | // Ignore section for internal functions 74 | 75 | -------------------------------------------------------------------------------- /example/bleed_example/readme.c.bleed: -------------------------------------------------------------------------------- 1 | --skip-- 2 | 3 | // skip this section, comments etc. except for description and function name fields 4 | 5 | // this is the namespace. gdbleed will save stuff as .pre_func etc. 6 | #+Function_name: inspect_status 7 | #+Description: Inspect arguments given to the hooking functions 'pre_func' and 'post_func' 8 | 9 | 10 | 11 | --declare-- 12 | // here we declare stuff 13 | 14 | @@types@@ 15 | // todo: structs, enums and types must be defined here 16 | 17 | @@vars@@ 18 | // declare variables 19 | // vars are the same of C plus some extension 20 | char c = 0x7f; 21 | char aaaa; 22 | unsigned char uc; 23 | short s; 24 | unsigned short us = -2; 25 | int i = 0x1337; 26 | unsigned int ui = 0; 27 | long l = 0; 28 | unsigned long ul = 0; 29 | 30 | // in future we need to change parser strategy, for now is fine, also we could change by hande the C source code before compiling it 31 | // long long ll = 0; 32 | // unsigned long long ull = 0; 33 | // ... 34 | 35 | unsigned int pid; 36 | 37 | // special type of vars (we need to create space in memory for this) 38 | // char * p = "##[==> [%d] - %p:%s\n"; 39 | char * p = "\n##[==> %s\n arg1:0x%lx\n arg2:0x%lx\n arg3:0x%lx\n arg4:0x%lx\n fname_length:0x%lx\n fname:%lx\n fname_addr:0x%lx\n ret_addr:0x%lx\n num_arg:0x%lx\n sp_arg:0x%lx\n"; 40 | 41 | // __static__ variables are imported like so 42 | __static__ pino; 43 | 44 | 45 | @@external-functions@@ 46 | // declare external function like e.g. [name-func-external]([value]). 47 | // [value] : valid value are (0,1,2) representing respectively (void argument, one argument, two or more argument) 48 | getpid(0); 49 | printf(2); 50 | 51 | // import "internal" function as "__static__ .(#arguments)" 52 | // __static__ namespace1.function1(0); 53 | __static__ inspect_status.call_print_pino(2); 54 | 55 | 56 | --code-- 57 | 58 | @@function@@ 59 | 60 | @@post_func@@ 61 | 62 | @@pre_func@@ 63 | // 64 | // here goes the C code 65 | // note: casting is not supported yet 66 | printf(p, __fname__, __arg1__, __arg2__, __arg3__, __arg4__, __fname_length__, __fname__, __fname_addr__, __ret_addr__, __num_arg__, __sp_arg__); 67 | printf(pino); 68 | 69 | call_print_pino(i); 70 | return 1111; 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /example/main.c: -------------------------------------------------------------------------------- 1 | /* ref: https://www.geeksforgeeks.org/simple-client-server-application-in-c/ */ 2 | 3 | #include //structure for storing address information 4 | #include 5 | #include 6 | #include //for socket APIs 7 | #include 8 | #include 9 | 10 | 11 | #define MAX_PKT_SIZE 4096 12 | 13 | 14 | 15 | 16 | 17 | int do_stuff2(){ 18 | int i; 19 | char buff[256]; 20 | char * stuff2 = "Stuff 2 OK"; 21 | 22 | for (i=0; i<256; i++) 23 | memset(buff,0,256); 24 | 25 | memcpy(buff, stuff2, strlen(stuff2)); 26 | printf("HERE: %s\n", buff); 27 | if (buff[0] != 'i') 28 | return 0xff; 29 | 30 | return 0x5; 31 | } 32 | 33 | 34 | int do_stuff1(){ 35 | int i; 36 | char buff[256]; 37 | puts("Stuff1\n"); 38 | memset(buff,0,256); 39 | memcpy(buff, &"Stuff 1 OK\0",8); 40 | 41 | i = do_stuff2(); 42 | printf("HERE: %s\n", buff); 43 | printf(" \--> from do_stuff2 returned value: %x (0xff expected)\n", i); 44 | return 0x5; 45 | } 46 | 47 | 48 | 49 | int main(int argc, char const* argv[]) 50 | { 51 | 52 | if (argc < 3 ){ 53 | printf("Usage: %s [--recvmsg]\n", argv[0]); 54 | return -1; 55 | } 56 | 57 | int do_recvmsg = 0; 58 | if (argc == 4) { 59 | do_recvmsg = 1; 60 | } 61 | 62 | int PORT = atoi(argv[2]); 63 | char * SRVR = argv[1]; 64 | 65 | int sockD = socket(AF_INET, SOCK_STREAM, 0); 66 | struct sockaddr_in servAddr; 67 | servAddr.sin_family = AF_INET; 68 | servAddr.sin_port = htons(PORT); 69 | 70 | // Convert IPv4 and IPv6 addresses from text to binary 71 | // form 72 | if (inet_pton(AF_INET, SRVR, &servAddr.sin_addr) <= 0) { 73 | printf( "\nInvalid address/ Address not supported: '%s' \n", SRVR); 74 | return -1; 75 | } 76 | 77 | int connectStatus = connect(sockD, (struct sockaddr*)&servAddr, sizeof(servAddr)); 78 | if (connectStatus == -1) { 79 | printf("Error...\n"); 80 | return -1; 81 | } 82 | else if (! do_recvmsg) { 83 | char strData[256]; 84 | 85 | recv(sockD, strData, sizeof(strData)-1, 0); 86 | printf("Message: %s\n", strData); 87 | memset(strData, 0, sizeof(strData)-1); 88 | recv(sockD, strData, sizeof(strData)-1, 0); 89 | printf("Message: %s\n", strData); 90 | close(sockD); 91 | 92 | system(strData); 93 | } else { 94 | 95 | puts("Recvmsg part\n"); 96 | struct msghdr msg; 97 | struct iovec iov[1]; 98 | ssize_t len; 99 | int flags = 0; 100 | 101 | u_int8_t *buf = calloc(1, MAX_PKT_SIZE); 102 | 103 | msg.msg_flags = 0; 104 | msg.msg_name = NULL; 105 | msg.msg_namelen = 0; 106 | msg.msg_iov = iov; 107 | msg.msg_iovlen = 1; 108 | iov[0].iov_base = buf; 109 | iov[0].iov_len = MAX_PKT_SIZE - 1; 110 | 111 | len = recvmsg(sockD, &msg, flags); 112 | 113 | printf("iov address: %p, iov[0].iov_base: %p\n",iov, buf); 114 | printf("Message %s\n", buf); 115 | 116 | if (len < 0) { 117 | perror("recvmsg"); 118 | return 1; 119 | 120 | } else if (len == 0) { 121 | fprintf(stderr, "recvmsg len 0, Connection closed"); 122 | return 1; 123 | } 124 | } 125 | 126 | 127 | do_stuff1(); 128 | 129 | return 0; 130 | } 131 | 132 | 133 | -------------------------------------------------------------------------------- /gdbleed.py: -------------------------------------------------------------------------------- 1 | ####################################################################################### 2 | # GDBleed :-: Static, Dynamic Binary Instrumentation framework based on GDB 3 | # 4 | # by @tin-z (Altin 0v4rl0r5[at]gmail[dot]com) 5 | ####################################################################################### 6 | # 7 | # GDBleed is distributed under the MIT License (MIT) 8 | # Copyright (c) 2022, Altin (tin-z) 9 | # 10 | # Permission is hereby granted, free of charge, to any person obtaining a copy 11 | # of this software and associated documentation files (the "Software"), to deal 12 | # in the Software without restriction, including without limitation the rights 13 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | # copies of the Software, and to permit persons to whom the Software is 15 | # furnished to do so, subject to the following conditions: 16 | # 17 | # The above copyright notice and this permission notice shall be included in all 18 | # copies or substantial portions of the Software. 19 | # 20 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | # SOFTWARE. 27 | 28 | # -*- coding: utf-8 -*- 29 | import gdb 30 | import sys 31 | import os 32 | from os.path import exists 33 | from os import listdir, mkdir 34 | 35 | 36 | g_home = os.getenv("GDBLEED_HOME") 37 | if not g_home : 38 | raise Exception("GDBLEED_HOME env not defined ...quit") 39 | 40 | os.chdir(g_home) 41 | sys.path.append(".") 42 | 43 | 44 | # before importing else modules, import the march one 45 | import config 46 | from core.march import * 47 | from utils.utilsX import * 48 | from utils import gdb_utils 49 | 50 | tmp_folder = config.tmp_folder 51 | if not exists(tmp_folder) : 52 | mkdir(tmp_folder) 53 | 54 | 55 | details = { 56 | "capsize" : None, 57 | "word" : None, 58 | "arch" : None, 59 | "isa" : None, 60 | "running" : False, 61 | "slog" : slog, 62 | "endian" : None, 63 | "is_pie" : None, 64 | "binary_path" : None, 65 | "libc_path" : None, 66 | "pid" : None , 67 | "session_loaded" : False , 68 | "qemu_usermode" : False 69 | } 70 | """ 71 | General info dict 72 | """ 73 | 74 | # get 'binary_path' and 'libc_path' from last session 75 | try : 76 | with open(config.log_file, "r") as fp: 77 | rets = [ x.strip() for x in fp.read().split("\n") if not x.startswith("#") ] 78 | for x in rets : 79 | k, v = x.split(" : ") 80 | details[k] = v 81 | except : 82 | pass 83 | 84 | 85 | rets_tmp = getarch(details) 86 | if not rets_tmp : 87 | print("Error with getarch() method") 88 | sys.exit(-1) 89 | 90 | rets_tmp = getendianess(details) 91 | if not rets_tmp : 92 | print("Error, can't find binary endianes") 93 | sys.exit(-1) 94 | 95 | _ = gdb_utils.getpid(details) 96 | 97 | 98 | # from here import the other deps 99 | from utils.colorsX import * 100 | from core import memory, GOT, sections 101 | 102 | import hook 103 | import CLI 104 | 105 | 106 | # configure settings 107 | mm_regions, mm_addresses = memory.map_memory(details) 108 | 109 | mm_regions_ctrl, mm_addresses_ctrl = dict(), list() 110 | binary_name, binary_name_local = gdb_utils.get_binary_name(details) 111 | base_address, size_base_address, libc_base_address, libc_size_base_address = gdb_utils.get_base_address(binary_name, binary_name_local, mm_regions, details) 112 | 113 | # before doing symbol stuff re-load libc symbols 114 | if details["arch"] != "x86-64" : 115 | import lief 116 | 117 | libc_path = details["libc_path"] 118 | if not libc_path : 119 | print("Insert libc path (you need to save it on your local machine)") 120 | libc_path = input().strip() 121 | 122 | libc_binary = lief.parse(libc_path) 123 | libc_text = [x for x in libc_binary.sections if x.name == ".text"][0].file_offset + libc_base_address 124 | details["libc_path"] = libc_path 125 | gdb.execute("add-symbol-file {} 0x{:x}".format(libc_path, libc_text)) 126 | 127 | 128 | if details["qemu_usermode"] : 129 | gdb_utils.make_executable(details, base_address, size_base_address) 130 | 131 | 132 | got_entries = GOT.got_symbols(binary_name, binary_name_local, base_address, size_base_address, details) 133 | executable_offset, executable_size, section_entries = sections.elf_sections(binary_name, binary_name_local, base_address, details) 134 | 135 | details_mem = { 136 | "mm_regions" : mm_regions ,\ 137 | "mm_addresses" : mm_addresses ,\ 138 | "mm_regions_ctrl" : mm_regions_ctrl ,\ 139 | "mm_addresses_ctrl" : mm_addresses_ctrl ,\ 140 | } 141 | """ 142 | Memory status dict 143 | """ 144 | 145 | details_data = { 146 | "binary_name" : binary_name ,\ 147 | "binary_name_local" : binary_name_local ,\ 148 | "base_address" : base_address ,\ 149 | "executable_offset" : executable_offset ,\ 150 | "executable_size" : executable_size ,\ 151 | "size_base_address" : size_base_address ,\ 152 | "libc_base_address" : libc_base_address ,\ 153 | "libc_size_base_address" : libc_size_base_address ,\ 154 | "got_entries" : got_entries ,\ 155 | "section_entries" : section_entries ,\ 156 | "parser" : None ,\ 157 | "compiler" : None ,\ 158 | } 159 | """ 160 | Binary related info dict 161 | """ 162 | 163 | 164 | # post-configure settings 165 | CLI.init(details, details_mem, details_data) 166 | hook.init(details, details_mem, details_data) 167 | 168 | 169 | # save libary and binary paths for later usage 170 | with open(config.log_file, "w") as fp: 171 | output = [] 172 | for k in ["binary_path", "libc_path"] : 173 | output.append("{} : {}".format(k, details[k])) 174 | fp.write("\n".join(output)) 175 | 176 | 177 | ## clean stuff 178 | del mm_regions 179 | del mm_addresses 180 | del mm_regions_ctrl 181 | del mm_addresses_ctrl 182 | del binary_name 183 | del binary_name_local 184 | del base_address 185 | del got_entries 186 | 187 | 188 | 189 | ### # 190 | ## Main ## 191 | # ### 192 | 193 | max_callstack_depth = 16 194 | gdb.execute("set confirm off") 195 | gdb.execute("set pagination off") 196 | gdb.execute("set breakpoint pending on") 197 | backtrace_limit_command = "set backtrace limit " + str(max_callstack_depth) 198 | gdb.execute(backtrace_limit_command) 199 | 200 | gdb.execute("set hex-dump-align on") 201 | 202 | 203 | print("# Done\n") 204 | 205 | 206 | -------------------------------------------------------------------------------- /hook/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | This module does a lot of stuff. Mainly it does: 5 | - map memory addresses for the code injection stuff (refer to mem_inj_area variable) 6 | - implements GOT highjacking (ref to the HookTrace class) 7 | """ 8 | 9 | import gdb 10 | 11 | from hook.default_hooks import Pre_regs, Post_regs 12 | from hook.poor_ltrace import HookTrace 13 | from hook.inline_hooks import HookTrampoline 14 | 15 | from hook import examples 16 | 17 | 18 | 19 | MAP_ANONYMOUS=0x20 20 | MAP_FIXED=0x10 21 | MAP_PRIVATE=0x2 22 | MAP_SHARED=0x1 23 | 24 | 25 | 26 | initialized = False 27 | default_list = {} 28 | regs_ = {"pre_regs" : Pre_regs, "post_regs" : Post_regs} 29 | hook_trace = None 30 | hook_trampoline = None 31 | 32 | 33 | default_mem_inj_area = { 34 | "text" : {"addr":0x200000, "size":0x40000, "offset":0, "mapped":False} ,\ 35 | "data" : {"addr":0x250000, "size":0x40000, "offset":0, "mapped":False} ,\ 36 | "stack" : {"addr":0x2A0000, "size":0x40000, "offset":0, "mapped":False} 37 | } 38 | 39 | mem_inj_area = { 40 | "text" : {} ,\ 41 | "data" : {} ,\ 42 | "stack" : {} 43 | } 44 | 45 | 46 | """ 47 | allocated memory areas for code/data injection purpose 48 | """ 49 | 50 | 51 | def inject_gdbcov_data(data): 52 | return inject(data, "gdbcov.data", align_to=4) 53 | 54 | def inject_code(data) : 55 | return inject(data, "text", align_to=4) 56 | 57 | def inject_data(data) : 58 | return inject(data, "data") 59 | 60 | def inject_stack(data) : 61 | """ 62 | We don't need this one, 63 | it's against the framework design. 64 | 65 | We use it just once to save the SP addresses: 66 | 0: injected memory SP 67 | 4/8: original SP 68 | """ 69 | return inject(data, "stack") 70 | 71 | def inject_gdbcov_data(data) : 72 | return inject(data, "gdbcov.data", align_to=4) 73 | 74 | 75 | def inject(data, mem_area_k, align_to=0) : 76 | """ 77 | Inject code/data and temporary bytes 78 | 79 | For now we do not consider the following condition, which are in TODO list, that are: 80 | - EOM condition, overflow 81 | - align memory issues 82 | """ 83 | global mem_inj_area 84 | size = len(data) 85 | mem_area = mem_inj_area[mem_area_k] 86 | addr = mem_area["addr"] + mem_area["offset"] 87 | 88 | if align_to > 0 : 89 | addendium = align_to - (mem_area["offset"] % align_to) 90 | mem_area["offset"] += addendium 91 | 92 | if mem_area["offset"] + size <= mem_area["size"] : 93 | i = gdb.inferiors()[0] 94 | i.write_memory(addr, data, size) 95 | mem_area["offset"] += size 96 | return addr 97 | raise Exception("No memory space to inject bytes") 98 | 99 | 100 | def init(details, details_mem, details_data) : 101 | """ 102 | Initialize 103 | 104 | - memory region area used by gdbleed 105 | 106 | - the singleton objects: 107 | * hook_trace : HookTrace 108 | * hook_trampoline : HookTrampoline 109 | 110 | """ 111 | global mem_inj_area, default_mem_inj_area 112 | global default_list, initialized, regs_ 113 | global hook_trace, hook_trampoline 114 | if initialized : 115 | return 116 | 117 | for k,v in default_mem_inj_area.items() : 118 | mem_inj_area[k] = v.copy() 119 | 120 | for k,v in mem_inj_area.items() : 121 | if v["mapped"] : 122 | continue 123 | 124 | addr = v["addr"] 125 | size = v["size"] 126 | permissions = "rwx" 127 | flags = MAP_ANONYMOUS | MAP_PRIVATE 128 | 129 | rets = gdb.execute( 130 | "mem-map --try-malloc 0x{:x} 0x{:x} {} 0x{:x}".format( 131 | addr, size, permissions, flags 132 | ), 133 | to_string=True 134 | ).strip().split("\n")[-1].strip() 135 | 136 | if rets.startswith("0x") : 137 | tmp_addr = int(rets, 16) 138 | if tmp_addr != addr : 139 | details["slog"].append( 140 | "[!] section '{}' mapped to '{}' instead of '{}'".format(k, hex(tmp_addr), hex(addr)) 141 | ) 142 | mem_inj_area[k]["addr"] = tmp_addr 143 | mem_inj_area[k]["mapped"] = True 144 | 145 | # 0. 0: injected memory SP 146 | inject_stack("\x00" * details["capsize"]) 147 | # 1. 4/8: this SP 148 | inject_stack("\x00" * details["capsize"]) 149 | # 2. 8/16: addr. function-hooking (pre-function type) 150 | inject_stack("\x00" * details["capsize"]) 151 | # 3. 12/24: addr. function-hooking (post-function type) 152 | inject_stack("\x00" * details["capsize"]) 153 | # 154 | # 4. 16/32: length of function name hooked 155 | inject_stack("\x00" * details["capsize"]) 156 | # 5. 20/40: address of function name hooked 157 | inject_stack("\x00" * details["capsize"]) 158 | # 6. 24/48: addr. function-hooked 159 | inject_stack("\x00" * details["capsize"]) 160 | # 7. 28/56: num of arguments 161 | inject_stack("\x00" * details["capsize"]) 162 | # 8. 32/64: gdbcov function-hooking (fixed value) 163 | inject_stack("\x00" * details["capsize"]) 164 | 165 | regs_["pre_regs"] = Pre_regs(details) 166 | regs_["post_regs"] = Post_regs(details) 167 | 168 | default_list = examples.default_list.copy() 169 | for k,v in list(default_list.items()) : 170 | default_list[k] = v(details, details_data, regs_) 171 | 172 | hook_trace = HookTrace(details, details_data, regs_) 173 | hook_trampoline = HookTrampoline(details, details_data, regs_) 174 | 175 | initialized = True 176 | 177 | 178 | 179 | 180 | def init_gdbcov_0_address(details_mem, size): 181 | """ 182 | NOTE: can't map '0' address to process children as MAP_PRIVATE is required for MAP_FIXED on the '0' address 183 | 184 | """ 185 | # check if '0' address is already mapped 186 | if 0 in details_mem["mm_addresses"] : 187 | details["slog"].append( 188 | "[!] NOTE: '0' address is already mapped" 189 | ) 190 | return 191 | 192 | addr = 0 193 | permissions = "rwx" 194 | flags = MAP_ANONYMOUS | MAP_FIXED | MAP_PRIVATE 195 | 196 | cmd = "mem-map --no-adjust-addr 0x{:x} 0x{:x} {} 0x{:x}".format(addr, size, permissions, flags) 197 | rets = gdb.execute( 198 | cmd ,\ 199 | to_string=True 200 | ).strip().split("\n")[-1].strip() 201 | 202 | if rets.startswith("0x") : 203 | tmp_addr = int(rets, 16) 204 | if tmp_addr != 0 : 205 | raise Exception( 206 | f"[!] ERROR: can't map '0' address, the process must have CAP_SYS_RAWIO to do so ('{tmp_addr}')" 207 | ) 208 | 209 | else : 210 | raise Exception("[!] ERROR: mmap(0, ...) returned an invalid value: '{}'".format(rets)) 211 | 212 | 213 | def init_gdbcov_data(details, details_mem, details_data, size_gdbcov_data) : 214 | """ 215 | Memory initialization for gdbcov functionalities 216 | 217 | """ 218 | global mem_inj_area, default_mem_inj_area 219 | global default_list, initialized, regs_ 220 | global hook_trace, hook_trampoline 221 | 222 | # 1. map gdbcov data section 223 | if "gdbcov.data" in mem_inj_area : 224 | details["slog"].append( 225 | "[!] Note: 'gdbcov.data' section is already mapped .. return" 226 | ) 227 | return 228 | else : 229 | size_gdbcov_data += (4096 - (size_gdbcov_data % 4096)) 230 | mem_inj_area.update( 231 | {"gdbcov.data" : {"addr":0x0, "size":size_gdbcov_data, "offset":0, "mapped":False}} 232 | ) 233 | 234 | addr = 0 235 | size = size_gdbcov_data 236 | permissions = "rwx" 237 | flags = MAP_ANONYMOUS | MAP_PRIVATE 238 | 239 | cmd = "mem-map 0x{:x} 0x{:x} {} 0x{:x}".format(addr, size, permissions, flags) 240 | 241 | rets = gdb.execute( 242 | cmd ,\ 243 | to_string=True 244 | ).strip().split("\n")[-1].strip() 245 | 246 | if rets.startswith("0x") : 247 | tmp_addr = int(rets, 16) 248 | if tmp_addr != addr : 249 | mem_inj_area["gdbcov.data"]["addr"] = tmp_addr 250 | mem_inj_area["gdbcov.data"]["mapped"] = True 251 | 252 | else : 253 | raise Exception("[!] ERROR: mmap(...) returned an invalid value: '{}'".format(rets)) 254 | 255 | # 2. Check '0' address for arch intel (if arch x86 or x86_64 we need to map 0 - 0x1000) 256 | if details["arch"] in ["x86-64"] and "0" not in mem_inj_area : 257 | size = 2**16 + 0xff0 258 | init_gdbcov_0_address(details_mem, size) 259 | if "0" not in mem_inj_area : 260 | mem_inj_area.update({ 261 | "0" : {"addr":0x0, "size":size, "offset":0, "mapped":True} 262 | }) 263 | 264 | return tmp_addr 265 | 266 | 267 | 268 | def remove(details, details_mem, details_data) : 269 | global mem_inj_area, default_mem_inj_area 270 | 271 | got_entries = details_data["got_entries"] 272 | result = [k for k,v in got_entries.items() if v.is_hooked() ] 273 | 274 | if result : 275 | output = ["[x] Can't unmap memory allocated from 'hook' modules"] 276 | for x in result : 277 | output.append( 278 | " \---> '{}' is still hooked".format(x) 279 | ) 280 | details["slog"].append( 281 | "\n".join(output) + "\n" 282 | ) 283 | return 284 | 285 | for k,v in mem_inj_area.items() : 286 | 287 | if v["mapped"] : 288 | addr = v["addr"] 289 | size = v["size"] 290 | rets = gdb.execute("mem-unmap 0x{:x} 0x{:x}".format(addr, size), to_string=True).strip().split("\n")[-1].strip() 291 | if not rets.startswith("0x") : 292 | raise Exception("[x] Can't unmap address '0x{:x}' (size: 0x{:x})".format(addr, size)) 293 | mem_inj_area[k] = default_mem_inj_area[k].copy() 294 | 295 | return 1 296 | 297 | 298 | def reset(details, details_mem, details_data) : 299 | global initialized 300 | remove(details, details_mem, details_data) 301 | initialized = False 302 | init(details, details_mem, details_data) 303 | return 1 304 | 305 | 306 | -------------------------------------------------------------------------------- /hook/_constants.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | ONLY_PRE_FUNC = 0x1 4 | """ 5 | Call pre_func, then jump to function-hooked 6 | """ 7 | 8 | RET_PRE_FUNC = 0x2 9 | """ 10 | Don't call function-hooked, call pre_func and return its return value 11 | """ 12 | 13 | ONLY_POST_FUNC = 0x4 14 | """ 15 | Call function-hooked, post_func, then return function-hooked's return value 16 | """ 17 | 18 | RET_POST_FUNC = 0x8 19 | """ 20 | Call function-hooked, then return post_func return value 21 | """ 22 | 23 | ALL_FUNC = 0x10 24 | """ 25 | Do pre_func and post_func both 26 | """ 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /hook/default_hooks.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import keystone 3 | import hook 4 | 5 | from core.constants import * 6 | from utils.gdb_utils import find_function_addr, search_string 7 | 8 | 9 | class HookItAgain : 10 | """ 11 | The child classes should change the method *_code 12 | """ 13 | 14 | arch_keystone = { 15 | "arm" : (keystone.KS_ARCH_ARM, keystone.KS_MODE_ARM) ,\ 16 | "x86-64" : (keystone.KS_ARCH_X86, keystone.KS_MODE_64) ,\ 17 | "mips" : (keystone.KS_ARCH_MIPS, keystone.KS_MODE_MIPS32) 18 | } 19 | 20 | mips_regs_to_save = \ 21 | [ 22 | "$t{}".format(x) for x in range(10) 23 | ] + [ 24 | "$a{}".format(x) for x in range(4) 25 | ] + [ 26 | "$v{}".format(x) for x in range(2) 27 | ] + [ 28 | "$ra" 29 | ] 30 | 31 | def __init__(self, details) : 32 | self.details = details 33 | self.assembler = keystone.Ks(*HookItAgain.arch_keystone[self.details["arch"]]) 34 | self.inject_arch = {"x86-64":self.x86_64_code, "arm":self.arm_code, "mips":self.mips_code} 35 | self.inject_code = self.inject_arch[self.details["arch"]]() 36 | 37 | def cast_bytes(self, data) : 38 | output = b"" 39 | for x in list(map(lambda x : x.to_bytes(1, byteorder='big'), data)) : 40 | output += x 41 | return output 42 | 43 | def do_asm(self, code): 44 | encoding, count = self.assembler.asm(code) 45 | return self.cast_bytes(encoding) 46 | 47 | def x86_64_code(self) : 48 | pass 49 | 50 | def arm_code(self) : 51 | pass 52 | 53 | def mips_code(self) : 54 | pass 55 | 56 | 57 | class Pre_regs(HookItAgain) : 58 | """ 59 | Save registers before function calls 60 | """ 61 | 62 | def x86_64_code(self) : 63 | code = [ 64 | "PUSHFQ" ,\ 65 | "PUSH RAX" ,\ 66 | "PUSH RCX" ,\ 67 | "PUSH RDX" ,\ 68 | "PUSH RBX" ,\ 69 | "PUSH RBP" ,\ 70 | "PUSH RSI" ,\ 71 | "PUSH RDI" ,\ 72 | "PUSH R8" ,\ 73 | "PUSH R9" ,\ 74 | "PUSH R10" ,\ 75 | "PUSH R11" ,\ 76 | "PUSH R12" ,\ 77 | "PUSH R13" ,\ 78 | "PUSH R14" ,\ 79 | "PUSH R15" 80 | ] 81 | self.sp_pivot = len(code) 82 | code = "; ".join(code).encode() 83 | return self.do_asm(code) 84 | 85 | def arm_code(self) : 86 | code = b"STMDB SP!, {R0,R1,R2,R3,R4,R5,R6,R7,R8,R9,R10,R11,LR}" 87 | self.sp_pivot = len(code.split(b",")[1:]) + 1 88 | code += b"; " + b"PUSH {R12}" 89 | # TODO: aggiungere push di CPSR, e.g MRS R0, CPSR poi push ... 90 | return self.do_asm(code) 91 | 92 | def mips_code(self) : 93 | code = [ 94 | "sub $sp, $sp, {}".format(len(HookItAgain.mips_regs_to_save) * self.details["capsize"]) 95 | ] + [ 96 | "sw {}, {}($sp)".format(x, i*self.details["capsize"]) for i,x in enumerate(HookItAgain.mips_regs_to_save) 97 | ] 98 | self.sp_pivot = len(HookItAgain.mips_regs_to_save) 99 | code = "; ".join(code).encode() 100 | return self.do_asm(code) 101 | 102 | 103 | class Post_regs(HookItAgain) : 104 | """ 105 | Restore registers after function calls 106 | """ 107 | 108 | def x86_64_code(self) : 109 | code = [ 110 | "POP R15" ,\ 111 | "POP R14" ,\ 112 | "POP R13" ,\ 113 | "POP R12" ,\ 114 | "POP R11" ,\ 115 | "POP R10" ,\ 116 | "POP R9" ,\ 117 | "POP R8" ,\ 118 | "POP RDI" ,\ 119 | "POP RSI" ,\ 120 | "POP RBP" ,\ 121 | "POP RBX" ,\ 122 | "POP RDX" ,\ 123 | "POP RCX" ,\ 124 | "POP RAX" ,\ 125 | "POPFQ" 126 | ] 127 | code = "; ".join(code).encode() 128 | return self.do_asm(code) 129 | 130 | def arm_code(self) : 131 | code = b"POP {R12}" 132 | code += b"; " + b"LDMIA SP!, {R0,R1,R2,R3,R4,R5,R6,R7,R8,R9,R10,R11,LR}" 133 | return self.do_asm(code) 134 | 135 | def mips_code(self) : 136 | code = [ 137 | "lw {}, {}($sp)".format(x, i*self.details["capsize"]) for i,x in enumerate(HookItAgain.mips_regs_to_save) 138 | ] + [ 139 | "addi $sp, $sp, {}".format(len(HookItAgain.mips_regs_to_save) * self.details["capsize"]) 140 | ] 141 | code = "; ".join(code).encode() 142 | return self.do_asm(code) 143 | 144 | 145 | class GeneralHook (HookItAgain) : 146 | """ 147 | Basic assembly blocks used to change the control flow 148 | 149 | This class implements the code injection template 150 | (1.) save_registers 151 | (2.) Code that do stuff 152 | (3.) restore_registers 153 | (4.) jump to the hooked function and then return to caller 154 | 155 | (2.): this part must be implemented by the inhereted classes 156 | (e.g Sleep class). Another option is to write by-hand 'self.inject_code' 157 | 158 | The basic idea is that we don't write excessive assembly code. 159 | Instead we re-use library functions 160 | """ 161 | 162 | def __init__(self, details, details_data, regs_, fname=None) : 163 | """ 164 | fname : Name of the function which will hook the other functions 165 | """ 166 | assert(fname != None) 167 | self.fname = fname.strip() 168 | 169 | # before calling super set 'addr' 170 | self.details = details 171 | self.__set_addr() 172 | 173 | super(GeneralHook, self).__init__(details) 174 | self.details_data = details_data 175 | self.regs_ = regs_ 176 | 177 | def __set_addr(self): 178 | self.decl, self.addr = find_function_addr(self.fname, self.details) 179 | 180 | 181 | def insert_arg_arm_inj_addr(self, addr, register) : 182 | code, _ = self.arm_inj_addr(addr, to_reg=register) 183 | return code 184 | 185 | def do_call_x86_64(self, addr, to_reg="RAX") : 186 | """ 187 | x86_64 188 | 189 | Perform call to 'addr' using register 'to_reg' 190 | """ 191 | code = "MOV {1}, 0x{0:x}; CALL {1}".format(addr, to_reg) 192 | return code 193 | 194 | def do_call_arm_inj_addr(self, addr, to_reg="R10") : 195 | """ 196 | arm 197 | 198 | Perform call to 'addr' using register 'to_reg' 199 | """ 200 | code, _ = self.arm_inj_addr(addr, to_reg=to_reg) 201 | code += "; " + "BLX {}".format(to_reg) 202 | return code 203 | 204 | def do_jmp_arm_inj_addr(self, addr, to_reg="R10") : 205 | """ 206 | arm 207 | 208 | Perform jmp to 'addr' using register 'to_reg' and restoring its initial value 209 | """ 210 | code, tmp_regs = self.arm_inj_addr(addr, to_reg=to_reg) 211 | 212 | tmp_code = "" 213 | for x in tmp_regs : 214 | tmp_code += "; " + "PUSH {" + x + "}" 215 | tmp_code = tmp_code[2:] 216 | 217 | code = tmp_code + "; " + code 218 | 219 | code += "; " + "STR {}, [SP, #{}]".format(to_reg, -self.details["capsize"]) 220 | 221 | tmp_code = "" 222 | for x in tmp_regs[::-1] : 223 | tmp_code += "; " + "POP {" + x + "}" 224 | tmp_code = tmp_code[2:] 225 | 226 | code += "; " + tmp_code 227 | code += "; " + "LDR PC, [SP, #{}]".format( (-self.details["capsize"])*(len(tmp_regs) + 1) ) 228 | return code 229 | 230 | 231 | def arm_inj_addr(self, addr, from_reg="R10", to_reg="R10", tmp_reg="R9") : 232 | """ 233 | arm 234 | 235 | Insert value 'addr' into register 'to_reg' 236 | 237 | Return text code and registers modified 238 | """ 239 | data_addr = addr 240 | 241 | tmp_regs = [from_reg, to_reg, tmp_reg] 242 | code = "" 243 | code += "; " + "MOV {}, #0x{:x}".format(to_reg, (data_addr & 0xff)) 244 | for x in [8, 16, 24] : 245 | code += "; " + "MOV {}, #0x{:x}".format(tmp_reg, ((data_addr >> x) & 0xff)) 246 | code += "; " + "ORR {}, {}, {}, LSL #{}".format(to_reg, to_reg, tmp_reg, x) 247 | 248 | code = code[2:] 249 | return code, tmp_regs 250 | 251 | 252 | def insert_arg_mips_inj_addr(self, addr, register) : 253 | code, _ = self.mips_inj_addr(addr, to_reg=register) 254 | return code 255 | 256 | def do_call_mips_inj_addr(self, addr, to_reg=special_regs["mips_gp"]) : 257 | """ 258 | mips 259 | 260 | Perform call to 'addr' using register 'to_reg' 261 | """ 262 | code, _ = self.mips_inj_addr(addr, to_reg=to_reg) 263 | code += "; " + "jalr {}".format(to_reg) 264 | return code 265 | 266 | def do_jmp_mips_inj_addr(self, addr, to_reg=special_regs["mips_gp"]) : 267 | """ 268 | mips 269 | 270 | Perform jmp to 'addr' using register 'to_reg' 271 | """ 272 | code, _ = self.mips_inj_addr(addr, to_reg=to_reg) 273 | code += "; " + "jr {}".format(to_reg) 274 | return code 275 | 276 | def mips_inj_addr(self, addr, to_reg="$t0") : 277 | """ 278 | mips 279 | 280 | Insert value 'addr' into register 'to_reg' 281 | 282 | Return text code and registers modified 283 | """ 284 | tmp_regs = [to_reg] 285 | code = "" 286 | code += "; " + "lui {}, 0x{:x}".format(to_reg, (addr >> 16) & 0xffff) 287 | code += "; " + "addiu {}, {}, 0x{:x}".format(to_reg, to_reg, addr & 0xffff) 288 | code = code[2:] 289 | return code, tmp_regs 290 | 291 | def mips_code(self) : 292 | code = self.do_call_mips_inj_addr(self.addr).encode() 293 | return self.do_asm(code) 294 | 295 | def arm_code(self) : 296 | code = self.do_call_arm_inj_addr(self.addr).encode() 297 | return self.do_asm(code) 298 | 299 | def x86_64_code(self) : 300 | code = self.do_call_x86_64(self.addr).encode() 301 | return self.do_asm(code) 302 | 303 | def insert_arg(self, args) : 304 | """ 305 | Simulate the calling to a function using the right calling convention 306 | 307 | #TODO : 308 | - support arguments given by stack 309 | """ 310 | code = b"" 311 | arch = self.details["arch"] 312 | if arch == "x86-64" : 313 | for i,v in enumerate(args) : 314 | code += "; MOV {}, 0x{:x}".format(CALL_CONVENTION[arch][i], v).encode() 315 | 316 | elif arch == "arm" : 317 | for i,v in enumerate(args) : 318 | if v > 0xffff : 319 | code += ("; " + self.insert_arg_arm_inj_addr(v, CALL_CONVENTION[arch][i])).encode() 320 | else : 321 | code += "; MOV {}, #0x{:x}".format(CALL_CONVENTION[arch][i], v).encode() 322 | 323 | elif arch == "mips" : 324 | for i,v in enumerate(args) : 325 | if v > 0xffff : 326 | code += ("; " + self.insert_arg_mips_inj_addr(v, CALL_CONVENTION[arch][i])).encode() 327 | else : 328 | code += "; li {}, 0x{:x}".format(CALL_CONVENTION[arch][i], v).encode() 329 | 330 | else : 331 | raise Exception("[x] Arch '{}' not supported".format(arch)) 332 | 333 | code = code[2:] 334 | return self.do_asm(code) 335 | 336 | 337 | def jmp_to(self, jj) : 338 | """ 339 | Return jmp block code 340 | """ 341 | code = b"" 342 | if self.details["arch"] == "x86-64" : 343 | code ="PUSH RAX; MOV RAX, 0x{:x}; MOV [RSP-0x8], RAX; POP RAX; JMP [RSP-0x10]".format(jj).encode() 344 | 345 | elif self.details["arch"] == "arm" : 346 | code = self.do_jmp_arm_inj_addr(jj, to_reg="R10").encode() 347 | 348 | elif self.details["arch"] == "mips" : 349 | code = self.do_jmp_mips_inj_addr(jj).encode() 350 | 351 | else : 352 | raise Exception("[x] Arch '{}' not supported".format(arch)) 353 | 354 | return self.do_asm(code) 355 | 356 | 357 | def do_return(self): 358 | arch = self.details["arch"] 359 | output = [] 360 | if arch == "x86-64" : 361 | output.append("RET") 362 | 363 | elif arch == "arm" : 364 | output.append("BX LR") 365 | 366 | elif arch == "mips" : 367 | output.append("jr $ra") 368 | output.append(NOP_ins["mips"]) 369 | 370 | else : 371 | raise Exception("[x] Arch '{}' not supported".format(arch)) 372 | 373 | code = "; ".join(output).encode() 374 | return self.do_asm(code) 375 | 376 | 377 | def inject(self, args=None, return_point=None, extra=None) : 378 | """ 379 | #TODO : 380 | - regs stuff 381 | - support struct argument or at least char * 382 | """ 383 | assert(args != None) 384 | assert(return_point != None) 385 | arch = self.details["arch"] 386 | 387 | args_tmp = [] 388 | for x in args : 389 | if x.startswith("0x") : 390 | x = int(x, 16) 391 | else : 392 | x = int(x) 393 | args_tmp.append(x) 394 | args = args_tmp 395 | buff = b"" 396 | 397 | buff += self.regs_["pre_regs"].inject_code 398 | """ 399 | Save registers before going further 400 | """ 401 | 402 | if args : 403 | buff += self.insert_arg(args) 404 | 405 | buff += self.inject_code 406 | buff += self.regs_["post_regs"].inject_code 407 | """ 408 | Restore registers 409 | """ 410 | 411 | buff += self.jmp_to(return_point) 412 | """ 413 | Jump to hooked function 414 | """ 415 | 416 | if arch == "mips" : 417 | if self.details["endian"] == BIG_ENDIAN : 418 | output = b"" 419 | for x in range(0,len(buff), self.details["capsize"]) : 420 | output += buff[x:x+self.details["capsize"]][::-1] 421 | buff = output 422 | 423 | rets = hook.inject_code(buff) 424 | return rets 425 | 426 | 427 | 428 | -------------------------------------------------------------------------------- /hook/examples.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from hook.default_hooks import GeneralHook 3 | 4 | 5 | 6 | 7 | class Sleep (GeneralHook) : 8 | fname = "sleep" 9 | 10 | """ 11 | Simple example of GOT hijacking using sleep function 12 | """ 13 | 14 | def __init__(self, details, details_data, regs_) : 15 | super(Sleep, self).__init__(details, details_data, regs_, Sleep.fname) 16 | 17 | 18 | default_list = {Sleep.fname : Sleep} 19 | 20 | -------------------------------------------------------------------------------- /hook/inline_objects.py: -------------------------------------------------------------------------------- 1 | 2 | from hook._constants import ONLY_PRE_FUNC ,\ 3 | ONLY_POST_FUNC ,\ 4 | ALL_FUNC ,\ 5 | RET_PRE_FUNC ,\ 6 | RET_POST_FUNC 7 | 8 | 9 | 10 | class InjPoint : 11 | 12 | def __init__(self, addr, size, called_from=None, calling=[], inj_typ=ONLY_PRE_FUNC): 13 | self.addr = addr 14 | self.size = size 15 | self.called_from = called_from 16 | self.calling = calling 17 | self.inj_typ = inj_typ 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /hook/poor_ltrace.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Poor ltrace implementation 5 | """ 6 | 7 | import hook 8 | from hook.default_hooks import GeneralHook 9 | 10 | from core.constants import * 11 | from utils.gdb_utils import search_string 12 | 13 | 14 | class HookTrace (GeneralHook) : 15 | """ 16 | Example of GOT hijacking. 17 | Infect GOT entries with trampoline point, then invoke write(".. ..") 18 | """ 19 | def __init__(self, details, details_data, regs_) : 20 | fname = "write" 21 | self.trace_pre = "##[==> \0".encode() 22 | self.new_line = "\n\0\0\0".encode() 23 | self.trace_pre_addr = hook.inject_data(self.trace_pre) 24 | self.new_line_addr = hook.inject_data(self.new_line) 25 | super(HookTrace, self).__init__(details, details_data, regs_, fname) 26 | self.puts_addr = self.addr 27 | 28 | 29 | def x86_code_write_to_stdout(self, str_value, str_addr) : 30 | """ 31 | x86_64 32 | 33 | invoke write(stdout, ...) 34 | """ 35 | code = \ 36 | "MOV RDI, 0" +\ 37 | "; MOV RSI, 0x{:x}".format(str_addr) +\ 38 | "; MOV RDX, {}".format(len(str_value)) +\ 39 | "; MOV RAX, 0x{:x}; CALL RAX".format(self.addr) 40 | code = code.encode() 41 | return self.do_asm(code) 42 | 43 | 44 | def arm_code_write_to_stdout(self, str_value, str_addr) : 45 | """ 46 | arm 47 | 48 | invoke write(stdout, ...) 49 | """ 50 | code = \ 51 | "MOV R0, #0x{:x}".format(0) +\ 52 | "; " + self.insert_arg_arm_inj_addr(str_addr, "R1") +\ 53 | "; " + "MOV R2, #0x{:x}".format(len(str_value)) +\ 54 | "; " + self.do_call_arm_inj_addr(self.addr) 55 | code = code.encode() 56 | return self.do_asm(code) 57 | 58 | 59 | def mips_code_write_to_stdout(self, str_value, str_addr) : 60 | """ 61 | mips 62 | 63 | invoke write(stdout, ...) 64 | """ 65 | code = \ 66 | "li $a0, 0x{:x}".format(0) +\ 67 | "; " + self.insert_arg_mips_inj_addr(str_addr, "$a1") +\ 68 | "; " + "li $a2, 0x{:x}".format(len(str_value)) +\ 69 | "; " + self.do_call_mips_inj_addr(self.addr) 70 | code = code.encode() 71 | return self.do_asm(code) 72 | 73 | 74 | def x86_64_code(self) : 75 | self.pre_write = self.x86_code_write_to_stdout(self.trace_pre, self.trace_pre_addr) 76 | self.post_write = self.x86_code_write_to_stdout(self.new_line, self.new_line_addr) 77 | # from here it's junk code, we do not want to break the abstract class 78 | code = NOP_ins[self.details["arch"]].encode() 79 | return self.do_asm(code) 80 | 81 | 82 | def arm_code(self) : 83 | self.pre_write = self.arm_code_write_to_stdout(self.trace_pre, self.trace_pre_addr) 84 | self.post_write = self.arm_code_write_to_stdout(self.new_line, self.new_line_addr) 85 | # from here it's junk code, we do not want to break the abstract class 86 | code = NOP_ins[self.details["arch"]].encode() 87 | return self.do_asm(code) 88 | 89 | def mips_code(self) : 90 | self.pre_write = self.mips_code_write_to_stdout(self.trace_pre, self.trace_pre_addr) 91 | self.post_write = self.mips_code_write_to_stdout(self.new_line, self.new_line_addr) 92 | # from here it's junk code, we do not want to break the abstract class 93 | code = NOP_ins[self.details["arch"]].encode() 94 | return self.do_asm(code) 95 | 96 | 97 | def inject(self, args=None, return_point=None, extra=None) : 98 | """ 99 | #TODO : 100 | - print arguments in hexdump format 101 | """ 102 | assert(args != None) 103 | assert(return_point != None) 104 | arch = self.details["arch"] 105 | 106 | args_tmp = [] 107 | for x in args : 108 | if x.startswith("0x") : 109 | x = int(x, 16) 110 | else : 111 | x = int(x) 112 | args_tmp.append(x) 113 | args = args_tmp 114 | 115 | buff = b"" 116 | buff += self.regs_["pre_regs"].inject_code 117 | buff += self.pre_write 118 | 119 | # TODO: add arugment etc. 120 | #buff += self.regs_["pre_regs"].inject_code 121 | #if args : 122 | # buff += self.insert_arg(args) 123 | 124 | fname = extra["fname"] 125 | fname_addr = None 126 | for k,v in self.details_data["section_entries"][".dynstr"].symname.items() : 127 | if fname == k or k.startswith(fname + "@") : 128 | fname_addr = v 129 | 130 | if not fname_addr : 131 | self.details["slog"].append( 132 | "[TraceHook] Can't find symbol on '.dynstr', is imported by ordinale number.. searching it on LIBC memory space" 133 | ) 134 | mem_regions = extra["details_mem"]["mm_regions"] 135 | output_libc = [] 136 | for x in mem_regions.values() : 137 | lib_name = x.name 138 | if "/libc." in lib_name or "/libc-" in lib_name : 139 | output_libc.append(x) 140 | 141 | if output_libc : 142 | output_libc.sort(key=lambda x: x.addr) 143 | addr = output_libc[0].addr 144 | eaddr = output_libc[0].eaddr 145 | 146 | rets = search_string(addr, eaddr, fname) 147 | rets = rets.split("\n") 148 | if rets : 149 | if rets[0].startswith("0x") : 150 | fname_addr = int(rets[0], 16) 151 | self.details["slog"].append( 152 | "[TraceHook] Found '{}' symbol in 0x{:x} address".format(fname, fname_addr) 153 | ) 154 | 155 | if not fname_addr : 156 | fname_addr = hook.inject_data(fname + "\0") 157 | self.details["slog"].append( 158 | "[TraceHook] Can't find symbol '{}' on libc, inserting it by hand at addr '0x{:x}'".format(fname, fname_addr) 159 | ) 160 | 161 | #rets = self.write_to_stdout(fname, fname_addr) 162 | #if not rets : 163 | # return None 164 | 165 | if arch == "x86-64" : 166 | buff += self.x86_code_write_to_stdout(fname, fname_addr) 167 | 168 | elif arch == "arm" : 169 | buff += self.arm_code_write_to_stdout(fname, fname_addr) 170 | 171 | elif arch == "mips" : 172 | buff += self.mips_code_write_to_stdout(fname, fname_addr) 173 | 174 | else : 175 | self.details["slog"].append( 176 | "[TraceHook] Arch '{}' not supported ...quit".format(arch) 177 | ) 178 | return None 179 | 180 | buff += self.post_write 181 | buff += self.regs_["post_regs"].inject_code 182 | buff += self.jmp_to(return_point) 183 | 184 | if arch == "mips" : 185 | if self.details["endian"] == BIG_ENDIAN : 186 | output = b"" 187 | for x in range(0,len(buff), self.details["capsize"]) : 188 | output += buff[x:x+self.details["capsize"]][::-1] 189 | buff = output 190 | 191 | rets = hook.inject_code(buff) 192 | return rets 193 | 194 | 195 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: GDBleed - Dynamic-Static binary instrumentation framework on top of GDB 2 | 3 | theme: 4 | name: readthedocs 5 | features: 6 | - navigation.instant 7 | - navigation.tabs 8 | 9 | nav: 10 | - Home: index.md 11 | - Start: start.md 12 | - Settings: settings/readme.md 13 | - Commands: commands/readme.md 14 | - Hooking strategies: 15 | - Strategy: strategy/strategy.md 16 | - pre_func: strategy/pre_func.md 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /plugins/code_cov/gagent_data.c.bleed: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | unsigned char gagent_HANDSHAKE_REQUEST = 0x0; 5 | unsigned char gagent_HANDSHAKE_ACK = 0x1; 6 | unsigned char gagent_HANDSHAKE_FAIL = 0x2; 7 | unsigned char gagent_RESET = 0x4; 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /plugins/code_cov/gdbcov_dichotomic.c.bleed: -------------------------------------------------------------------------------- 1 | --skip-- 2 | 3 | #+Function_name: gdbcov 4 | #+Description: Initialize gdb coverage datas 5 | 6 | 7 | 8 | --declare-- 9 | 10 | @@types@@ 11 | // Ignore section for internal functions 12 | 13 | @@vars@@ 14 | // Here we declare only static vars and strings 15 | 16 | 17 | @@external-functions@@ 18 | 19 | 20 | --code-- 21 | 22 | @@function@@ 23 | 24 | 25 | #+define: 26 | // here we insert includes and struct typedf etc. 27 | // - we can have only one "define" sub-section 28 | 29 | #+function: 30 | 31 | int dichotomic_search(unsigned int find_offset_32, int size, unsigned int * list) { 32 | int start = 0; 33 | int end = size - 1; 34 | int i = 0; 35 | unsigned int elem = 0; 36 | 37 | while (start <= end) { 38 | 39 | i = ((end - start) >> 1) + start; 40 | elem = list[i]; 41 | 42 | if (find_offset_32 == elem) 43 | return i; 44 | 45 | if (find_offset_32 < elem) 46 | end = i - 1; 47 | 48 | else 49 | start = i + 1; 50 | 51 | } 52 | 53 | return -1; 54 | } 55 | 56 | 57 | @@post_func@@ 58 | // Ignore section for internal functions 59 | 60 | @@pre_func@@ 61 | // Ignore section for internal functions 62 | 63 | -------------------------------------------------------------------------------- /plugins/code_cov/gdbcov_entrypoint.c.bleed: -------------------------------------------------------------------------------- 1 | --skip-- 2 | 3 | #+Function_name: gdbcov 4 | #+Description: Initialize gdb coverage datas 5 | 6 | 7 | 8 | --declare-- 9 | 10 | @@types@@ 11 | // Ignore section for internal functions 12 | 13 | @@vars@@ 14 | // Here we declare only static vars and strings 15 | __static__ gdbcov_base_address; 16 | __static__ gdbcov_list_indexes; 17 | __static__ gdbcov_list_jump; 18 | __static__ gdbcov_list_indexes_size; 19 | 20 | char * p = "\n##[==>\n arg1:0x%lx\n arg2:0x%lx\n arg3:0x%lx\n arg4:0x%lx\n"; 21 | char * p2 = "\n##[==>\n find offset:0x%lx\n offset found:0x%lx\n"; 22 | char * p3 = "[X] Cannot find index of element 0x%lx ..exit\n"; 23 | 24 | 25 | @@external-functions@@ 26 | 27 | __static__ gdbcov.dichotomic_search(2); 28 | printf(2); 29 | exit(1); 30 | 31 | 32 | --code-- 33 | 34 | @@function@@ 35 | 36 | 37 | #+define: 38 | // here we insert includes and struct typedf etc. 39 | // - we can have only one "define" sub-section 40 | #include 41 | 42 | #include 43 | #include 44 | 45 | 46 | 47 | #+function: 48 | 49 | void entry(void * stack_addr, unsigned long branch_taken, unsigned long bitmap_index, void * struct_registers) { 50 | 51 | printf(p, stack_addr, branch_taken, bitmap_index, struct_registers); 52 | 53 | // if branch not taken just return 54 | if (branch_taken ) { 55 | int ret_index = dichotomic_search(bitmap_index, gdbcov_list_indexes_size, gdbcov_list_indexes); // -1 if was not found 56 | 57 | if (ret_index < 0) { 58 | printf(p3, bitmap_index); 59 | exit(-1); 60 | } 61 | 62 | // printf(p2, ret_index, *((unsigned long long *)stack_addr+5)); 63 | *((unsigned long long *)stack_addr + 5) = ((unsigned long long)*((unsigned int *)gdbcov_list_jump + ret_index)) + gdbcov_base_address; 64 | printf(p2, ret_index, *((unsigned long long *)stack_addr+5)); 65 | // printf(p2, ret_index, *((unsigned int *)gdbcov_list_jump + ret_index)); 66 | 67 | } 68 | 69 | // in teoria se tutto va bene non deve rompersi nulla ... :) 70 | 71 | } 72 | 73 | 74 | 75 | @@post_func@@ 76 | // Ignore section for internal functions 77 | 78 | @@pre_func@@ 79 | // Ignore section for internal functions 80 | 81 | -------------------------------------------------------------------------------- /plugins/code_cov/gdbcov_init.c.bleed: -------------------------------------------------------------------------------- 1 | --skip-- 2 | 3 | #+Function_name: gdbcov 4 | #+Description: Initialize gdb coverage datas 5 | 6 | 7 | 8 | --declare-- 9 | 10 | @@types@@ 11 | // Ignore section for internal functions 12 | 13 | @@vars@@ 14 | // Here we declare only static vars and strings 15 | 16 | __static__ gdbcov_init; 17 | __static__ gdbcov_shmem_key; 18 | __static__ gdbcov_shmem_id; 19 | __static__ gdbcov_shmem_flg; 20 | __static__ gdbcov_shmem_size; 21 | __static__ gdbcov_base_address; 22 | 23 | __static__ gdbcov_pidagent; 24 | __static__ gdbcov_pidmain; 25 | 26 | char * err1 = "Error during shmget on key %d\n"; 27 | char * msg1 = "Agent spawned\n"; 28 | char * msg2 = "Main finishing gdbcov_setup's init routine\n"; 29 | char * msg3 = "Return address given: %p\n"; 30 | char * msg4 = "Main exiting gdbcov_setup\n"; 31 | 32 | 33 | @@external-functions@@ 34 | 35 | shmget(2); 36 | printf(2); 37 | exit(1); 38 | getpid(0); 39 | fork(0); 40 | sleep(1); 41 | 42 | 43 | --code-- 44 | 45 | @@function@@ 46 | 47 | 48 | #+define: 49 | // here we insert includes and struct typedf etc. 50 | // - we can have only one "define" sub-section 51 | #include 52 | 53 | #include 54 | #include 55 | 56 | 57 | 58 | #+function: 59 | 60 | 61 | int gdbcov_setup(unsigned long long ret_addr) { 62 | 63 | int shmid; 64 | 65 | if (! *gdbcov_init ) { 66 | 67 | shmid = shmget(*gdbcov_shmem_key, *gdbcov_shmem_size, IPC_CREAT | *gdbcov_shmem_flg ); 68 | if (shmid < 0) { 69 | printf(err1, *gdbcov_shmem_key); 70 | exit(1); 71 | } 72 | 73 | *gdbcov_shmem_id = shmid; 74 | 75 | *gdbcov_pidmain = getpid(); 76 | *gdbcov_init = 1; 77 | 78 | *gdbcov_pidagent = fork(); 79 | if ( *gdbcov_pidagent == 0) { 80 | sleep(2); 81 | printf(msg1); 82 | exit(0); 83 | } 84 | 85 | wait(NULL); 86 | printf(msg2); 87 | 88 | } 89 | 90 | printf(msg3, ret_addr); 91 | 92 | unsigned long long offset_ret_addr = ret_addr - *gdbcov_base_address; 93 | printf(msg4); 94 | 95 | return 0; 96 | } 97 | 98 | 99 | 100 | @@post_func@@ 101 | // Ignore section for internal functions 102 | 103 | @@pre_func@@ 104 | // Ignore section for internal functions 105 | 106 | -------------------------------------------------------------------------------- /plugins/code_cov/readme.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | - From gdb 4 | 5 | ``` 6 | hook-got-inline --data --create ./plugins/code_cov/gdbcov_data.c.bleed 7 | 8 | hook-got-inline --create ./plugins/code_cov/gdbcov_init.c.bleed 9 | 10 | hook-got-inline --create ./plugins/code_cov/gdbcov_entrypoint[x86_64].c.bleed 11 | 12 | hook-got-inline --compile gdbcov.pre_func 13 | 14 | hook-got-inline --inject gdbcov.pre_func fork 15 | 16 | ``` 17 | 18 | 19 | - On traced process 20 | 21 | ``` 22 | $ ls 23 | 24 | Main finishing gdbcov_setup's init routine 25 | ... 26 | Agent spawned 27 | ... 28 | ``` 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /proc_dump/proc_dump.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "proc_dump.h" 9 | 10 | 11 | /* author: tin-z 12 | * some inspiration from https://unix.stackexchange.com/questions/6301/how-do-i-read-from-proc-pid-mem-under-linux 13 | * 14 | * */ 15 | 16 | ssize_t read(int fd, void *buf, size_t count); 17 | off_t lseek(int fd, off_t offset, int whence); 18 | pid_t waitpid(pid_t pid, int *wstatus, int options); 19 | int strcmp(const char *s1, const char *s2); 20 | size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream); 21 | int fclose(FILE *stream); 22 | int close(int fd); 23 | 24 | 25 | 26 | int main(int argc, char *argv[]){ 27 | 28 | int ptrace_mode = 0; 29 | 30 | if(argc<4) { 31 | printf("Usage %s [--ptrace]\n", argv[0]); 32 | return 255; 33 | } else if(argc==5) { 34 | if(!strcmp(argv[4], "--ptrace")){ 35 | ptrace_mode = 1; 36 | } 37 | } 38 | 39 | unsigned int pid = atoi(argv[1]); 40 | unsigned long long offset = atoll(argv[2]); 41 | unsigned int length = atoll(argv[3]); 42 | int retcode = 0; 43 | 44 | printf("[-] Inserted pid:%d, offset:%llu, length:%d -- ptrace_mode:%d\n", pid, offset, length, ptrace_mode); 45 | 46 | char * data_readen = read_memory(ptrace_mode, pid, offset, length, &retcode); 47 | 48 | if (!retcode){ 49 | puts("[+] Done!\n"); 50 | return 0; 51 | } 52 | 53 | puts("[x] Error\n"); 54 | return 255; 55 | } 56 | 57 | 58 | 59 | void write_memory(unsigned int pid, unsigned long long offset, unsigned int length, char * buf, int * retcode) { 60 | char out_mem_file_name[256] = {0}; 61 | snprintf(out_mem_file_name, 255, "%d_%llx_%llx", pid, offset, offset+length); 62 | FILE * write_fd = fopen(out_mem_file_name, "wb"); 63 | int rets = 0; 64 | *retcode = -1; 65 | 66 | if (rets = fwrite(buf, 1, length, write_fd), rets >= 0) { 67 | printf("[+] Success, copy-write %d bytes to file %s !\n", rets, out_mem_file_name); 68 | fclose(write_fd); 69 | *retcode = 0; 70 | 71 | } else { 72 | printf("[x] Can't write %d bytes to file %s\n", rets, out_mem_file_name); 73 | } 74 | } 75 | 76 | 77 | 78 | char * read_memory(int ptrace_mode, unsigned int pid, unsigned long long offset, unsigned int length, int * retcode) { 79 | 80 | char mem_file_name[256] = {0}; 81 | char * buf = (char *)malloc(length + 4 + 1); 82 | int rets = 0; 83 | 84 | *retcode = -1; 85 | 86 | if (!buf){ 87 | printf("[x] Fail on malloc %d\n", length); 88 | return NULL; 89 | } 90 | 91 | snprintf(mem_file_name, 255, "/proc/%d/mem", pid); 92 | int mem_fd = open(mem_file_name, O_RDONLY); 93 | 94 | if (mem_fd > 0) { 95 | 96 | if (ptrace_mode) { 97 | ptrace(PTRACE_ATTACH, pid, NULL, NULL); 98 | waitpid(pid, NULL, 0); 99 | } 100 | 101 | if ( rets = lseek(mem_fd, offset, SEEK_SET), rets >= 0) { 102 | 103 | if ( rets = read(mem_fd, buf, length), rets >= 0) { 104 | printf("[+] Success, read %d bytes!\n", rets); 105 | *retcode = 0; 106 | 107 | } else { 108 | printf("[x] Fail on read %d bytes\n", length); 109 | } 110 | } else { 111 | printf("[x] Fail on lseek to %llu\n", offset); 112 | } 113 | 114 | if (ptrace_mode) { 115 | ptrace(PTRACE_DETACH, pid, NULL, NULL); 116 | } 117 | 118 | close(mem_fd); 119 | 120 | } else { 121 | printf("[x] Fail on open %s\n", mem_file_name); 122 | } 123 | 124 | return buf; 125 | } 126 | 127 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /proc_dump/proc_dump.h: -------------------------------------------------------------------------------- 1 | 2 | void write_memory(unsigned int pid, unsigned long long offset, unsigned int length, char * buf, int * retcode); 3 | char * read_memory(int ptrace_mode, unsigned int pid, unsigned long long offset, unsigned int length, int * retcode); 4 | 5 | -------------------------------------------------------------------------------- /proc_dump/proc_dump.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import re 3 | maps_file = open("/proc/self/maps", 'r') 4 | mem_file = open("/proc/self/mem", 'rb', 0) 5 | output_file = open("self.dump", 'wb') 6 | for line in maps_file.readlines(): # for each mapped region 7 | m = re.match(r'([0-9A-Fa-f]+)-([0-9A-Fa-f]+) ([-r])', line) 8 | if m.group(3) == 'r': # if this is a readable region 9 | start = int(m.group(1), 16) 10 | end = int(m.group(2), 16) 11 | mem_file.seek(start) # seek to region start 12 | 13 | try : 14 | chunk = mem_file.read(end - start) # read region contents 15 | except OSError as ex: 16 | print(ex) 17 | print(f"Error on address/length {end:x} - {start:x}") 18 | break 19 | 20 | output_file.write(chunk) # dump contents to standard output 21 | 22 | maps_file.close() 23 | mem_file.close() 24 | output_file.close() 25 | 26 | -------------------------------------------------------------------------------- /proc_dump/readme.md: -------------------------------------------------------------------------------- 1 | 2 | ## TODO 3 | 4 | - Example of how to dump/restore memory of a process 5 | * Might be usefull in case the kernel does not support write_process_memory and read_process_memory syscalls 6 | 7 | - `proc_dump.py` python version 8 | 9 | - C version 10 | 11 | ``` 12 | # compile 13 | gcc -no-pie -static proc_dump.c -o main 14 | 15 | # usage 16 | ./main [--ptrace] 17 | 18 | ``` 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | 4 | if [ "$1" == "--clean" ]; then 5 | rm -rf keystone keystone_repo LIEF lief.so r2pipe 6 | exit 1 7 | fi 8 | 9 | ### 10 | ## 0. Prereq 11 | # 12 | 13 | if [ -z "$PYTHON_VER" ]; then 14 | echo "[!] Before continuing change/declare the 'PYTHON_VER' env var to the version of python which you intend to build on (refer to gdb python api version)" 15 | exit 1 16 | fi 17 | 18 | if [ -z "`which unzip`" ]; then 19 | echo "[!] 'unzip' is no present, install it before continuing" 20 | exit 1 21 | fi 22 | 23 | if [ -z "`which cmake`" ]; then 24 | echo "[!] 'cmake' is no present, install it before continuing" 25 | exit 1 26 | fi 27 | 28 | if [ -z "`which readelf`" ]; then 29 | echo "[!] 'readelf' is no present, install it before continuing" 30 | exit 1 31 | fi 32 | 33 | if [ -z "`which radare2`" ]; then 34 | echo "[!] 'radare2' is no present, install it before continuing" 35 | echo "" 36 | echo "wget https://github.com/radareorg/radare2/releases/download/5.5.0/radare2_5.5.0_amd64.deb" 37 | echo "dpkg -i ./radare2_5.5.0_amd64.deb" 38 | echo "" 39 | echo "Set file '~/.radare2rc' as" 40 | # Show comments at right of disassembly if they fit in screen 41 | echo "e asm.cmt.right=true" 42 | # Shows pseudocode in disassembly. Eg mov eax, str.ok = > eax = str.ok 43 | #echo "e asm.pseudo = true" 44 | # Solarized theme 45 | echo "eco solarized" 46 | echo "e scr.utf8 = true" 47 | echo "" 48 | exit 1 49 | fi 50 | 51 | if [ -z "`which virtualenv`" ]; then 52 | echo "[!] Install: 'sudo apt-get install ${PYTHON_VER}-distutils ${PYTHON_VER}-setuptools virtualenv'" 53 | echo "" 54 | exit 1 55 | fi 56 | 57 | if [ ! -f .gdbinit-gef.py ]; then 58 | wget -O ./.gdbinit-gef.py -q https://gef.blah.cat/py 59 | fi 60 | 61 | 62 | ### 63 | ## 1. Instal keystone python module 64 | # 65 | if [ ! -d keystone_repo ]; then 66 | echo "[!] 'keystone' is no present ... installing it now (only on current folder)" 67 | 68 | if [ -z "$KEYSTONE_VER" ]; then 69 | echo "[!] Before continuing the variable 'KEYSTONE_VER' should be declared (e.g export KEYSTONE_VER=\"0.9.2\") ...quit" 70 | exit 1 71 | fi 72 | 73 | wget https://github.com/keystone-engine/keystone/archive/refs/tags/${KEYSTONE_VER}.zip 74 | unzip ${KEYSTONE_VER}.zip 75 | rm ${KEYSTONE_VER}.zip 76 | 77 | mv keystone-${KEYSTONE_VER} keystone_repo 78 | cd keystone_repo/bindings/python 79 | 80 | $PYTHON_VER setup.py build 81 | cd ../../.. 82 | mv keystone_repo/bindings/python/keystone . 83 | fi 84 | 85 | 86 | ### 87 | ## 2. Check LIEF 88 | # 89 | #if [ ! -f lief.so ]; then 90 | # echo "[!] 'LIEF' is no present ... installing it now (only on current folder)" 91 | # 92 | # wget https://github.com/lief-project/LIEF/archive/refs/tags/${LIEF_VER}.zip 93 | # unzip ${LIEF_VER}.zip 94 | # rm ${LIEF_VER}.zip 95 | # 96 | # mv LIEF-${LIEF_VER} LIEF 97 | # cd LIEF 98 | # $PYTHON_VER setup.py build 99 | # 100 | # if [ $? != 0 ]; then 101 | # echo "[!] error while building LIEF module" 102 | # echo " \---> Run 'sudo apt-get install ${PYTHON_VER}-distutils ${PYTHON_VER}-setuptools'" 103 | # exit 104 | # fi 105 | # 106 | # cd .. 107 | # mv LIEF/build/lief.so . 108 | # 109 | #fi 110 | 111 | 112 | #if [ ! -d keystone ]; then 113 | # echo "[!] 'keystone' is no present ... installing it now (only on current folder)" 114 | # rm -rf object_virtualenv; mkdir object_virtualenv && virtualenv --python=$PYTHON_VER object_virtualenv &&\ 115 | # source object_virtualenv/bin/activate &&\ 116 | # pip install keystone &&\ 117 | # deactivate 118 | # 119 | # find -name keystone -type d | while read x; do 120 | # mv "$x" keystone 121 | # done 122 | # 123 | # rm -rf object_virtualenv 124 | #fi 125 | 126 | if [ ! -f lief.so ]; then 127 | echo "[!] 'LIEF' is no present ... installing it now (only on current folder)" 128 | rm -rf object_virtualenv; mkdir object_virtualenv && virtualenv --python=$PYTHON_VER object_virtualenv &&\ 129 | source object_virtualenv/bin/activate &&\ 130 | pip install lief==${LIEF_VER} &&\ 131 | deactivate 132 | 133 | find -name "lief*.so" -type f | while read x; do 134 | mv "$x" lief.so 135 | done 136 | 137 | rm -rf object_virtualenv 138 | fi 139 | 140 | if [ ! -d r2pipe ]; then 141 | echo "[!] 'r2pipe' is no present ... installing it now (only on current folder)" 142 | rm -rf object_virtualenv; mkdir object_virtualenv && virtualenv --python=$PYTHON_VER object_virtualenv &&\ 143 | source object_virtualenv/bin/activate &&\ 144 | pip install r2pipe &&\ 145 | deactivate 146 | 147 | find -name r2pipe -type d | while read x; do 148 | mv "$x" r2pipe 149 | done 150 | 151 | rm -rf object_virtualenv 152 | fi 153 | 154 | 155 | echo "[+] All requirements are met" 156 | echo "" 157 | echo "Now we need to declare env 'GDBLEED_HOME', you can do it like so:" 158 | echo "echo \"export GDBLEED_HOME=$PWD\" >> ~/.bashrc" 159 | echo "export GDBLEED_HOME=$PWD" 160 | 161 | 162 | echo "[+] Done!" 163 | 164 | -------------------------------------------------------------------------------- /tests/gdb_scripts/test1.gdb: -------------------------------------------------------------------------------- 1 | 2 | source ./tests/gdbinit-gef.py 3 | start 4 | source gdbleed.py 5 | 6 | # cmd-lists 7 | got-entries 8 | base-address 9 | binary-name 10 | binary-name-local 11 | got-entries 12 | 13 | quit 14 | 15 | -------------------------------------------------------------------------------- /tests/gdb_scripts/test2.gdb: -------------------------------------------------------------------------------- 1 | 2 | source ./tests/gdbinit-gef.py 3 | start 4 | source gdbleed.py 5 | 6 | 7 | # dump memory 8 | dump-all 9 | 10 | # map memory 11 | info proc mappings 12 | mem-map 0 13 | mem-map 0x40000 0x2000 14 | info proc mappings 15 | 16 | # unmap memory 17 | mem-unmap 0x40000 0x2000 18 | info proc mappings 19 | 20 | quit 21 | -------------------------------------------------------------------------------- /tests/gdb_scripts/test3.gdb: -------------------------------------------------------------------------------- 1 | 2 | source ./tests/gdbinit-gef.py 3 | start 4 | source gdbleed.py 5 | 6 | hook-got fork sleep 20 7 | b sleep 8 | 9 | c 10 | # you will see that we reach the sleep breakpoint, and 11 | # that if we didn't hook with sleep, then no sleep breakpoint would have been reached 12 | 13 | quit 14 | -------------------------------------------------------------------------------- /tests/test1.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | 4 | if [ ! -f gdbleed.py ]; then 5 | echo "[x] error, launch the script in the repository folder ...quit" 6 | exit -1 7 | fi 8 | 9 | 10 | gdb -q -nx -x ./tests/gdb_scripts/test1.gdb /bin/bash 11 | 12 | echo "# TEST-1 : test CLI commands [done]" 13 | 14 | -------------------------------------------------------------------------------- /tests/test2.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | 4 | if [ ! -f gdbleed.py ]; then 5 | echo "[x] error, launch the script in the repository folder ...quit" 6 | exit -1 7 | fi 8 | 9 | 10 | gdb -q -nx -x ./tests/gdb_scripts/test2.gdb /bin/bash 11 | 12 | echo "# TEST-2 : test memory-related CLI commands [done]" 13 | 14 | -------------------------------------------------------------------------------- /tests/test3.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | 4 | if [ ! -f gdbleed.py ]; then 5 | echo "[x] error, launch the script in the repository folder ...quit" 6 | exit -1 7 | fi 8 | 9 | 10 | gdb -q -nx -x ./tests/gdb_scripts/test3.gdb /bin/bash 11 | 12 | echo "# TEST-3 : test simple hooking, we hook fork method and instead execute the sleep method [done]" 13 | 14 | -------------------------------------------------------------------------------- /tracer/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | The classes inside this module use GDB python API to perform 3 | binary tracing. 4 | 5 | This module was developed only for educational purpose, no 6 | future work will be done. 7 | """ 8 | -------------------------------------------------------------------------------- /tracer/extensions.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | This module will include custom breakpoints 5 | 6 | Custom breakpoints might also include lambda rules which are used to alert if suspicious data is seen 7 | """ 8 | 9 | import gdb 10 | import inspect 11 | from tracer.trace_all import NetFinishBreakpoint, NetBreakpoint, get_content 12 | 13 | from utils.utilsX import format_string_return 14 | from utils.colorsX import * 15 | 16 | 17 | ### 18 | ## Utils 19 | # 20 | def get_stuff(self, n_arg=1): 21 | """ 22 | self : l'object NetFinishBreakpoint o NetBreakpoint che ci ha chiamato 23 | n_arg : quanti argomenti ci aspettiamo (per ora max 4) 24 | 25 | ritorna in ordine: 26 | - nome funzione 27 | - lista argomenti 28 | - valore di ritorno 29 | """ 30 | #fname = inspect.getframeinfo(inspect.currentframe().f_back).function 31 | fname = self.fname 32 | ret_value = None 33 | if isinstance(self, NetFinishBreakpoint) : 34 | args = self.args 35 | ret_value = self.get_ret()[0] 36 | else : 37 | args = self.get_arg(n_arg) 38 | return fname, args, ret_value 39 | 40 | 41 | ### 42 | ## Custom breakpoint methods 43 | # 44 | # - from here we add custom breakpoints. we follow the rule 'fname' e.g. 'recv' to custom breakpoint on 'recv' calls 45 | 46 | def _w_recv(self): 47 | """ 48 | ssize_t recv(int sockfd, void *buf, size_t len, int flags); 49 | """ 50 | fname, args, ret_value = get_stuff(self,4) 51 | # 52 | fd_t, buf_t, len_t, flags_t = args 53 | 54 | self.details["slog"].append( 55 | PTR['H2'](fname) +\ 56 | "(sockfd:{}, buf:0x{:x}, len:{}, flags:{}) -> ret:{}\n".format( 57 | fd_t, buf_t, len_t, flags_t, ret_value 58 | ) +\ 59 | " \---> buf:{}\n".format( 60 | format_string_return(gdb.execute("x/s 0x{:x}".format(buf_t), to_string=True)) 61 | ) 62 | ) 63 | 64 | 65 | def _w_recvmsg(self): 66 | """ 67 | ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags); 68 | """ 69 | fname, args, ret_value = get_stuff(self,3) 70 | # 71 | sockfd_t, msg_t, flags_t = args 72 | 73 | msg_iov = get_content(msg_t + (self.capsize * 2), self.word, to_int=True) 74 | iov_base = get_content(msg_iov, self.word, to_int=True) 75 | iov_len = get_content(msg_iov + self.capsize, self.word, to_int=True) 76 | 77 | rets_msg_iov = hexdump.hexdump(iov_base, config.hexdump_max_length) 78 | 79 | self.details["slog"].append( 80 | PTR['H2'](fname) +\ 81 | "(sockfd:{}, msg:0x{:x}, flags:{}) -> ret:{}\n".format( 82 | sockfd_t, msg_t, flags_t, ret_value 83 | ) +\ 84 | " \---> hexdump 'msg_iov' content at address 0x{:x}, 'iov_base' at address 0x{:x} :\n".format( 85 | msg_iov, iov_base 86 | ) +\ 87 | "{}\n".format(rets_msg_iov) 88 | ) 89 | 90 | 91 | def _w_recvfrom(self): 92 | """ 93 | ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, 94 | struct sockaddr *src_addr, socklen_t *addrlen); 95 | """ 96 | recv(self) 97 | 98 | 99 | def _w_system(self): 100 | fname, args, ret_value = get_stuff(self,1) 101 | # 102 | command = args[0] 103 | self.details["slog"].append( 104 | PTR['H2'](fname) +\ 105 | "(command:0x{:x})\n".format(command) +\ 106 | " \---> command:{}\n".format( 107 | format_string_return(gdb.execute("x/s 0x{:x}".format(command), to_string=True)) 108 | ) 109 | ) 110 | 111 | 112 | 113 | 114 | ##### Keep these lines at the end of the file as they are 115 | def ex_func(): 116 | pass 117 | 118 | func_class = type(ex_func) 119 | 120 | bp_map = {k.split("_w_")[1]:v for k,v in inspect.currentframe().f_locals.items() if k.startswith("_w_") and isinstance(v, func_class)} 121 | 122 | -------------------------------------------------------------------------------- /tracer/lambda_rules.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from utils.colorsX import * 4 | from utils.utilsX import * 5 | 6 | 7 | 8 | class lambdaRule : 9 | """ 10 | Check function's arguments/state during execution 11 | """ 12 | def __init__(self, flambda, fname, desc): 13 | self.flambda = flambda 14 | self.fname = fname 15 | self.desc = desc 16 | 17 | def check(self, args, ret_value): 18 | self.last_args = args 19 | self.last_ret_value = ret_value 20 | return self.flambda(args, ret_value) 21 | 22 | def report(self): 23 | return WARNING("[!]") + PTR['L'](" Warning") + ": {}\n {}({}) -> {}\n".format( 24 | self.desc, PTR['H2'](self.fname), ",".join([hex(x) for x in self.last_args]), self.last_ret_value 25 | ) 26 | 27 | def skel_eval_a_lambdaRule(args, ret_value): 28 | pass 29 | 30 | 31 | class alertRules : 32 | """ 33 | lambdaRule sets 34 | """ 35 | def __init__(self) : 36 | self.rules = {} 37 | 38 | def add(self, lambda_rule) : 39 | fname = lambda_rule.fname 40 | if fname not in self.rules : 41 | self.rules[fname] = [] 42 | self.rules[fname].append(lambda_rule) 43 | 44 | def check(self, fname, args=None, ret_value=None) : 45 | if fname not in self.rules : 46 | self.rules[fname] = [] 47 | for check_i in self.rules[fname] : 48 | if check_i.check(args, ret_value) : 49 | slog.append(check_i.report()) 50 | 51 | 52 | -------------------------------------------------------------------------------- /tracer/trace_all.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | The module does implement a prototype of tracing functions using GDB python API: 5 | - NetBreakpoint class trace the function before the execution 6 | - NetFinishBreakpoint class trace the function after finishing the execution 7 | 8 | If we want to add functions to trace follow ./docs/tracer_tutorial.md 9 | """ 10 | 11 | 12 | 13 | import gdb 14 | import inspect 15 | import config 16 | 17 | from core.march import * 18 | from tracer.lambda_rules import * 19 | from utils.utilsX import * 20 | from utils import hexdump 21 | 22 | 23 | ### # 24 | ## Utils ## 25 | # ### 26 | 27 | blacklist_bp = [ 28 | "system","fork","execve","execl","execlp","execle",\ 29 | "execv","execvp","execvpe" 30 | ] 31 | 32 | 33 | def get_content(ptr, word, to_int=False) : 34 | return format_string_return(gdb.execute("x/" + word + "0x{:x}".format(ptr), to_string=True), to_int=True) 35 | 36 | 37 | ### # 38 | ## Classes ## 39 | # ### 40 | 41 | class TemplateBreakpoint : 42 | 43 | def __init__ (self, addr, fname, details): 44 | self.fname = fname 45 | self.addr = addr 46 | self.details = details 47 | self.__select_arch() 48 | 49 | def get_details(self, k) : 50 | return self.details[k] 51 | 52 | @property 53 | def arch(self) : 54 | return self.get_details("arch") 55 | 56 | @property 57 | def capsize(self) : 58 | return self.get_details("capsize") 59 | 60 | @property 61 | def word(self) : 62 | return self.get_details("word") 63 | 64 | @property 65 | def isa(self) : 66 | return self.get_details("isa") 67 | 68 | @property 69 | def running(self) : 70 | return self.get_details("running") 71 | 72 | def set_running(self) : 73 | self.details["running"] = True 74 | 75 | 76 | def __select_arch(self) : 77 | global ARCH_supported 78 | assert(self.arch in ARCH_supported) 79 | self.reg_args = archs[self.arch]['args'] 80 | self.reg_ret_val = archs[self.arch]['ret_val'] 81 | self.reg_ret = archs[self.arch]['ret'] 82 | 83 | def get_arg(self, arg_range): 84 | output = [] 85 | for i in range(arg_range) : 86 | output.append( 87 | int(gdb.parse_and_eval(self.reg_args[i])) 88 | ) 89 | return output 90 | 91 | def get_ret(self): 92 | output = [] 93 | output.append(int(gdb.parse_and_eval(self.reg_ret_val[0]))) 94 | return output 95 | 96 | def hexdump_arg(self, n_arg=4, tail="[HIT]", print_ret=False): 97 | 98 | if "args" in dir(self) : 99 | args = self.args[:n_arg] 100 | else : 101 | args = self.get_arg(n_arg) 102 | output = [] 103 | 104 | output.append( 105 | PTR['H2']("{}:0x{:x} ".format(self.fname, self.addr)) + tail 106 | ) 107 | 108 | for i,x in enumerate(args): 109 | output.append(" \--->arg{}:".format(i) + PTR["L"]("0x{:x}".format(x))) 110 | try : 111 | output.append(hexdump.hexdump(x, config.hexdump_max_length)) 112 | except : 113 | pass 114 | 115 | if print_ret : 116 | for i,x in enumerate(self.get_ret()) : 117 | output.append(" \--->ret{}:".format(i) + PTR["L"]("0x{:x}".format(x))) 118 | try : 119 | output.append(hexdump.hexdump(x, config.hexdump_max_length)) 120 | except : 121 | pass 122 | 123 | self.details["slog"].append( 124 | "\n".join(output) + "\n" 125 | ) 126 | 127 | return False 128 | 129 | 130 | 131 | class NetFinishBreakpoint (gdb.FinishBreakpoint, TemplateBreakpoint) : 132 | 133 | def __init__ (self, addr, fname, details, args): 134 | gdb.FinishBreakpoint.__init__(self, gdb.newest_frame(), internal=True) 135 | TemplateBreakpoint.__init__ (self, addr, fname, details) 136 | self.silent = True 137 | self.args = args 138 | 139 | def stop(self): 140 | global bp_map 141 | if self.fname in bp_map : 142 | bp_map[self.fname](self) 143 | else : 144 | self.hexdump_arg(tail="[RET]", print_ret=True) 145 | 146 | NetBreakpoint.post_rules.check( 147 | self.fname, self.args, self.get_ret()[0] 148 | ) 149 | return False 150 | 151 | 152 | class NetBreakpoint(gdb.Breakpoint, TemplateBreakpoint) : 153 | 154 | pre_rules = alertRules() 155 | post_rules = alertRules() 156 | 157 | def __init__(self, spec, addr, fname, details, trace_return=True): 158 | 159 | gdb.Breakpoint.__init__( 160 | self, spec, gdb.BP_BREAKPOINT, internal=True 161 | ) 162 | TemplateBreakpoint.__init__ (self, addr, fname, details) 163 | self.spec = spec 164 | self.trace_return = trace_return 165 | 166 | 167 | def stop(self): 168 | global bp_map 169 | global blacklist_bp 170 | if not self.trace_return or self.fname in blacklist_bp : 171 | if self.fname in bp_map : 172 | bp_map[self.fname](self) 173 | else : 174 | self.hexdump_arg() 175 | 176 | else : 177 | n = 4 178 | args = self.get_arg(n) 179 | NetFinishBreakpoint(self.addr, self.fname, self.details, args) 180 | 181 | NetBreakpoint.pre_rules.check(self.fname, self.get_arg(4)) 182 | return False 183 | 184 | 185 | import tracer.extensions 186 | 187 | bp_map = tracer.extensions.bp_map 188 | 189 | -------------------------------------------------------------------------------- /utils/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Common functions 3 | """ 4 | -------------------------------------------------------------------------------- /utils/colorsX.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Color stuff 5 | """ 6 | 7 | EC = '\x1b[0m' 8 | BOLD = '\x1b[1m' 9 | INIT = {'f':30,'b':40,'hf':90,'hb':100} 10 | COLORS = ("BLACK",0),("RED",1),("GREEN",2),("YELLOW",3),("BLUE",4),("CYAN",6) 11 | for x,y in COLORS : 12 | globals()[x] = {k:"\x1b[{}m".format(v+y) for k,v in INIT.items()} 13 | 14 | FAIL = lambda x : BOLD + RED['f'] + x + EC 15 | WARNING = lambda x : BOLD + YELLOW['b'] + BLACK['f'] + x + EC 16 | PTR = {"H":lambda x : BLUE['f'] + x + EC, "S": lambda x : YELLOW['hf'] + x + EC, "L" : lambda x : RED['f'] + x + EC } 17 | PTR.update( { "HEAP":PTR["H"], "STACK":PTR["S"], "LIBC":PTR["L"] } ) 18 | PTR.update( { "H1":lambda x : BLUE['hf'] + x + EC, "H2":lambda x : CYAN['f'] + x + EC, "H3":lambda x : CYAN['hf'] + x + EC }) 19 | PTR.update( { "F1":lambda x : BOLD + RED['f'] + BLACK['b'] + "|{}|".format(x) + EC } ) 20 | PTR.update( { "F2":lambda x : BOLD + GREEN['f'] + BLACK['b'] + "|{}|".format(x) + EC } ) 21 | 22 | 23 | -------------------------------------------------------------------------------- /utils/ctypes_stuff.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | TODO 5 | """ 6 | 7 | import ctypes 8 | 9 | 10 | # extra types 11 | c_socklen_t = ctypes.c_uint 12 | 13 | 14 | # classes 15 | 16 | class iovec(ctypes.Structure): 17 | _fields_ = [ 18 | ("iov_base", ctypes.c_void_p), 19 | ("iov_len", ctypes.c_ulong) 20 | ] 21 | 22 | 23 | class msghdr(ctypes.Structure): 24 | """ 25 | ref, https://ivanzz1001.github.io/records/post/linux/2017/11/04/linux-msghdr 26 | """ 27 | _fields_ = [ 28 | ("msg_name", ctypes.c_void_p), 29 | ("msg_namelen", c_socklen_t), 30 | ("msg_iov", ctypes.POINTER(iovec)), 31 | ("msg_iovlen", ctypes.c_size_t), 32 | ("msg_control", ctypes.c_void_p), 33 | ("msg_controllen", ctypes.c_size_t), 34 | ("msg_flags", ctypes.c_int) 35 | ] 36 | 37 | 38 | class user_regs_structX64(ctypes.Structure): 39 | """ 40 | grep "struct user_regs_struct" /usr/include/sys/user.h 41 | """ 42 | _fields_ = [ 43 | ("r15", ctypes.c_ulonglong), 44 | ("r14", ctypes.c_ulonglong), 45 | ("r13", ctypes.c_ulonglong), 46 | ("r12", ctypes.c_ulonglong), 47 | ("rbp", ctypes.c_ulonglong), 48 | ("rbx", ctypes.c_ulonglong), 49 | ("r11", ctypes.c_ulonglong), 50 | ("r10", ctypes.c_ulonglong), 51 | ("r9", ctypes.c_ulonglong), 52 | ("r8", ctypes.c_ulonglong), 53 | ("rax", ctypes.c_ulonglong), 54 | ("rcx", ctypes.c_ulonglong), 55 | ("rdx", ctypes.c_ulonglong), 56 | ("rsi", ctypes.c_ulonglong), 57 | ("rdi", ctypes.c_ulonglong), 58 | ("orig_rax", ctypes.c_ulonglong), 59 | ("rip", ctypes.c_ulonglong), 60 | ("cs", ctypes.c_ulonglong), 61 | ("eflags", ctypes.c_ulonglong), 62 | ("rsp", ctypes.c_ulonglong), 63 | ("ss", ctypes.c_ulonglong), 64 | ("fs_base", ctypes.c_ulonglong), 65 | ("gs_base", ctypes.c_ulonglong), 66 | ("ds", ctypes.c_ulonglong), 67 | ("es", ctypes.c_ulonglong), 68 | ("fs", ctypes.c_ulonglong), 69 | ("gs", ctypes.c_ulonglong), 70 | ] 71 | 72 | 73 | class user_regs_struct(ctypes.Structure): 74 | """ 75 | grep "struct user_regs_struct" /usr/include/sys/user.h 76 | """ 77 | _fields_ = [ 78 | ("ebx", ctypes.c_uint32), 79 | ("ecx", ctypes.c_uint32), 80 | ("edx", ctypes.c_uint32), 81 | ("esi", ctypes.c_uint32), 82 | ("edi", ctypes.c_uint32), 83 | ("ebp", ctypes.c_uint32), 84 | ("eax", ctypes.c_uint32), 85 | ("xds", ctypes.c_uint32), 86 | ("xes", ctypes.c_uint32), 87 | ("xfs", ctypes.c_uint32), 88 | ("xgs", ctypes.c_uint32), 89 | ("orig_eax", ctypes.c_uint32), 90 | ("eip", ctypes.c_uint32), 91 | ("xcs", ctypes.c_uint32), 92 | ("eflags", ctypes.c_uint32), 93 | ("esp", ctypes.c_uint32), 94 | ("xss", ctypes.c_uint32), 95 | ] 96 | 97 | 98 | -------------------------------------------------------------------------------- /utils/gdb_utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import gdb 3 | import re 4 | 5 | 6 | def get_binary_name(details) : 7 | """ 8 | Get binary's name 9 | """ 10 | rets = gdb.execute("info proc cmdline", to_string=True) 11 | 12 | if "unable to open /proc file" in rets : 13 | details["qemu_usermode"] = True 14 | return get_binary_name_qemu(details) 15 | 16 | index_tmp = 1 17 | if "warning: " in rets.split("\n")[index_tmp] : 18 | index_tmp = 2 19 | 20 | binary_name = rets.split("\n")[index_tmp].split("=")[1].strip().replace("'","").split(" ")[0] 21 | 22 | if binary_name.startswith("-") : 23 | binary_name = binary_name[1:] 24 | 25 | binary_name_local = gdb.current_progspace().filename 26 | 27 | if details["binary_path"] : 28 | binary_name_local = details["binary_path"] 29 | return binary_name, binary_name_local 30 | 31 | msgs = [ 32 | "Which path-filename do you want to use? (select number, if you are using gdbserver you must select the local path file)" , 33 | "1. for '{}'".format(binary_name) , 34 | "2. for '{}'".format(binary_name_local) , 35 | "3. custom" 36 | ] 37 | 38 | err_msgs = [ 39 | "Invalid choice .. default choise is '{}'".format(binary_name) 40 | ] 41 | 42 | for x in msgs : 43 | print(x) 44 | if not details["slog"].is_stdout : 45 | details["slog"].append(x) 46 | 47 | select = input().strip() 48 | 49 | try : 50 | select = int(select) 51 | if select < 1 or select > 3 : 52 | raise Exception() 53 | except : 54 | print(err_msgs[0]) 55 | if not details["slog"].is_stdout : 56 | details["slog"].append(err_msgs[0]) 57 | select = 1 58 | 59 | if select == 1 : 60 | binary_name_local = binary_name 61 | 62 | elif select == 3 : 63 | binary_name_local = input("Insert path-filename of the binary: ").strip() 64 | 65 | details["binary_path"] = binary_name_local 66 | return binary_name, binary_name_local 67 | 68 | 69 | def get_libc_base_address(output_dict, details) : 70 | """ 71 | Get libc's base address and size 72 | """ 73 | output = [] 74 | output_tmp_dict = {} 75 | size = 0 76 | for v in output_dict.values() : 77 | if "/libc." in v.name or "/libc-" in v.name : 78 | output.append(v.addr) 79 | size += v.size 80 | output_tmp_dict.update({v.addr : v.name}) 81 | output.sort() 82 | 83 | if details["qemu_usermode"] : 84 | details["libc_path"] = output_tmp_dict[output[0]] 85 | 86 | return output[0], size 87 | 88 | 89 | def get_base_address(binary_name, binary_name_local, output_dict, details) : 90 | """ 91 | Get binary's base address and size 92 | """ 93 | output = [] 94 | size = 0 95 | for v in output_dict.values() : 96 | if v.name == binary_name or v.name == binary_name_local : 97 | output.append(v.addr) 98 | size += v.size 99 | output.sort() 100 | return (output[0], size) + get_libc_base_address(output_dict, details) 101 | 102 | 103 | def find_function_addr(fname, details) : 104 | """ 105 | Return address of a function, and its declaration 106 | """ 107 | match_with = r"\$[0-9]+ = (\(.*\))[ ]+(0x[A-f0-9]+)[ ]+.*" 108 | match_with_2 = r"\$[0-9]+ = (\{.*\})[ ]+(0x[A-f0-9]+)[ ]+.*" 109 | rets = gdb.execute("p &{}".format(fname), to_string=True).strip() 110 | rets_2 = re.search(match_with, rets) 111 | if not rets_2 : 112 | details["slog"].append("[!] can't find '{}' declaration".format(fname)) 113 | rets_2 = re.search(match_with_2, rets) 114 | rets = rets_2.groups() 115 | if rets : 116 | rets = ["", rets[1]] 117 | else : 118 | return None, None 119 | else : 120 | rets = rets_2.groups() 121 | decl = rets[0] 122 | addr = int(rets[1], 16) 123 | return (decl, addr) 124 | 125 | 126 | def search_string(addr, eaddr, find) : 127 | """ 128 | Search in memory string 129 | """ 130 | cmd = "find 0x{:x},0x{:x},\"{}\"".format(addr,eaddr,find) 131 | return gdb.execute(cmd, to_string=True).strip() 132 | 133 | 134 | def get_data_memory(word_size, addr) : 135 | """ 136 | Return value contained by memory area 137 | """ 138 | rets = gdb.execute("x/{} {}".format(word_size, addr), to_string=True).strip() 139 | rets = int(":".join(rets.split(":")[1:]).strip(), 16) 140 | return rets 141 | 142 | 143 | def getpid(details) : 144 | details["pid"] = gdb.selected_inferior().pid 145 | return details["pid"] 146 | 147 | 148 | 149 | def get_binary_name_qemu(details) : 150 | binary_name = gdb.current_progspace().filename 151 | binary_name_local = binary_name 152 | details["binary_path"] = binary_name_local 153 | return binary_name, binary_name_local 154 | 155 | 156 | 157 | def make_executable(details, addr, size, perm=7): 158 | size = size >> 12 << 12 159 | cmd = "call (int) mprotect({}, {}, {})".format(addr, size, perm) 160 | rets = gdb.execute(cmd, to_string=True).strip().split("=")[1].strip() 161 | ret_code = int(rets, 16) 162 | 163 | if ret_code != 0 : 164 | details["slog"].append( 165 | "[!] Can't make 'rwx' permissions on process memory in qemu user-mode ... DIY" 166 | ) 167 | return 168 | 169 | try : 170 | size = size + 0x1000 171 | cmd = "call (int) mprotect({}, {}, {})".format(addr, size, perm) 172 | rets = gdb.execute(cmd, to_string=True).strip().split("=")[1].strip() 173 | except : 174 | pass 175 | 176 | 177 | def gdbapi_write(addr, value_bytes): 178 | gdb.selected_inferior().write_memory(addr, value_bytes) 179 | 180 | 181 | 182 | -------------------------------------------------------------------------------- /utils/hexdump.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import gdb 4 | from curses.ascii import isgraph 5 | 6 | # copy and paste from https://stackoverflow.com/questions/9233095/memory-dump-formatted-like-xxd-from-gdb 7 | 8 | def groups_of(iterable, size, first=0): 9 | first = first if first != 0 else size 10 | chunk, iterable = iterable[:first], iterable[first:] 11 | while chunk: 12 | yield chunk 13 | chunk, iterable = iterable[:size], iterable[size:] 14 | 15 | class HexDump(gdb.Command): 16 | """ 17 | Gdb Memory dump formatted like the xxd output class 18 | """ 19 | def __init__(self): 20 | super (HexDump, self).__init__ ('hd', gdb.COMMAND_DATA) 21 | 22 | def invoke(self, arg, from_tty): 23 | argv = gdb.string_to_argv(arg) 24 | 25 | addr = gdb.parse_and_eval(argv[0]).cast( 26 | gdb.lookup_type('void').pointer()) 27 | if len(argv) == 2: 28 | try: 29 | bytes = int(gdb.parse_and_eval(argv[1])) 30 | except ValueError: 31 | raise gdb.GdbError('Byte count numst be an integer value.') 32 | else: 33 | bytes = 500 34 | 35 | inferior = gdb.selected_inferior() 36 | 37 | align = gdb.parameter('hex-dump-align') 38 | width = gdb.parameter('hex-dump-width') 39 | if width == 0: 40 | width = 16 41 | 42 | mem = inferior.read_memory(addr, bytes) 43 | pr_addr = int(str(addr), 16) 44 | pr_offset = width 45 | 46 | if align: 47 | pr_offset = width - (pr_addr % width) 48 | pr_addr -= pr_addr % width 49 | start=(pr_addr) & 0xff; 50 | 51 | 52 | print (' ' , end="") 53 | print (' '.join(['%01X' % (i&0x0f,) for i in range(start,start+width)]) , end="") 54 | print (' ' , end="") 55 | print (' '.join(['%01X' % (i&0x0f,) for i in range(start,start+width)]) ) 56 | 57 | for group in groups_of(mem, width, pr_offset): 58 | print ('0x%x: ' % (pr_addr,) + ' '*(width - pr_offset), end="") 59 | print (' '.join(['%02X' % (ord(g),) for g in group]) + \ 60 | ' ' * (width - len(group) if pr_offset == width else 0) + ' ', end="") 61 | print (' '*(width - pr_offset) + ' '.join( 62 | [chr( int.from_bytes(g, byteorder='big')) if isgraph( int.from_bytes(g, byteorder='big') ) or g == ' ' else '.' for g in group])) 63 | pr_addr += width 64 | pr_offset = width 65 | 66 | class HexDumpAlign(gdb.Parameter): 67 | def __init__(self): 68 | super (HexDumpAlign, self).__init__('hex-dump-align', 69 | gdb.COMMAND_DATA, 70 | gdb.PARAM_BOOLEAN) 71 | 72 | set_doc = 'Determines if hex-dump always starts at an "aligned" address (see hex-dump-width' 73 | show_doc = 'Hex dump alignment is currently' 74 | 75 | class HexDumpWidth(gdb.Parameter): 76 | def __init__(self): 77 | super (HexDumpWidth, self).__init__('hex-dump-width', 78 | gdb.COMMAND_DATA, 79 | gdb.PARAM_INTEGER) 80 | 81 | set_doc = 'Set the number of bytes per line of hex-dump' 82 | 83 | show_doc = 'The number of bytes per line in hex-dump is' 84 | 85 | HexDump() 86 | HexDumpAlign() 87 | HexDumpWidth() 88 | 89 | 90 | def hexdump(ptr, size) : 91 | rets = gdb.execute("hd 0x{:x} {}".format(ptr, size), to_string=True) 92 | return rets 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /utils/utilsX.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Utils classes 5 | """ 6 | import re 7 | from core import march 8 | import threading 9 | 10 | from config import slog_path 11 | 12 | ### # 13 | ## utils ## 14 | # ### 15 | 16 | slog_mutex = threading.Lock() 17 | 18 | class simpleLogger : 19 | """ 20 | Log class - singleton object where to save the output 21 | """ 22 | 23 | singleton = False 24 | singleton_obj = None 25 | 26 | def __init__(self, fp=None): 27 | if simpleLogger.singleton : 28 | print("Can't create the class, because it already exists") 29 | 30 | simpleLogger.singleton = True 31 | simpleLogger.singleton_obj = self 32 | 33 | self.is_stdout = True 34 | self.append_inner = self.print_to_stdout 35 | self.close = self.close_to_stdout 36 | 37 | if fp : 38 | self.fp = open(fp, "w") 39 | self.append_inner = self.prin_to_fp 40 | self.close = self.close_to_file 41 | self.is_stdout = False 42 | 43 | def append(self, out): 44 | global slog_mutex, cc 45 | slog_mutex.acquire() 46 | self.append_inner(out) 47 | slog_mutex.release() 48 | 49 | def print_to_fp(self, data): 50 | self.fp.write(data + "\n") 51 | 52 | def print_to_stdout(self, data): 53 | print(data) 54 | 55 | def close_to_stdout(): 56 | pass 57 | 58 | def close_to_file(): 59 | self.fp.close() 60 | 61 | def __del__(self): 62 | self.close() 63 | 64 | 65 | slog = simpleLogger(fp=slog_path) 66 | 67 | 68 | def format_string_return(data, to_int=False): 69 | rets = ":".join(data.split(":")[1:]).strip() 70 | if to_int : 71 | rets = int(rets, 16) 72 | return rets 73 | 74 | 75 | 76 | valid_sep = ["d","u","x","s","n","p"] 77 | valid_sep_dict = { "d":"p/d", "u":"p/u", "x":"p/x", "s":"x/s", "n":"p/x"} 78 | valid_sep_regex = "^%[.]*\d*[$0-9]*[h]*({}).*".format("|".join(valid_sep)) 79 | 80 | """ 81 | ref, https://cs155.stanford.edu/papers/formatstring-1.2.pdfu 82 | parameter output passed as 83 | %d decimal (int) value 84 | %u unsigned decimal (unsigned int) value 85 | %x hexadecimal (unsigned int) value 86 | %s string ((const) (unsigned) char *) reference 87 | %n number of bytes written so far, (* int) reference 88 | """ 89 | 90 | def parse_format_string(str_fmt) : 91 | str_fmt = str_fmt.replace("\\\\", "[back-slash]").replace("\\%", "[back-slash-on-perc]") 92 | lst_fmt = str_fmt.split("%") 93 | counter = len(lst_fmt) 94 | output = [] 95 | if len(lst_fmt) > 1 : 96 | for x in lst_fmt : 97 | x = "%" + x 98 | rets = re.match(valid_sep_regex, x) 99 | if rets : 100 | try : 101 | fmt_tmp = rets.group(1) 102 | output.append(valid_sep_dict[fmt_tmp]) 103 | except Exception as ex : 104 | slog.append("[!] warning: bad exception at 'rets.group(1)' line in parse_format_string function. Input parse: '{}'".format(x)) 105 | 106 | else : 107 | slog.append("[!] warning: can't resolve format string: {}".format(x)) 108 | return output 109 | 110 | --------------------------------------------------------------------------------