Coverage for gef.py: 71.6649%

7947 statements  

« prev     ^ index     » next       coverage.py v7.6.0, created at 2024-07-21 16:06 +0000

1####################################################################################### 

2# GEF - Multi-Architecture GDB Enhanced Features for Exploiters & Reverse-Engineers 

3# 

4# by @_hugsy_ 

5####################################################################################### 

6# 

7# GEF is a kick-ass set of commands for X86, ARM, MIPS, PowerPC and SPARC to 

8# make GDB cool again for exploit dev. It is aimed to be used mostly by exploit 

9# devs and reversers, to provides additional features to GDB using the Python 

10# API to assist during the process of dynamic analysis. 

11# 

12# GEF fully relies on GDB API and other Linux-specific sources of information 

13# (such as /proc/<pid>). As a consequence, some of the features might not work 

14# on custom or hardened systems such as GrSec. 

15# 

16# Since January 2020, GEF solely support GDB compiled with Python3 and was tested on 

17# * x86-32 & x86-64 

18# * arm v5,v6,v7 

19# * aarch64 (armv8) 

20# * mips & mips64 

21# * powerpc & powerpc64 

22# * sparc & sparc64(v9) 

23# 

24# For GEF with Python2 (only) support was moved to the GEF-Legacy 

25# (https://github.com/hugsy/gef-legacy) 

26# 

27# To start: in gdb, type `source /path/to/gef.py` 

28# 

29####################################################################################### 

30# 

31# gef is distributed under the MIT License (MIT) 

32# Copyright (c) 2013-2023 crazy rabbidz 

33# 

34# Permission is hereby granted, free of charge, to any person obtaining a copy 

35# of this software and associated documentation files (the "Software"), to deal 

36# in the Software without restriction, including without limitation the rights 

37# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 

38# copies of the Software, and to permit persons to whom the Software is 

39# furnished to do so, subject to the following conditions: 

40# 

41# The above copyright notice and this permission notice shall be included in all 

42# copies or substantial portions of the Software. 

43# 

44# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 

45# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 

46# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 

47# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 

48# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 

49# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 

50# SOFTWARE. 

51 

52import abc 

53import argparse 

54import ast 

55import atexit 

56import binascii 

57import codecs 

58import collections 

59import configparser 

60import ctypes 

61import enum 

62import functools 

63import hashlib 

64import importlib 

65import importlib.util 

66import inspect 

67import itertools 

68import os 

69import pathlib 

70import platform 

71import re 

72import shutil 

73import socket 

74import string 

75import struct 

76import subprocess 

77import sys 

78import tempfile 

79import time 

80import traceback 

81import warnings 

82from functools import lru_cache 

83from io import StringIO, TextIOWrapper 

84from types import ModuleType 

85from typing import (Any, ByteString, Callable, Dict, Generator, Iterable, 

86 Iterator, List, NoReturn, Optional, Sequence, Set, Tuple, Type, TypeVar, 

87 Union, cast) 

88from urllib.request import urlopen 

89 

90 

91GEF_DEFAULT_BRANCH = "main" 

92GEF_EXTRAS_DEFAULT_BRANCH = "main" 

93 

94def http_get(url: str) -> Optional[bytes]: 

95 """Basic HTTP wrapper for GET request. Return the body of the page if HTTP code is OK, 

96 otherwise return None.""" 

97 try: 

98 http = urlopen(url) 

99 return http.read() if http.getcode() == 200 else None 

100 except Exception: 

101 return None 

102 

103 

104def update_gef(argv: List[str]) -> int: 

105 """Obsolete. Use `gef.sh`.""" 

106 return -1 

107 

108 

109try: 

110 import gdb # type:ignore 

111except ImportError: 

112 if len(sys.argv) >= 2 and sys.argv[1].lower() in ("--update", "--upgrade"): 

113 print("[-] `update_gef` is obsolete. Use the `gef.sh` script to update gef from the command line.") 

114 print("[-] gef cannot run as standalone") 

115 sys.exit(1) 

116 

117 

118GDB_MIN_VERSION: Tuple[int, int] = (8, 0) 

119PYTHON_MIN_VERSION: Tuple[int, int] = (3, 6) 

120PYTHON_VERSION: Tuple[int, int] = sys.version_info[0:2] 

121GDB_VERSION: Tuple[int, int] = tuple(map(int, re.search(r"(\d+)[^\d]+(\d+)", gdb.VERSION).groups())) # type:ignore 

122 

123DEFAULT_PAGE_ALIGN_SHIFT = 12 

124DEFAULT_PAGE_SIZE = 1 << DEFAULT_PAGE_ALIGN_SHIFT 

125 

126GEF_RC = (pathlib.Path(os.getenv("GEF_RC", "")).absolute() 

127 if os.getenv("GEF_RC") 

128 else pathlib.Path().home() / ".gef.rc") 

129GEF_TEMP_DIR = pathlib.Path(tempfile.gettempdir())/ "gef" 

130GEF_MAX_STRING_LENGTH = 50 

131 

132LIBC_HEAP_MAIN_ARENA_DEFAULT_NAME = "main_arena" 

133ANSI_SPLIT_RE = r"(\033\[[\d;]*m)" 

134 

135LEFT_ARROW = " ← " 

136RIGHT_ARROW = " → " 

137DOWN_ARROW = "↳" 

138HORIZONTAL_LINE = "─" 

139VERTICAL_LINE = "│" 

140CROSS = "✘ " 

141TICK = "✓ " 

142BP_GLYPH = "●" 

143GEF_PROMPT = "gef➤ " 

144GEF_PROMPT_ON = f"\001\033[1;32m\002{GEF_PROMPT}\001\033[0m\002" 

145GEF_PROMPT_OFF = f"\001\033[1;31m\002{GEF_PROMPT}\001\033[0m\002" 

146 

147__registered_commands__ : Set[Type["GenericCommand"]] = set() 

148__registered_functions__ : Set[Type["GenericFunction"]] = set() 

149__registered_architectures__ : Dict[Union["Elf.Abi", str], Type["Architecture"]] = {} 

150__registered_file_formats__ : Set[ Type["FileFormat"] ] = set() 

151 

152GefMemoryMapProvider = Callable[[], Generator["Section", None, None]] 

153 

154 

155def reset_all_caches() -> None: 

156 """Free all caches. If an object is cached, it will have a callable attribute `cache_clear` 

157 which will be invoked to purge the function cache.""" 

158 

159 for mod in dir(sys.modules["__main__"]): 

160 obj = getattr(sys.modules["__main__"], mod) 

161 if hasattr(obj, "cache_clear"): 

162 obj.cache_clear() 

163 

164 gef.reset_caches() 

165 return 

166 

167 

168def reset() -> None: 

169 global gef 

170 

171 arch = None 

172 if "gef" in locals().keys(): 172 ↛ 173line 172 didn't jump to line 173 because the condition on line 172 was never true

173 reset_all_caches() 

174 arch = gef.arch 

175 del gef 

176 

177 gef = Gef() 

178 gef.setup() 

179 

180 if arch: 180 ↛ 181line 180 didn't jump to line 181 because the condition on line 180 was never true

181 gef.arch = arch 

182 return 

183 

184 

185def highlight_text(text: str) -> str: 

186 """ 

187 Highlight text using `gef.ui.highlight_table` { match -> color } settings. 

188 

189 If RegEx is enabled it will create a match group around all items in the 

190 `gef.ui.highlight_table` and wrap the specified color in the `gef.ui.highlight_table` 

191 around those matches. 

192 

193 If RegEx is disabled, split by ANSI codes and 'colorify' each match found 

194 within the specified string. 

195 """ 

196 global gef 

197 

198 if not gef.ui.highlight_table: 

199 return text 

200 

201 if gef.config["highlight.regex"]: 201 ↛ 202line 201 didn't jump to line 202 because the condition on line 201 was never true

202 for match, color in gef.ui.highlight_table.items(): 

203 text = re.sub("(" + match + ")", Color.colorify("\\1", color), text) 

204 return text 

205 

206 ansiSplit = re.split(ANSI_SPLIT_RE, text) 

207 

208 for match, color in gef.ui.highlight_table.items(): 

209 for index, val in enumerate(ansiSplit): 209 ↛ 214line 209 didn't jump to line 214 because the loop on line 209 didn't complete

210 found = val.find(match) 

211 if found > -1: 

212 ansiSplit[index] = val.replace(match, Color.colorify(match, color)) 

213 break 

214 text = "".join(ansiSplit) 

215 ansiSplit = re.split(ANSI_SPLIT_RE, text) 

216 

217 return "".join(ansiSplit) 

218 

219 

220def gef_print(*args: str, end="\n", sep=" ", **kwargs: Any) -> None: 

221 """Wrapper around print(), using string buffering feature.""" 

222 parts = [highlight_text(a) for a in args] 

223 if buffer_output() and gef.ui.stream_buffer and not is_debug(): 223 ↛ 224line 223 didn't jump to line 224 because the condition on line 223 was never true

224 gef.ui.stream_buffer.write(sep.join(parts) + end) 

225 return 

226 

227 print(*parts, sep=sep, end=end, **kwargs) 

228 return 

229 

230 

231def bufferize(f: Callable) -> Callable: 

232 """Store the content to be printed for a function in memory, and flush it on function exit.""" 

233 

234 @functools.wraps(f) 

235 def wrapper(*args: Any, **kwargs: Any) -> Any: 

236 global gef 

237 

238 if gef.ui.stream_buffer: 

239 return f(*args, **kwargs) 

240 

241 gef.ui.stream_buffer = StringIO() 

242 try: 

243 rv = f(*args, **kwargs) 

244 finally: 

245 redirect = gef.config["context.redirect"] 

246 if redirect.startswith("/dev/pts/"): 246 ↛ 247line 246 didn't jump to line 247 because the condition on line 246 was never true

247 if not gef.ui.redirect_fd: 

248 # if the FD has never been open, open it 

249 fd = open(redirect, "wt") 

250 gef.ui.redirect_fd = fd 

251 elif redirect != gef.ui.redirect_fd.name: 

252 # if the user has changed the redirect setting during runtime, update the state 

253 gef.ui.redirect_fd.close() 

254 fd = open(redirect, "wt") 

255 gef.ui.redirect_fd = fd 

256 else: 

257 # otherwise, keep using it 

258 fd = gef.ui.redirect_fd 

259 else: 

260 fd = sys.stdout 

261 gef.ui.redirect_fd = None 

262 

263 if gef.ui.redirect_fd and fd.closed: 263 ↛ 265line 263 didn't jump to line 265 because the condition on line 263 was never true

264 # if the tty was closed, revert back to stdout 

265 fd = sys.stdout 

266 gef.ui.redirect_fd = None 

267 gef.config["context.redirect"] = "" 

268 

269 fd.write(gef.ui.stream_buffer.getvalue()) 

270 fd.flush() 

271 gef.ui.stream_buffer = None 

272 return rv 

273 

274 return wrapper 

275 

276 

277class ValidationError(Exception): pass 

278 

279# 

280# Helpers 

281# 

282 

283class ObsoleteException(Exception): pass 

284 

285class AlreadyRegisteredException(Exception): pass 

286 

287def p8(x: int, s: bool = False, e: Optional["Endianness"] = None) -> bytes: 

288 """Pack one byte respecting the current architecture endianness.""" 

289 endian = e or gef.arch.endianness 

290 return struct.pack(f"{endian}B", x) if not s else struct.pack(f"{endian:s}b", x) 

291 

292 

293def p16(x: int, s: bool = False, e: Optional["Endianness"] = None) -> bytes: 

294 """Pack one word respecting the current architecture endianness.""" 

295 endian = e or gef.arch.endianness 

296 return struct.pack(f"{endian}H", x) if not s else struct.pack(f"{endian:s}h", x) 

297 

298 

299def p32(x: int, s: bool = False, e: Optional["Endianness"] = None) -> bytes: 

300 """Pack one dword respecting the current architecture endianness.""" 

301 endian = e or gef.arch.endianness 

302 return struct.pack(f"{endian}I", x) if not s else struct.pack(f"{endian:s}i", x) 

303 

304 

305def p64(x: int, s: bool = False, e: Optional["Endianness"] = None) -> bytes: 

306 """Pack one qword respecting the current architecture endianness.""" 

307 endian = e or gef.arch.endianness 

308 return struct.pack(f"{endian}Q", x) if not s else struct.pack(f"{endian:s}q", x) 

309 

310 

311def u8(x: bytes, s: bool = False, e: Optional["Endianness"] = None) -> int: 

312 """Unpack one byte respecting the current architecture endianness.""" 

313 endian = e or gef.arch.endianness 

314 return struct.unpack(f"{endian}B", x)[0] if not s else struct.unpack(f"{endian:s}b", x)[0] 

315 

316 

317def u16(x: bytes, s: bool = False, e: Optional["Endianness"] = None) -> int: 

318 """Unpack one word respecting the current architecture endianness.""" 

319 endian = e or gef.arch.endianness 

320 return struct.unpack(f"{endian}H", x)[0] if not s else struct.unpack(f"{endian:s}h", x)[0] 

321 

322 

323def u32(x: bytes, s: bool = False, e: Optional["Endianness"] = None) -> int: 

324 """Unpack one dword respecting the current architecture endianness.""" 

325 endian = e or gef.arch.endianness 

326 return struct.unpack(f"{endian}I", x)[0] if not s else struct.unpack(f"{endian:s}i", x)[0] 

327 

328 

329def u64(x: bytes, s: bool = False, e: Optional["Endianness"] = None) -> int: 

330 """Unpack one qword respecting the current architecture endianness.""" 

331 endian = e or gef.arch.endianness 

332 return struct.unpack(f"{endian}Q", x)[0] if not s else struct.unpack(f"{endian:s}q", x)[0] 

333 

334 

335def is_ascii_string(address: int) -> bool: 

336 """Helper function to determine if the buffer pointed by `address` is an ASCII string (in GDB)""" 

337 try: 

338 return gef.memory.read_ascii_string(address) is not None 

339 except Exception: 

340 return False 

341 

342 

343def is_alive() -> bool: 

344 """Check if GDB is running.""" 

345 try: 

346 return gdb.selected_inferior().pid > 0 

347 except Exception: 

348 return False 

349 

350 

351def calling_function() -> Optional[str]: 

352 """Return the name of the calling function""" 

353 try: 

354 stack_info = traceback.extract_stack()[-3] 

355 return stack_info.name 

356 except: 

357 return None 

358 

359 

360# 

361# Decorators 

362# 

363def only_if_gdb_running(f: Callable) -> Callable: 

364 """Decorator wrapper to check if GDB is running.""" 

365 

366 @functools.wraps(f) 

367 def wrapper(*args: Any, **kwargs: Any) -> Any: 

368 if is_alive(): 

369 return f(*args, **kwargs) 

370 else: 

371 warn("No debugging session active") 

372 

373 return wrapper 

374 

375 

376def only_if_gdb_target_local(f: Callable) -> Callable: 

377 """Decorator wrapper to check if GDB is running locally (target not remote).""" 

378 

379 @functools.wraps(f) 

380 def wrapper(*args: Any, **kwargs: Any) -> Any: 

381 if not is_remote_debug(): 381 ↛ 384line 381 didn't jump to line 384 because the condition on line 381 was always true

382 return f(*args, **kwargs) 

383 else: 

384 warn("This command cannot work for remote sessions.") 

385 

386 return wrapper 

387 

388 

389def deprecated(solution: str = "") -> Callable: 

390 """Decorator to add a warning when a command is obsolete and will be removed.""" 

391 def decorator(f: Callable) -> Callable: 

392 @functools.wraps(f) 

393 def wrapper(*args: Any, **kwargs: Any) -> Any: 

394 caller = inspect.stack()[1] 

395 caller_file = pathlib.Path(caller.filename) 

396 caller_loc = caller.lineno 

397 msg = f"{caller_file.name}:L{caller_loc} '{f.__name__}' is deprecated and will be removed in a feature release. " 

398 if not gef: 398 ↛ 399line 398 didn't jump to line 399 because the condition on line 398 was never true

399 print(msg) 

400 elif gef.config["gef.show_deprecation_warnings"] is True: 400 ↛ 404line 400 didn't jump to line 404 because the condition on line 400 was always true

401 if solution: 

402 msg += solution 

403 warn(msg) 

404 return f(*args, **kwargs) 

405 

406 if not wrapper.__doc__: 

407 wrapper.__doc__ = "" 

408 wrapper.__doc__ += f"\r\n`{f.__name__}` is **DEPRECATED** and will be removed in the future.\r\n{solution}" 

409 return wrapper 

410 return decorator 

411 

412 

413def experimental_feature(f: Callable) -> Callable: 

414 """Decorator to add a warning when a feature is experimental.""" 

415 

416 @functools.wraps(f) 

417 def wrapper(*args: Any, **kwargs: Any) -> Any: 

418 warn("This feature is under development, expect bugs and unstability...") 

419 return f(*args, **kwargs) 

420 

421 return wrapper 

422 

423 

424def only_if_events_supported(event_type: str) -> Callable: 

425 """Checks if GDB supports events without crashing.""" 

426 def wrap(f: Callable) -> Callable: 

427 def wrapped_f(*args: Any, **kwargs: Any) -> Any: 

428 if getattr(gdb, "events") and getattr(gdb.events, event_type): 428 ↛ 430line 428 didn't jump to line 430 because the condition on line 428 was always true

429 return f(*args, **kwargs) 

430 warn("GDB events cannot be set") 

431 return wrapped_f 

432 return wrap 

433 

434 

435class classproperty(property): 

436 """Make the attribute a `classproperty`.""" 

437 def __get__(self, cls, owner): 

438 assert self.fget 

439 return classmethod(self.fget).__get__(None, owner)() 

440 

441 

442def FakeExit(*args: Any, **kwargs: Any) -> NoReturn: 

443 raise RuntimeWarning 

444 

445 

446sys.exit = FakeExit 

447 

448 

449def parse_arguments(required_arguments: Dict[Union[str, Tuple[str, str]], Any], 

450 optional_arguments: Dict[Union[str, Tuple[str, str]], Any]) -> Callable: 

451 """Argument parsing decorator.""" 

452 

453 def int_wrapper(x: str) -> int: return int(x, 0) 

454 

455 def decorator(f: Callable) -> Optional[Callable]: 

456 def wrapper(*args: Any, **kwargs: Any) -> Callable: 

457 parser = argparse.ArgumentParser(prog=args[0]._cmdline_, add_help=True) 

458 for argname in required_arguments: 

459 argvalue = required_arguments[argname] 

460 argtype = type(argvalue) 

461 if argtype is int: 

462 argtype = int_wrapper 

463 

464 argname_is_list = not isinstance(argname, str) 

465 assert not argname_is_list and isinstance(argname, str) 

466 if not argname_is_list and argname.startswith("-"): 466 ↛ 468line 466 didn't jump to line 468 because the condition on line 466 was never true

467 # optional args 

468 if argtype is bool: 

469 parser.add_argument(argname, action="store_true" if argvalue else "store_false") 

470 else: 

471 parser.add_argument(argname, type=argtype, required=True, default=argvalue) 

472 else: 

473 if argtype in (list, tuple): 

474 nargs = "*" 

475 argtype = type(argvalue[0]) 

476 else: 

477 nargs = "?" 

478 # positional args 

479 parser.add_argument(argname, type=argtype, default=argvalue, nargs=nargs) 

480 

481 for argname in optional_arguments: 

482 if isinstance(argname, str) and not argname.startswith("-"): 482 ↛ 484line 482 didn't jump to line 484 because the condition on line 482 was never true

483 # refuse positional arguments 

484 continue 

485 argvalue = optional_arguments[argname] 

486 argtype = type(argvalue) 

487 if isinstance(argname, str): 

488 argname = [argname,] 

489 if argtype is int: 

490 argtype = int_wrapper 

491 elif argtype is bool: 

492 parser.add_argument(*argname, action="store_false" if argvalue else "store_true") 

493 continue 

494 elif argtype in (list, tuple): 494 ↛ 495line 494 didn't jump to line 495 because the condition on line 494 was never true

495 parser.add_argument(*argname, type=type(argvalue[0]), 

496 default=[], action="append") 

497 continue 

498 parser.add_argument(*argname, type=argtype, default=argvalue) 

499 

500 parsed_args = parser.parse_args(*(args[1:])) 

501 kwargs["arguments"] = parsed_args 

502 return f(*args, **kwargs) 

503 return wrapper 

504 return decorator 

505 

506 

507class Color: 

508 """Used to colorify terminal output.""" 

509 

510 ### Special chars: 

511 # \001 -> Tell the readline library that we start a special sequence 

512 # which won't be displayed (takes no column in the output) 

513 # \002 -> Tell the readline library that we end a special sequence 

514 # started with \001 

515 # \033 -> Start an ANSI escape code for displaying colors 

516 colors = { 

517 "normal" : "\001\033[0m\002", 

518 "gray" : "\001\033[1;38;5;240m\002", 

519 "light_gray" : "\001\033[0;37m\002", 

520 "red" : "\001\033[31m\002", 

521 "green" : "\001\033[32m\002", 

522 "yellow" : "\001\033[33m\002", 

523 "blue" : "\001\033[34m\002", 

524 "pink" : "\001\033[35m\002", 

525 "cyan" : "\001\033[36m\002", 

526 "bold" : "\001\033[1m\002", 

527 "underline" : "\001\033[4m\002", 

528 "underline_off" : "\001\033[24m\002", 

529 "highlight" : "\001\033[3m\002", 

530 "highlight_off" : "\001\033[23m\002", 

531 "blink" : "\001\033[5m\002", 

532 "blink_off" : "\001\033[25m\002", 

533 } 

534 

535 @staticmethod 

536 def redify(msg: str) -> str: return Color.colorify(msg, "red") 

537 @staticmethod 

538 def greenify(msg: str) -> str: return Color.colorify(msg, "green") 

539 @staticmethod 

540 def blueify(msg: str) -> str: return Color.colorify(msg, "blue") 

541 @staticmethod 

542 def yellowify(msg: str) -> str: return Color.colorify(msg, "yellow") 

543 @staticmethod 

544 def grayify(msg: str) -> str: return Color.colorify(msg, "gray") 544 ↛ exitline 544 didn't return from function 'grayify' because the return on line 544 wasn't executed

545 @staticmethod 

546 def light_grayify(msg: str) -> str: return Color.colorify(msg, "light_gray") 546 ↛ exitline 546 didn't return from function 'light_grayify' because the return on line 546 wasn't executed

547 @staticmethod 

548 def pinkify(msg: str) -> str: return Color.colorify(msg, "pink") 

549 @staticmethod 

550 def cyanify(msg: str) -> str: return Color.colorify(msg, "cyan") 550 ↛ exitline 550 didn't return from function 'cyanify' because the return on line 550 wasn't executed

551 @staticmethod 

552 def boldify(msg: str) -> str: return Color.colorify(msg, "bold") 

553 @staticmethod 

554 def underlinify(msg: str) -> str: return Color.colorify(msg, "underline") 554 ↛ exitline 554 didn't return from function 'underlinify' because the return on line 554 wasn't executed

555 @staticmethod 

556 def highlightify(msg: str) -> str: return Color.colorify(msg, "highlight") 556 ↛ exitline 556 didn't return from function 'highlightify' because the return on line 556 wasn't executed

557 @staticmethod 

558 def blinkify(msg: str) -> str: return Color.colorify(msg, "blink") 558 ↛ exitline 558 didn't return from function 'blinkify' because the return on line 558 wasn't executed

559 

560 @staticmethod 

561 def colorify(text: str, attrs: str) -> str: 

562 """Color text according to the given attributes.""" 

563 if gef.config["gef.disable_color"] is True: return text 

564 

565 colors = Color.colors 

566 msg = [colors[attr] for attr in attrs.split() if attr in colors] 

567 msg.append(str(text)) 

568 if colors["highlight"] in msg: msg.append(colors["highlight_off"]) 

569 if colors["underline"] in msg: msg.append(colors["underline_off"]) 

570 if colors["blink"] in msg: msg.append(colors["blink_off"]) 

571 msg.append(colors["normal"]) 

572 return "".join(msg) 

573 

574 

575class Address: 

576 """GEF representation of memory addresses.""" 

577 def __init__(self, **kwargs: Any) -> None: 

578 self.value: int = kwargs.get("value", 0) 

579 self.section: "Section" = kwargs.get("section", None) 

580 self.info: "Zone" = kwargs.get("info", None) 

581 return 

582 

583 def __str__(self) -> str: 

584 value = format_address(self.value) 

585 code_color = gef.config["theme.address_code"] 

586 stack_color = gef.config["theme.address_stack"] 

587 heap_color = gef.config["theme.address_heap"] 

588 if self.is_in_text_segment(): 

589 return Color.colorify(value, code_color) 

590 if self.is_in_heap_segment(): 

591 return Color.colorify(value, heap_color) 

592 if self.is_in_stack_segment(): 

593 return Color.colorify(value, stack_color) 

594 return value 

595 

596 def __int__(self) -> int: 

597 return self.value 

598 

599 def is_in_text_segment(self) -> bool: 

600 return (hasattr(self.info, "name") and ".text" in self.info.name) or \ 

601 (hasattr(self.section, "path") and get_filepath() == self.section.path and self.section.is_executable()) 

602 

603 def is_in_stack_segment(self) -> bool: 

604 return hasattr(self.section, "path") and "[stack]" == self.section.path 

605 

606 def is_in_heap_segment(self) -> bool: 

607 return hasattr(self.section, "path") and "[heap]" == self.section.path 

608 

609 def dereference(self) -> Optional[int]: 

610 addr = align_address(int(self.value)) 

611 derefed = dereference(addr) 

612 return None if derefed is None else int(derefed) 

613 

614 @property 

615 def valid(self) -> bool: 

616 return any(map(lambda x: x.page_start <= self.value < x.page_end, gef.memory.maps)) 

617 

618 

619class Permission(enum.Flag): 

620 """GEF representation of Linux permission.""" 

621 NONE = 0 

622 EXECUTE = 1 

623 WRITE = 2 

624 READ = 4 

625 ALL = 7 

626 

627 def __str__(self) -> str: 

628 perm_str = "" 

629 perm_str += "r" if self & Permission.READ else "-" 

630 perm_str += "w" if self & Permission.WRITE else "-" 

631 perm_str += "x" if self & Permission.EXECUTE else "-" 

632 return perm_str 

633 

634 @classmethod 

635 def from_info_sections(cls, *args: str) -> "Permission": 

636 perm = cls(0) 

637 for arg in args: 

638 if "READONLY" in arg: perm |= Permission.READ 

639 if "DATA" in arg: perm |= Permission.WRITE 

640 if "CODE" in arg: perm |= Permission.EXECUTE 

641 return perm 

642 

643 @classmethod 

644 def from_process_maps(cls, perm_str: str) -> "Permission": 

645 perm = cls(0) 

646 if perm_str[0] == "r": perm |= Permission.READ 

647 if perm_str[1] == "w": perm |= Permission.WRITE 

648 if perm_str[2] == "x": perm |= Permission.EXECUTE 

649 return perm 

650 

651 @classmethod 

652 def from_monitor_info_mem(cls, perm_str: str) -> "Permission": 

653 perm = cls(0) 

654 # perm_str[0] shows if this is a user page, which 

655 # we don't track 

656 if perm_str[1] == "r": perm |= Permission.READ 

657 if perm_str[2] == "w": perm |= Permission.WRITE 

658 return perm 

659 

660 @classmethod 

661 def from_info_mem(cls, perm_str: str) -> "Permission": 

662 perm = cls(0) 

663 if "r" in perm_str: perm |= Permission.READ 

664 if "w" in perm_str: perm |= Permission.WRITE 

665 if "x" in perm_str: perm |= Permission.EXECUTE 

666 return perm 

667 

668 

669class Section: 

670 """GEF representation of process memory sections.""" 

671 

672 def __init__(self, **kwargs: Any) -> None: 

673 self.page_start: int = kwargs.get("page_start", 0) 

674 self.page_end: int = kwargs.get("page_end", 0) 

675 self.offset: int = kwargs.get("offset", 0) 

676 self.permission: Permission = kwargs.get("permission", Permission(0)) 

677 self.inode: int = kwargs.get("inode", 0) 

678 self.path: str = kwargs.get("path", "") 

679 return 

680 

681 def is_readable(self) -> bool: 

682 return (self.permission & Permission.READ) != 0 

683 

684 def is_writable(self) -> bool: 

685 return (self.permission & Permission.WRITE) != 0 

686 

687 def is_executable(self) -> bool: 

688 return (self.permission & Permission.EXECUTE) != 0 

689 

690 @property 

691 def size(self) -> int: 

692 if self.page_end is None or self.page_start is None: 692 ↛ 693line 692 didn't jump to line 693 because the condition on line 692 was never true

693 raise AttributeError 

694 return self.page_end - self.page_start 

695 

696 @property 

697 def realpath(self) -> str: 

698 # when in a `gef-remote` session, realpath returns the path to the binary on the local disk, not remote 

699 return self.path if gef.session.remote is None else f"/tmp/gef/{gef.session.remote:d}/{self.path}" 

700 

701 def __str__(self) -> str: 

702 return (f"Section(start={self.page_start:#x}, end={self.page_end:#x}, " 

703 f"perm={self.permission!s})") 

704 

705 def __repr__(self) -> str: 

706 return str(self) 

707 

708 def __eq__(self, other: "Section") -> bool: 

709 return other and \ 

710 self.page_start == other.page_start and \ 

711 self.size == other.size and \ 

712 self.permission == other.permission and \ 

713 self.path == other.path 

714 

715 def overlaps(self, other: "Section") -> bool: 

716 return max(self.page_start, other.page_start) <= min(self.page_end, other.page_end) 

717 

718 def contains(self, addr: int) -> bool: 

719 return addr in range(self.page_start, self.page_end) 

720 

721 

722Zone = collections.namedtuple("Zone", ["name", "zone_start", "zone_end", "filename"]) 

723 

724 

725class Endianness(enum.Enum): 

726 LITTLE_ENDIAN = 1 

727 BIG_ENDIAN = 2 

728 

729 def __str__(self) -> str: 

730 return "<" if self == Endianness.LITTLE_ENDIAN else ">" 

731 

732 def __repr__(self) -> str: 

733 return self.name 

734 

735 def __int__(self) -> int: 

736 return self.value 

737 

738 

739class FileFormatSection: 

740 misc: Any 

741 

742 

743class FileFormat: 

744 name: str 

745 path: pathlib.Path 

746 entry_point: int 

747 checksec: Dict[str, bool] 

748 sections: List[FileFormatSection] 

749 

750 def __init__(self, path: Union[str, pathlib.Path]) -> None: 

751 raise NotImplementedError 

752 

753 def __init_subclass__(cls: Type["FileFormat"], **kwargs): 

754 global __registered_file_formats__ 

755 super().__init_subclass__(**kwargs) 

756 required_attributes = ("name", "entry_point", "is_valid", "checksec",) 

757 for attr in required_attributes: 

758 if not hasattr(cls, attr): 758 ↛ 759line 758 didn't jump to line 759 because the condition on line 758 was never true

759 raise NotImplementedError(f"File format '{cls.__name__}' is invalid: missing attribute '{attr}'") 

760 __registered_file_formats__.add(cls) 

761 return 

762 

763 @classmethod 

764 def is_valid(cls, _: pathlib.Path) -> bool: 

765 raise NotImplementedError 

766 

767 def __str__(self) -> str: 

768 return f"{self.name}('{self.path.absolute()}', entry @ {self.entry_point:#x})" 

769 

770 

771class Elf(FileFormat): 

772 """Basic ELF parsing. 

773 Ref: 

774 - http://www.skyfree.org/linux/references/ELF_Format.pdf 

775 - https://refspecs.linuxfoundation.org/elf/elfspec_ppc.pdf 

776 - https://refspecs.linuxfoundation.org/ELF/ppc64/PPC-elf64abi.html 

777 """ 

778 class Class(enum.Enum): 

779 ELF_32_BITS = 0x01 

780 ELF_64_BITS = 0x02 

781 

782 ELF_MAGIC = 0x7f454c46 

783 

784 class Abi(enum.Enum): 

785 X86_64 = 0x3e 

786 X86_32 = 0x03 

787 ARM = 0x28 

788 MIPS = 0x08 

789 POWERPC = 0x14 

790 POWERPC64 = 0x15 

791 SPARC = 0x02 

792 SPARC64 = 0x2b 

793 AARCH64 = 0xb7 

794 RISCV = 0xf3 

795 IA64 = 0x32 

796 M68K = 0x04 

797 

798 class Type(enum.Enum): 

799 ET_RELOC = 1 

800 ET_EXEC = 2 

801 ET_DYN = 3 

802 ET_CORE = 4 

803 

804 class OsAbi(enum.Enum): 

805 SYSTEMV = 0x00 

806 HPUX = 0x01 

807 NETBSD = 0x02 

808 LINUX = 0x03 

809 SOLARIS = 0x06 

810 AIX = 0x07 

811 IRIX = 0x08 

812 FREEBSD = 0x09 

813 OPENBSD = 0x0C 

814 

815 e_magic: int = ELF_MAGIC 

816 e_class: "Elf.Class" = Class.ELF_32_BITS 

817 e_endianness: Endianness = Endianness.LITTLE_ENDIAN 

818 e_eiversion: int 

819 e_osabi: "Elf.OsAbi" 

820 e_abiversion: int 

821 e_pad: bytes 

822 e_type: "Elf.Type" = Type.ET_EXEC 

823 e_machine: Abi = Abi.X86_32 

824 e_version: int 

825 e_entry: int 

826 e_phoff: int 

827 e_shoff: int 

828 e_flags: int 

829 e_ehsize: int 

830 e_phentsize: int 

831 e_phnum: int 

832 e_shentsize: int 

833 e_shnum: int 

834 e_shstrndx: int 

835 

836 path: pathlib.Path 

837 phdrs : List["Phdr"] 

838 shdrs : List["Shdr"] 

839 name: str = "ELF" 

840 

841 __checksec : Dict[str, bool] 

842 

843 def __init__(self, path: Union[str, pathlib.Path]) -> None: 

844 """Instantiate an ELF object. A valid ELF must be provided, or an exception will be thrown.""" 

845 

846 if isinstance(path, str): 

847 self.path = pathlib.Path(path).expanduser() 

848 elif isinstance(path, pathlib.Path): 848 ↛ 851line 848 didn't jump to line 851 because the condition on line 848 was always true

849 self.path = path 

850 else: 

851 raise TypeError 

852 

853 if not self.path.exists(): 853 ↛ 854line 853 didn't jump to line 854 because the condition on line 853 was never true

854 raise FileNotFoundError(f"'{self.path}' not found/readable, most gef features will not work") 

855 

856 self.__checksec = {} 

857 

858 with self.path.open("rb") as self.fd: 

859 # off 0x0 

860 self.e_magic, e_class, e_endianness, self.e_eiversion = self.read_and_unpack(">IBBB") 

861 if self.e_magic != Elf.ELF_MAGIC: 861 ↛ 863line 861 didn't jump to line 863 because the condition on line 861 was never true

862 # The ELF is corrupted, GDB won't handle it, no point going further 

863 raise RuntimeError("Not a valid ELF file (magic)") 

864 

865 self.e_class, self.e_endianness = Elf.Class(e_class), Endianness(e_endianness) 

866 

867 if self.e_endianness != gef.arch.endianness: 867 ↛ 868line 867 didn't jump to line 868 because the condition on line 867 was never true

868 warn("Unexpected endianness for architecture") 

869 

870 endian = self.e_endianness 

871 

872 # off 0x7 

873 e_osabi, self.e_abiversion = self.read_and_unpack(f"{endian}BB") 

874 self.e_osabi = Elf.OsAbi(e_osabi) 

875 

876 # off 0x9 

877 self.e_pad = self.read(7) 

878 

879 # off 0x10 

880 e_type, e_machine, self.e_version = self.read_and_unpack(f"{endian}HHI") 

881 self.e_type, self.e_machine = Elf.Type(e_type), Elf.Abi(e_machine) 

882 

883 # off 0x18 

884 if self.e_class == Elf.Class.ELF_64_BITS: 884 ↛ 887line 884 didn't jump to line 887 because the condition on line 884 was always true

885 self.e_entry, self.e_phoff, self.e_shoff = self.read_and_unpack(f"{endian}QQQ") 

886 else: 

887 self.e_entry, self.e_phoff, self.e_shoff = self.read_and_unpack(f"{endian}III") 

888 

889 self.e_flags, self.e_ehsize, self.e_phentsize, self.e_phnum = self.read_and_unpack(f"{endian}IHHH") 

890 self.e_shentsize, self.e_shnum, self.e_shstrndx = self.read_and_unpack(f"{endian}HHH") 

891 

892 self.phdrs = [] 

893 for i in range(self.e_phnum): 

894 self.phdrs.append(Phdr(self, self.e_phoff + self.e_phentsize * i)) 

895 

896 self.shdrs = [] 

897 for i in range(self.e_shnum): 

898 self.shdrs.append(Shdr(self, self.e_shoff + self.e_shentsize * i)) 

899 return 

900 

901 def read(self, size: int) -> bytes: 

902 return self.fd.read(size) 

903 

904 def read_and_unpack(self, fmt: str) -> Tuple[Any, ...]: 

905 size = struct.calcsize(fmt) 

906 data = self.fd.read(size) 

907 return struct.unpack(fmt, data) 

908 

909 def seek(self, off: int) -> None: 

910 self.fd.seek(off, 0) 

911 

912 def __str__(self) -> str: 

913 return f"ELF('{self.path.absolute()}', {self.e_class.name}, {self.e_machine.name})" 

914 

915 def __repr__(self) -> str: 

916 return f"ELF('{self.path.absolute()}', {self.e_class.name}, {self.e_machine.name})" 

917 

918 @property 

919 def entry_point(self) -> int: 

920 return self.e_entry 

921 

922 @classmethod 

923 def is_valid(cls, path: pathlib.Path) -> bool: 

924 return u32(path.open("rb").read(4), e = Endianness.BIG_ENDIAN) == Elf.ELF_MAGIC 

925 

926 @property 

927 def checksec(self) -> Dict[str, bool]: 

928 """Check the security property of the ELF binary. The following properties are: 

929 - Canary 

930 - NX 

931 - PIE 

932 - Fortify 

933 - Partial/Full RelRO. 

934 Return a dict() with the different keys mentioned above, and the boolean 

935 associated whether the protection was found.""" 

936 if not self.__checksec: 936 ↛ 959line 936 didn't jump to line 959 because the condition on line 936 was always true

937 def __check_security_property(opt: str, filename: str, pattern: str) -> bool: 

938 cmd = [readelf,] 

939 cmd += opt.split() 

940 cmd += [filename,] 

941 lines = gef_execute_external(cmd, as_list=True) 

942 for line in lines: 

943 if re.search(pattern, line): 

944 return True 

945 return False 

946 

947 abspath = str(self.path.absolute()) 

948 readelf = gef.session.constants["readelf"] 

949 self.__checksec["Canary"] = __check_security_property("-rs", abspath, r"__stack_chk_fail") is True 

950 has_gnu_stack = __check_security_property("-W -l", abspath, r"GNU_STACK") is True 

951 if has_gnu_stack: 951 ↛ 954line 951 didn't jump to line 954 because the condition on line 951 was always true

952 self.__checksec["NX"] = __check_security_property("-W -l", abspath, r"GNU_STACK.*RWE") is False 

953 else: 

954 self.__checksec["NX"] = False 

955 self.__checksec["PIE"] = __check_security_property("-h", abspath, r":.*EXEC") is False 

956 self.__checksec["Fortify"] = __check_security_property("-s", abspath, r"_chk@GLIBC") is True 

957 self.__checksec["Partial RelRO"] = __check_security_property("-l", abspath, r"GNU_RELRO") is True 

958 self.__checksec["Full RelRO"] = self.__checksec["Partial RelRO"] and __check_security_property("-d", abspath, r"BIND_NOW") is True 

959 return self.__checksec 

960 

961 @classproperty 

962 @deprecated("use `Elf.Abi.X86_64`") 

963 def X86_64(cls) -> int: return Elf.Abi.X86_64.value # pylint: disable=no-self-argument 

964 

965 @classproperty 

966 @deprecated("use `Elf.Abi.X86_32`") 

967 def X86_32(cls) -> int : return Elf.Abi.X86_32.value # pylint: disable=no-self-argument 

968 

969 @classproperty 

970 @deprecated("use `Elf.Abi.ARM`") 

971 def ARM(cls) -> int : return Elf.Abi.ARM.value # pylint: disable=no-self-argument 

972 

973 @classproperty 

974 @deprecated("use `Elf.Abi.MIPS`") 

975 def MIPS(cls) -> int : return Elf.Abi.MIPS.value # pylint: disable=no-self-argument 

976 

977 @classproperty 

978 @deprecated("use `Elf.Abi.POWERPC`") 

979 def POWERPC(cls) -> int : return Elf.Abi.POWERPC.value # pylint: disable=no-self-argument 

980 

981 @classproperty 

982 @deprecated("use `Elf.Abi.POWERPC64`") 

983 def POWERPC64(cls) -> int : return Elf.Abi.POWERPC64.value # pylint: disable=no-self-argument 

984 

985 @classproperty 

986 @deprecated("use `Elf.Abi.SPARC`") 

987 def SPARC(cls) -> int : return Elf.Abi.SPARC.value # pylint: disable=no-self-argument 

988 

989 @classproperty 

990 @deprecated("use `Elf.Abi.SPARC64`") 

991 def SPARC64(cls) -> int : return Elf.Abi.SPARC64.value # pylint: disable=no-self-argument 

992 

993 @classproperty 

994 @deprecated("use `Elf.Abi.AARCH64`") 

995 def AARCH64(cls) -> int : return Elf.Abi.AARCH64.value # pylint: disable=no-self-argument 

996 

997 @classproperty 

998 @deprecated("use `Elf.Abi.RISCV`") 

999 def RISCV(cls) -> int : return Elf.Abi.RISCV.value # pylint: disable=no-self-argument 

1000 

1001 

1002class Phdr: 

1003 class Type(enum.IntEnum): 

1004 PT_NULL = 0 

1005 PT_LOAD = 1 

1006 PT_DYNAMIC = 2 

1007 PT_INTERP = 3 

1008 PT_NOTE = 4 

1009 PT_SHLIB = 5 

1010 PT_PHDR = 6 

1011 PT_TLS = 7 

1012 PT_LOOS = 0x60000000 

1013 PT_GNU_EH_FRAME = 0x6474e550 

1014 PT_GNU_STACK = 0x6474e551 

1015 PT_GNU_RELRO = 0x6474e552 

1016 PT_GNU_PROPERTY = 0x6474e553 

1017 PT_LOSUNW = 0x6ffffffa 

1018 PT_SUNWBSS = 0x6ffffffa 

1019 PT_SUNWSTACK = 0x6ffffffb 

1020 PT_HISUNW = PT_HIOS = 0x6fffffff 

1021 PT_LOPROC = 0x70000000 

1022 PT_ARM_EIDX = 0x70000001 

1023 PT_MIPS_ABIFLAGS= 0x70000003 

1024 PT_HIPROC = 0x7fffffff 

1025 UNKNOWN_PHDR = 0xffffffff 

1026 

1027 @classmethod 

1028 def _missing_(cls, _:int) -> "Phdr.Type": 

1029 return cls.UNKNOWN_PHDR 

1030 

1031 class Flags(enum.IntFlag): 

1032 PF_X = 1 

1033 PF_W = 2 

1034 PF_R = 4 

1035 

1036 p_type: "Phdr.Type" 

1037 p_flags: "Phdr.Flags" 

1038 p_offset: int 

1039 p_vaddr: int 

1040 p_paddr: int 

1041 p_filesz: int 

1042 p_memsz: int 

1043 p_align: int 

1044 

1045 def __init__(self, elf: Elf, off: int) -> None: 

1046 if not elf: 1046 ↛ 1047line 1046 didn't jump to line 1047 because the condition on line 1046 was never true

1047 return 

1048 elf.seek(off) 

1049 self.offset = off 

1050 endian = elf.e_endianness 

1051 if elf.e_class == Elf.Class.ELF_64_BITS: 1051 ↛ 1056line 1051 didn't jump to line 1056 because the condition on line 1051 was always true

1052 p_type, p_flags, self.p_offset = elf.read_and_unpack(f"{endian}IIQ") 

1053 self.p_vaddr, self.p_paddr = elf.read_and_unpack(f"{endian}QQ") 

1054 self.p_filesz, self.p_memsz, self.p_align = elf.read_and_unpack(f"{endian}QQQ") 

1055 else: 

1056 p_type, self.p_offset = elf.read_and_unpack(f"{endian}II") 

1057 self.p_vaddr, self.p_paddr = elf.read_and_unpack(f"{endian}II") 

1058 self.p_filesz, self.p_memsz, p_flags, self.p_align = elf.read_and_unpack(f"{endian}IIII") 

1059 

1060 self.p_type, self.p_flags = Phdr.Type(p_type), Phdr.Flags(p_flags) 

1061 return 

1062 

1063 def __str__(self) -> str: 

1064 return (f"Phdr(offset={self.offset}, type={self.p_type.name}, flags={self.p_flags.name}, " 

1065 f"vaddr={self.p_vaddr}, paddr={self.p_paddr}, filesz={self.p_filesz}, " 

1066 f"memsz={self.p_memsz}, align={self.p_align})") 

1067 

1068 

1069class Shdr: 

1070 class Type(enum.IntEnum): 

1071 SHT_NULL = 0 

1072 SHT_PROGBITS = 1 

1073 SHT_SYMTAB = 2 

1074 SHT_STRTAB = 3 

1075 SHT_RELA = 4 

1076 SHT_HASH = 5 

1077 SHT_DYNAMIC = 6 

1078 SHT_NOTE = 7 

1079 SHT_NOBITS = 8 

1080 SHT_REL = 9 

1081 SHT_SHLIB = 10 

1082 SHT_DYNSYM = 11 

1083 SHT_NUM = 12 

1084 SHT_INIT_ARRAY = 14 

1085 SHT_FINI_ARRAY = 15 

1086 SHT_PREINIT_ARRAY = 16 

1087 SHT_GROUP = 17 

1088 SHT_SYMTAB_SHNDX = 18 

1089 SHT_LOOS = 0x60000000 

1090 SHT_GNU_ATTRIBUTES = 0x6ffffff5 

1091 SHT_GNU_HASH = 0x6ffffff6 

1092 SHT_GNU_LIBLIST = 0x6ffffff7 

1093 SHT_CHECKSUM = 0x6ffffff8 

1094 SHT_LOSUNW = 0x6ffffffa 

1095 SHT_SUNW_move = 0x6ffffffa 

1096 SHT_SUNW_COMDAT = 0x6ffffffb 

1097 SHT_SUNW_syminfo = 0x6ffffffc 

1098 SHT_GNU_verdef = 0x6ffffffd 

1099 SHT_GNU_verneed = 0x6ffffffe 

1100 SHT_GNU_versym = 0x6fffffff 

1101 SHT_LOPROC = 0x70000000 

1102 SHT_ARM_EXIDX = 0x70000001 

1103 SHT_X86_64_UNWIND = 0x70000001 

1104 SHT_ARM_ATTRIBUTES = 0x70000003 

1105 SHT_MIPS_OPTIONS = 0x7000000d 

1106 DT_MIPS_INTERFACE = 0x7000002a 

1107 SHT_HIPROC = 0x7fffffff 

1108 SHT_LOUSER = 0x80000000 

1109 SHT_HIUSER = 0x8fffffff 

1110 UNKNOWN_SHDR = 0xffffffff 

1111 

1112 @classmethod 

1113 def _missing_(cls, _:int) -> "Shdr.Type": 

1114 return cls.UNKNOWN_SHDR 

1115 

1116 class Flags(enum.IntFlag): 

1117 WRITE = 1 

1118 ALLOC = 2 

1119 EXECINSTR = 4 

1120 MERGE = 0x10 

1121 STRINGS = 0x20 

1122 INFO_LINK = 0x40 

1123 LINK_ORDER = 0x80 

1124 OS_NONCONFORMING = 0x100 

1125 GROUP = 0x200 

1126 TLS = 0x400 

1127 COMPRESSED = 0x800 

1128 RELA_LIVEPATCH = 0x00100000 

1129 RO_AFTER_INIT = 0x00200000 

1130 ORDERED = 0x40000000 

1131 EXCLUDE = 0x80000000 

1132 UNKNOWN_FLAG = 0xffffffff 

1133 

1134 @classmethod 

1135 def _missing_(cls, _:int): 

1136 return cls.UNKNOWN_FLAG 

1137 

1138 sh_name: int 

1139 sh_type: "Shdr.Type" 

1140 sh_flags: "Shdr.Flags" 

1141 sh_addr: int 

1142 sh_offset: int 

1143 sh_size: int 

1144 sh_link: int 

1145 sh_info: int 

1146 sh_addralign: int 

1147 sh_entsize: int 

1148 name: str 

1149 

1150 def __init__(self, elf: Optional[Elf], off: int) -> None: 

1151 if elf is None: 1151 ↛ 1152line 1151 didn't jump to line 1152 because the condition on line 1151 was never true

1152 return 

1153 elf.seek(off) 

1154 endian = elf.e_endianness 

1155 if elf.e_class == Elf.Class.ELF_64_BITS: 1155 ↛ 1161line 1155 didn't jump to line 1161 because the condition on line 1155 was always true

1156 self.sh_name, sh_type, sh_flags = elf.read_and_unpack(f"{endian}IIQ") 

1157 self.sh_addr, self.sh_offset = elf.read_and_unpack(f"{endian}QQ") 

1158 self.sh_size, self.sh_link, self.sh_info = elf.read_and_unpack(f"{endian}QII") 

1159 self.sh_addralign, self.sh_entsize = elf.read_and_unpack(f"{endian}QQ") 

1160 else: 

1161 self.sh_name, sh_type, sh_flags = elf.read_and_unpack(f"{endian}III") 

1162 self.sh_addr, self.sh_offset = elf.read_and_unpack(f"{endian}II") 

1163 self.sh_size, self.sh_link, self.sh_info = elf.read_and_unpack(f"{endian}III") 

1164 self.sh_addralign, self.sh_entsize = elf.read_and_unpack(f"{endian}II") 

1165 

1166 self.sh_type = Shdr.Type(sh_type) 

1167 self.sh_flags = Shdr.Flags(sh_flags) 

1168 stroff = elf.e_shoff + elf.e_shentsize * elf.e_shstrndx 

1169 

1170 if elf.e_class == Elf.Class.ELF_64_BITS: 1170 ↛ 1174line 1170 didn't jump to line 1174 because the condition on line 1170 was always true

1171 elf.seek(stroff + 16 + 8) 

1172 offset = u64(elf.read(8)) 

1173 else: 

1174 elf.seek(stroff + 12 + 4) 

1175 offset = u32(elf.read(4)) 

1176 elf.seek(offset + self.sh_name) 

1177 self.name = "" 

1178 while True: 

1179 c = u8(elf.read(1)) 

1180 if c == 0: 

1181 break 

1182 self.name += chr(c) 

1183 return 

1184 

1185 def __str__(self) -> str: 

1186 return (f"Shdr(name={self.name}, type={self.sh_type.name}, flags={self.sh_flags.name}, " 

1187 f"addr={self.sh_addr:#x}, offset={self.sh_offset}, size={self.sh_size}, link={self.sh_link}, " 

1188 f"info={self.sh_info}, addralign={self.sh_addralign}, entsize={self.sh_entsize})") 

1189 

1190 

1191class Instruction: 

1192 """GEF representation of a CPU instruction.""" 

1193 

1194 def __init__(self, address: int, location: str, mnemo: str, operands: List[str], opcodes: bytes) -> None: 

1195 self.address, self.location, self.mnemonic, self.operands, self.opcodes = \ 

1196 address, location, mnemo, operands, opcodes 

1197 return 

1198 

1199 # Allow formatting an instruction with {:o} to show opcodes. 

1200 # The number of bytes to display can be configured, e.g. {:4o} to only show 4 bytes of the opcodes 

1201 def __format__(self, format_spec: str) -> str: 

1202 if len(format_spec) == 0 or format_spec[-1] != "o": 1202 ↛ 1203line 1202 didn't jump to line 1203 because the condition on line 1202 was never true

1203 return str(self) 

1204 

1205 if format_spec == "o": 1205 ↛ 1206line 1205 didn't jump to line 1206 because the condition on line 1205 was never true

1206 opcodes_len = len(self.opcodes) 

1207 else: 

1208 opcodes_len = int(format_spec[:-1]) 

1209 

1210 opcodes_text = "".join(f"{b:02x}" for b in self.opcodes[:opcodes_len]) 

1211 if opcodes_len < len(self.opcodes): 

1212 opcodes_text += "..." 

1213 return (f"{self.address:#10x} {opcodes_text:{opcodes_len * 2 + 3:d}s} {self.location:16} " 

1214 f"{self.mnemonic:6} {', '.join(self.operands)}") 

1215 

1216 def __str__(self) -> str: 

1217 return f"{self.address:#10x} {self.location:16} {self.mnemonic:6} {', '.join(self.operands)}" 

1218 

1219 def is_valid(self) -> bool: 

1220 return "(bad)" not in self.mnemonic 

1221 

1222 def size(self) -> int: 

1223 return len(self.opcodes) 

1224 

1225 def next(self) -> "Instruction": 

1226 address = self.address + self.size() 

1227 return gef_get_instruction_at(address) 

1228 

1229 

1230@deprecated("Use GefHeapManager.find_main_arena_addr()") 

1231def search_for_main_arena() -> int: 

1232 return GefHeapManager.find_main_arena_addr() 

1233 

1234class GlibcHeapInfo: 

1235 """Glibc heap_info struct""" 

1236 

1237 @staticmethod 

1238 def heap_info_t() -> Type[ctypes.Structure]: 

1239 assert gef.libc.version 

1240 class heap_info_cls(ctypes.Structure): 

1241 pass 

1242 pointer = ctypes.c_uint64 if gef.arch.ptrsize == 8 else ctypes.c_uint32 

1243 pad_size = -5 * gef.arch.ptrsize & (gef.heap.malloc_alignment - 1) 

1244 fields = [ 

1245 ("ar_ptr", ctypes.POINTER(GlibcArena.malloc_state_t())), 

1246 ("prev", ctypes.POINTER(heap_info_cls)), 

1247 ("size", pointer) 

1248 ] 

1249 if gef.libc.version >= (2, 5): 1249 ↛ 1254line 1249 didn't jump to line 1254 because the condition on line 1249 was always true

1250 fields += [ 

1251 ("mprotect_size", pointer) 

1252 ] 

1253 pad_size = -6 * gef.arch.ptrsize & (gef.heap.malloc_alignment - 1) 

1254 if gef.libc.version >= (2, 34): 1254 ↛ 1259line 1254 didn't jump to line 1259 because the condition on line 1254 was always true

1255 fields += [ 

1256 ("pagesize", pointer) 

1257 ] 

1258 pad_size = -3 * gef.arch.ptrsize & (gef.heap.malloc_alignment - 1) 

1259 fields += [ 

1260 ("pad", ctypes.c_uint8*pad_size) 

1261 ] 

1262 heap_info_cls._fields_ = fields 

1263 return heap_info_cls 

1264 

1265 def __init__(self, addr: Union[str, int]) -> None: 

1266 self.__address : int = parse_address(f"&{addr}") if isinstance(addr, str) else addr 

1267 self.reset() 

1268 return 

1269 

1270 def reset(self): 

1271 self._sizeof = ctypes.sizeof(GlibcHeapInfo.heap_info_t()) 

1272 self._data = gef.memory.read(self.__address, ctypes.sizeof(GlibcHeapInfo.heap_info_t())) 

1273 self._heap_info = GlibcHeapInfo.heap_info_t().from_buffer_copy(self._data) 

1274 return 

1275 

1276 def __getattr__(self, item: Any) -> Any: 

1277 if item in dir(self._heap_info): 1277 ↛ 1279line 1277 didn't jump to line 1279 because the condition on line 1277 was always true

1278 return ctypes.cast(getattr(self._heap_info, item), ctypes.c_void_p).value 

1279 return getattr(self, item) 

1280 

1281 def __abs__(self) -> int: 

1282 return self.__address 

1283 

1284 def __int__(self) -> int: 

1285 return self.__address 

1286 

1287 @property 

1288 def address(self) -> int: 

1289 return self.__address 

1290 

1291 @property 

1292 def sizeof(self) -> int: 

1293 return self._sizeof 

1294 

1295 @property 

1296 def addr(self) -> int: 

1297 return int(self) 

1298 

1299 @property 

1300 def heap_start(self) -> int: 

1301 # check special case: first heap of non-main-arena 

1302 if self.ar_ptr - self.address < 0x60: 1302 ↛ 1314line 1302 didn't jump to line 1314 because the condition on line 1302 was always true

1303 # the first heap of a non-main-arena starts with a `heap_info` 

1304 # struct, which should fit easily into 0x60 bytes throughout 

1305 # all architectures and glibc versions. If this check succeeds 

1306 # then we are currently looking at such a "first heap" 

1307 arena = GlibcArena(f"*{self.ar_ptr:#x}") 

1308 heap_addr = arena.heap_addr() 

1309 if heap_addr: 1309 ↛ 1312line 1309 didn't jump to line 1312 because the condition on line 1309 was always true

1310 return heap_addr 

1311 else: 

1312 err(f"Cannot find heap address for arena {self.ar_ptr:#x}") 

1313 return 0 

1314 return self.address + self.sizeof 

1315 

1316 @property 

1317 def heap_end(self) -> int: 

1318 return self.address + self.size 

1319 

1320 

1321class GlibcArena: 

1322 """Glibc arena class""" 

1323 

1324 NFASTBINS = 10 

1325 NBINS = 128 

1326 NSMALLBINS = 64 

1327 BINMAPSHIFT = 5 

1328 BITSPERMAP = 1 << BINMAPSHIFT 

1329 BINMAPSIZE = NBINS // BITSPERMAP 

1330 

1331 @staticmethod 

1332 def malloc_state_t() -> Type[ctypes.Structure]: 

1333 pointer = ctypes.c_uint64 if gef and gef.arch.ptrsize == 8 else ctypes.c_uint32 

1334 fields = [ 

1335 ("mutex", ctypes.c_uint32), 

1336 ("flags", ctypes.c_uint32), 

1337 ] 

1338 if gef and gef.libc.version and gef.libc.version >= (2, 27): 1338 ↛ 1344line 1338 didn't jump to line 1344 because the condition on line 1338 was always true

1339 # https://elixir.bootlin.com/glibc/glibc-2.27/source/malloc/malloc.c#L1684 

1340 fields += [ 

1341 ("have_fastchunks", ctypes.c_uint32), 

1342 ("UNUSED_c", ctypes.c_uint32), # padding to align to 0x10 

1343 ] 

1344 fields += [ 

1345 ("fastbinsY", GlibcArena.NFASTBINS * pointer), 

1346 ("top", pointer), 

1347 ("last_remainder", pointer), 

1348 ("bins", (GlibcArena.NBINS * 2 - 2) * pointer), 

1349 ("binmap", GlibcArena.BINMAPSIZE * ctypes.c_uint32), 

1350 ("next", pointer), 

1351 ("next_free", pointer) 

1352 ] 

1353 if gef and gef.libc.version and gef.libc.version >= (2, 23): 1353 ↛ 1358line 1353 didn't jump to line 1358 because the condition on line 1353 was always true

1354 # https://elixir.bootlin.com/glibc/glibc-2.23/source/malloc/malloc.c#L1719 

1355 fields += [ 

1356 ("attached_threads", pointer) 

1357 ] 

1358 fields += [ 

1359 ("system_mem", pointer), 

1360 ("max_system_mem", pointer), 

1361 ] 

1362 class malloc_state_cls(ctypes.Structure): 

1363 _fields_ = fields 

1364 return malloc_state_cls 

1365 

1366 def __init__(self, addr: str) -> None: 

1367 try: 

1368 self.__address : int = parse_address(f"&{addr}") 

1369 except gdb.error: 

1370 self.__address : int = GefHeapManager.find_main_arena_addr() 

1371 # if `find_main_arena_addr` throws `gdb.error` on symbol lookup: 

1372 # it means the session is not started, so just propagate the exception 

1373 self.reset() 

1374 return 

1375 

1376 def reset(self): 

1377 self._sizeof = ctypes.sizeof(GlibcArena.malloc_state_t()) 

1378 self._data = gef.memory.read(self.__address, ctypes.sizeof(GlibcArena.malloc_state_t())) 

1379 self.__arena = GlibcArena.malloc_state_t().from_buffer_copy(self._data) 

1380 return 

1381 

1382 def __abs__(self) -> int: 

1383 return self.__address 

1384 

1385 def __int__(self) -> int: 

1386 return self.__address 

1387 

1388 def __iter__(self) -> Generator["GlibcArena", None, None]: 

1389 assert gef.heap.main_arena 

1390 main_arena = int(gef.heap.main_arena) 

1391 

1392 current_arena = self 

1393 yield current_arena 

1394 

1395 while True: 

1396 if current_arena.next == 0 or current_arena.next == main_arena: 

1397 break 

1398 

1399 current_arena = GlibcArena(f"*{current_arena.next:#x} ") 

1400 yield current_arena 

1401 return 

1402 

1403 def __eq__(self, other: "GlibcArena") -> bool: 

1404 return self.__address == int(other) 

1405 

1406 def __str__(self) -> str: 

1407 properties = f"base={self.__address:#x}, top={self.top:#x}, " \ 

1408 f"last_remainder={self.last_remainder:#x}, next={self.next:#x}, " \ 

1409 f"mem={self.system_mem}, mempeak={self.max_system_mem}" 

1410 return (f"{Color.colorify('Arena', 'blue bold underline')}({properties})") 

1411 

1412 def __repr__(self) -> str: 

1413 return f"GlibcArena(address={self.__address:#x}, size={self._sizeof})" 

1414 

1415 @property 

1416 def address(self) -> int: 

1417 return self.__address 

1418 

1419 @property 

1420 def sizeof(self) -> int: 

1421 return self._sizeof 

1422 

1423 @property 

1424 def addr(self) -> int: 

1425 return int(self) 

1426 

1427 @property 

1428 def top(self) -> int: 

1429 return self.__arena.top 

1430 

1431 @property 

1432 def last_remainder(self) -> int: 

1433 return self.__arena.last_remainder 

1434 

1435 @property 

1436 def fastbinsY(self) -> ctypes.Array: 

1437 return self.__arena.fastbinsY 

1438 

1439 @property 

1440 def bins(self) -> ctypes.Array: 

1441 return self.__arena.bins 

1442 

1443 @property 

1444 def binmap(self) -> ctypes.Array: 

1445 return self.__arena.binmap 

1446 

1447 @property 

1448 def next(self) -> int: 

1449 return self.__arena.next 

1450 

1451 @property 

1452 def next_free(self) -> int: 

1453 return self.__arena.next_free 

1454 

1455 @property 

1456 def attached_threads(self) -> int: 

1457 return self.__arena.attached_threads 

1458 

1459 @property 

1460 def system_mem(self) -> int: 

1461 return self.__arena.system_mem 

1462 

1463 @property 

1464 def max_system_mem(self) -> int: 

1465 return self.__arena.max_system_mem 

1466 

1467 def fastbin(self, i: int) -> Optional["GlibcFastChunk"]: 

1468 """Return head chunk in fastbinsY[i].""" 

1469 addr = int(self.fastbinsY[i]) 

1470 if addr == 0: 

1471 return None 

1472 return GlibcFastChunk(addr + 2 * gef.arch.ptrsize) 

1473 

1474 def bin(self, i: int) -> Tuple[int, int]: 

1475 idx = i * 2 

1476 fd = int(self.bins[idx]) 

1477 bk = int(self.bins[idx + 1]) 

1478 return fd, bk 

1479 

1480 def bin_at(self, i) -> int: 

1481 header_sz = 2 * gef.arch.ptrsize 

1482 offset = ctypes.addressof(self.__arena.bins) - ctypes.addressof(self.__arena) 

1483 return self.__address + offset + (i-1) * 2 * gef.arch.ptrsize + header_sz 

1484 

1485 def is_main_arena(self) -> bool: 

1486 return gef.heap.main_arena is not None and int(self) == int(gef.heap.main_arena) 

1487 

1488 def heap_addr(self, allow_unaligned: bool = False) -> Optional[int]: 

1489 if self.is_main_arena(): 

1490 heap_section = gef.heap.base_address 

1491 if not heap_section: 1491 ↛ 1492line 1491 didn't jump to line 1492 because the condition on line 1491 was never true

1492 return None 

1493 return heap_section 

1494 _addr = int(self) + self.sizeof 

1495 if allow_unaligned: 1495 ↛ 1496line 1495 didn't jump to line 1496 because the condition on line 1495 was never true

1496 return _addr 

1497 return gef.heap.malloc_align_address(_addr) 

1498 

1499 def get_heap_info_list(self) -> Optional[List[GlibcHeapInfo]]: 

1500 if self.is_main_arena(): 1500 ↛ 1501line 1500 didn't jump to line 1501 because the condition on line 1500 was never true

1501 return None 

1502 heap_addr = self.get_heap_for_ptr(self.top) 

1503 heap_infos = [GlibcHeapInfo(heap_addr)] 

1504 while heap_infos[-1].prev is not None: 1504 ↛ 1505line 1504 didn't jump to line 1505 because the condition on line 1504 was never true

1505 prev = int(heap_infos[-1].prev) 

1506 heap_info = GlibcHeapInfo(prev) 

1507 heap_infos.append(heap_info) 

1508 return heap_infos[::-1] 

1509 

1510 @staticmethod 

1511 def get_heap_for_ptr(ptr: int) -> int: 

1512 """Find the corresponding heap for a given pointer (int). 

1513 See https://github.com/bminor/glibc/blob/glibc-2.34/malloc/arena.c#L129""" 

1514 if is_32bit(): 1514 ↛ 1515line 1514 didn't jump to line 1515 because the condition on line 1514 was never true

1515 default_mmap_threshold_max = 512 * 1024 

1516 else: # 64bit 

1517 val = cached_lookup_type("long") 

1518 sz = val.sizeof if val else gef.arch.ptrsize 

1519 default_mmap_threshold_max = 4 * 1024 * 1024 * sz 

1520 heap_max_size = 2 * default_mmap_threshold_max 

1521 return ptr & ~(heap_max_size - 1) 

1522 

1523 @staticmethod 

1524 def verify(addr: int) -> bool: 

1525 """Verify that the address matches a possible valid GlibcArena""" 

1526 try: 

1527 test_arena = GlibcArena(f"*{addr:#x}") 

1528 cur_arena = GlibcArena(f"*{test_arena.next:#x}") 

1529 while cur_arena != test_arena: 

1530 if cur_arena == 0: 

1531 return False 

1532 cur_arena = GlibcArena(f"*{cur_arena.next:#x}") 

1533 except Exception as e: 

1534 dbg(f"GlibcArena.verify({addr:#x}) failed: {str(e)}") 

1535 return False 

1536 return True 

1537 

1538 

1539class GlibcChunk: 

1540 """Glibc chunk class. The default behavior (from_base=False) is to interpret the data starting at the memory 

1541 address pointed to as the chunk data. Setting from_base to True instead treats that data as the chunk header. 

1542 Ref: https://sploitfun.wordpress.com/2015/02/10/understanding-glibc-malloc/.""" 

1543 

1544 class ChunkFlags(enum.IntFlag): 

1545 PREV_INUSE = 1 

1546 IS_MMAPPED = 2 

1547 NON_MAIN_ARENA = 4 

1548 

1549 def __str__(self) -> str: 

1550 return f" | ".join([ 

1551 Color.greenify("PREV_INUSE") if self.value & self.PREV_INUSE else Color.redify("PREV_INUSE"), 

1552 Color.greenify("IS_MMAPPED") if self.value & self.IS_MMAPPED else Color.redify("IS_MMAPPED"), 

1553 Color.greenify("NON_MAIN_ARENA") if self.value & self.NON_MAIN_ARENA else Color.redify("NON_MAIN_ARENA") 

1554 ]) 

1555 

1556 @staticmethod 

1557 def malloc_chunk_t() -> Type[ctypes.Structure]: 

1558 pointer = ctypes.c_uint64 if gef and gef.arch.ptrsize == 8 else ctypes.c_uint32 

1559 class malloc_chunk_cls(ctypes.Structure): 

1560 pass 

1561 

1562 malloc_chunk_cls._fields_ = [ 

1563 ("prev_size", pointer), 

1564 ("size", pointer), 

1565 ("fd", pointer), 

1566 ("bk", pointer), 

1567 ("fd_nextsize", ctypes.POINTER(malloc_chunk_cls)), 

1568 ("bk_nextsize", ctypes.POINTER(malloc_chunk_cls)), 

1569 ] 

1570 return malloc_chunk_cls 

1571 

1572 def __init__(self, addr: int, from_base: bool = False, allow_unaligned: bool = True) -> None: 

1573 ptrsize = gef.arch.ptrsize 

1574 self.data_address = addr + 2 * ptrsize if from_base else addr 

1575 self.base_address = addr if from_base else addr - 2 * ptrsize 

1576 if not allow_unaligned: 

1577 self.data_address = gef.heap.malloc_align_address(self.data_address) 

1578 self.size_addr = int(self.data_address - ptrsize) 

1579 self.prev_size_addr = self.base_address 

1580 self.reset() 

1581 return 

1582 

1583 def reset(self): 

1584 self._sizeof = ctypes.sizeof(GlibcChunk.malloc_chunk_t()) 

1585 self._data = gef.memory.read( 

1586 self.base_address, ctypes.sizeof(GlibcChunk.malloc_chunk_t())) 

1587 self._chunk = GlibcChunk.malloc_chunk_t().from_buffer_copy(self._data) 

1588 return 

1589 

1590 @property 

1591 def prev_size(self) -> int: 

1592 return self._chunk.prev_size 

1593 

1594 @property 

1595 def size(self) -> int: 

1596 return self._chunk.size & (~0x07) 

1597 

1598 @property 

1599 def flags(self) -> ChunkFlags: 

1600 return GlibcChunk.ChunkFlags(self._chunk.size & 0x07) 

1601 

1602 @property 

1603 def fd(self) -> int: 

1604 return self._chunk.fd 

1605 

1606 @property 

1607 def bk(self) -> int: 

1608 return self._chunk.bk 

1609 

1610 @property 

1611 def fd_nextsize(self) -> int: 

1612 return self._chunk.fd_nextsize 

1613 

1614 @property 

1615 def bk_nextsize(self) -> int: 

1616 return self._chunk.bk_nextsize 

1617 

1618 def get_usable_size(self) -> int: 

1619 # https://github.com/sploitfun/lsploits/blob/master/glibc/malloc/malloc.c#L4537 

1620 ptrsz = gef.arch.ptrsize 

1621 cursz = self.size 

1622 if cursz == 0: return cursz 1622 ↛ exitline 1622 didn't return from function 'get_usable_size' because the return on line 1622 wasn't executed

1623 if self.has_m_bit(): return cursz - 2 * ptrsz 1623 ↛ exitline 1623 didn't return from function 'get_usable_size' because the return on line 1623 wasn't executed

1624 return cursz - ptrsz 

1625 

1626 @property 

1627 def usable_size(self) -> int: 

1628 return self.get_usable_size() 

1629 

1630 def get_prev_chunk_size(self) -> int: 

1631 return gef.memory.read_integer(self.prev_size_addr) 

1632 

1633 def __iter__(self) -> Generator["GlibcChunk", None, None]: 

1634 assert gef.heap.main_arena 

1635 current_chunk = self 

1636 top = gef.heap.main_arena.top 

1637 

1638 while True: 

1639 yield current_chunk 

1640 

1641 if current_chunk.base_address == top: 

1642 break 

1643 

1644 if current_chunk.size == 0: 1644 ↛ 1645line 1644 didn't jump to line 1645 because the condition on line 1644 was never true

1645 break 

1646 

1647 next_chunk_addr = current_chunk.get_next_chunk_addr() 

1648 

1649 if not Address(value=next_chunk_addr).valid: 1649 ↛ 1650line 1649 didn't jump to line 1650 because the condition on line 1649 was never true

1650 break 

1651 

1652 next_chunk = current_chunk.get_next_chunk() 

1653 if next_chunk is None: 1653 ↛ 1654line 1653 didn't jump to line 1654 because the condition on line 1653 was never true

1654 break 

1655 

1656 current_chunk = next_chunk 

1657 return 

1658 

1659 def get_next_chunk(self, allow_unaligned: bool = False) -> "GlibcChunk": 

1660 addr = self.get_next_chunk_addr() 

1661 return GlibcChunk(addr, allow_unaligned=allow_unaligned) 

1662 

1663 def get_next_chunk_addr(self) -> int: 

1664 return self.data_address + self.size 

1665 

1666 def has_p_bit(self) -> bool: 

1667 return bool(self.flags & GlibcChunk.ChunkFlags.PREV_INUSE) 

1668 

1669 def has_m_bit(self) -> bool: 

1670 return bool(self.flags & GlibcChunk.ChunkFlags.IS_MMAPPED) 

1671 

1672 def has_n_bit(self) -> bool: 

1673 return bool(self.flags & GlibcChunk.ChunkFlags.NON_MAIN_ARENA) 

1674 

1675 def is_used(self) -> bool: 

1676 """Check if the current block is used by: 

1677 - checking the M bit is true 

1678 - or checking that next chunk PREV_INUSE flag is true""" 

1679 if self.has_m_bit(): 1679 ↛ 1680line 1679 didn't jump to line 1680 because the condition on line 1679 was never true

1680 return True 

1681 

1682 next_chunk = self.get_next_chunk() 

1683 return True if next_chunk.has_p_bit() else False 

1684 

1685 def __str_sizes(self) -> str: 

1686 msg = [] 

1687 failed = False 

1688 

1689 try: 

1690 msg.append(f"Chunk size: {self.size:d} ({self.size:#x})") 

1691 msg.append(f"Usable size: {self.usable_size:d} ({self.usable_size:#x})") 

1692 failed = True 

1693 except gdb.MemoryError: 

1694 msg.append(f"Chunk size: Cannot read at {self.size_addr:#x} (corrupted?)") 

1695 

1696 try: 

1697 prev_chunk_sz = self.get_prev_chunk_size() 

1698 msg.append(f"Previous chunk size: {prev_chunk_sz:d} ({prev_chunk_sz:#x})") 

1699 failed = True 

1700 except gdb.MemoryError: 

1701 msg.append(f"Previous chunk size: Cannot read at {self.base_address:#x} (corrupted?)") 

1702 

1703 if failed: 1703 ↛ 1706line 1703 didn't jump to line 1706 because the condition on line 1703 was always true

1704 msg.append(str(self.flags)) 

1705 

1706 return "\n".join(msg) 

1707 

1708 def _str_pointers(self) -> str: 

1709 fwd = self.data_address 

1710 bkw = self.data_address + gef.arch.ptrsize 

1711 

1712 msg = [] 

1713 try: 

1714 msg.append(f"Forward pointer: {self.fd:#x}") 

1715 except gdb.MemoryError: 

1716 msg.append(f"Forward pointer: {fwd:#x} (corrupted?)") 

1717 

1718 try: 

1719 msg.append(f"Backward pointer: {self.bk:#x}") 

1720 except gdb.MemoryError: 

1721 msg.append(f"Backward pointer: {bkw:#x} (corrupted?)") 

1722 

1723 return "\n".join(msg) 

1724 

1725 def __str__(self) -> str: 

1726 return (f"{Color.colorify('Chunk', 'yellow bold underline')}(addr={self.data_address:#x}, " 

1727 f"size={self.size:#x}, flags={self.flags!s})") 

1728 

1729 def psprint(self) -> str: 

1730 msg = [ 

1731 str(self), 

1732 self.__str_sizes(), 

1733 ] 

1734 if not self.is_used(): 1734 ↛ 1735line 1734 didn't jump to line 1735 because the condition on line 1734 was never true

1735 msg.append(f"\n\n{self._str_pointers()}") 

1736 return "\n".join(msg) + "\n" 

1737 

1738 def resolve_type(self) -> str: 

1739 ptr_data = gef.memory.read_integer(self.data_address) 

1740 if ptr_data != 0: 

1741 sym = gdb_get_location_from_symbol(ptr_data) 

1742 if sym is not None and "vtable for" in sym[0]: 

1743 return sym[0].replace("vtable for ", "") 

1744 

1745 return "" 

1746 

1747 

1748class GlibcFastChunk(GlibcChunk): 

1749 

1750 @property 

1751 def fd(self) -> int: 

1752 assert(gef and gef.libc.version) 

1753 if gef.libc.version < (2, 32): 1753 ↛ 1754line 1753 didn't jump to line 1754 because the condition on line 1753 was never true

1754 return self._chunk.fd 

1755 return self.reveal_ptr(self.data_address) 

1756 

1757 def protect_ptr(self, pos: int, pointer: int) -> int: 

1758 """https://elixir.bootlin.com/glibc/glibc-2.32/source/malloc/malloc.c#L339""" 

1759 assert(gef and gef.libc.version) 

1760 if gef.libc.version < (2, 32): 

1761 return pointer 

1762 return (pos >> 12) ^ pointer 

1763 

1764 def reveal_ptr(self, pointer: int) -> int: 

1765 """https://elixir.bootlin.com/glibc/glibc-2.32/source/malloc/malloc.c#L341""" 

1766 assert(gef and gef.libc.version) 

1767 if gef.libc.version < (2, 32): 1767 ↛ 1768line 1767 didn't jump to line 1768 because the condition on line 1767 was never true

1768 return pointer 

1769 return gef.memory.read_integer(pointer) ^ (pointer >> 12) 

1770 

1771class GlibcTcacheChunk(GlibcFastChunk): 

1772 pass 

1773 

1774@deprecated("Use GefLibcManager.find_libc_version()") 

1775def get_libc_version() -> Tuple[int, ...]: 

1776 return GefLibcManager.find_libc_version() 

1777 

1778def titlify(text: str, color: Optional[str] = None, msg_color: Optional[str] = None) -> str: 

1779 """Print a centered title.""" 

1780 _, cols = get_terminal_size() 

1781 nb = (cols - len(text) - 2) // 2 

1782 line_color = color or gef.config["theme.default_title_line"] 

1783 text_color = msg_color or gef.config["theme.default_title_message"] 

1784 

1785 msg = [Color.colorify(f"{HORIZONTAL_LINE * nb} ", line_color), 

1786 Color.colorify(text, text_color), 

1787 Color.colorify(f" {HORIZONTAL_LINE * nb}", line_color)] 

1788 return "".join(msg) 

1789 

1790 

1791def dbg(msg: str) -> None: 

1792 if gef.config["gef.debug"] is True: 

1793 gef_print(f"{Color.colorify('[=]', 'bold cyan')} {msg}") 

1794 return 

1795 

1796 

1797def err(msg: str) -> None: 

1798 gef_print(f"{Color.colorify('[!]', 'bold red')} {msg}") 

1799 return 

1800 

1801 

1802def warn(msg: str) -> None: 

1803 gef_print(f"{Color.colorify('[*]', 'bold yellow')} {msg}") 

1804 return 

1805 

1806 

1807def ok(msg: str) -> None: 

1808 gef_print(f"{Color.colorify('[+]', 'bold green')} {msg}") 

1809 return 

1810 

1811 

1812def info(msg: str) -> None: 

1813 gef_print(f"{Color.colorify('[+]', 'bold blue')} {msg}") 

1814 return 

1815 

1816 

1817def push_context_message(level: str, message: str) -> None: 

1818 """Push the message to be displayed the next time the context is invoked.""" 

1819 if level not in ("error", "warn", "ok", "info"): 1819 ↛ 1820line 1819 didn't jump to line 1820 because the condition on line 1819 was never true

1820 err(f"Invalid level '{level}', discarding message") 

1821 return 

1822 gef.ui.context_messages.append((level, message)) 

1823 return 

1824 

1825 

1826def show_last_exception() -> None: 

1827 """Display the last Python exception.""" 

1828 

1829 def _show_code_line(fname: str, idx: int) -> str: 

1830 fname = os.path.expanduser(os.path.expandvars(fname)) 

1831 with open(fname, "r") as f: 

1832 _data = f.readlines() 

1833 return _data[idx - 1] if 0 < idx < len(_data) else "" 

1834 

1835 gef_print("") 

1836 exc_type, exc_value, exc_traceback = sys.exc_info() 

1837 

1838 gef_print(" Exception raised ".center(80, HORIZONTAL_LINE)) 

1839 gef_print(f"{Color.colorify(exc_type.__name__, 'bold underline red')}: {exc_value}") 

1840 gef_print(" Detailed stacktrace ".center(80, HORIZONTAL_LINE)) 

1841 

1842 for fs in traceback.extract_tb(exc_traceback)[::-1]: 

1843 filename, lineno, method, code = fs 

1844 

1845 if not code or not code.strip(): 1845 ↛ 1846line 1845 didn't jump to line 1846 because the condition on line 1845 was never true

1846 code = _show_code_line(filename, lineno) 

1847 

1848 gef_print(f"""{DOWN_ARROW} File "{Color.yellowify(filename)}", line {lineno:d}, in {Color.greenify(method)}()""") 

1849 gef_print(f" {RIGHT_ARROW} {code}") 

1850 

1851 gef_print(" Version ".center(80, HORIZONTAL_LINE)) 

1852 gdb.execute("version full") 

1853 gef_print(" Last 10 GDB commands ".center(80, HORIZONTAL_LINE)) 

1854 gdb.execute("show commands") 

1855 gef_print(" Runtime environment ".center(80, HORIZONTAL_LINE)) 

1856 gef_print(f"* GDB: {gdb.VERSION}") 

1857 gef_print(f"* Python: {sys.version_info.major:d}.{sys.version_info.minor:d}.{sys.version_info.micro:d} - {sys.version_info.releaselevel}") 

1858 gef_print(f"* OS: {platform.system()} - {platform.release()} ({platform.machine()})") 

1859 

1860 try: 

1861 lsb_release = which("lsb_release") 

1862 gdb.execute(f"!'{lsb_release}' -a") 

1863 except FileNotFoundError: 

1864 gef_print("lsb_release is missing, cannot collect additional debug information") 

1865 

1866 gef_print(HORIZONTAL_LINE*80) 

1867 gef_print("") 

1868 return 

1869 

1870 

1871def gef_pystring(x: bytes) -> str: 

1872 """Returns a sanitized version as string of the bytes list given in input.""" 

1873 res = str(x, encoding="utf-8") 

1874 substs = [("\n", "\\n"), ("\r", "\\r"), ("\t", "\\t"), ("\v", "\\v"), ("\b", "\\b"), ] 

1875 for _x, _y in substs: res = res.replace(_x, _y) 

1876 return res 

1877 

1878 

1879def gef_pybytes(x: str) -> bytes: 

1880 """Returns an immutable bytes list from the string given as input.""" 

1881 return bytes(str(x), encoding="utf-8") 

1882 

1883 

1884@lru_cache() 

1885def which(program: str) -> pathlib.Path: 

1886 """Locate a command on the filesystem.""" 

1887 for path in os.environ["PATH"].split(os.pathsep): 

1888 dirname = pathlib.Path(path) 

1889 fpath = dirname / program 

1890 if os.access(fpath, os.X_OK): 

1891 return fpath 

1892 

1893 raise FileNotFoundError(f"Missing file `{program}`") 

1894 

1895 

1896def style_byte(b: int, color: bool = True) -> str: 

1897 style = { 

1898 "nonprintable": "yellow", 

1899 "printable": "white", 

1900 "00": "gray", 

1901 "0a": "blue", 

1902 "ff": "green", 

1903 } 

1904 sbyte = f"{b:02x}" 

1905 if not color or gef.config["highlight.regex"]: 

1906 return sbyte 

1907 

1908 if sbyte in style: 

1909 st = style[sbyte] 

1910 elif chr(b) in (string.ascii_letters + string.digits + string.punctuation + " "): 

1911 st = style.get("printable") 

1912 else: 

1913 st = style.get("nonprintable") 

1914 if st: 1914 ↛ 1916line 1914 didn't jump to line 1916 because the condition on line 1914 was always true

1915 sbyte = Color.colorify(sbyte, st) 

1916 return sbyte 

1917 

1918 

1919def hexdump(source: ByteString, length: int = 0x10, separator: str = ".", show_raw: bool = False, show_symbol: bool = True, base: int = 0x00) -> str: 

1920 """Return the hexdump of `src` argument. 

1921 @param source *MUST* be of type bytes or bytearray 

1922 @param length is the length of items per line 

1923 @param separator is the default character to use if one byte is not printable 

1924 @param show_raw if True, do not add the line nor the text translation 

1925 @param base is the start address of the block being hexdump 

1926 @return a string with the hexdump""" 

1927 result = [] 

1928 align = gef.arch.ptrsize * 2 + 2 if is_alive() else 18 

1929 

1930 for i in range(0, len(source), length): 

1931 chunk = bytearray(source[i : i + length]) 

1932 hexa = " ".join([style_byte(b, color=not show_raw) for b in chunk]) 

1933 

1934 if show_raw: 

1935 result.append(hexa) 

1936 continue 

1937 

1938 text = "".join([chr(b) if 0x20 <= b < 0x7F else separator for b in chunk]) 

1939 if show_symbol: 1939 ↛ 1943line 1939 didn't jump to line 1943 because the condition on line 1939 was always true

1940 sym = gdb_get_location_from_symbol(base + i) 

1941 sym = f"<{sym[0]:s}+{sym[1]:04x}>" if sym else "" 

1942 else: 

1943 sym = "" 

1944 

1945 result.append(f"{base + i:#0{align}x} {sym} {hexa:<{3 * length}} {text}") 

1946 return "\n".join(result) 

1947 

1948 

1949def is_debug() -> bool: 

1950 """Check if debug mode is enabled.""" 

1951 return gef.config["gef.debug"] is True 

1952 

1953 

1954def buffer_output() -> bool: 

1955 """Check if output should be buffered until command completion.""" 

1956 return gef.config["gef.buffer"] is True 

1957 

1958 

1959def hide_context() -> bool: 

1960 """Helper function to hide the context pane.""" 

1961 gef.ui.context_hidden = True 

1962 return True 

1963 

1964 

1965def unhide_context() -> bool: 

1966 """Helper function to unhide the context pane.""" 

1967 gef.ui.context_hidden = False 

1968 return True 

1969 

1970 

1971class DisableContextOutputContext: 

1972 def __enter__(self) -> None: 

1973 hide_context() 

1974 return 

1975 

1976 def __exit__(self, *exc: Any) -> None: 

1977 unhide_context() 

1978 return 

1979 

1980 

1981class RedirectOutputContext: 

1982 def __init__(self, to_file: str = "/dev/null") -> None: 

1983 if " " in to_file: raise ValueError("Target filepath cannot contain spaces") 1983 ↛ exitline 1983 didn't except from function '__init__' because the raise on line 1983 wasn't executed

1984 self.redirection_target_file = to_file 

1985 return 

1986 

1987 def __enter__(self) -> None: 

1988 """Redirect all GDB output to `to_file` parameter. By default, `to_file` redirects to `/dev/null`.""" 

1989 gdb.execute("set logging overwrite") 

1990 gdb.execute(f"set logging file {self.redirection_target_file}") 

1991 gdb.execute("set logging redirect on") 

1992 

1993 if GDB_VERSION >= (12, 0): 1993 ↛ 1996line 1993 didn't jump to line 1996 because the condition on line 1993 was always true

1994 gdb.execute("set logging enabled on") 

1995 else: 

1996 gdb.execute("set logging on") 

1997 return 

1998 

1999 def __exit__(self, *exc: Any) -> None: 

2000 """Disable the output redirection, if any.""" 

2001 if GDB_VERSION >= (12, 0): 2001 ↛ 2004line 2001 didn't jump to line 2004 because the condition on line 2001 was always true

2002 gdb.execute("set logging enabled off") 

2003 else: 

2004 gdb.execute("set logging off") 

2005 gdb.execute("set logging redirect off") 

2006 return 

2007 

2008 

2009def enable_redirect_output(to_file: str = "/dev/null") -> None: 

2010 """Redirect all GDB output to `to_file` parameter. By default, `to_file` redirects to `/dev/null`.""" 

2011 if " " in to_file: raise ValueError("Target filepath cannot contain spaces") 

2012 gdb.execute("set logging overwrite") 

2013 gdb.execute(f"set logging file {to_file}") 

2014 gdb.execute("set logging redirect on") 

2015 

2016 if GDB_VERSION >= (12, 0): 

2017 gdb.execute("set logging enabled on") 

2018 else: 

2019 gdb.execute("set logging on") 

2020 return 

2021 

2022 

2023def disable_redirect_output() -> None: 

2024 """Disable the output redirection, if any.""" 

2025 if GDB_VERSION >= (12, 0): 

2026 gdb.execute("set logging enabled off") 

2027 else: 

2028 gdb.execute("set logging off") 

2029 gdb.execute("set logging redirect off") 

2030 return 

2031 

2032@deprecated("use `pathlib.Path(...).mkdir()`") 

2033def gef_makedirs(path: str, mode: int = 0o755) -> pathlib.Path: 

2034 """Recursive mkdir() creation. If successful, return the absolute path of the directory created.""" 

2035 fpath = pathlib.Path(path) 

2036 if not fpath.is_dir(): 

2037 fpath.mkdir(mode=mode, exist_ok=True, parents=True) 

2038 return fpath.absolute() 

2039 

2040 

2041@lru_cache() 

2042def gdb_lookup_symbol(sym: str) -> Optional[Tuple[gdb.Symtab_and_line, ...]]: 

2043 """Fetch the proper symbol or None if not defined.""" 

2044 try: 

2045 res = gdb.decode_line(sym)[1] # pylint: disable=E1136 

2046 return res 

2047 except gdb.error: 

2048 return None 

2049 

2050@lru_cache(maxsize=512) 

2051def gdb_get_location_from_symbol(address: int) -> Optional[Tuple[str, int]]: 

2052 """Retrieve the location of the `address` argument from the symbol table. 

2053 Return a tuple with the name and offset if found, None otherwise.""" 

2054 # this is horrible, ugly hack and shitty perf... 

2055 # find a *clean* way to get gdb.Location from an address 

2056 sym = str(gdb.execute(f"info symbol {address:#x}", to_string=True)) 

2057 if sym.startswith("No symbol matches"): 

2058 return None 

2059 

2060 # gdb outputs symbols with format: "<symbol_name> + <offset> in section <section_name> of <file>", 

2061 # here, we are only interested in symbol name and offset. 

2062 i = sym.find(" in section ") 

2063 sym = sym[:i].split(" + ") 

2064 name, offset = sym[0], 0 

2065 if len(sym) == 2 and sym[1].isdigit(): 

2066 offset = int(sym[1]) 

2067 return name, offset 

2068 

2069 

2070def gdb_disassemble(start_pc: int, **kwargs: int) -> Generator[Instruction, None, None]: 

2071 """Disassemble instructions from `start_pc` (Integer). Accepts the following named 

2072 parameters: 

2073 - `end_pc` (Integer) only instructions whose start address fall in the interval from 

2074 start_pc to end_pc are returned. 

2075 - `count` (Integer) list at most this many disassembled instructions 

2076 If `end_pc` and `count` are not provided, the function will behave as if `count=1`. 

2077 Return an iterator of Instruction objects 

2078 """ 

2079 frame = gdb.selected_frame() 

2080 arch = frame.architecture() 

2081 

2082 for insn in arch.disassemble(start_pc, **kwargs): 

2083 assert isinstance(insn["addr"], int) 

2084 assert isinstance(insn["length"], int) 

2085 assert isinstance(insn["asm"], str) 

2086 address = insn["addr"] 

2087 asm = insn["asm"].rstrip().split(None, 1) 

2088 if len(asm) > 1: 

2089 mnemo, operands = asm 

2090 operands = operands.split(",") 

2091 else: 

2092 mnemo, operands = asm[0], [] 

2093 

2094 loc = gdb_get_location_from_symbol(address) 

2095 location = f"<{loc[0]}+{loc[1]:04x}>" if loc else "" 

2096 

2097 opcodes = gef.memory.read(insn["addr"], insn["length"]) 

2098 

2099 yield Instruction(address, location, mnemo, operands, opcodes) 

2100 

2101 

2102def gdb_get_nth_previous_instruction_address(addr: int, n: int) -> Optional[int]: 

2103 """Return the address (Integer) of the `n`-th instruction before `addr`.""" 

2104 # fixed-length ABI 

2105 if gef.arch.instruction_length: 2105 ↛ 2106line 2105 didn't jump to line 2106 because the condition on line 2105 was never true

2106 return max(0, addr - n * gef.arch.instruction_length) 

2107 

2108 # variable-length ABI 

2109 cur_insn_addr = gef_current_instruction(addr).address 

2110 

2111 # we try to find a good set of previous instructions by "guessing" disassembling backwards 

2112 # the 15 comes from the longest instruction valid size 

2113 for i in range(15 * n, 0, -1): 2113 ↛ 2136line 2113 didn't jump to line 2136 because the loop on line 2113 didn't complete

2114 try: 

2115 insns = list(gdb_disassemble(addr - i, end_pc=cur_insn_addr)) 

2116 except gdb.MemoryError: 

2117 # this is because we can hit an unmapped page trying to read backward 

2118 break 

2119 

2120 # 1. check that the disassembled instructions list size can satisfy 

2121 if len(insns) < n + 1: # we expect the current instruction plus the n before it 2121 ↛ 2122line 2121 didn't jump to line 2122 because the condition on line 2121 was never true

2122 continue 

2123 

2124 # If the list of instructions is longer than what we need, then we 

2125 # could get lucky and already have more than what we need, so slice down 

2126 insns = insns[-n - 1 :] 

2127 

2128 # 2. check that the sequence ends with the current address 

2129 if insns[-1].address != cur_insn_addr: 2129 ↛ 2130line 2129 didn't jump to line 2130 because the condition on line 2129 was never true

2130 continue 

2131 

2132 # 3. check all instructions are valid 

2133 if all(insn.is_valid() for insn in insns): 2133 ↛ 2113line 2133 didn't jump to line 2113 because the condition on line 2133 was always true

2134 return insns[0].address 

2135 

2136 return None 

2137 

2138 

2139@deprecated(solution="Use `gef_instruction_n().address`") 

2140def gdb_get_nth_next_instruction_address(addr: int, n: int) -> int: 

2141 """Return the address of the `n`-th instruction after `addr`. """ 

2142 return gef_instruction_n(addr, n).address 

2143 

2144 

2145def gef_instruction_n(addr: int, n: int) -> Instruction: 

2146 """Return the `n`-th instruction after `addr` as an Instruction object. Note that `n` is treated as 

2147 an positive index, starting from 0 (current instruction address)""" 

2148 return list(gdb_disassemble(addr, count=n + 1))[n] 

2149 

2150 

2151def gef_get_instruction_at(addr: int) -> Instruction: 

2152 """Return the full Instruction found at the specified address.""" 

2153 insn = next(gef_disassemble(addr, 1)) 

2154 return insn 

2155 

2156 

2157def gef_current_instruction(addr: int) -> Instruction: 

2158 """Return the current instruction as an Instruction object.""" 

2159 return gef_instruction_n(addr, 0) 

2160 

2161 

2162def gef_next_instruction(addr: int) -> Instruction: 

2163 """Return the next instruction as an Instruction object.""" 

2164 return gef_instruction_n(addr, 1) 

2165 

2166 

2167def gef_disassemble(addr: int, nb_insn: int, nb_prev: int = 0) -> Generator[Instruction, None, None]: 

2168 """Disassemble `nb_insn` instructions after `addr` and `nb_prev` before `addr`. 

2169 Return an iterator of Instruction objects.""" 

2170 nb_insn = max(1, nb_insn) 

2171 

2172 if nb_prev: 

2173 try: 

2174 start_addr = gdb_get_nth_previous_instruction_address(addr, nb_prev) 

2175 if start_addr: 

2176 for insn in gdb_disassemble(start_addr, count=nb_prev): 

2177 if insn.address == addr: break 2177 ↛ 2183line 2177 didn't jump to line 2183 because the break on line 2177 wasn't executed

2178 yield insn 

2179 except gdb.MemoryError: 

2180 # If the address pointing to the previous instruction(s) is not mapped, simply skip them 

2181 pass 

2182 

2183 for insn in gdb_disassemble(addr, count=nb_insn): 

2184 yield insn 

2185 

2186 

2187def gef_execute_external(command: Sequence[str], as_list: bool = False, **kwargs: Any) -> Union[str, List[str]]: 

2188 """Execute an external command and return the result.""" 

2189 res = subprocess.check_output(command, stderr=subprocess.STDOUT, shell=kwargs.get("shell", False)) 

2190 return [gef_pystring(_) for _ in res.splitlines()] if as_list else gef_pystring(res) 

2191 

2192 

2193def gef_execute_gdb_script(commands: str) -> None: 

2194 """Execute the parameter `source` as GDB command. This is done by writing `commands` to 

2195 a temporary file, which is then executed via GDB `source` command. The tempfile is then deleted.""" 

2196 fd, fname = tempfile.mkstemp(suffix=".gdb", prefix="gef_") 

2197 with os.fdopen(fd, "w") as f: 

2198 f.write(commands) 

2199 f.flush() 

2200 

2201 fname = pathlib.Path(fname) 

2202 if fname.is_file() and os.access(fname, os.R_OK): 

2203 gdb.execute(f"source {fname}") 

2204 fname.unlink() 

2205 return 

2206 

2207 

2208@deprecated("Use Elf(fname).checksec()") 

2209def checksec(filename: str) -> Dict[str, bool]: 

2210 return Elf(filename).checksec 

2211 

2212 

2213@deprecated("Use `gef.arch` instead") 

2214def get_arch() -> str: 

2215 """Return the binary's architecture.""" 

2216 if is_alive(): 

2217 arch = gdb.selected_frame().architecture() 

2218 return arch.name() 

2219 

2220 arch_str = (gdb.execute("show architecture", to_string=True) or "").strip() 

2221 pat = "The target architecture is set automatically (currently " 

2222 if arch_str.startswith(pat): 

2223 arch_str = arch_str[len(pat):].rstrip(")") 

2224 return arch_str 

2225 

2226 pat = "The target architecture is assumed to be " 

2227 if arch_str.startswith(pat): 

2228 return arch_str[len(pat):] 

2229 

2230 pat = "The target architecture is set to " 

2231 if arch_str.startswith(pat): 

2232 # GDB version >= 10.1 

2233 if '"auto"' in arch_str: 

2234 return re.findall(r"currently \"(.+)\"", arch_str)[0] 

2235 return re.findall(r"\"(.+)\"", arch_str)[0] 

2236 

2237 # Unknown, we throw an exception to be safe 

2238 raise RuntimeError(f"Unknown architecture: {arch_str}") 

2239 

2240 

2241@deprecated("Use `gef.binary.entry_point` instead") 

2242def get_entry_point() -> Optional[int]: 

2243 """Return the binary entry point.""" 

2244 return gef.binary.entry_point if gef.binary else None 

2245 

2246 

2247def is_pie(fpath: str) -> bool: 

2248 return Elf(fpath).checksec["PIE"] 

2249 

2250 

2251@deprecated("Prefer `gef.arch.endianness == Endianness.BIG_ENDIAN`") 

2252def is_big_endian() -> bool: 

2253 return gef.arch.endianness == Endianness.BIG_ENDIAN 

2254 

2255 

2256@deprecated("gef.arch.endianness == Endianness.LITTLE_ENDIAN") 

2257def is_little_endian() -> bool: 

2258 return gef.arch.endianness == Endianness.LITTLE_ENDIAN 

2259 

2260 

2261def flags_to_human(reg_value: int, value_table: Dict[int, str]) -> str: 

2262 """Return a human readable string showing the flag states.""" 

2263 flags = [] 

2264 for bit_index, name in value_table.items(): 

2265 flags.append(Color.boldify(name.upper()) if reg_value & (1<<bit_index) != 0 else name.lower()) 

2266 return f"[{' '.join(flags)}]" 

2267 

2268 

2269@lru_cache() 

2270def get_section_base_address(name: str) -> Optional[int]: 

2271 section = process_lookup_path(name) 

2272 return section.page_start if section else None 

2273 

2274 

2275@lru_cache() 

2276def get_zone_base_address(name: str) -> Optional[int]: 

2277 zone = file_lookup_name_path(name, get_filepath()) 

2278 return zone.zone_start if zone else None 

2279 

2280 

2281# 

2282# Architecture classes 

2283# 

2284 

2285@deprecated("Using the decorator `register_architecture` is unecessary") 

2286def register_architecture(cls: Type["Architecture"]) -> Type["Architecture"]: 

2287 return cls 

2288 

2289class ArchitectureBase: 

2290 """Class decorator for declaring an architecture to GEF.""" 

2291 aliases: Union[Tuple[()], Tuple[Union[str, Elf.Abi], ...]] = () 

2292 

2293 def __init_subclass__(cls: Type["ArchitectureBase"], **kwargs): 

2294 global __registered_architectures__ 

2295 super().__init_subclass__(**kwargs) 

2296 for key in getattr(cls, "aliases"): 

2297 if issubclass(cls, Architecture): 2297 ↛ 2296line 2297 didn't jump to line 2296 because the condition on line 2297 was always true

2298 if isinstance(key, str): 

2299 __registered_architectures__[key.lower()] = cls 

2300 else: 

2301 __registered_architectures__[key] = cls 

2302 return 

2303 

2304 

2305class Architecture(ArchitectureBase): 

2306 """Generic metaclass for the architecture supported by GEF.""" 

2307 

2308 # Mandatory defined attributes by inheriting classes 

2309 arch: str 

2310 mode: str 

2311 all_registers: Union[Tuple[()], Tuple[str, ...]] 

2312 nop_insn: bytes 

2313 return_register: str 

2314 flag_register: Optional[str] 

2315 instruction_length: Optional[int] 

2316 flags_table: Dict[int, str] 

2317 syscall_register: Optional[str] 

2318 syscall_instructions: Union[Tuple[()], Tuple[str, ...]] 

2319 function_parameters: Union[Tuple[()], Tuple[str, ...]] 

2320 

2321 # Optionally defined attributes 

2322 _ptrsize: Optional[int] = None 

2323 _endianness: Optional[Endianness] = None 

2324 special_registers: Union[Tuple[()], Tuple[str, ...]] = () 

2325 maps: Optional[GefMemoryMapProvider] = None 

2326 

2327 def __init_subclass__(cls, **kwargs): 

2328 super().__init_subclass__(**kwargs) 

2329 attributes = ("arch", "mode", "aliases", "all_registers", "nop_insn", 

2330 "return_register", "flag_register", "instruction_length", "flags_table", 

2331 "function_parameters",) 

2332 if not all(map(lambda x: hasattr(cls, x), attributes)): 2332 ↛ 2333line 2332 didn't jump to line 2333 because the condition on line 2332 was never true

2333 raise NotImplementedError 

2334 

2335 def __str__(self) -> str: 

2336 return f"Architecture({self.arch}, {self.mode or 'None'}, {repr(self.endianness)})" 

2337 

2338 def __repr__(self) -> str: 

2339 return self.__str__() 

2340 

2341 @staticmethod 

2342 def supports_gdb_arch(gdb_arch: str) -> Optional[bool]: 

2343 """If implemented by a child `Architecture`, this function dictates if the current class 

2344 supports the loaded ELF file (which can be accessed via `gef.binary`). This callback 

2345 function will override any assumption made by GEF to determine the architecture.""" 

2346 return None 

2347 

2348 def flag_register_to_human(self, val: Optional[int] = None) -> str: 

2349 raise NotImplementedError 

2350 

2351 def is_call(self, insn: Instruction) -> bool: 

2352 raise NotImplementedError 

2353 

2354 def is_ret(self, insn: Instruction) -> bool: 

2355 raise NotImplementedError 

2356 

2357 def is_conditional_branch(self, insn: Instruction) -> bool: 

2358 raise NotImplementedError 

2359 

2360 def is_branch_taken(self, insn: Instruction) -> Tuple[bool, str]: 

2361 raise NotImplementedError 

2362 

2363 def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> Optional[int]: 

2364 raise NotImplementedError 

2365 

2366 def canary_address(self) -> int: 

2367 raise NotImplementedError 

2368 

2369 @classmethod 

2370 def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str: 

2371 raise NotImplementedError 

2372 

2373 def reset_caches(self) -> None: 

2374 self.__get_register_for_selected_frame.cache_clear() 

2375 return 

2376 

2377 def __get_register(self, regname: str) -> int: 

2378 """Return a register's value.""" 

2379 curframe = gdb.selected_frame() 

2380 key = curframe.pc() ^ int(curframe.read_register('sp')) # todo: check when/if gdb.Frame implements `level()` 

2381 return self.__get_register_for_selected_frame(regname, int(key)) 

2382 

2383 @lru_cache() 

2384 def __get_register_for_selected_frame(self, regname: str, hash_key: int) -> int: 

2385 # 1st chance 

2386 try: 

2387 return parse_address(regname) 

2388 except gdb.error: 

2389 pass 

2390 

2391 # 2nd chance - if an exception, propagate it 

2392 regname = regname.lstrip("$") 

2393 value = gdb.selected_frame().read_register(regname) 

2394 return int(value) 

2395 

2396 def register(self, name: str) -> int: 

2397 if not is_alive(): 

2398 raise gdb.error("No debugging session active") 

2399 return self.__get_register(name) 

2400 

2401 @property 

2402 def registers(self) -> Generator[str, None, None]: 

2403 yield from self.all_registers 

2404 

2405 @property 

2406 def pc(self) -> int: 

2407 return self.register("$pc") 

2408 

2409 @property 

2410 def sp(self) -> int: 

2411 return self.register("$sp") 

2412 

2413 @property 

2414 def fp(self) -> int: 

2415 return self.register("$fp") 

2416 

2417 @property 

2418 def ptrsize(self) -> int: 

2419 if not self._ptrsize: 2419 ↛ 2420line 2419 didn't jump to line 2420 because the condition on line 2419 was never true

2420 res = cached_lookup_type("size_t") 

2421 if res is not None: 

2422 self._ptrsize = res.sizeof 

2423 else: 

2424 self._ptrsize = gdb.parse_and_eval("$pc").type.sizeof 

2425 return self._ptrsize 

2426 

2427 @property 

2428 def endianness(self) -> Endianness: 

2429 if not self._endianness: 

2430 output = (gdb.execute("show endian", to_string=True) or "").strip().lower() 

2431 if "little endian" in output: 2431 ↛ 2433line 2431 didn't jump to line 2433 because the condition on line 2431 was always true

2432 self._endianness = Endianness.LITTLE_ENDIAN 

2433 elif "big endian" in output: 

2434 self._endianness = Endianness.BIG_ENDIAN 

2435 else: 

2436 raise OSError(f"No valid endianess found in '{output}'") 

2437 return self._endianness 

2438 

2439 def get_ith_parameter(self, i: int, in_func: bool = True) -> Tuple[str, Optional[int]]: 

2440 """Retrieves the correct parameter used for the current function call.""" 

2441 reg = self.function_parameters[i] 

2442 val = self.register(reg) 

2443 key = reg 

2444 return key, val 

2445 

2446 

2447class GenericArchitecture(Architecture): 

2448 arch = "Generic" 

2449 mode = "" 

2450 aliases = ("GenericArchitecture",) 

2451 all_registers = () 

2452 instruction_length = 0 

2453 return_register = "" 

2454 function_parameters = () 

2455 syscall_register = "" 

2456 syscall_instructions = () 

2457 nop_insn = b"" 

2458 flag_register = None 

2459 flags_table = {} 

2460 

2461 

2462class RISCV(Architecture): 

2463 arch = "RISCV" 

2464 mode = "RISCV" 

2465 aliases = ("RISCV", Elf.Abi.RISCV) 

2466 all_registers = ("$zero", "$ra", "$sp", "$gp", "$tp", "$t0", "$t1", 

2467 "$t2", "$fp", "$s1", "$a0", "$a1", "$a2", "$a3", 

2468 "$a4", "$a5", "$a6", "$a7", "$s2", "$s3", "$s4", 

2469 "$s5", "$s6", "$s7", "$s8", "$s9", "$s10", "$s11", 

2470 "$t3", "$t4", "$t5", "$t6",) 

2471 return_register = "$a0" 

2472 function_parameters = ("$a0", "$a1", "$a2", "$a3", "$a4", "$a5", "$a6", "$a7") 

2473 syscall_register = "$a7" 

2474 syscall_instructions = ("ecall",) 

2475 nop_insn = b"\x00\x00\x00\x13" 

2476 # RISC-V has no flags registers 

2477 flag_register = None 

2478 flags_table = {} 

2479 

2480 @property 

2481 def instruction_length(self) -> int: 

2482 return 4 

2483 

2484 def is_call(self, insn: Instruction) -> bool: 

2485 return insn.mnemonic == "call" 

2486 

2487 def is_ret(self, insn: Instruction) -> bool: 

2488 mnemo = insn.mnemonic 

2489 if mnemo == "ret": 

2490 return True 

2491 elif (mnemo == "jalr" and insn.operands[0] == "zero" and 

2492 insn.operands[1] == "ra" and insn.operands[2] == 0): 

2493 return True 

2494 elif (mnemo == "c.jalr" and insn.operands[0] == "ra"): 

2495 return True 

2496 return False 

2497 

2498 @classmethod 

2499 def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str: 

2500 raise OSError(f"Architecture {cls.arch} not supported yet") 

2501 

2502 @property 

2503 def ptrsize(self) -> int: 

2504 if self._ptrsize is not None: 

2505 return self._ptrsize 

2506 if is_alive(): 

2507 self._ptrsize = gdb.parse_and_eval("$pc").type.sizeof 

2508 return self._ptrsize 

2509 return 4 

2510 

2511 def is_conditional_branch(self, insn: Instruction) -> bool: 

2512 return insn.mnemonic.startswith("b") 

2513 

2514 def is_branch_taken(self, insn: Instruction) -> Tuple[bool, str]: 

2515 def long_to_twos_complement(v: int) -> int: 

2516 """Convert a python long value to its two's complement.""" 

2517 if is_32bit(): 

2518 if v & 0x80000000: 

2519 return v - 0x100000000 

2520 elif is_64bit(): 

2521 if v & 0x8000000000000000: 

2522 return v - 0x10000000000000000 

2523 else: 

2524 raise OSError("RISC-V: ELF file is not ELF32 or ELF64. This is not currently supported") 

2525 return v 

2526 

2527 mnemo = insn.mnemonic 

2528 condition = mnemo[1:] 

2529 

2530 if condition.endswith("z"): 

2531 # r2 is the zero register if we are comparing to 0 

2532 rs1 = gef.arch.register(insn.operands[0]) 

2533 rs2 = gef.arch.register("$zero") 

2534 condition = condition[:-1] 

2535 elif len(insn.operands) > 2: 

2536 # r2 is populated with the second operand 

2537 rs1 = gef.arch.register(insn.operands[0]) 

2538 rs2 = gef.arch.register(insn.operands[1]) 

2539 else: 

2540 raise OSError(f"RISC-V: Failed to get rs1 and rs2 for instruction: `{insn}`") 

2541 

2542 # If the conditional operation is not unsigned, convert the python long into 

2543 # its two's complement 

2544 if not condition.endswith("u"): 

2545 rs2 = long_to_twos_complement(rs2) 

2546 rs1 = long_to_twos_complement(rs1) 

2547 else: 

2548 condition = condition[:-1] 

2549 

2550 if condition == "eq": 

2551 if rs1 == rs2: taken, reason = True, f"{rs1}={rs2}" 

2552 else: taken, reason = False, f"{rs1}!={rs2}" 

2553 elif condition == "ne": 

2554 if rs1 != rs2: taken, reason = True, f"{rs1}!={rs2}" 

2555 else: taken, reason = False, f"{rs1}={rs2}" 

2556 elif condition == "lt": 

2557 if rs1 < rs2: taken, reason = True, f"{rs1}<{rs2}" 

2558 else: taken, reason = False, f"{rs1}>={rs2}" 

2559 elif condition == "le": 

2560 if rs1 <= rs2: taken, reason = True, f"{rs1}<={rs2}" 

2561 else: taken, reason = False, f"{rs1}>{rs2}" 

2562 elif condition == "ge": 

2563 if rs1 < rs2: taken, reason = True, f"{rs1}>={rs2}" 

2564 else: taken, reason = False, f"{rs1}<{rs2}" 

2565 else: 

2566 raise OSError(f"RISC-V: Conditional instruction `{insn}` not supported yet") 

2567 

2568 return taken, reason 

2569 

2570 def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> Optional[int]: 

2571 ra = None 

2572 if self.is_ret(insn): 

2573 ra = gef.arch.register("$ra") 

2574 else: 

2575 older = frame.older() 

2576 if older: 

2577 ra = to_unsigned_long(older.pc()) 

2578 return ra 

2579 

2580 def flag_register_to_human(self, val: Optional[int] = None) -> str: 

2581 # RISC-V has no flags registers, return an empty string to 

2582 # preserve the Architecture API 

2583 return "" 

2584 

2585class ARM(Architecture): 

2586 aliases = ("ARM", Elf.Abi.ARM) 

2587 arch = "ARM" 

2588 all_registers = ("$r0", "$r1", "$r2", "$r3", "$r4", "$r5", "$r6", 

2589 "$r7", "$r8", "$r9", "$r10", "$r11", "$r12", "$sp", 

2590 "$lr", "$pc", "$cpsr",) 

2591 

2592 nop_insn = b"\x00\xf0\x20\xe3" # hint #0 

2593 return_register = "$r0" 

2594 flag_register: str = "$cpsr" 

2595 flags_table = { 

2596 31: "negative", 

2597 30: "zero", 

2598 29: "carry", 

2599 28: "overflow", 

2600 7: "interrupt", 

2601 6: "fast", 

2602 5: "thumb", 

2603 } 

2604 function_parameters = ("$r0", "$r1", "$r2", "$r3") 

2605 syscall_register = "$r7" 

2606 syscall_instructions = ("swi 0x0", "swi NR") 

2607 _endianness = Endianness.LITTLE_ENDIAN 

2608 

2609 def is_thumb(self) -> bool: 

2610 """Determine if the machine is currently in THUMB mode.""" 

2611 return is_alive() and (self.cpsr & (1 << 5) == 1) 

2612 

2613 @property 

2614 def pc(self) -> Optional[int]: 

2615 pc = gef.arch.register("$pc") 

2616 if self.is_thumb(): 

2617 pc += 1 

2618 return pc 

2619 

2620 @property 

2621 def cpsr(self) -> int: 

2622 if not is_alive(): 

2623 raise RuntimeError("Cannot get CPSR, program not started?") 

2624 return gef.arch.register(self.flag_register) 

2625 

2626 @property 

2627 def mode(self) -> str: 

2628 return "THUMB" if self.is_thumb() else "ARM" 

2629 

2630 @property 

2631 def instruction_length(self) -> Optional[int]: 

2632 # Thumb instructions have variable-length (2 or 4-byte) 

2633 return None if self.is_thumb() else 4 

2634 

2635 @property 

2636 def ptrsize(self) -> int: 

2637 return 4 

2638 

2639 def is_call(self, insn: Instruction) -> bool: 

2640 mnemo = insn.mnemonic 

2641 call_mnemos = {"bl", "blx"} 

2642 return mnemo in call_mnemos 

2643 

2644 def is_ret(self, insn: Instruction) -> bool: 

2645 pop_mnemos = {"pop"} 

2646 branch_mnemos = {"bl", "bx"} 

2647 write_mnemos = {"ldr", "add"} 

2648 if insn.mnemonic in pop_mnemos: 

2649 return insn.operands[-1] == " pc}" 

2650 if insn.mnemonic in branch_mnemos: 

2651 return insn.operands[-1] == "lr" 

2652 if insn.mnemonic in write_mnemos: 

2653 return insn.operands[0] == "pc" 

2654 return False 

2655 

2656 def flag_register_to_human(self, val: Optional[int] = None) -> str: 

2657 # https://www.botskool.com/user-pages/tutorials/electronics/arm-7-tutorial-part-1 

2658 if val is None: 

2659 reg = self.flag_register 

2660 val = gef.arch.register(reg) 

2661 return flags_to_human(val, self.flags_table) 

2662 

2663 def is_conditional_branch(self, insn: Instruction) -> bool: 

2664 conditions = {"eq", "ne", "lt", "le", "gt", "ge", "vs", "vc", "mi", "pl", "hi", "ls", "cc", "cs"} 

2665 return insn.mnemonic[-2:] in conditions 

2666 

2667 def is_branch_taken(self, insn: Instruction) -> Tuple[bool, str]: 

2668 mnemo = insn.mnemonic 

2669 # ref: https://www.davespace.co.uk/arm/introduction-to-arm/conditional.html 

2670 flags = dict((self.flags_table[k], k) for k in self.flags_table) 

2671 val = gef.arch.register(self.flag_register) 

2672 taken, reason = False, "" 

2673 

2674 if mnemo.endswith("eq"): taken, reason = bool(val&(1<<flags["zero"])), "Z" 

2675 elif mnemo.endswith("ne"): taken, reason = not bool(val&(1<<flags["zero"])), "!Z" 

2676 elif mnemo.endswith("lt"): 

2677 taken, reason = bool(val&(1<<flags["negative"])) != bool(val&(1<<flags["overflow"])), "N!=V" 

2678 elif mnemo.endswith("le"): 

2679 taken, reason = bool(val&(1<<flags["zero"])) or \ 

2680 bool(val&(1<<flags["negative"])) != bool(val&(1<<flags["overflow"])), "Z || N!=V" 

2681 elif mnemo.endswith("gt"): 

2682 taken, reason = bool(val&(1<<flags["zero"])) == 0 and \ 

2683 bool(val&(1<<flags["negative"])) == bool(val&(1<<flags["overflow"])), "!Z && N==V" 

2684 elif mnemo.endswith("ge"): 

2685 taken, reason = bool(val&(1<<flags["negative"])) == bool(val&(1<<flags["overflow"])), "N==V" 

2686 elif mnemo.endswith("vs"): taken, reason = bool(val&(1<<flags["overflow"])), "V" 

2687 elif mnemo.endswith("vc"): taken, reason = not val&(1<<flags["overflow"]), "!V" 

2688 elif mnemo.endswith("mi"): 

2689 taken, reason = bool(val&(1<<flags["negative"])), "N" 

2690 elif mnemo.endswith("pl"): 

2691 taken, reason = not val&(1<<flags["negative"]), "N==0" 

2692 elif mnemo.endswith("hi"): 

2693 taken, reason = bool(val&(1<<flags["carry"])) and not bool(val&(1<<flags["zero"])), "C && !Z" 

2694 elif mnemo.endswith("ls"): 

2695 taken, reason = not val&(1<<flags["carry"]) or bool(val&(1<<flags["zero"])), "!C || Z" 

2696 elif mnemo.endswith("cs"): taken, reason = bool(val&(1<<flags["carry"])), "C" 

2697 elif mnemo.endswith("cc"): taken, reason = not val&(1<<flags["carry"]), "!C" 

2698 return taken, reason 

2699 

2700 def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> Optional[int]: 

2701 if not self.is_ret(insn): 

2702 older = frame.older() 

2703 if not older: 

2704 return None 

2705 return int(older.pc()) 

2706 

2707 # If it's a pop, we have to peek into the stack, otherwise use lr 

2708 if insn.mnemonic == "pop": 

2709 ra_addr = gef.arch.sp + (len(insn.operands)-1) * self.ptrsize 

2710 if not ra_addr: 

2711 return None 

2712 ra = dereference(ra_addr) 

2713 if ra is None: 

2714 return None 

2715 return to_unsigned_long(ra) 

2716 elif insn.mnemonic == "ldr": 

2717 ra = dereference(gef.arch.sp) 

2718 if ra is None: 

2719 return None 

2720 return to_unsigned_long(ra) 

2721 else: # 'bx lr' or 'add pc, lr, #0' 

2722 return gef.arch.register("$lr") 

2723 

2724 @classmethod 

2725 def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str: 

2726 _NR_mprotect = 125 

2727 insns = [ 

2728 "push {r0-r2, r7}", 

2729 f"mov r1, {addr & 0xffff:d}", 

2730 f"mov r0, {(addr & 0xffff0000) >> 16:d}", 

2731 "lsl r0, r0, 16", 

2732 "add r0, r0, r1", 

2733 f"mov r1, {size & 0xffff:d}", 

2734 f"mov r2, {perm.value & 0xff:d}", 

2735 f"mov r7, {_NR_mprotect:d}", 

2736 "svc 0", 

2737 "pop {r0-r2, r7}", 

2738 ] 

2739 return "; ".join(insns) 

2740 

2741 

2742class AARCH64(ARM): 

2743 aliases = ("ARM64", "AARCH64", Elf.Abi.AARCH64) 

2744 arch = "ARM64" 

2745 mode: str = "" 

2746 

2747 all_registers = ( 

2748 "$x0", "$x1", "$x2", "$x3", "$x4", "$x5", "$x6", "$x7", 

2749 "$x8", "$x9", "$x10", "$x11", "$x12", "$x13", "$x14","$x15", 

2750 "$x16", "$x17", "$x18", "$x19", "$x20", "$x21", "$x22", "$x23", 

2751 "$x24", "$x25", "$x26", "$x27", "$x28", "$x29", "$x30", "$sp", 

2752 "$pc", "$cpsr", "$fpsr", "$fpcr",) 

2753 return_register = "$x0" 

2754 flag_register = "$cpsr" 

2755 flags_table = { 

2756 31: "negative", 

2757 30: "zero", 

2758 29: "carry", 

2759 28: "overflow", 

2760 7: "interrupt", 

2761 9: "endian", 

2762 6: "fast", 

2763 5: "t32", 

2764 4: "m[4]", 

2765 } 

2766 nop_insn = b"\x1f\x20\x03\xd5" # hint #0 

2767 function_parameters = ("$x0", "$x1", "$x2", "$x3", "$x4", "$x5", "$x6", "$x7",) 

2768 syscall_register = "$x8" 

2769 syscall_instructions = ("svc $x0",) 

2770 

2771 def is_call(self, insn: Instruction) -> bool: 

2772 mnemo = insn.mnemonic 

2773 call_mnemos = {"bl", "blr"} 

2774 return mnemo in call_mnemos 

2775 

2776 def flag_register_to_human(self, val: Optional[int] = None) -> str: 

2777 # https://events.linuxfoundation.org/sites/events/files/slides/KoreaLinuxForum-2014.pdf 

2778 reg = self.flag_register 

2779 if not val: 

2780 val = gef.arch.register(reg) 

2781 return flags_to_human(val, self.flags_table) 

2782 

2783 def is_aarch32(self) -> bool: 

2784 """Determine if the CPU is currently in AARCH32 mode from runtime.""" 

2785 return (self.cpsr & (1 << 4) != 0) and (self.cpsr & (1 << 5) == 0) 

2786 

2787 def is_thumb32(self) -> bool: 

2788 """Determine if the CPU is currently in THUMB32 mode from runtime.""" 

2789 return (self.cpsr & (1 << 4) == 1) and (self.cpsr & (1 << 5) == 1) 

2790 

2791 @property 

2792 def ptrsize(self) -> int: 

2793 """Determine the size of pointer from the current CPU mode""" 

2794 if not is_alive(): 

2795 return 8 

2796 if self.is_aarch32(): 

2797 return 4 

2798 if self.is_thumb32(): 

2799 return 2 

2800 return 8 

2801 

2802 @classmethod 

2803 def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str: 

2804 _NR_mprotect = 226 

2805 insns = [ 

2806 "str x8, [sp, -16]!", 

2807 "str x0, [sp, -16]!", 

2808 "str x1, [sp, -16]!", 

2809 "str x2, [sp, -16]!", 

2810 f"mov x8, {_NR_mprotect:d}", 

2811 f"movz x0, {addr & 0xFFFF:#x}", 

2812 f"movk x0, {(addr >> 16) & 0xFFFF:#x}, lsl 16", 

2813 f"movk x0, {(addr >> 32) & 0xFFFF:#x}, lsl 32", 

2814 f"movk x0, {(addr >> 48) & 0xFFFF:#x}, lsl 48", 

2815 f"movz x1, {size & 0xFFFF:#x}", 

2816 f"movk x1, {(size >> 16) & 0xFFFF:#x}, lsl 16", 

2817 f"mov x2, {perm.value:d}", 

2818 "svc 0", 

2819 "ldr x2, [sp], 16", 

2820 "ldr x1, [sp], 16", 

2821 "ldr x0, [sp], 16", 

2822 "ldr x8, [sp], 16", 

2823 ] 

2824 return "; ".join(insns) 

2825 

2826 def is_conditional_branch(self, insn: Instruction) -> bool: 

2827 # https://www.element14.com/community/servlet/JiveServlet/previewBody/41836-102-1-229511/ARM.Reference_Manual.pdf 

2828 # sect. 5.1.1 

2829 mnemo = insn.mnemonic 

2830 branch_mnemos = {"cbnz", "cbz", "tbnz", "tbz"} 

2831 return mnemo.startswith("b.") or mnemo in branch_mnemos 

2832 

2833 def is_branch_taken(self, insn: Instruction) -> Tuple[bool, str]: 

2834 mnemo, operands = insn.mnemonic, insn.operands 

2835 taken, reason = False, "" 

2836 

2837 if mnemo in {"cbnz", "cbz", "tbnz", "tbz"}: 

2838 reg = f"${operands[0]}" 

2839 op = gef.arch.register(reg) 

2840 if mnemo == "cbnz": 

2841 if op!=0: taken, reason = True, f"{reg}!=0" 

2842 else: taken, reason = False, f"{reg}==0" 

2843 elif mnemo == "cbz": 

2844 if op == 0: taken, reason = True, f"{reg}==0" 

2845 else: taken, reason = False, f"{reg}!=0" 

2846 elif mnemo == "tbnz": 

2847 # operands[1] has one or more white spaces in front, then a #, then the number 

2848 # so we need to eliminate them 

2849 i = int(operands[1].strip().lstrip("#")) 

2850 if (op & 1<<i) != 0: taken, reason = True, f"{reg}&1<<{i}!=0" 

2851 else: taken, reason = False, f"{reg}&1<<{i}==0" 

2852 elif mnemo == "tbz": 

2853 # operands[1] has one or more white spaces in front, then a #, then the number 

2854 # so we need to eliminate them 

2855 i = int(operands[1].strip().lstrip("#")) 

2856 if (op & 1<<i) == 0: taken, reason = True, f"{reg}&1<<{i}==0" 

2857 else: taken, reason = False, f"{reg}&1<<{i}!=0" 

2858 

2859 if not reason: 

2860 taken, reason = super().is_branch_taken(insn) 

2861 return taken, reason 

2862 

2863 

2864class X86(Architecture): 

2865 aliases: Tuple[Union[str, Elf.Abi], ...] = ("X86", Elf.Abi.X86_32) 

2866 arch = "X86" 

2867 mode = "32" 

2868 

2869 nop_insn = b"\x90" 

2870 flag_register: str = "$eflags" 

2871 special_registers = ("$cs", "$ss", "$ds", "$es", "$fs", "$gs", ) 

2872 gpr_registers = ("$eax", "$ebx", "$ecx", "$edx", "$esp", "$ebp", "$esi", "$edi", "$eip", ) 

2873 all_registers = gpr_registers + ( flag_register,) + special_registers 

2874 instruction_length = None 

2875 return_register = "$eax" 

2876 function_parameters = ("$esp", ) 

2877 flags_table = { 

2878 6: "zero", 

2879 0: "carry", 

2880 2: "parity", 

2881 4: "adjust", 

2882 7: "sign", 

2883 8: "trap", 

2884 9: "interrupt", 

2885 10: "direction", 

2886 11: "overflow", 

2887 16: "resume", 

2888 17: "virtualx86", 

2889 21: "identification", 

2890 } 

2891 syscall_register = "$eax" 

2892 syscall_instructions = ("sysenter", "int 0x80") 

2893 _ptrsize = 4 

2894 _endianness = Endianness.LITTLE_ENDIAN 

2895 

2896 def flag_register_to_human(self, val: Optional[int] = None) -> str: 

2897 reg = self.flag_register 

2898 if val is None: 

2899 val = gef.arch.register(reg) 

2900 return flags_to_human(val, self.flags_table) 

2901 

2902 def is_call(self, insn: Instruction) -> bool: 

2903 mnemo = insn.mnemonic 

2904 call_mnemos = {"call", "callq"} 

2905 return mnemo in call_mnemos 

2906 

2907 def is_ret(self, insn: Instruction) -> bool: 

2908 return insn.mnemonic == "ret" 

2909 

2910 def is_conditional_branch(self, insn: Instruction) -> bool: 

2911 mnemo = insn.mnemonic 

2912 branch_mnemos = { 

2913 "ja", "jnbe", "jae", "jnb", "jnc", "jb", "jc", "jnae", "jbe", "jna", 

2914 "jcxz", "jecxz", "jrcxz", "je", "jz", "jg", "jnle", "jge", "jnl", 

2915 "jl", "jnge", "jle", "jng", "jne", "jnz", "jno", "jnp", "jpo", "jns", 

2916 "jo", "jp", "jpe", "js" 

2917 } 

2918 return mnemo in branch_mnemos 

2919 

2920 def is_branch_taken(self, insn: Instruction) -> Tuple[bool, str]: 

2921 mnemo = insn.mnemonic 

2922 # all kudos to fG! (https://github.com/gdbinit/Gdbinit/blob/master/gdbinit#L1654) 

2923 flags = dict((self.flags_table[k], k) for k in self.flags_table) 

2924 val = gef.arch.register(self.flag_register) 

2925 

2926 taken, reason = False, "" 

2927 

2928 if mnemo in ("ja", "jnbe"): 

2929 taken, reason = not val&(1<<flags["carry"]) and not bool(val&(1<<flags["zero"])), "!C && !Z" 

2930 elif mnemo in ("jae", "jnb", "jnc"): 

2931 taken, reason = not val&(1<<flags["carry"]), "!C" 

2932 elif mnemo in ("jb", "jc", "jnae"): 

2933 taken, reason = bool(val&(1<<flags["carry"])) != 0, "C" 

2934 elif mnemo in ("jbe", "jna"): 

2935 taken, reason = bool(val&(1<<flags["carry"])) or bool(val&(1<<flags["zero"])), "C || Z" 

2936 elif mnemo in ("jcxz", "jecxz", "jrcxz"): 

2937 cx = gef.arch.register("$rcx") if is_x86_64() else gef.arch.register("$ecx") 

2938 taken, reason = cx == 0, "!$CX" 

2939 elif mnemo in ("je", "jz"): 

2940 taken, reason = bool(val&(1<<flags["zero"])), "Z" 

2941 elif mnemo in ("jne", "jnz"): 

2942 taken, reason = not bool(val&(1<<flags["zero"])), "!Z" 

2943 elif mnemo in ("jg", "jnle"): 

2944 taken, reason = not bool(val&(1<<flags["zero"])) and bool(val&(1<<flags["overflow"])) == bool(val&(1<<flags["sign"])), "!Z && S==O" 

2945 elif mnemo in ("jge", "jnl"): 

2946 taken, reason = bool(val&(1<<flags["sign"])) == bool(val&(1<<flags["overflow"])), "S==O" 

2947 elif mnemo in ("jl", "jnge"): 

2948 taken, reason = bool(val&(1<<flags["overflow"]) != val&(1<<flags["sign"])), "S!=O" 

2949 elif mnemo in ("jle", "jng"): 

2950 taken, reason = bool(val&(1<<flags["zero"])) or bool(val&(1<<flags["overflow"])) != bool(val&(1<<flags["sign"])), "Z || S!=O" 

2951 elif mnemo in ("jo",): 

2952 taken, reason = bool(val&(1<<flags["overflow"])), "O" 

2953 elif mnemo in ("jno",): 

2954 taken, reason = not val&(1<<flags["overflow"]), "!O" 

2955 elif mnemo in ("jpe", "jp"): 

2956 taken, reason = bool(val&(1<<flags["parity"])), "P" 

2957 elif mnemo in ("jnp", "jpo"): 

2958 taken, reason = not val&(1<<flags["parity"]), "!P" 

2959 elif mnemo in ("js",): 

2960 taken, reason = bool(val&(1<<flags["sign"])) != 0, "S" 

2961 elif mnemo in ("jns",): 

2962 taken, reason = not val&(1<<flags["sign"]), "!S" 

2963 return taken, reason 

2964 

2965 def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> Optional[int]: 

2966 ra = None 

2967 if self.is_ret(insn): 2967 ↛ 2970line 2967 didn't jump to line 2970 because the condition on line 2967 was always true

2968 ra = dereference(gef.arch.sp) 

2969 else: 

2970 older = frame.older() 

2971 if older: 

2972 ra = older.pc() 

2973 if ra is None: 2973 ↛ 2974line 2973 didn't jump to line 2974 because the condition on line 2973 was never true

2974 return None 

2975 return to_unsigned_long(ra) 

2976 

2977 @classmethod 

2978 def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str: 

2979 _NR_mprotect = 125 

2980 insns = [ 

2981 "pushad", 

2982 "pushfd", 

2983 f"mov eax, {_NR_mprotect:d}", 

2984 f"mov ebx, {addr:d}", 

2985 f"mov ecx, {size:d}", 

2986 f"mov edx, {perm.value:d}", 

2987 "int 0x80", 

2988 "popfd", 

2989 "popad", 

2990 ] 

2991 return "; ".join(insns) 

2992 

2993 def get_ith_parameter(self, i: int, in_func: bool = True) -> Tuple[str, Optional[int]]: 

2994 if in_func: 

2995 i += 1 # Account for RA being at the top of the stack 

2996 sp = gef.arch.sp 

2997 sz = gef.arch.ptrsize 

2998 loc = sp + (i * sz) 

2999 val = gef.memory.read_integer(loc) 

3000 key = f"[sp + {i * sz:#x}]" 

3001 return key, val 

3002 

3003 

3004class X86_64(X86): 

3005 aliases = ("X86_64", Elf.Abi.X86_64, "i386:x86-64") 

3006 arch = "X86" 

3007 mode = "64" 

3008 

3009 gpr_registers = ( 

3010 "$rax", "$rbx", "$rcx", "$rdx", "$rsp", "$rbp", "$rsi", "$rdi", "$rip", 

3011 "$r8", "$r9", "$r10", "$r11", "$r12", "$r13", "$r14", "$r15", ) 

3012 all_registers = gpr_registers + ( X86.flag_register, ) + X86.special_registers 

3013 return_register = "$rax" 

3014 function_parameters = ["$rdi", "$rsi", "$rdx", "$rcx", "$r8", "$r9"] 

3015 syscall_register = "$rax" 

3016 syscall_instructions = ["syscall"] 

3017 # We don't want to inherit x86's stack based param getter 

3018 get_ith_parameter = Architecture.get_ith_parameter 

3019 _ptrsize = 8 

3020 

3021 @classmethod 

3022 def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str: 

3023 _NR_mprotect = 10 

3024 insns = [ 

3025 "pushfq", 

3026 "push rax", 

3027 "push rdi", 

3028 "push rsi", 

3029 "push rdx", 

3030 "push rcx", 

3031 "push r11", 

3032 f"mov rax, {_NR_mprotect:d}", 

3033 f"mov rdi, {addr:d}", 

3034 f"mov rsi, {size:d}", 

3035 f"mov rdx, {perm.value:d}", 

3036 "syscall", 

3037 "pop r11", 

3038 "pop rcx", 

3039 "pop rdx", 

3040 "pop rsi", 

3041 "pop rdi", 

3042 "pop rax", 

3043 "popfq", 

3044 ] 

3045 return "; ".join(insns) 

3046 

3047 def canary_address(self) -> int: 

3048 return self.register("fs_base") + 0x28 

3049 

3050class PowerPC(Architecture): 

3051 aliases = ("PowerPC", Elf.Abi.POWERPC, "PPC") 

3052 arch = "PPC" 

3053 mode = "PPC32" 

3054 

3055 all_registers = ( 

3056 "$r0", "$r1", "$r2", "$r3", "$r4", "$r5", "$r6", "$r7", 

3057 "$r8", "$r9", "$r10", "$r11", "$r12", "$r13", "$r14", "$r15", 

3058 "$r16", "$r17", "$r18", "$r19", "$r20", "$r21", "$r22", "$r23", 

3059 "$r24", "$r25", "$r26", "$r27", "$r28", "$r29", "$r30", "$r31", 

3060 "$pc", "$msr", "$cr", "$lr", "$ctr", "$xer", "$trap",) 

3061 instruction_length = 4 

3062 nop_insn = b"\x60\x00\x00\x00" # https://developer.ibm.com/articles/l-ppc/ 

3063 return_register = "$r0" 

3064 flag_register: str = "$cr" 

3065 flags_table = { 

3066 3: "negative[0]", 

3067 2: "positive[0]", 

3068 1: "equal[0]", 

3069 0: "overflow[0]", 

3070 # cr7 

3071 31: "less[7]", 

3072 30: "greater[7]", 

3073 29: "equal[7]", 

3074 28: "overflow[7]", 

3075 } 

3076 function_parameters = ("$i0", "$i1", "$i2", "$i3", "$i4", "$i5") 

3077 syscall_register = "$r0" 

3078 syscall_instructions = ("sc",) 

3079 _ptrsize = 4 

3080 

3081 

3082 def flag_register_to_human(self, val: Optional[int] = None) -> str: 

3083 # https://www.cebix.net/downloads/bebox/pem32b.pdf (% 2.1.3) 

3084 if val is None: 

3085 reg = self.flag_register 

3086 val = gef.arch.register(reg) 

3087 return flags_to_human(val, self.flags_table) 

3088 

3089 def is_call(self, insn: Instruction) -> bool: 

3090 return False 

3091 

3092 def is_ret(self, insn: Instruction) -> bool: 

3093 return insn.mnemonic == "blr" 

3094 

3095 def is_conditional_branch(self, insn: Instruction) -> bool: 

3096 mnemo = insn.mnemonic 

3097 branch_mnemos = {"beq", "bne", "ble", "blt", "bgt", "bge"} 

3098 return mnemo in branch_mnemos 

3099 

3100 def is_branch_taken(self, insn: Instruction) -> Tuple[bool, str]: 

3101 mnemo = insn.mnemonic 

3102 flags = dict((self.flags_table[k], k) for k in self.flags_table) 

3103 val = gef.arch.register(self.flag_register) 

3104 taken, reason = False, "" 

3105 if mnemo == "beq": taken, reason = bool(val&(1<<flags["equal[7]"])), "E" 

3106 elif mnemo == "bne": taken, reason = val&(1<<flags["equal[7]"]) == 0, "!E" 

3107 elif mnemo == "ble": taken, reason = bool(val&(1<<flags["equal[7]"])) or bool(val&(1<<flags["less[7]"])), "E || L" 

3108 elif mnemo == "blt": taken, reason = bool(val&(1<<flags["less[7]"])), "L" 

3109 elif mnemo == "bge": taken, reason = bool(val&(1<<flags["equal[7]"])) or bool(val&(1<<flags["greater[7]"])), "E || G" 

3110 elif mnemo == "bgt": taken, reason = bool(val&(1<<flags["greater[7]"])), "G" 

3111 return taken, reason 

3112 

3113 def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> Optional[int]: 

3114 ra = None 

3115 if self.is_ret(insn): 

3116 ra = gef.arch.register("$lr") 

3117 else: 

3118 older = frame.older() 

3119 if older: 

3120 ra = to_unsigned_long(older.pc()) 

3121 return ra 

3122 

3123 @classmethod 

3124 def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str: 

3125 # Ref: https://developer.ibm.com/articles/l-ppc/ 

3126 _NR_mprotect = 125 

3127 insns = [ 

3128 "addi 1, 1, -16", # 1 = r1 = sp 

3129 "stw 0, 0(1)", 

3130 "stw 3, 4(1)", # r0 = syscall_code | r3, r4, r5 = args 

3131 "stw 4, 8(1)", 

3132 "stw 5, 12(1)", 

3133 f"li 0, {_NR_mprotect:d}", 

3134 f"lis 3, {addr:#x}@h", 

3135 f"ori 3, 3, {addr:#x}@l", 

3136 f"lis 4, {size:#x}@h", 

3137 f"ori 4, 4, {size:#x}@l", 

3138 f"li 5, {perm.value:d}", 

3139 "sc", 

3140 "lwz 0, 0(1)", 

3141 "lwz 3, 4(1)", 

3142 "lwz 4, 8(1)", 

3143 "lwz 5, 12(1)", 

3144 "addi 1, 1, 16", 

3145 ] 

3146 return ";".join(insns) 

3147 

3148 

3149class PowerPC64(PowerPC): 

3150 aliases = ("PowerPC64", Elf.Abi.POWERPC64, "PPC64") 

3151 arch = "PPC" 

3152 mode = "PPC64" 

3153 _ptrsize = 8 

3154 

3155 

3156class SPARC(Architecture): 

3157 """ Refs: 

3158 - https://www.cse.scu.edu/~atkinson/teaching/sp05/259/sparc.pdf 

3159 """ 

3160 aliases = ("SPARC", Elf.Abi.SPARC) 

3161 arch = "SPARC" 

3162 mode = "" 

3163 

3164 all_registers = ( 

3165 "$g0", "$g1", "$g2", "$g3", "$g4", "$g5", "$g6", "$g7", 

3166 "$o0", "$o1", "$o2", "$o3", "$o4", "$o5", "$o7", 

3167 "$l0", "$l1", "$l2", "$l3", "$l4", "$l5", "$l6", "$l7", 

3168 "$i0", "$i1", "$i2", "$i3", "$i4", "$i5", "$i7", 

3169 "$pc", "$npc", "$sp ", "$fp ", "$psr",) 

3170 instruction_length = 4 

3171 nop_insn = b"\x00\x00\x00\x00" # sethi 0, %g0 

3172 return_register = "$i0" 

3173 flag_register: str = "$psr" 

3174 flags_table = { 

3175 23: "negative", 

3176 22: "zero", 

3177 21: "overflow", 

3178 20: "carry", 

3179 7: "supervisor", 

3180 5: "trap", 

3181 } 

3182 function_parameters = ("$o0 ", "$o1 ", "$o2 ", "$o3 ", "$o4 ", "$o5 ", "$o7 ",) 

3183 syscall_register = "%g1" 

3184 syscall_instructions = ("t 0x10",) 

3185 

3186 def flag_register_to_human(self, val: Optional[int] = None) -> str: 

3187 # https://www.gaisler.com/doc/sparcv8.pdf 

3188 reg = self.flag_register 

3189 if val is None: 

3190 val = gef.arch.register(reg) 

3191 return flags_to_human(val, self.flags_table) 

3192 

3193 def is_call(self, insn: Instruction) -> bool: 

3194 return False 

3195 

3196 def is_ret(self, insn: Instruction) -> bool: 

3197 return insn.mnemonic == "ret" 

3198 

3199 def is_conditional_branch(self, insn: Instruction) -> bool: 

3200 mnemo = insn.mnemonic 

3201 # http://moss.csc.ncsu.edu/~mueller/codeopt/codeopt00/notes/condbranch.html 

3202 branch_mnemos = { 

3203 "be", "bne", "bg", "bge", "bgeu", "bgu", "bl", "ble", "blu", "bleu", 

3204 "bneg", "bpos", "bvs", "bvc", "bcs", "bcc" 

3205 } 

3206 return mnemo in branch_mnemos 

3207 

3208 def is_branch_taken(self, insn: Instruction) -> Tuple[bool, str]: 

3209 mnemo = insn.mnemonic 

3210 flags = dict((self.flags_table[k], k) for k in self.flags_table) 

3211 val = gef.arch.register(self.flag_register) 

3212 taken, reason = False, "" 

3213 

3214 if mnemo == "be": taken, reason = bool(val&(1<<flags["zero"])), "Z" 

3215 elif mnemo == "bne": taken, reason = bool(val&(1<<flags["zero"])) == 0, "!Z" 

3216 elif mnemo == "bg": taken, reason = bool(val&(1<<flags["zero"])) == 0 and (val&(1<<flags["negative"]) == 0 or val&(1<<flags["overflow"]) == 0), "!Z && (!N || !O)" 

3217 elif mnemo == "bge": taken, reason = val&(1<<flags["negative"]) == 0 or val&(1<<flags["overflow"]) == 0, "!N || !O" 

3218 elif mnemo == "bgu": taken, reason = val&(1<<flags["carry"]) == 0 and val&(1<<flags["zero"]) == 0, "!C && !Z" 

3219 elif mnemo == "bgeu": taken, reason = val&(1<<flags["carry"]) == 0, "!C" 

3220 elif mnemo == "bl": taken, reason = bool(val&(1<<flags["negative"])) and bool(val&(1<<flags["overflow"])), "N && O" 

3221 elif mnemo == "blu": taken, reason = bool(val&(1<<flags["carry"])), "C" 

3222 elif mnemo == "ble": taken, reason = bool(val&(1<<flags["zero"])) or bool(val&(1<<flags["negative"]) or val&(1<<flags["overflow"])), "Z || (N || O)" 

3223 elif mnemo == "bleu": taken, reason = bool(val&(1<<flags["carry"])) or bool(val&(1<<flags["zero"])), "C || Z" 

3224 elif mnemo == "bneg": taken, reason = bool(val&(1<<flags["negative"])), "N" 

3225 elif mnemo == "bpos": taken, reason = val&(1<<flags["negative"]) == 0, "!N" 

3226 elif mnemo == "bvs": taken, reason = bool(val&(1<<flags["overflow"])), "O" 

3227 elif mnemo == "bvc": taken, reason = val&(1<<flags["overflow"]) == 0, "!O" 

3228 elif mnemo == "bcs": taken, reason = bool(val&(1<<flags["carry"])), "C" 

3229 elif mnemo == "bcc": taken, reason = val&(1<<flags["carry"]) == 0, "!C" 

3230 return taken, reason 

3231 

3232 def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> Optional[int]: 

3233 ra = None 

3234 if self.is_ret(insn): 

3235 ra = gef.arch.register("$o7") 

3236 else: 

3237 older = frame.older() 

3238 if older: 

3239 ra = to_unsigned_long(older.pc()) 

3240 return ra 

3241 

3242 @classmethod 

3243 def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str: 

3244 hi = (addr & 0xffff0000) >> 16 

3245 lo = (addr & 0x0000ffff) 

3246 _NR_mprotect = 125 

3247 insns = ["add %sp, -16, %sp", 

3248 "st %g1, [ %sp ]", "st %o0, [ %sp + 4 ]", 

3249 "st %o1, [ %sp + 8 ]", "st %o2, [ %sp + 12 ]", 

3250 f"sethi %hi({hi}), %o0", 

3251 f"or %o0, {lo}, %o0", 

3252 "clr %o1", 

3253 "clr %o2", 

3254 f"mov {_NR_mprotect}, %g1", 

3255 "t 0x10", 

3256 "ld [ %sp ], %g1", "ld [ %sp + 4 ], %o0", 

3257 "ld [ %sp + 8 ], %o1", "ld [ %sp + 12 ], %o2", 

3258 "add %sp, 16, %sp",] 

3259 return "; ".join(insns) 

3260 

3261 

3262class SPARC64(SPARC): 

3263 """Refs: 

3264 - http://math-atlas.sourceforge.net/devel/assembly/abi_sysV_sparc.pdf 

3265 - https://cr.yp.to/2005-590/sparcv9.pdf 

3266 """ 

3267 aliases = ("SPARC64", Elf.Abi.SPARC64) 

3268 arch = "SPARC" 

3269 mode = "V9" 

3270 

3271 all_registers = [ 

3272 "$g0", "$g1", "$g2", "$g3", "$g4", "$g5", "$g6", "$g7", 

3273 "$o0", "$o1", "$o2", "$o3", "$o4", "$o5", "$o7", 

3274 "$l0", "$l1", "$l2", "$l3", "$l4", "$l5", "$l6", "$l7", 

3275 "$i0", "$i1", "$i2", "$i3", "$i4", "$i5", "$i7", 

3276 "$pc", "$npc", "$sp", "$fp", "$state", ] 

3277 

3278 flag_register = "$state" # sparcv9.pdf, 5.1.5.1 (ccr) 

3279 flags_table = { 

3280 35: "negative", 

3281 34: "zero", 

3282 33: "overflow", 

3283 32: "carry", 

3284 } 

3285 

3286 syscall_instructions = ["t 0x6d"] 

3287 

3288 @classmethod 

3289 def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str: 

3290 hi = (addr & 0xffff0000) >> 16 

3291 lo = (addr & 0x0000ffff) 

3292 _NR_mprotect = 125 

3293 insns = ["add %sp, -16, %sp", 

3294 "st %g1, [ %sp ]", "st %o0, [ %sp + 4 ]", 

3295 "st %o1, [ %sp + 8 ]", "st %o2, [ %sp + 12 ]", 

3296 f"sethi %hi({hi}), %o0", 

3297 f"or %o0, {lo}, %o0", 

3298 "clr %o1", 

3299 "clr %o2", 

3300 f"mov {_NR_mprotect}, %g1", 

3301 "t 0x6d", 

3302 "ld [ %sp ], %g1", "ld [ %sp + 4 ], %o0", 

3303 "ld [ %sp + 8 ], %o1", "ld [ %sp + 12 ], %o2", 

3304 "add %sp, 16, %sp",] 

3305 return "; ".join(insns) 

3306 

3307 

3308class MIPS(Architecture): 

3309 aliases: Tuple[Union[str, Elf.Abi], ...] = ("MIPS", Elf.Abi.MIPS) 

3310 arch = "MIPS" 

3311 mode = "MIPS32" 

3312 

3313 # https://vhouten.home.xs4all.nl/mipsel/r3000-isa.html 

3314 all_registers = ( 

3315 "$zero", "$at", "$v0", "$v1", "$a0", "$a1", "$a2", "$a3", 

3316 "$t0", "$t1", "$t2", "$t3", "$t4", "$t5", "$t6", "$t7", 

3317 "$s0", "$s1", "$s2", "$s3", "$s4", "$s5", "$s6", "$s7", 

3318 "$t8", "$t9", "$k0", "$k1", "$s8", "$pc", "$sp", "$hi", 

3319 "$lo", "$fir", "$ra", "$gp", ) 

3320 instruction_length = 4 

3321 _ptrsize = 4 

3322 nop_insn = b"\x00\x00\x00\x00" # sll $0,$0,0 

3323 return_register = "$v0" 

3324 flag_register = "$fcsr" 

3325 flags_table = {} 

3326 function_parameters = ("$a0", "$a1", "$a2", "$a3") 

3327 syscall_register = "$v0" 

3328 syscall_instructions = ("syscall",) 

3329 

3330 def flag_register_to_human(self, val: Optional[int] = None) -> str: 

3331 return Color.colorify("No flag register", "yellow underline") 

3332 

3333 def is_call(self, insn: Instruction) -> bool: 

3334 return False 

3335 

3336 def is_ret(self, insn: Instruction) -> bool: 

3337 return insn.mnemonic == "jr" and insn.operands[0] == "ra" 

3338 

3339 def is_conditional_branch(self, insn: Instruction) -> bool: 

3340 mnemo = insn.mnemonic 

3341 branch_mnemos = {"beq", "bne", "beqz", "bnez", "bgtz", "bgez", "bltz", "blez"} 

3342 return mnemo in branch_mnemos 

3343 

3344 def is_branch_taken(self, insn: Instruction) -> Tuple[bool, str]: 

3345 mnemo, ops = insn.mnemonic, insn.operands 

3346 taken, reason = False, "" 

3347 

3348 if mnemo == "beq": 

3349 taken, reason = gef.arch.register(ops[0]) == gef.arch.register(ops[1]), f"{ops[0]} == {ops[1]}" 

3350 elif mnemo == "bne": 

3351 taken, reason = gef.arch.register(ops[0]) != gef.arch.register(ops[1]), f"{ops[0]} != {ops[1]}" 

3352 elif mnemo == "beqz": 

3353 taken, reason = gef.arch.register(ops[0]) == 0, f"{ops[0]} == 0" 

3354 elif mnemo == "bnez": 

3355 taken, reason = gef.arch.register(ops[0]) != 0, f"{ops[0]} != 0" 

3356 elif mnemo == "bgtz": 

3357 taken, reason = gef.arch.register(ops[0]) > 0, f"{ops[0]} > 0" 

3358 elif mnemo == "bgez": 

3359 taken, reason = gef.arch.register(ops[0]) >= 0, f"{ops[0]} >= 0" 

3360 elif mnemo == "bltz": 

3361 taken, reason = gef.arch.register(ops[0]) < 0, f"{ops[0]} < 0" 

3362 elif mnemo == "blez": 

3363 taken, reason = gef.arch.register(ops[0]) <= 0, f"{ops[0]} <= 0" 

3364 return taken, reason 

3365 

3366 def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> Optional[int]: 

3367 ra = None 

3368 if self.is_ret(insn): 

3369 ra = gef.arch.register("$ra") 

3370 else: 

3371 older = frame.older() 

3372 if older: 

3373 ra = to_unsigned_long(older.pc()) 

3374 return ra 

3375 

3376 @classmethod 

3377 def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str: 

3378 _NR_mprotect = 4125 

3379 insns = ["addi $sp, $sp, -16", 

3380 "sw $v0, 0($sp)", "sw $a0, 4($sp)", 

3381 "sw $a3, 8($sp)", "sw $a3, 12($sp)", 

3382 f"li $v0, {_NR_mprotect:d}", 

3383 f"li $a0, {addr:d}", 

3384 f"li $a1, {size:d}", 

3385 f"li $a2, {perm.value:d}", 

3386 "syscall", 

3387 "lw $v0, 0($sp)", "lw $a1, 4($sp)", 

3388 "lw $a3, 8($sp)", "lw $a3, 12($sp)", 

3389 "addi $sp, $sp, 16",] 

3390 return "; ".join(insns) 

3391 

3392 

3393class MIPS64(MIPS): 

3394 aliases = ("MIPS64",) 

3395 arch = "MIPS" 

3396 mode = "MIPS64" 

3397 _ptrsize = 8 

3398 

3399 @staticmethod 

3400 def supports_gdb_arch(gdb_arch: str) -> Optional[bool]: 

3401 if not gef.binary or not isinstance(gef.binary, Elf): 3401 ↛ 3402line 3401 didn't jump to line 3402 because the condition on line 3401 was never true

3402 return False 

3403 return gdb_arch.startswith("mips") and gef.binary.e_class == Elf.Class.ELF_64_BITS 

3404 

3405 

3406def copy_to_clipboard(data: bytes) -> None: 

3407 """Helper function to submit data to the clipboard""" 

3408 if sys.platform == "linux": 

3409 xclip = which("xclip") 

3410 prog = [xclip, "-selection", "clipboard", "-i"] 

3411 elif sys.platform == "darwin": 

3412 pbcopy = which("pbcopy") 

3413 prog = [pbcopy] 

3414 else: 

3415 raise NotImplementedError("copy: Unsupported OS") 

3416 

3417 with subprocess.Popen(prog, stdin=subprocess.PIPE) as p: 

3418 assert p.stdin 

3419 p.stdin.write(data) 

3420 p.stdin.close() 

3421 p.wait() 

3422 return 

3423 

3424 

3425def use_stdtype() -> str: 

3426 if is_32bit(): return "uint32_t" 3426 ↛ exitline 3426 didn't return from function 'use_stdtype' because the return on line 3426 wasn't executed

3427 elif is_64bit(): return "uint64_t" 3427 ↛ 3428line 3427 didn't jump to line 3428 because the condition on line 3427 was always true

3428 return "uint16_t" 

3429 

3430 

3431def use_default_type() -> str: 

3432 if is_32bit(): return "unsigned int" 3432 ↛ exitline 3432 didn't return from function 'use_default_type' because the return on line 3432 wasn't executed

3433 elif is_64bit(): return "unsigned long" 3433 ↛ 3434line 3433 didn't jump to line 3434 because the condition on line 3433 was always true

3434 return "unsigned short" 

3435 

3436 

3437def use_golang_type() -> str: 

3438 if is_32bit(): return "uint32" 

3439 elif is_64bit(): return "uint64" 

3440 return "uint16" 

3441 

3442 

3443def use_rust_type() -> str: 

3444 if is_32bit(): return "u32" 

3445 elif is_64bit(): return "u64" 

3446 return "u16" 

3447 

3448 

3449def to_unsigned_long(v: gdb.Value) -> int: 

3450 """Cast a gdb.Value to unsigned long.""" 

3451 mask = (1 << (gef.arch.ptrsize*8)) - 1 

3452 return int(v.cast(gdb.Value(mask).type)) & mask 

3453 

3454 

3455def get_path_from_info_proc() -> Optional[str]: 

3456 for x in (gdb.execute("info proc", to_string=True) or "").splitlines(): 

3457 if x.startswith("exe = "): 

3458 return x.split(" = ")[1].replace("'", "") 

3459 return None 

3460 

3461 

3462@deprecated("Use `gef.session.os`") 

3463def get_os() -> str: 

3464 return gef.session.os 

3465 

3466 

3467@lru_cache() 

3468def is_qemu() -> bool: 

3469 if not is_remote_debug(): 

3470 return False 

3471 response = gdb.execute("maintenance packet Qqemu.sstepbits", to_string=True, from_tty=False) or "" 

3472 return "ENABLE=" in response 

3473 

3474 

3475@lru_cache() 

3476def is_qemu_usermode() -> bool: 

3477 if not is_qemu(): 

3478 return False 

3479 response = gdb.execute("maintenance packet qOffsets", to_string=True, from_tty=False) or "" 

3480 return "Text=" in response 

3481 

3482 

3483@lru_cache() 

3484def is_qemu_system() -> bool: 

3485 if not is_qemu(): 

3486 return False 

3487 response = gdb.execute("maintenance packet qOffsets", to_string=True, from_tty=False) or "" 

3488 return "received: \"\"" in response 

3489 

3490 

3491def get_filepath() -> Optional[str]: 

3492 """Return the local absolute path of the file currently debugged.""" 

3493 if gef.session.remote: 

3494 return str(gef.session.remote.lfile.absolute()) 

3495 if gef.session.file: 3495 ↛ 3497line 3495 didn't jump to line 3497 because the condition on line 3495 was always true

3496 return str(gef.session.file.absolute()) 

3497 return None 

3498 

3499 

3500def get_function_length(sym: str) -> int: 

3501 """Attempt to get the length of the raw bytes of a function.""" 

3502 dis = (gdb.execute(f"disassemble '{sym}'", to_string=True) or "").splitlines() 

3503 start_addr = int(dis[1].split()[0], 16) 

3504 end_addr = int(dis[-2].split()[0], 16) 

3505 return end_addr - start_addr 

3506 

3507 

3508@lru_cache() 

3509def get_info_files() -> List[Zone]: 

3510 """Retrieve all the files loaded by debuggee.""" 

3511 lines = (gdb.execute("info files", to_string=True) or "").splitlines() 

3512 infos = [] 

3513 for line in lines: 

3514 line = line.strip() 

3515 if not line: 3515 ↛ 3516line 3515 didn't jump to line 3516 because the condition on line 3515 was never true

3516 break 

3517 

3518 if not line.startswith("0x"): 

3519 continue 

3520 

3521 blobs = [x.strip() for x in line.split(" ")] 

3522 addr_start = int(blobs[0], 16) 

3523 addr_end = int(blobs[2], 16) 

3524 section_name = blobs[4] 

3525 

3526 if len(blobs) == 7: 

3527 filename = blobs[6] 

3528 else: 

3529 filename = get_filepath() 

3530 

3531 infos.append(Zone(section_name, addr_start, addr_end, filename)) 

3532 return infos 

3533 

3534 

3535def process_lookup_address(address: int) -> Optional[Section]: 

3536 """Look up for an address in memory. 

3537 Return an Address object if found, None otherwise.""" 

3538 if not is_alive(): 3538 ↛ 3539line 3538 didn't jump to line 3539 because the condition on line 3538 was never true

3539 err("Process is not running") 

3540 return None 

3541 

3542 if is_x86(): 3542 ↛ 3546line 3542 didn't jump to line 3546 because the condition on line 3542 was always true

3543 if is_in_x86_kernel(address): 3543 ↛ 3544line 3543 didn't jump to line 3544 because the condition on line 3543 was never true

3544 return None 

3545 

3546 for sect in gef.memory.maps: 

3547 if sect.page_start <= address < sect.page_end: 

3548 return sect 

3549 

3550 return None 

3551 

3552 

3553@lru_cache() 

3554def process_lookup_path(name: str, perm: Permission = Permission.ALL) -> Optional[Section]: 

3555 """Look up for a path in the process memory mapping. 

3556 Return a Section object if found, None otherwise.""" 

3557 if not is_alive(): 3557 ↛ 3558line 3557 didn't jump to line 3558 because the condition on line 3557 was never true

3558 err("Process is not running") 

3559 return None 

3560 

3561 matches: Dict[str, Section] = dict() 

3562 for sect in gef.memory.maps: 

3563 filename = pathlib.Path(sect.path).name 

3564 

3565 if name in filename and sect.permission & perm: 

3566 if sect.path not in matches.keys(): 

3567 matches[sect.path] = sect 

3568 

3569 matches_count = len(matches) 

3570 

3571 if matches_count == 0: 

3572 return None 

3573 

3574 if matches_count > 1: 3574 ↛ 3575line 3574 didn't jump to line 3575 because the condition on line 3574 was never true

3575 warn(f"{matches_count} matches! You should probably refine your search!") 

3576 

3577 for x in matches.keys(): 

3578 warn(f"- '{x}'") 

3579 

3580 warn("Returning the first match") 

3581 

3582 return list(matches.values())[0] 

3583 

3584 

3585@lru_cache() 

3586def file_lookup_name_path(name: str, path: str) -> Optional[Zone]: 

3587 """Look up a file by name and path. 

3588 Return a Zone object if found, None otherwise.""" 

3589 for xfile in get_info_files(): 3589 ↛ 3592line 3589 didn't jump to line 3592 because the loop on line 3589 didn't complete

3590 if path == xfile.filename and name == xfile.name: 

3591 return xfile 

3592 return None 

3593 

3594 

3595@lru_cache() 

3596def file_lookup_address(address: int) -> Optional[Zone]: 

3597 """Look up for a file by its address. 

3598 Return a Zone object if found, None otherwise.""" 

3599 for info in get_info_files(): 

3600 if info.zone_start <= address < info.zone_end: 

3601 return info 

3602 return None 

3603 

3604 

3605@lru_cache() 

3606def lookup_address(address: int) -> Address: 

3607 """Try to find the address in the process address space. 

3608 Return an Address object, with validity flag set based on success.""" 

3609 sect = process_lookup_address(address) 

3610 info = file_lookup_address(address) 

3611 if sect is None and info is None: 

3612 # i.e. there is no info on this address 

3613 return Address(value=address, valid=False) 

3614 return Address(value=address, section=sect, info=info) 

3615 

3616 

3617def xor(data: ByteString, key: str) -> bytearray: 

3618 """Return `data` xor-ed with `key`.""" 

3619 key_raw = binascii.unhexlify(key.lstrip("0x")) 

3620 return bytearray(x ^ y for x, y in zip(data, itertools.cycle(key_raw))) 

3621 

3622 

3623def is_hex(pattern: str) -> bool: 

3624 """Return whether provided string is a hexadecimal value.""" 

3625 if not pattern.lower().startswith("0x"): 

3626 return False 

3627 return len(pattern) % 2 == 0 and all(c in string.hexdigits for c in pattern[2:]) 

3628 

3629 

3630def continue_handler(_: "gdb.events.ContinueEvent") -> None: 

3631 """GDB event handler for new object continue cases.""" 

3632 return 

3633 

3634 

3635def hook_stop_handler(_: "gdb.events.StopEvent") -> None: 

3636 """GDB event handler for stop cases.""" 

3637 reset_all_caches() 

3638 gdb.execute("context") 

3639 return 

3640 

3641 

3642def new_objfile_handler(evt: Optional["gdb.events.NewObjFileEvent"]) -> None: 

3643 """GDB event handler for new object file cases.""" 

3644 reset_all_caches() 

3645 progspace = gdb.current_progspace() 

3646 if evt: 

3647 path = evt.new_objfile.filename or "" 

3648 elif progspace: 3648 ↛ 3651line 3648 didn't jump to line 3651 because the condition on line 3648 was always true

3649 path = progspace.filename 

3650 else: 

3651 raise RuntimeError("Cannot determine file path") 

3652 try: 

3653 if gef.session.root and path.startswith("target:"): 

3654 # If the process is in a container, replace the "target:" prefix 

3655 # with the actual root directory of the process. 

3656 path = path.replace("target:", str(gef.session.root), 1) 

3657 target = pathlib.Path(path) 

3658 FileFormatClasses = list(filter(lambda fmtcls: fmtcls.is_valid(target), __registered_file_formats__)) 

3659 GuessedFileFormatClass : Type[FileFormat] = FileFormatClasses.pop() if len(FileFormatClasses) else Elf 

3660 binary = GuessedFileFormatClass(target) 

3661 if not gef.binary: 

3662 gef.binary = binary 

3663 reset_architecture() 

3664 else: 

3665 gef.session.modules.append(binary) 

3666 except FileNotFoundError as fne: 3666 ↛ 3672line 3666 didn't jump to line 3672

3667 # Linux automatically maps the vDSO into our process, and GDB 

3668 # will give us the string 'system-supplied DSO' as a path. 

3669 # This is normal, so we shouldn't warn the user about it 

3670 if "system-supplied DSO" not in path: 3670 ↛ 3671line 3670 didn't jump to line 3671 because the condition on line 3670 was never true

3671 warn(f"Failed to find objfile or not a valid file format: {str(fne)}") 

3672 except RuntimeError as re: 

3673 warn(f"Not a valid file format: {str(re)}") 

3674 return 

3675 

3676 

3677def exit_handler(_: "gdb.events.ExitedEvent") -> None: 

3678 """GDB event handler for exit cases.""" 

3679 global gef 

3680 # flush the caches 

3681 reset_all_caches() 

3682 

3683 # disconnect properly the remote session 

3684 gef.session.qemu_mode = False 

3685 if gef.session.remote: 

3686 gef.session.remote.close() 

3687 del gef.session.remote 

3688 gef.session.remote = None 

3689 gef.session.remote_initializing = False 

3690 

3691 # if `autosave_breakpoints_file` setting is configured, save the breakpoints to disk 

3692 setting = (gef.config["gef.autosave_breakpoints_file"] or "").strip() 

3693 if not setting: 3693 ↛ 3696line 3693 didn't jump to line 3696 because the condition on line 3693 was always true

3694 return 

3695 

3696 bkp_fpath = pathlib.Path(setting).expanduser().absolute() 

3697 if bkp_fpath.exists(): 

3698 warn(f"{bkp_fpath} exists, content will be overwritten") 

3699 

3700 with bkp_fpath.open("w") as fd: 

3701 for bp in list(gdb.breakpoints()): 

3702 if not bp.enabled or not bp.is_valid: 

3703 continue 

3704 fd.write(f"{'t' if bp.temporary else ''}break {bp.location}\n") 

3705 return 

3706 

3707 

3708def memchanged_handler(_: "gdb.events.MemoryChangedEvent") -> None: 

3709 """GDB event handler for mem changes cases.""" 

3710 reset_all_caches() 

3711 return 

3712 

3713 

3714def regchanged_handler(_: "gdb.events.RegisterChangedEvent") -> None: 

3715 """GDB event handler for reg changes cases.""" 

3716 reset_all_caches() 

3717 return 

3718 

3719 

3720def get_terminal_size() -> Tuple[int, int]: 

3721 """Return the current terminal size.""" 

3722 if is_debug(): 3722 ↛ 3725line 3722 didn't jump to line 3725 because the condition on line 3722 was always true

3723 return 600, 100 

3724 

3725 if platform.system() == "Windows": 

3726 from ctypes import create_string_buffer, windll # type: ignore 

3727 hStdErr = -12 

3728 herr = windll.kernel32.GetStdHandle(hStdErr) 

3729 csbi = create_string_buffer(22) 

3730 res = windll.kernel32.GetConsoleScreenBufferInfo(herr, csbi) 

3731 if res: 

3732 _, _, _, _, _, left, top, right, bottom, _, _ = struct.unpack("hhhhHhhhhhh", csbi.raw) 

3733 tty_columns = right - left + 1 

3734 tty_rows = bottom - top + 1 

3735 return tty_rows, tty_columns 

3736 else: 

3737 return 600, 100 

3738 else: 

3739 import fcntl 

3740 import termios 

3741 try: 

3742 tty_rows, tty_columns = struct.unpack("hh", fcntl.ioctl(1, termios.TIOCGWINSZ, "1234")) # type: ignore 

3743 return tty_rows, tty_columns 

3744 except OSError: 

3745 return 600, 100 

3746 

3747 

3748@lru_cache() 

3749def is_64bit() -> bool: 

3750 """Checks if current target is 64bit.""" 

3751 return gef.arch.ptrsize == 8 

3752 

3753 

3754@lru_cache() 

3755def is_32bit() -> bool: 

3756 """Checks if current target is 32bit.""" 

3757 return gef.arch.ptrsize == 4 

3758 

3759 

3760@lru_cache() 

3761def is_x86_64() -> bool: 

3762 """Checks if current target is x86-64""" 

3763 return Elf.Abi.X86_64 in gef.arch.aliases 

3764 

3765 

3766@lru_cache() 

3767def is_x86_32(): 

3768 """Checks if current target is an x86-32""" 

3769 return Elf.Abi.X86_32 in gef.arch.aliases 

3770 

3771 

3772@lru_cache() 

3773def is_x86() -> bool: 

3774 return is_x86_32() or is_x86_64() 

3775 

3776 

3777@lru_cache() 

3778def is_arch(arch: Elf.Abi) -> bool: 

3779 return arch in gef.arch.aliases 

3780 

3781 

3782def reset_architecture(arch: Optional[str] = None) -> None: 

3783 """Sets the current architecture. 

3784 If an architecture is explicitly specified by parameter, try to use that one. If this fails, an `OSError` 

3785 exception will occur. 

3786 If no architecture is specified, then GEF will attempt to determine automatically based on the current 

3787 ELF target. If this fails, an `OSError` exception will occur. 

3788 """ 

3789 global gef 

3790 arches = __registered_architectures__ 

3791 

3792 # check if the architecture is forced by parameter 

3793 if arch: 

3794 try: 

3795 gef.arch = arches[arch]() 

3796 gef.arch_reason = "The architecture has been set manually" 

3797 except KeyError: 

3798 raise OSError(f"Specified arch {arch.upper()} is not supported") 

3799 return 

3800 

3801 # check for bin running 

3802 if is_alive(): 

3803 gdb_arch = gdb.selected_frame().architecture().name() 

3804 preciser_arch = next((a for a in arches.values() if a.supports_gdb_arch(gdb_arch)), None) 

3805 if preciser_arch: 3805 ↛ 3806line 3805 didn't jump to line 3806 because the condition on line 3805 was never true

3806 gef.arch = preciser_arch() 

3807 gef.arch_reason = "The architecture has been detected by GDB" 

3808 return 

3809 

3810 # last resort, use the info from elf header to find it from the known architectures 

3811 if gef.binary and isinstance(gef.binary, Elf): 3811 ↛ 3819line 3811 didn't jump to line 3819 because the condition on line 3811 was always true

3812 try: 

3813 gef.arch = arches[gef.binary.e_machine]() 

3814 gef.arch_reason = "The architecture has been detected via the ELF headers" 

3815 except KeyError: 

3816 raise OSError(f"CPU type is currently not supported: {gef.binary.e_machine}") 

3817 return 

3818 

3819 warn("Did not find any way to guess the correct architecture :(") 

3820 

3821 

3822@lru_cache() 

3823def cached_lookup_type(_type: str) -> Optional[gdb.Type]: 

3824 try: 

3825 return gdb.lookup_type(_type).strip_typedefs() 

3826 except RuntimeError: 

3827 return None 

3828 

3829 

3830@deprecated("Use `gef.arch.ptrsize` instead") 

3831def get_memory_alignment(in_bits: bool = False) -> int: 

3832 """Try to determine the size of a pointer on this system. 

3833 First, try to parse it out of the ELF header. 

3834 Next, use the size of `size_t`. 

3835 Finally, try the size of $pc. 

3836 If `in_bits` is set to True, the result is returned in bits, otherwise in 

3837 bytes.""" 

3838 res = cached_lookup_type("size_t") 

3839 if res is not None: 

3840 return res.sizeof if not in_bits else res.sizeof * 8 

3841 

3842 try: 

3843 return gdb.parse_and_eval("$pc").type.sizeof 

3844 except: 

3845 pass 

3846 

3847 raise OSError("GEF is running under an unsupported mode") 

3848 

3849 

3850def clear_screen(tty: str = "") -> None: 

3851 """Clear the screen.""" 

3852 global gef 

3853 if not tty: 3853 ↛ 3859line 3853 didn't jump to line 3859 because the condition on line 3853 was always true

3854 gdb.execute("shell clear -x") 

3855 return 

3856 

3857 # Since the tty can be closed at any time, a PermissionError exception can 

3858 # occur when `clear_screen` is called. We handle this scenario properly 

3859 try: 

3860 with open(tty, "wt") as f: 

3861 f.write("\x1b[H\x1b[J") 

3862 except PermissionError: 

3863 gef.ui.redirect_fd = None 

3864 gef.config["context.redirect"] = "" 

3865 return 

3866 

3867 

3868def format_address(addr: int) -> str: 

3869 """Format the address according to its size.""" 

3870 memalign_size = gef.arch.ptrsize 

3871 addr = align_address(addr) 

3872 return f"0x{addr:016x}" if memalign_size == 8 else f"0x{addr:08x}" 

3873 

3874 

3875def format_address_spaces(addr: int, left: bool = True) -> str: 

3876 """Format the address according to its size, but with spaces instead of zeroes.""" 

3877 width = gef.arch.ptrsize * 2 + 2 

3878 addr = align_address(addr) 

3879 

3880 if not left: 3880 ↛ 3881line 3880 didn't jump to line 3881 because the condition on line 3880 was never true

3881 return f"{addr:#x}".rjust(width) 

3882 

3883 return f"{addr:#x}".ljust(width) 

3884 

3885 

3886def align_address(address: int) -> int: 

3887 """Align the provided address to the process's native length.""" 

3888 return address & 0xFFFFFFFFFFFFFFFF if gef.arch.ptrsize == 8 else address & 0xFFFFFFFF 

3889 

3890 

3891def align_address_to_size(address: int, align: int) -> int: 

3892 """Align the address to the given size.""" 

3893 return address + ((align - (address % align)) % align) 

3894 

3895 

3896def align_address_to_page(address: int) -> int: 

3897 """Align the address to a page.""" 

3898 a = align_address(address) >> DEFAULT_PAGE_ALIGN_SHIFT 

3899 return a << DEFAULT_PAGE_ALIGN_SHIFT 

3900 

3901 

3902def parse_address(address: str) -> int: 

3903 """Parse an address and return it as an Integer.""" 

3904 if is_hex(address): 

3905 return int(address, 16) 

3906 return int(gdb.parse_and_eval(address)) 

3907 

3908 

3909def is_in_x86_kernel(address: int) -> bool: 

3910 address = align_address(address) 

3911 memalign = gef.arch.ptrsize*8 - 1 

3912 return (address >> memalign) == 0xF 

3913 

3914 

3915def is_remote_debug() -> bool: 

3916 """"Return True is the current debugging session is running through GDB remote session.""" 

3917 return gef.session.remote_initializing or gef.session.remote is not None 

3918 

3919 

3920def de_bruijn(alphabet: bytes, n: int) -> Generator[int, None, None]: 

3921 """De Bruijn sequence for alphabet and subsequences of length n (for compat. w/ pwnlib).""" 

3922 k = len(alphabet) 

3923 a = [0] * k * n 

3924 

3925 def db(t: int, p: int) -> Generator[int, None, None]: 

3926 if t > n: 

3927 if n % p == 0: 

3928 for j in range(1, p + 1): 

3929 yield alphabet[a[j]] 

3930 else: 

3931 a[t] = a[t - p] 

3932 yield from db(t + 1, p) 

3933 

3934 for j in range(a[t - p] + 1, k): 

3935 a[t] = j 

3936 yield from db(t + 1, t) 

3937 

3938 return db(1, 1) 

3939 

3940 

3941def generate_cyclic_pattern(length: int, cycle: int = 4) -> bytearray: 

3942 """Create a `length` byte bytearray of a de Bruijn cyclic pattern.""" 

3943 charset = bytearray(b"abcdefghijklmnopqrstuvwxyz") 

3944 return bytearray(itertools.islice(de_bruijn(charset, cycle), length)) 

3945 

3946 

3947def safe_parse_and_eval(value: str) -> Optional["gdb.Value"]: 

3948 """GEF wrapper for gdb.parse_and_eval(): this function returns None instead of raising 

3949 gdb.error if the eval failed.""" 

3950 try: 

3951 return gdb.parse_and_eval(value) 

3952 except gdb.error as e: 

3953 dbg(f"gdb.parse_and_eval() failed, reason: {str(e)}") 

3954 return None 

3955 

3956 

3957@lru_cache() 

3958def dereference(addr: int) -> Optional["gdb.Value"]: 

3959 """GEF wrapper for gdb dereference function.""" 

3960 try: 

3961 ulong_t = cached_lookup_type(use_stdtype()) or \ 

3962 cached_lookup_type(use_default_type()) or \ 

3963 cached_lookup_type(use_golang_type()) or \ 

3964 cached_lookup_type(use_rust_type()) 

3965 if not ulong_t: 3965 ↛ 3966line 3965 didn't jump to line 3966 because the condition on line 3965 was never true

3966 raise gdb.MemoryError("Failed to determine unsigned long type") 

3967 unsigned_long_type = ulong_t.pointer() 

3968 res = gdb.Value(addr).cast(unsigned_long_type).dereference() 

3969 # GDB does lazy fetch by default so we need to force access to the value 

3970 res.fetch_lazy() 

3971 return res 

3972 except gdb.MemoryError as e: 

3973 dbg(str(e)) 

3974 return None 

3975 

3976 

3977def gef_convenience(value: Union[str, bytes]) -> str: 

3978 """Defines a new convenience value.""" 

3979 global gef 

3980 var_name = f"$_gef{gef.session.convenience_vars_index:d}" 

3981 gef.session.convenience_vars_index += 1 

3982 if isinstance(value, str): 

3983 gdb.execute(f"""set {var_name} = "{value}" """) 

3984 elif isinstance(value, bytes): 3984 ↛ 3988line 3984 didn't jump to line 3988 because the condition on line 3984 was always true

3985 value_as_array = "{" + ", ".join([f"0x{b:02x}" for b in value]) + "}" 

3986 gdb.execute(f"""set {var_name} = {value_as_array} """) 

3987 else: 

3988 raise TypeError 

3989 return var_name 

3990 

3991 

3992def parse_string_range(s: str) -> Iterator[int]: 

3993 """Parses an address range (e.g. 0x400000-0x401000)""" 

3994 addrs = s.split("-") 

3995 return map(lambda x: int(x, 16), addrs) 

3996 

3997 

3998@lru_cache() 

3999def is_syscall(instruction: Union[Instruction,int]) -> bool: 

4000 """Checks whether an instruction or address points to a system call.""" 

4001 if isinstance(instruction, int): 

4002 instruction = gef_current_instruction(instruction) 

4003 insn_str = instruction.mnemonic 

4004 if len(instruction.operands): 

4005 insn_str += f" {', '.join(instruction.operands)}" 

4006 return insn_str in gef.arch.syscall_instructions 

4007 

4008 

4009# 

4010# Deprecated API 

4011# 

4012 

4013@deprecated("Use `gef.session.pie_breakpoints[num]`") 

4014def gef_get_pie_breakpoint(num: int) -> "PieVirtualBreakpoint": 

4015 return gef.session.pie_breakpoints[num] 

4016 

4017 

4018@deprecated("Use `str(gef.arch.endianness)` instead") 

4019def endian_str() -> str: 

4020 return str(gef.arch.endianness) 

4021 

4022 

4023@deprecated("Use `gef.config[key]`") 

4024def get_gef_setting(name: str) -> Any: 

4025 return gef.config[name] 

4026 

4027 

4028@deprecated("Use `gef.config[key] = value`") 

4029def set_gef_setting(name: str, value: Any) -> None: 

4030 gef.config[name] = value 

4031 return 

4032 

4033 

4034@deprecated("Use `gef.session.pagesize`") 

4035def gef_getpagesize() -> int: 

4036 return gef.session.pagesize 

4037 

4038 

4039@deprecated("Use `gef.session.canary`") 

4040def gef_read_canary() -> Optional[Tuple[int, int]]: 

4041 return gef.session.canary 

4042 

4043 

4044@deprecated("Use `gef.session.pid`") 

4045def get_pid() -> int: 

4046 return gef.session.pid 

4047 

4048 

4049@deprecated("Use `gef.session.file.name`") 

4050def get_filename() -> str: 

4051 assert gef.session.file 

4052 return gef.session.file.name 

4053 

4054 

4055@deprecated("Use `gef.heap.main_arena`") 

4056def get_glibc_arena() -> Optional[GlibcArena]: 

4057 return gef.heap.main_arena 

4058 

4059 

4060@deprecated("Use `gef.arch.register(regname)`") 

4061def get_register(regname) -> Optional[int]: 

4062 return gef.arch.register(regname) 

4063 

4064 

4065@deprecated("Use `gef.memory.maps`") 

4066def get_process_maps() -> List[Section]: 

4067 return gef.memory.maps 

4068 

4069 

4070@deprecated("Use `reset_architecture`") 

4071def set_arch(arch: Optional[str] = None, _: Optional[str] = None) -> None: 

4072 return reset_architecture(arch) 

4073 

4074# 

4075# GDB event hooking 

4076# 

4077 

4078@only_if_events_supported("cont") 

4079def gef_on_continue_hook(func: Callable[["gdb.events.ContinueEvent"], None]) -> None: 

4080 gdb.events.cont.connect(func) 

4081 

4082 

4083@only_if_events_supported("cont") 

4084def gef_on_continue_unhook(func: Callable[["gdb.events.ThreadEvent"], None]) -> None: 

4085 gdb.events.cont.disconnect(func) 

4086 

4087 

4088@only_if_events_supported("stop") 

4089def gef_on_stop_hook(func: Callable[["gdb.events.StopEvent"], None]) -> None: 

4090 gdb.events.stop.connect(func) 

4091 

4092 

4093@only_if_events_supported("stop") 

4094def gef_on_stop_unhook(func: Callable[["gdb.events.StopEvent"], None]) -> None: 

4095 gdb.events.stop.disconnect(func) 

4096 

4097 

4098@only_if_events_supported("exited") 

4099def gef_on_exit_hook(func: Callable[["gdb.events.ExitedEvent"], None]) -> None: 

4100 gdb.events.exited.connect(func) 

4101 

4102 

4103@only_if_events_supported("exited") 

4104def gef_on_exit_unhook(func: Callable[["gdb.events.ExitedEvent"], None]) -> None: 

4105 gdb.events.exited.disconnect(func) 

4106 

4107 

4108@only_if_events_supported("new_objfile") 

4109def gef_on_new_hook(func: Callable[["gdb.events.NewObjFileEvent"], None]) -> None: 

4110 gdb.events.new_objfile.connect(func) 

4111 

4112 

4113@only_if_events_supported("new_objfile") 

4114def gef_on_new_unhook(func: Callable[["gdb.events.NewObjFileEvent"], None]) -> None: 

4115 gdb.events.new_objfile.disconnect(func) 

4116 

4117 

4118@only_if_events_supported("clear_objfiles") 

4119def gef_on_unload_objfile_hook(func: Callable[["gdb.events.ClearObjFilesEvent"], None]) -> None: 

4120 gdb.events.clear_objfiles.connect(func) 

4121 

4122 

4123@only_if_events_supported("clear_objfiles") 

4124def gef_on_unload_objfile_unhook(func: Callable[["gdb.events.ClearObjFilesEvent"], None]) -> None: 

4125 gdb.events.clear_objfiles.disconnect(func) 

4126 

4127 

4128@only_if_events_supported("memory_changed") 

4129def gef_on_memchanged_hook(func: Callable[["gdb.events.MemoryChangedEvent"], None]) -> None: 

4130 gdb.events.memory_changed.connect(func) 

4131 

4132 

4133@only_if_events_supported("memory_changed") 

4134def gef_on_memchanged_unhook(func: Callable[["gdb.events.MemoryChangedEvent"], None]) -> None: 

4135 gdb.events.memory_changed.disconnect(func) 

4136 

4137 

4138@only_if_events_supported("register_changed") 

4139def gef_on_regchanged_hook(func: Callable[["gdb.events.RegisterChangedEvent"], None]) -> None: 

4140 gdb.events.register_changed.connect(func) 

4141 

4142 

4143@only_if_events_supported("register_changed") 

4144def gef_on_regchanged_unhook(func: Callable[["gdb.events.RegisterChangedEvent"], None]) -> None: 

4145 gdb.events.register_changed.disconnect(func) 

4146 

4147 

4148# 

4149# Virtual breakpoints 

4150# 

4151 

4152class PieVirtualBreakpoint: 

4153 """PIE virtual breakpoint (not real breakpoint).""" 

4154 

4155 def __init__(self, set_func: Callable[[int], str], vbp_num: int, addr: int) -> None: 

4156 # set_func(base): given a base address return a 

4157 # "set breakpoint" gdb command string 

4158 self.set_func = set_func 

4159 self.vbp_num = vbp_num 

4160 # breakpoint num, 0 represents not instantiated yet 

4161 self.bp_num = 0 

4162 self.bp_addr = 0 

4163 # this address might be a symbol, just to know where to break 

4164 if isinstance(addr, int): 4164 ↛ 4167line 4164 didn't jump to line 4167 because the condition on line 4164 was always true

4165 self.addr: Union[int, str] = hex(addr) 

4166 else: 

4167 self.addr = addr 

4168 return 

4169 

4170 def instantiate(self, base: int) -> None: 

4171 if self.bp_num: 4171 ↛ 4172line 4171 didn't jump to line 4172 because the condition on line 4171 was never true

4172 self.destroy() 

4173 

4174 try: 

4175 res = gdb.execute(self.set_func(base), to_string=True) or "" 

4176 if not res: return 4176 ↛ exitline 4176 didn't return from function 'instantiate' because the return on line 4176 wasn't executed

4177 except gdb.error as e: 

4178 err(str(e)) 

4179 return 

4180 

4181 if "Breakpoint" not in res: 4181 ↛ 4182line 4181 didn't jump to line 4182 because the condition on line 4181 was never true

4182 err(res) 

4183 return 

4184 

4185 res_list = res.split() 

4186 self.bp_num = res_list[1] 

4187 self.bp_addr = res_list[3] 

4188 return 

4189 

4190 def destroy(self) -> None: 

4191 if not self.bp_num: 

4192 err("Destroy PIE breakpoint not even set") 

4193 return 

4194 gdb.execute(f"delete {self.bp_num}") 

4195 self.bp_num = 0 

4196 return 

4197 

4198 

4199# 

4200# Breakpoints 

4201# 

4202 

4203class FormatStringBreakpoint(gdb.Breakpoint): 

4204 """Inspect stack for format string.""" 

4205 def __init__(self, spec: str, num_args: int) -> None: 

4206 super().__init__(spec, type=gdb.BP_BREAKPOINT, internal=False) 

4207 self.num_args = num_args 

4208 self.enabled = True 

4209 return 

4210 

4211 def stop(self) -> bool: 

4212 reset_all_caches() 

4213 msg = [] 

4214 ptr, addr = gef.arch.get_ith_parameter(self.num_args) 

4215 addr = lookup_address(addr) 

4216 

4217 if not addr.valid: 4217 ↛ 4218line 4217 didn't jump to line 4218 because the condition on line 4217 was never true

4218 return False 

4219 

4220 if addr.section.is_writable(): 4220 ↛ 4230line 4220 didn't jump to line 4230 because the condition on line 4220 was always true

4221 content = gef.memory.read_cstring(addr.value) 

4222 name = addr.info.name if addr.info else addr.section.path 

4223 msg.append(Color.colorify("Format string helper", "yellow bold")) 

4224 msg.append(f"Possible insecure format string: {self.location}('{ptr}' {RIGHT_ARROW} {addr.value:#x}: '{content}')") 

4225 msg.append(f"Reason: Call to '{self.location}()' with format string argument in position " 

4226 f"#{self.num_args:d} is in page {addr.section.page_start:#x} ({name}) that has write permission") 

4227 push_context_message("warn", "\n".join(msg)) 

4228 return True 

4229 

4230 return False 

4231 

4232 

4233class StubBreakpoint(gdb.Breakpoint): 

4234 """Create a breakpoint to permanently disable a call (fork/alarm/signal/etc.).""" 

4235 

4236 def __init__(self, func: str, retval: Optional[int]) -> None: 

4237 super().__init__(func, gdb.BP_BREAKPOINT, internal=False) 

4238 self.func = func 

4239 self.retval = retval 

4240 

4241 m = f"All calls to '{self.func}' will be skipped" 

4242 if self.retval is not None: 4242 ↛ 4244line 4242 didn't jump to line 4244 because the condition on line 4242 was always true

4243 m += f" (with return value set to {self.retval:#x})" 

4244 info(m) 

4245 return 

4246 

4247 def stop(self) -> bool: 

4248 size = "long" if gef.arch.ptrsize == 8 else "int" 

4249 gdb.execute(f"return (unsigned {size}){self.retval:#x}") 

4250 ok(f"Ignoring call to '{self.func}' " 

4251 f"(setting return value to {self.retval:#x})") 

4252 return False 

4253 

4254 

4255class ChangePermissionBreakpoint(gdb.Breakpoint): 

4256 """When hit, this temporary breakpoint will restore the original code, and position 

4257 $pc correctly.""" 

4258 

4259 def __init__(self, loc: str, code: ByteString, pc: int) -> None: 

4260 super().__init__(loc, gdb.BP_BREAKPOINT, internal=False) 

4261 self.original_code = code 

4262 self.original_pc = pc 

4263 return 

4264 

4265 def stop(self) -> bool: 

4266 info("Restoring original context") 

4267 gef.memory.write(self.original_pc, self.original_code, len(self.original_code)) 

4268 info("Restoring $pc") 

4269 gdb.execute(f"set $pc = {self.original_pc:#x}") 

4270 return True 

4271 

4272 

4273class TraceMallocBreakpoint(gdb.Breakpoint): 

4274 """Track allocations done with malloc() or calloc().""" 

4275 

4276 def __init__(self, name: str) -> None: 

4277 super().__init__(name, gdb.BP_BREAKPOINT, internal=True) 

4278 self.silent = True 

4279 self.name = name 

4280 return 

4281 

4282 def stop(self) -> bool: 

4283 reset_all_caches() 

4284 _, size = gef.arch.get_ith_parameter(0) 

4285 assert size 

4286 self.retbp = TraceMallocRetBreakpoint(size, self.name) 

4287 return False 

4288 

4289 

4290class TraceMallocRetBreakpoint(gdb.FinishBreakpoint): 

4291 """Internal temporary breakpoint to retrieve the return value of malloc().""" 

4292 

4293 def __init__(self, size: int, name: str) -> None: 

4294 super().__init__(gdb.newest_frame(), internal=True) 

4295 self.size = size 

4296 self.name = name 

4297 self.silent = True 

4298 return 

4299 

4300 def stop(self) -> bool: 

4301 if self.return_value: 4301 ↛ 4304line 4301 didn't jump to line 4304 because the condition on line 4301 was always true

4302 loc = int(self.return_value) 

4303 else: 

4304 loc = parse_address(gef.arch.return_register) 

4305 

4306 size = self.size 

4307 ok(f"{Color.colorify('Heap-Analysis', 'yellow bold')} - {self.name}({size})={loc:#x}") 

4308 check_heap_overlap = gef.config["heap-analysis-helper.check_heap_overlap"] 

4309 

4310 # pop from free-ed list if it was in it 

4311 if gef.session.heap_freed_chunks: 4311 ↛ 4312line 4311 didn't jump to line 4312 because the condition on line 4311 was never true

4312 idx = 0 

4313 for item in gef.session.heap_freed_chunks: 

4314 addr = item[0] 

4315 if addr == loc: 

4316 gef.session.heap_freed_chunks.remove(item) 

4317 continue 

4318 idx += 1 

4319 

4320 # pop from uaf watchlist 

4321 if gef.session.heap_uaf_watchpoints: 4321 ↛ 4322line 4321 didn't jump to line 4322 because the condition on line 4321 was never true

4322 idx = 0 

4323 for wp in gef.session.heap_uaf_watchpoints: 

4324 wp_addr = wp.address 

4325 if loc <= wp_addr < loc + size: 

4326 gef.session.heap_uaf_watchpoints.remove(wp) 

4327 wp.enabled = False 

4328 continue 

4329 idx += 1 

4330 

4331 item = (loc, size) 

4332 

4333 if check_heap_overlap: 4333 ↛ 4355line 4333 didn't jump to line 4355 because the condition on line 4333 was always true

4334 # seek all the currently allocated chunks, read their effective size and check for overlap 

4335 msg = [] 

4336 align = gef.arch.ptrsize 

4337 for chunk_addr, _ in gef.session.heap_allocated_chunks: 

4338 current_chunk = GlibcChunk(chunk_addr) 

4339 current_chunk_size = current_chunk.size 

4340 

4341 if chunk_addr <= loc < chunk_addr + current_chunk_size: 4341 ↛ 4342line 4341 didn't jump to line 4342 because the condition on line 4341 was never true

4342 offset = loc - chunk_addr - 2*align 

4343 if offset < 0: continue # false positive, discard 

4344 

4345 msg.append(Color.colorify("Heap-Analysis", "yellow bold")) 

4346 msg.append("Possible heap overlap detected") 

4347 msg.append(f"Reason {RIGHT_ARROW} new allocated chunk {loc:#x} (of size {size:d}) overlaps in-used chunk {chunk_addr:#x} (of size {current_chunk_size:#x})") 

4348 msg.append(f"Writing {offset:d} bytes from {chunk_addr:#x} will reach chunk {loc:#x}") 

4349 msg.append(f"Payload example for chunk {chunk_addr:#x} (to overwrite {loc:#x} headers):") 

4350 msg.append(f" data = 'A'*{offset:d} + 'B'*{align:d} + 'C'*{align:d}") 

4351 push_context_message("warn", "\n".join(msg)) 

4352 return True 

4353 

4354 # add it to alloc-ed list 

4355 gef.session.heap_allocated_chunks.append(item) 

4356 return False 

4357 

4358 

4359class TraceReallocBreakpoint(gdb.Breakpoint): 

4360 """Track re-allocations done with realloc().""" 

4361 

4362 def __init__(self) -> None: 

4363 super().__init__("__libc_realloc", gdb.BP_BREAKPOINT, internal=True) 

4364 self.silent = True 

4365 return 

4366 

4367 def stop(self) -> bool: 

4368 _, ptr = gef.arch.get_ith_parameter(0) 

4369 _, size = gef.arch.get_ith_parameter(1) 

4370 assert ptr is not None and size is not None 

4371 self.retbp = TraceReallocRetBreakpoint(ptr, size) 

4372 return False 

4373 

4374 

4375class TraceReallocRetBreakpoint(gdb.FinishBreakpoint): 

4376 """Internal temporary breakpoint to retrieve the return value of realloc().""" 

4377 

4378 def __init__(self, ptr: int, size: int) -> None: 

4379 super().__init__(gdb.newest_frame(), internal=True) 

4380 self.ptr = ptr 

4381 self.size = size 

4382 self.silent = True 

4383 return 

4384 

4385 def stop(self) -> bool: 

4386 if self.return_value: 4386 ↛ 4389line 4386 didn't jump to line 4389 because the condition on line 4386 was always true

4387 newloc = int(self.return_value) 

4388 else: 

4389 newloc = parse_address(gef.arch.return_register) 

4390 

4391 title = Color.colorify("Heap-Analysis", "yellow bold") 

4392 if newloc != self: 4392 ↛ 4396line 4392 didn't jump to line 4396 because the condition on line 4392 was always true

4393 loc = Color.colorify(f"{newloc:#x}", "green") 

4394 ok(f"{title} - realloc({self.ptr:#x}, {self.size})={loc}") 

4395 else: 

4396 loc = Color.colorify(f"{newloc:#x}", "red") 

4397 ok(f"{title} - realloc({self.ptr:#x}, {self.size})={loc}") 

4398 

4399 item = (newloc, self.size) 

4400 

4401 try: 

4402 # check if item was in alloc-ed list 

4403 idx = [x for x, y in gef.session.heap_allocated_chunks].index(self.ptr) 

4404 # if so pop it out 

4405 item = gef.session.heap_allocated_chunks.pop(idx) 

4406 except ValueError: 

4407 if is_debug(): 

4408 warn(f"Chunk {self.ptr:#x} was not in tracking list") 

4409 finally: 

4410 # add new item to alloc-ed list 

4411 gef.session.heap_allocated_chunks.append(item) 

4412 

4413 return False 

4414 

4415 

4416class TraceFreeBreakpoint(gdb.Breakpoint): 

4417 """Track calls to free() and attempts to detect inconsistencies.""" 

4418 

4419 def __init__(self) -> None: 

4420 super().__init__("__libc_free", gdb.BP_BREAKPOINT, internal=True) 

4421 self.silent = True 

4422 return 

4423 

4424 def stop(self) -> bool: 

4425 reset_all_caches() 

4426 _, addr = gef.arch.get_ith_parameter(0) 

4427 msg = [] 

4428 check_free_null = gef.config["heap-analysis-helper.check_free_null"] 

4429 check_double_free = gef.config["heap-analysis-helper.check_double_free"] 

4430 check_weird_free = gef.config["heap-analysis-helper.check_weird_free"] 

4431 check_uaf = gef.config["heap-analysis-helper.check_uaf"] 

4432 

4433 ok(f"{Color.colorify('Heap-Analysis', 'yellow bold')} - free({addr:#x})") 

4434 if not addr: 4434 ↛ 4435line 4434 didn't jump to line 4435 because the condition on line 4434 was never true

4435 if check_free_null: 

4436 msg.append(Color.colorify("Heap-Analysis", "yellow bold")) 

4437 msg.append(f"Attempting to free(NULL) at {gef.arch.pc:#x}") 

4438 msg.append("Reason: if NULL page is allocatable, this can lead to code execution.") 

4439 push_context_message("warn", "\n".join(msg)) 

4440 return True 

4441 return False 

4442 

4443 if addr in [x for (x, _) in gef.session.heap_freed_chunks]: 4443 ↛ 4444line 4443 didn't jump to line 4444 because the condition on line 4443 was never true

4444 if check_double_free: 

4445 msg.append(Color.colorify("Heap-Analysis", "yellow bold")) 

4446 msg.append(f"Double-free detected {RIGHT_ARROW} free({addr:#x}) is called at {gef.arch.pc:#x} but is already in the free-ed list") 

4447 msg.append("Execution will likely crash...") 

4448 push_context_message("warn", "\n".join(msg)) 

4449 return True 

4450 return False 

4451 

4452 # if here, no error 

4453 # 1. move alloc-ed item to free list 

4454 try: 

4455 # pop from alloc-ed list 

4456 idx = [x for x, y in gef.session.heap_allocated_chunks].index(addr) 

4457 item = gef.session.heap_allocated_chunks.pop(idx) 

4458 

4459 except ValueError: 

4460 if check_weird_free: 

4461 msg.append(Color.colorify("Heap-Analysis", "yellow bold")) 

4462 msg.append("Heap inconsistency detected:") 

4463 msg.append(f"Attempting to free an unknown value: {addr:#x}") 

4464 push_context_message("warn", "\n".join(msg)) 

4465 return True 

4466 return False 

4467 

4468 # 2. add it to free-ed list 

4469 gef.session.heap_freed_chunks.append(item) 

4470 

4471 self.retbp = None 

4472 if check_uaf: 4472 ↛ 4475line 4472 didn't jump to line 4475 because the condition on line 4472 was always true

4473 # 3. (opt.) add a watchpoint on pointer 

4474 self.retbp = TraceFreeRetBreakpoint(addr) 

4475 return False 

4476 

4477 

4478class TraceFreeRetBreakpoint(gdb.FinishBreakpoint): 

4479 """Internal temporary breakpoint to track free()d values.""" 

4480 

4481 def __init__(self, addr: int) -> None: 

4482 super().__init__(gdb.newest_frame(), internal=True) 

4483 self.silent = True 

4484 self.addr = addr 

4485 return 

4486 

4487 def stop(self) -> bool: 

4488 reset_all_caches() 

4489 wp = UafWatchpoint(self.addr) 

4490 gef.session.heap_uaf_watchpoints.append(wp) 

4491 return False 

4492 

4493 

4494class UafWatchpoint(gdb.Breakpoint): 

4495 """Custom watchpoints set TraceFreeBreakpoint() to monitor free()d pointers being used.""" 

4496 

4497 def __init__(self, addr: int) -> None: 

4498 super().__init__(f"*{addr:#x}", gdb.BP_WATCHPOINT, internal=True) 

4499 self.address = addr 

4500 self.silent = True 

4501 self.enabled = True 

4502 return 

4503 

4504 def stop(self) -> bool: 

4505 """If this method is triggered, we likely have a UaF. Break the execution and report it.""" 

4506 reset_all_caches() 

4507 frame = gdb.selected_frame() 

4508 if frame.name() in ("_int_malloc", "malloc_consolidate", "__libc_calloc", ): 

4509 return False 

4510 

4511 # software watchpoints stop after the next statement (see 

4512 # https://sourceware.org/gdb/onlinedocs/gdb/Set-Watchpoints.html) 

4513 pc = gdb_get_nth_previous_instruction_address(gef.arch.pc, 2) 

4514 assert pc 

4515 insn = gef_current_instruction(pc) 

4516 msg = [] 

4517 msg.append(Color.colorify("Heap-Analysis", "yellow bold")) 

4518 msg.append(f"Possible Use-after-Free in '{get_filepath()}': " 

4519 f"pointer {self.address:#x} was freed, but is attempted to be used at {pc:#x}") 

4520 msg.append(f"{insn.address:#x} {insn.mnemonic} {Color.yellowify(', '.join(insn.operands))}") 

4521 push_context_message("warn", "\n".join(msg)) 

4522 return True 

4523 

4524 

4525class EntryBreakBreakpoint(gdb.Breakpoint): 

4526 """Breakpoint used internally to stop execution at the most convenient entry point.""" 

4527 

4528 def __init__(self, location: str) -> None: 

4529 super().__init__(location, gdb.BP_BREAKPOINT, internal=True, temporary=True) 

4530 self.silent = True 

4531 return 

4532 

4533 def stop(self) -> bool: 

4534 reset_all_caches() 

4535 return True 

4536 

4537 

4538class NamedBreakpoint(gdb.Breakpoint): 

4539 """Breakpoint which shows a specified name, when hit.""" 

4540 

4541 def __init__(self, location: str, name: str) -> None: 

4542 super().__init__(spec=location, type=gdb.BP_BREAKPOINT, internal=False, temporary=False) 

4543 self.name = name 

4544 self.loc = location 

4545 return 

4546 

4547 def stop(self) -> bool: 

4548 reset_all_caches() 

4549 push_context_message("info", f"Hit breakpoint {self.loc} ({Color.colorify(self.name, 'red bold')})") 

4550 return True 

4551 

4552 

4553class JustSilentStopBreakpoint(gdb.Breakpoint): 

4554 """When hit, this temporary breakpoint stop the execution.""" 

4555 

4556 def __init__(self, loc: str) -> None: 

4557 super().__init__(loc, gdb.BP_BREAKPOINT, temporary=True) 

4558 self.silent = True 

4559 return 

4560 

4561 

4562# 

4563# Context Panes 

4564# 

4565 

4566def register_external_context_pane(pane_name: str, display_pane_function: Callable[[], None], pane_title_function: Callable[[], Optional[str]], condition : Optional[Callable[[], bool]] = None) -> None: 

4567 """ 

4568 Registering function for new GEF Context View. 

4569 pane_name: a string that has no spaces (used in settings) 

4570 display_pane_function: a function that uses gef_print() to print strings 

4571 pane_title_function: a function that returns a string or None, which will be displayed as the title. 

4572 If None, no title line is displayed. 

4573 condition: an optional callback: if not None, the callback will be executed first. If it returns true, 

4574 then only the pane title and content will displayed. Otherwise, it's simply skipped. 

4575 

4576 Example usage for a simple text to show when we hit a syscall: 

4577 def only_syscall(): return gef_current_instruction(gef.arch.pc).is_syscall() 

4578 def display_pane(): 

4579 gef_print("Wow, I am a context pane!") 

4580 def pane_title(): 

4581 return "example:pane" 

4582 register_external_context_pane("example_pane", display_pane, pane_title, only_syscall) 

4583 """ 

4584 gef.gdb.add_context_pane(pane_name, display_pane_function, pane_title_function, condition) 

4585 return 

4586 

4587def register_external_context_layout_mapping(current_pane_name: str, display_pane_function: Callable[[], None], pane_title_function: Callable[[], Optional[str]], condition : Optional[Callable[[], bool]] = None) -> None: 

4588 gef.gdb.add_context_layout_mapping(current_pane_name, display_pane_function, pane_title_function, condition) 

4589 return 

4590 

4591 

4592# 

4593# Commands 

4594# 

4595@deprecated("Use `register()`, and inherit from `GenericCommand` instead") 

4596def register_external_command(cls: Type["GenericCommand"]) -> Type["GenericCommand"]: 

4597 """Registering function for new GEF (sub-)command to GDB.""" 

4598 return cls 

4599 

4600@deprecated("Use `register()`, and inherit from `GenericCommand` instead") 

4601def register_command(cls: Type["GenericCommand"]) -> Type["GenericCommand"]: 

4602 """Decorator for registering new GEF (sub-)command to GDB.""" 

4603 return cls 

4604 

4605@deprecated("") 

4606def register_priority_command(cls: Type["GenericCommand"]) -> Type["GenericCommand"]: 

4607 """Decorator for registering new command with priority, meaning that it must 

4608 loaded before the other generic commands.""" 

4609 return cls 

4610 

4611 

4612ValidCommandType = TypeVar("ValidCommandType", bound="GenericCommand") 

4613ValidFunctionType = TypeVar("ValidFunctionType", bound="GenericFunction") 

4614 

4615def register(cls: Union[Type["ValidCommandType"], Type["ValidFunctionType"]]) -> Union[Type["ValidCommandType"], Type["ValidFunctionType"]]: 

4616 global __registered_commands__, __registered_functions__ 

4617 if issubclass(cls, GenericCommand): 

4618 assert hasattr(cls, "_cmdline_") 

4619 assert hasattr(cls, "do_invoke") 

4620 if any(map(lambda x: x._cmdline_ == cls._cmdline_, __registered_commands__)): 4620 ↛ 4621line 4620 didn't jump to line 4621 because the condition on line 4620 was never true

4621 raise AlreadyRegisteredException(cls._cmdline_) 

4622 __registered_commands__.add(cls) 

4623 return cls 

4624 

4625 if issubclass(cls, GenericFunction): 4625 ↛ 4633line 4625 didn't jump to line 4633 because the condition on line 4625 was always true

4626 assert hasattr(cls, "_function_") 

4627 assert hasattr(cls, "invoke") 

4628 if any(map(lambda x: x._function_ == cls._function_, __registered_functions__)): 4628 ↛ 4629line 4628 didn't jump to line 4629 because the condition on line 4628 was never true

4629 raise AlreadyRegisteredException(cls._function_) 

4630 __registered_functions__.add(cls) 

4631 return cls 

4632 

4633 raise TypeError(f"`{cls.__class__}` is an illegal class for `register`") 

4634 

4635 

4636class GenericCommand(gdb.Command): 

4637 """This is an abstract class for invoking commands, should not be instantiated.""" 

4638 

4639 _cmdline_: str 

4640 _syntax_: str 

4641 _example_: Union[str, List[str]] = "" 

4642 _aliases_: List[str] = [] 

4643 

4644 def __init_subclass__(cls, **kwargs): 

4645 super().__init_subclass__(**kwargs) 

4646 attributes = ("_cmdline_", "_syntax_", ) 

4647 if not all(map(lambda x: hasattr(cls, x), attributes)): 4647 ↛ 4648line 4647 didn't jump to line 4648 because the condition on line 4647 was never true

4648 raise NotImplementedError 

4649 

4650 def __init__(self, *args: Any, **kwargs: Any) -> None: 

4651 self.pre_load() 

4652 syntax = Color.yellowify("\nSyntax: ") + self._syntax_ 

4653 example = Color.yellowify("\nExamples: \n\t") 

4654 if isinstance(self._example_, list): 

4655 example += "\n\t".join(self._example_) 

4656 elif isinstance(self._example_, str): 4656 ↛ 4658line 4656 didn't jump to line 4658 because the condition on line 4656 was always true

4657 example += self._example_ 

4658 self.__doc__ = (self.__doc__ or "").replace(" "*4, "") + syntax + example 

4659 self.repeat = False 

4660 self.repeat_count = 0 

4661 self.__last_command = None 

4662 command_type = kwargs.setdefault("command", gdb.COMMAND_USER) 

4663 complete_type = kwargs.setdefault("complete", -1) # -1=allow user-defined `complete()` 

4664 prefix = kwargs.setdefault("prefix", False) 

4665 super().__init__(name=self._cmdline_, command_class=command_type, 

4666 completer_class=complete_type, prefix=prefix) 

4667 self.post_load() 

4668 return 

4669 

4670 def invoke(self, args: str, from_tty: bool) -> None: 

4671 try: 

4672 argv = gdb.string_to_argv(args) 

4673 self.__set_repeat_count(argv, from_tty) 

4674 bufferize(self.do_invoke)(argv) 

4675 except Exception as e: 

4676 # Note: since we are intercepting cleaning exceptions here, commands preferably should avoid 

4677 # catching generic Exception, but rather specific ones. This is allows a much cleaner use. 

4678 if is_debug(): 4678 ↛ 4683line 4678 didn't jump to line 4683 because the condition on line 4678 was always true

4679 show_last_exception() 

4680 if gef.config["gef.propagate_debug_exception"] is True: 

4681 raise 

4682 else: 

4683 err(f"Command '{self._cmdline_}' failed to execute properly, reason: {e}") 

4684 return 

4685 

4686 def usage(self) -> None: 

4687 err(f"Syntax\n{self._syntax_}") 

4688 return 

4689 

4690 def do_invoke(self, argv: List[str]) -> None: 

4691 raise NotImplementedError 

4692 

4693 def pre_load(self) -> None: 

4694 return 

4695 

4696 def post_load(self) -> None: 

4697 return 

4698 

4699 def __get_setting_name(self, name: str) -> str: 

4700 clsname = self.__class__._cmdline_.replace(" ", "-") 

4701 return f"{clsname}.{name}" 

4702 

4703 def __iter__(self) -> Generator[str, None, None]: 

4704 for key in gef.config.keys(): 

4705 if key.startswith(self._cmdline_): 

4706 yield key.replace(f"{self._cmdline_}.", "", 1) 

4707 

4708 @property 

4709 def settings(self) -> List[str]: 

4710 """Return the list of settings for this command.""" 

4711 return list(iter(self)) 

4712 

4713 @deprecated(f"Use `self[setting_name]` instead") 

4714 def get_setting(self, name: str) -> Any: 

4715 return self.__getitem__(name) 

4716 

4717 def __getitem__(self, name: str) -> Any: 

4718 key = self.__get_setting_name(name) 

4719 return gef.config[key] 

4720 

4721 @deprecated(f"Use `setting_name in self` instead") 

4722 def has_setting(self, name: str) -> bool: 

4723 return self.__contains__(name) 

4724 

4725 def __contains__(self, name: str) -> bool: 

4726 return self.__get_setting_name(name) in gef.config 

4727 

4728 @deprecated(f"Use `self[setting_name] = value` instead") 

4729 def add_setting(self, name: str, value: Tuple[Any, type, str], description: str = "") -> None: 

4730 return self.__setitem__(name, (value, description)) 

4731 

4732 def __setitem__(self, name: str, value: Union["GefSetting", Tuple[Any, str]]) -> None: 

4733 # make sure settings are always associated to the root command (which derives from GenericCommand) 

4734 if "GenericCommand" not in [x.__name__ for x in self.__class__.__bases__]: 

4735 return 

4736 key = self.__get_setting_name(name) 

4737 if key in gef.config: 

4738 # If the setting already exists, update the entry 

4739 setting = gef.config.raw_entry(key) 

4740 setting.value = value 

4741 return 

4742 

4743 # otherwise create it 

4744 if isinstance(value, GefSetting): 4744 ↛ 4745line 4744 didn't jump to line 4745 because the condition on line 4744 was never true

4745 gef.config[key] = value 

4746 else: 

4747 if len(value) == 1: 4747 ↛ 4748line 4747 didn't jump to line 4748 because the condition on line 4747 was never true

4748 gef.config[key] = GefSetting(value[0]) 

4749 elif len(value) == 2: 4749 ↛ 4751line 4749 didn't jump to line 4751 because the condition on line 4749 was always true

4750 gef.config[key] = GefSetting(value[0], description=value[1]) 

4751 return 

4752 

4753 @deprecated(f"Use `del self[setting_name]` instead") 

4754 def del_setting(self, name: str) -> None: 

4755 return self.__delitem__(name) 

4756 

4757 def __delitem__(self, name: str) -> None: 

4758 del gef.config[self.__get_setting_name(name)] 

4759 return 

4760 

4761 def __set_repeat_count(self, argv: List[str], from_tty: bool) -> None: 

4762 if not from_tty: 4762 ↛ 4767line 4762 didn't jump to line 4767 because the condition on line 4762 was always true

4763 self.repeat = False 

4764 self.repeat_count = 0 

4765 return 

4766 

4767 command = (gdb.execute("show commands", to_string=True) or "").strip().split("\n")[-1] 

4768 self.repeat = self.__last_command == command 

4769 self.repeat_count = self.repeat_count + 1 if self.repeat else 0 

4770 self.__last_command = command 

4771 return 

4772 

4773 

4774@register 

4775class ArchCommand(GenericCommand): 

4776 """Manage the current loaded architecture.""" 

4777 

4778 _cmdline_ = "arch" 

4779 _syntax_ = f"{_cmdline_} (list|get|set) ..." 

4780 _example_ = f"{_cmdline_} set X86" 

4781 

4782 def __init__(self) -> None: 

4783 super().__init__(prefix=True) 

4784 return 

4785 

4786 def do_invoke(self, argv: List[str]) -> None: 

4787 if not argv: 

4788 self.usage() 

4789 return 

4790 

4791@register 

4792class ArchGetCommand(GenericCommand): 

4793 """Get the current loaded architecture.""" 

4794 

4795 _cmdline_ = "arch get" 

4796 _syntax_ = f"{_cmdline_}" 

4797 _example_ = f"{_cmdline_}" 

4798 

4799 def do_invoke(self, args: List[str]) -> None: 

4800 gef_print(f"{Color.greenify('Arch')}: {gef.arch}") 

4801 gef_print(f"{Color.greenify('Reason')}: {gef.arch_reason}") 

4802 

4803 

4804@register 

4805class ArchSetCommand(GenericCommand): 

4806 """Set the current loaded architecture.""" 

4807 

4808 _cmdline_ = "arch set" 

4809 _syntax_ = f"{_cmdline_} <arch>" 

4810 _example_ = f"{_cmdline_} X86" 

4811 

4812 def do_invoke(self, args: List[str]) -> None: 

4813 reset_architecture(args[0].lower() if args else None) 

4814 

4815 def complete(self, text: str, word: str) -> List[str]: 

4816 return sorted(x for x in __registered_architectures__.keys() if 

4817 isinstance(x, str) and x.lower().startswith(text.lower().strip())) 

4818 

4819@register 

4820class ArchListCommand(GenericCommand): 

4821 """List the available architectures.""" 

4822 

4823 _cmdline_ = "arch list" 

4824 _syntax_ = f"{_cmdline_}" 

4825 _example_ = f"{_cmdline_}" 

4826 

4827 def do_invoke(self, args: List[str]) -> None: 

4828 gef_print(Color.greenify("Available architectures:")) 

4829 for arch in sorted(set(__registered_architectures__.values()), key=lambda x: x.arch): 

4830 if arch is GenericArchitecture: 

4831 continue 

4832 

4833 gef_print(' ' + Color.yellowify(str(arch()))) 

4834 for alias in arch.aliases: 

4835 if isinstance(alias, str): 

4836 gef_print(f" {alias}") 

4837 

4838 

4839@register 

4840class VersionCommand(GenericCommand): 

4841 """Display GEF version info.""" 

4842 

4843 _cmdline_ = "version" 

4844 _syntax_ = f"{_cmdline_}" 

4845 _example_ = f"{_cmdline_}" 

4846 

4847 def do_invoke(self, argv: List[str]) -> None: 

4848 gef_fpath = pathlib.Path(inspect.stack()[0][1]).expanduser().absolute() 

4849 gef_dir = gef_fpath.parent 

4850 with gef_fpath.open("rb") as f: 

4851 gef_hash = hashlib.sha256(f.read()).hexdigest() 

4852 

4853 if os.access(f"{gef_dir}/.git", os.X_OK): 4853 ↛ 4858line 4853 didn't jump to line 4858 because the condition on line 4853 was always true

4854 ver = subprocess.check_output("git log --format='%H' -n 1 HEAD", cwd=gef_dir, shell=True).decode("utf8").strip() 

4855 extra = "dirty" if len(subprocess.check_output("git ls-files -m", cwd=gef_dir, shell=True).decode("utf8").strip()) else "clean" 

4856 gef_print(f"GEF: rev:{ver} (Git - {extra})") 

4857 else: 

4858 gef_blob_hash = subprocess.check_output(f"git hash-object {gef_fpath}", shell=True).decode().strip() 

4859 gef_print("GEF: (Standalone)") 

4860 gef_print(f"Blob Hash({gef_fpath}): {gef_blob_hash}") 

4861 gef_print(f"SHA256({gef_fpath}): {gef_hash}") 

4862 gef_print(f"GDB: {gdb.VERSION}") 

4863 py_ver = f"{sys.version_info.major:d}.{sys.version_info.minor:d}" 

4864 gef_print(f"GDB-Python: {py_ver}") 

4865 

4866 if "full" in argv: 

4867 gef_print(f"Loaded commands: {', '.join(gef.gdb.loaded_command_names)}") 

4868 return 

4869 

4870 

4871@register 

4872class PrintFormatCommand(GenericCommand): 

4873 """Print bytes format in commonly used formats, such as literals in high level languages.""" 

4874 

4875 valid_formats = ("py", "c", "js", "asm", "hex", "bytearray") 

4876 valid_bitness = (8, 16, 32, 64) 

4877 

4878 _cmdline_ = "print-format" 

4879 _aliases_ = ["pf",] 

4880 _syntax_ = (f"{_cmdline_} [--lang LANG] [--bitlen SIZE] [(--length,-l) LENGTH] [--clip] LOCATION" 

4881 f"\t--lang LANG specifies the output format for programming language (available: {valid_formats!s}, default 'py')." 

4882 f"\t--bitlen SIZE specifies size of bit (possible values: {valid_bitness!s}, default is 8)." 

4883 "\t--length LENGTH specifies length of array (default is 256)." 

4884 "\t--clip The output data will be copied to clipboard" 

4885 "\tLOCATION specifies where the address of bytes is stored.") 

4886 _example_ = f"{_cmdline_} --lang py -l 16 $rsp" 

4887 

4888 def __init__(self) -> None: 

4889 super().__init__(complete=gdb.COMPLETE_LOCATION) 

4890 self["max_size_preview"] = (10, "max size preview of bytes") 

4891 return 

4892 

4893 @property 

4894 def format_matrix(self) -> Dict[int, Tuple[str, str, str]]: 

4895 # `gef.arch.endianness` is a runtime property, should not be defined as a class property 

4896 return { 

4897 8: (f"{gef.arch.endianness}B", "char", "db"), 

4898 16: (f"{gef.arch.endianness}H", "short", "dw"), 

4899 32: (f"{gef.arch.endianness}I", "int", "dd"), 

4900 64: (f"{gef.arch.endianness}Q", "long long", "dq"), 

4901 } 

4902 

4903 @only_if_gdb_running 

4904 @parse_arguments({"location": "$pc", }, {("--length", "-l"): 256, "--bitlen": 0, "--lang": "py", "--clip": False,}) 

4905 def do_invoke(self, _: List[str], **kwargs: Any) -> None: 

4906 """Default value for print-format command.""" 

4907 args: argparse.Namespace = kwargs["arguments"] 

4908 args.bitlen = args.bitlen or gef.arch.ptrsize * 2 

4909 

4910 valid_bitlens = self.format_matrix.keys() 

4911 if args.bitlen not in valid_bitlens: 4911 ↛ 4912line 4911 didn't jump to line 4912 because the condition on line 4911 was never true

4912 err(f"Size of bit must be in: {valid_bitlens!s}") 

4913 return 

4914 

4915 if args.lang not in self.valid_formats: 

4916 err(f"Language must be in: {self.valid_formats!s}") 

4917 return 

4918 

4919 start_addr = parse_address(args.location) 

4920 size = int(args.bitlen / 8) 

4921 end_addr = start_addr + args.length * size 

4922 fmt = self.format_matrix[args.bitlen][0] 

4923 data = [] 

4924 

4925 if args.lang != "bytearray": 

4926 for addr in range(start_addr, end_addr, size): 

4927 value = struct.unpack(fmt, gef.memory.read(addr, size))[0] 

4928 data += [value] 

4929 sdata = ", ".join(map(hex, data)) 

4930 else: 

4931 sdata = "" 

4932 

4933 if args.lang == "bytearray": 

4934 data = gef.memory.read(start_addr, args.length) 

4935 preview = str(data[0:self["max_size_preview"]]) 

4936 out = f"Saved data {preview}... in '{gef_convenience(data)}'" 

4937 elif args.lang == "py": 

4938 out = f"buf = [{sdata}]" 

4939 elif args.lang == "c": 4939 ↛ 4940line 4939 didn't jump to line 4940 because the condition on line 4939 was never true

4940 c_type = self.format_matrix[args.bitlen][1] 

4941 out = f"unsigned {c_type} buf[{args.length}] = {{{sdata}}};" 

4942 elif args.lang == "js": 

4943 out = f"var buf = [{sdata}]" 

4944 elif args.lang == "asm": 4944 ↛ 4945line 4944 didn't jump to line 4945 because the condition on line 4944 was never true

4945 asm_type = self.format_matrix[args.bitlen][2] 

4946 out = f"buf {asm_type} {sdata}" 

4947 elif args.lang == "hex": 4947 ↛ 4950line 4947 didn't jump to line 4950 because the condition on line 4947 was always true

4948 out = gef.memory.read(start_addr, end_addr-start_addr).hex() 

4949 else: 

4950 raise ValueError(f"Invalid format: {args.lang}") 

4951 

4952 if args.clip: 4952 ↛ 4953line 4952 didn't jump to line 4953 because the condition on line 4952 was never true

4953 if copy_to_clipboard(gef_pybytes(out)): 

4954 info("Copied to clipboard") 

4955 else: 

4956 warn("There's a problem while copying") 

4957 

4958 gef_print(out) 

4959 return 

4960 

4961 

4962@register 

4963class PieCommand(GenericCommand): 

4964 """PIE breakpoint support.""" 

4965 

4966 _cmdline_ = "pie" 

4967 _syntax_ = f"{_cmdline_} (breakpoint|info|delete|run|attach|remote)" 

4968 

4969 def __init__(self) -> None: 

4970 super().__init__(prefix=True) 

4971 return 

4972 

4973 def do_invoke(self, argv: List[str]) -> None: 

4974 if not argv: 4974 ↛ 4976line 4974 didn't jump to line 4976 because the condition on line 4974 was always true

4975 self.usage() 

4976 return 

4977 

4978 

4979@register 

4980class PieBreakpointCommand(GenericCommand): 

4981 """Set a PIE breakpoint at an offset from the target binaries base address.""" 

4982 

4983 _cmdline_ = "pie breakpoint" 

4984 _syntax_ = f"{_cmdline_} OFFSET" 

4985 

4986 @parse_arguments({"offset": ""}, {}) 

4987 def do_invoke(self, _: List[str], **kwargs: Any) -> None: 

4988 args : argparse.Namespace = kwargs["arguments"] 

4989 if not args.offset: 4989 ↛ 4990line 4989 didn't jump to line 4990 because the condition on line 4989 was never true

4990 self.usage() 

4991 return 

4992 

4993 addr = parse_address(args.offset) 

4994 self.set_pie_breakpoint(lambda base: f"b *{base + addr}", addr) 

4995 

4996 # When the process is already on, set real breakpoints immediately 

4997 if is_alive(): 4997 ↛ 4998line 4997 didn't jump to line 4998 because the condition on line 4997 was never true

4998 vmmap = gef.memory.maps 

4999 base_address = [x.page_start for x in vmmap if x.path == get_filepath()][0] 

5000 for bp_ins in gef.session.pie_breakpoints.values(): 

5001 bp_ins.instantiate(base_address) 

5002 return 

5003 

5004 @staticmethod 

5005 def set_pie_breakpoint(set_func: Callable[[int], str], addr: int) -> None: 

5006 gef.session.pie_breakpoints[gef.session.pie_counter] = PieVirtualBreakpoint(set_func, gef.session.pie_counter, addr) 

5007 gef.session.pie_counter += 1 

5008 return 

5009 

5010 

5011@register 

5012class PieInfoCommand(GenericCommand): 

5013 """Display breakpoint info.""" 

5014 

5015 _cmdline_ = "pie info" 

5016 _syntax_ = f"{_cmdline_} BREAKPOINT" 

5017 

5018 @parse_arguments({"breakpoints": [-1,]}, {}) 

5019 def do_invoke(self, _: List[str], **kwargs: Any) -> None: 

5020 args : argparse.Namespace = kwargs["arguments"] 

5021 if args.breakpoints[0] == -1: 

5022 # No breakpoint info needed 

5023 bps = gef.session.pie_breakpoints.values() 

5024 else: 

5025 bps = [gef.session.pie_breakpoints[x] 

5026 for x in args.breakpoints 

5027 if x in gef.session.pie_breakpoints] 

5028 

5029 lines = [f"{'VNum':6s} {'Num':6s} {'Addr':18s}"] 

5030 lines += [ 

5031 f"{x.vbp_num:6d} {str(x.bp_num) if x.bp_num else 'N/A':6s} {x.addr:18s}" for x in bps 

5032 ] 

5033 gef_print("\n".join(lines)) 

5034 return 

5035 

5036 

5037@register 

5038class PieDeleteCommand(GenericCommand): 

5039 """Delete a PIE breakpoint.""" 

5040 

5041 _cmdline_ = "pie delete" 

5042 _syntax_ = f"{_cmdline_} [BREAKPOINT]" 

5043 

5044 @parse_arguments({"breakpoints": [-1,]}, {}) 

5045 def do_invoke(self, _: List[str], **kwargs: Any) -> None: 

5046 global gef 

5047 args : argparse.Namespace = kwargs["arguments"] 

5048 if args.breakpoints[0] == -1: 5048 ↛ 5050line 5048 didn't jump to line 5050 because the condition on line 5048 was never true

5049 # no arg, delete all 

5050 to_delete = list(gef.session.pie_breakpoints.values()) 

5051 self.delete_bp(to_delete) 

5052 else: 

5053 self.delete_bp([gef.session.pie_breakpoints[x] 

5054 for x in args.breakpoints 

5055 if x in gef.session.pie_breakpoints]) 

5056 return 

5057 

5058 

5059 @staticmethod 

5060 def delete_bp(breakpoints: List[PieVirtualBreakpoint]) -> None: 

5061 global gef 

5062 for bp in breakpoints: 

5063 # delete current real breakpoints if exists 

5064 if bp.bp_num: 5064 ↛ 5065line 5064 didn't jump to line 5065 because the condition on line 5064 was never true

5065 gdb.execute(f"delete {bp.bp_num}") 

5066 # delete virtual breakpoints 

5067 del gef.session.pie_breakpoints[bp.vbp_num] 

5068 return 

5069 

5070 

5071@register 

5072class PieRunCommand(GenericCommand): 

5073 """Run process with PIE breakpoint support.""" 

5074 

5075 _cmdline_ = "pie run" 

5076 _syntax_ = _cmdline_ 

5077 

5078 def do_invoke(self, argv: List[str]) -> None: 

5079 global gef 

5080 fpath = get_filepath() 

5081 if not fpath: 5081 ↛ 5082line 5081 didn't jump to line 5082 because the condition on line 5081 was never true

5082 warn("No executable to debug, use `file` to load a binary") 

5083 return 

5084 

5085 if not os.access(fpath, os.X_OK): 5085 ↛ 5086line 5085 didn't jump to line 5086 because the condition on line 5085 was never true

5086 warn(f"The file '{fpath}' is not executable.") 

5087 return 

5088 

5089 if is_alive(): 5089 ↛ 5090line 5089 didn't jump to line 5090 because the condition on line 5089 was never true

5090 warn("gdb is already running. Restart process.") 

5091 

5092 # get base address 

5093 gdb.execute("set stop-on-solib-events 1") 

5094 hide_context() 

5095 gdb.execute(f"run {' '.join(argv)}") 

5096 unhide_context() 

5097 gdb.execute("set stop-on-solib-events 0") 

5098 vmmap = gef.memory.maps 

5099 base_address = [x.page_start for x in vmmap if x.path == get_filepath()][0] 

5100 info(f"base address {hex(base_address)}") 

5101 

5102 # modify all breakpoints 

5103 for bp_ins in gef.session.pie_breakpoints.values(): 

5104 bp_ins.instantiate(base_address) 

5105 

5106 try: 

5107 gdb.execute("continue") 

5108 except gdb.error as e: 

5109 err(str(e)) 

5110 gdb.execute("kill") 

5111 return 

5112 

5113 

5114@register 

5115class PieAttachCommand(GenericCommand): 

5116 """Do attach with PIE breakpoint support.""" 

5117 

5118 _cmdline_ = "pie attach" 

5119 _syntax_ = f"{_cmdline_} PID" 

5120 

5121 def do_invoke(self, argv: List[str]) -> None: 

5122 try: 

5123 gdb.execute(f"attach {' '.join(argv)}", to_string=True) 

5124 except gdb.error as e: 

5125 err(str(e)) 

5126 return 

5127 # after attach, we are stopped so that we can 

5128 # get base address to modify our breakpoint 

5129 vmmap = gef.memory.maps 

5130 base_address = [x.page_start for x in vmmap if x.path == get_filepath()][0] 

5131 

5132 for bp_ins in gef.session.pie_breakpoints.values(): 

5133 bp_ins.instantiate(base_address) 

5134 gdb.execute("context") 

5135 return 

5136 

5137 

5138@register 

5139class PieRemoteCommand(GenericCommand): 

5140 """Attach to a remote connection with PIE breakpoint support.""" 

5141 

5142 _cmdline_ = "pie remote" 

5143 _syntax_ = f"{_cmdline_} REMOTE" 

5144 

5145 def do_invoke(self, argv: List[str]) -> None: 

5146 try: 

5147 gdb.execute(f"gef-remote {' '.join(argv)}") 

5148 except gdb.error as e: 

5149 err(str(e)) 

5150 return 

5151 # after remote attach, we are stopped so that we can 

5152 # get base address to modify our breakpoint 

5153 vmmap = gef.memory.maps 

5154 base_address = [x.page_start for x in vmmap if x.realpath == get_filepath()][0] 

5155 

5156 for bp_ins in gef.session.pie_breakpoints.values(): 

5157 bp_ins.instantiate(base_address) 

5158 gdb.execute("context") 

5159 return 

5160 

5161 

5162@register 

5163class SmartEvalCommand(GenericCommand): 

5164 """SmartEval: Smart eval (vague approach to mimic WinDBG `?`).""" 

5165 

5166 _cmdline_ = "$" 

5167 _syntax_ = f"{_cmdline_} EXPR\n{_cmdline_} ADDRESS1 ADDRESS2" 

5168 _example_ = (f"\n{_cmdline_} $pc+1" 

5169 f"\n{_cmdline_} 0x00007ffff7a10000 0x00007ffff7bce000") 

5170 

5171 def do_invoke(self, argv: List[str]) -> None: 

5172 argc = len(argv) 

5173 if argc == 1: 

5174 self.evaluate(argv) 

5175 return 

5176 

5177 if argc == 2: 5177 ↛ 5179line 5177 didn't jump to line 5179 because the condition on line 5177 was always true

5178 self.distance(argv) 

5179 return 

5180 

5181 def evaluate(self, expr: List[str]) -> None: 

5182 def show_as_int(i: int) -> None: 

5183 off = gef.arch.ptrsize*8 

5184 def comp2_x(x: Any) -> str: return f"{(x + (1 << off)) % (1 << off):x}" 

5185 def comp2_b(x: Any) -> str: return f"{(x + (1 << off)) % (1 << off):b}" 

5186 

5187 try: 

5188 s_i = comp2_x(res) 

5189 s_i = s_i.rjust(len(s_i)+1, "0") if len(s_i)%2 else s_i 

5190 gef_print(f"{i:d}") 

5191 gef_print("0x" + comp2_x(res)) 

5192 gef_print("0b" + comp2_b(res)) 

5193 gef_print(f"{binascii.unhexlify(s_i)}") 

5194 gef_print(f"{binascii.unhexlify(s_i)[::-1]}") 

5195 except: 

5196 pass 

5197 return 

5198 

5199 parsed_expr = [] 

5200 for xp in expr: 

5201 try: 

5202 xp = gdb.parse_and_eval(xp) 

5203 xp = int(xp) 

5204 parsed_expr.append(f"{xp:d}") 

5205 except gdb.error: 

5206 parsed_expr.append(str(xp)) 

5207 

5208 try: 

5209 res = eval(" ".join(parsed_expr)) 

5210 if isinstance(res, int): 5210 ↛ 5213line 5210 didn't jump to line 5213 because the condition on line 5210 was always true

5211 show_as_int(res) 

5212 else: 

5213 gef_print(f"{res}") 

5214 except SyntaxError: 

5215 gef_print(" ".join(parsed_expr)) 

5216 return 

5217 

5218 def distance(self, args: List[str]) -> None: 

5219 try: 

5220 x = int(args[0], 16) if is_hex(args[0]) else int(args[0]) 

5221 y = int(args[1], 16) if is_hex(args[1]) else int(args[1]) 

5222 gef_print(f"{abs(x - y)}") 

5223 except ValueError: 

5224 warn(f"Distance requires 2 numbers: {self._cmdline_} 0 0xffff") 

5225 return 

5226 

5227 

5228@register 

5229class CanaryCommand(GenericCommand): 

5230 """Shows the canary value of the current process.""" 

5231 

5232 _cmdline_ = "canary" 

5233 _syntax_ = _cmdline_ 

5234 

5235 @only_if_gdb_running 

5236 def do_invoke(self, argv: List[str]) -> None: 

5237 self.dont_repeat() 

5238 

5239 fname = get_filepath() 

5240 assert fname 

5241 has_canary = Elf(fname).checksec["Canary"] 

5242 if not has_canary: 5242 ↛ 5243line 5242 didn't jump to line 5243 because the condition on line 5242 was never true

5243 warn("This binary was not compiled with SSP.") 

5244 return 

5245 

5246 res = gef.session.canary 

5247 if not res: 5247 ↛ 5248line 5247 didn't jump to line 5248 because the condition on line 5247 was never true

5248 err("Failed to get the canary") 

5249 return 

5250 

5251 canary, location = res 

5252 info(f"The canary of process {gef.session.pid} is at {location:#x}, value is {canary:#x}") 

5253 return 

5254 

5255 

5256@register 

5257class ProcessStatusCommand(GenericCommand): 

5258 """Extends the info given by GDB `info proc`, by giving an exhaustive description of the 

5259 process status (file descriptors, ancestor, descendants, etc.).""" 

5260 

5261 _cmdline_ = "process-status" 

5262 _syntax_ = _cmdline_ 

5263 _aliases_ = ["status", ] 

5264 

5265 def __init__(self) -> None: 

5266 super().__init__(complete=gdb.COMPLETE_NONE) 

5267 return 

5268 

5269 @only_if_gdb_running 

5270 @only_if_gdb_target_local 

5271 def do_invoke(self, argv: List[str]) -> None: 

5272 self.show_info_proc() 

5273 self.show_ancestor() 

5274 self.show_descendants() 

5275 self.show_fds() 

5276 self.show_connections() 

5277 return 

5278 

5279 def get_state_of(self, pid: int) -> Dict[str, str]: 

5280 res = {} 

5281 with open(f"/proc/{pid}/status", "r") as f: 

5282 file = f.readlines() 

5283 for line in file: 

5284 key, value = line.split(":", 1) 

5285 res[key.strip()] = value.strip() 

5286 return res 

5287 

5288 def get_cmdline_of(self, pid: int) -> str: 

5289 with open(f"/proc/{pid}/cmdline", "r") as f: 

5290 return f.read().replace("\x00", "\x20").strip() 

5291 

5292 def get_process_path_of(self, pid: int) -> str: 

5293 return os.readlink(f"/proc/{pid}/exe") 

5294 

5295 def get_children_pids(self, pid: int) -> List[int]: 

5296 cmd = [gef.session.constants["ps"], "-o", "pid", "--ppid", f"{pid}", "--noheaders"] 

5297 try: 

5298 return [int(x) for x in gef_execute_external(cmd, as_list=True)] 5298 ↛ exit,   5298 ↛ exit2 missed branches: 1) line 5298 didn't run the list comprehension on line 5298, 2) line 5298 didn't return from function 'get_children_pids' because the return on line 5298 wasn't executed

5299 except Exception: 

5300 return [] 

5301 

5302 def show_info_proc(self) -> None: 

5303 info("Process Information") 

5304 pid = gef.session.pid 

5305 cmdline = self.get_cmdline_of(pid) 

5306 gef_print(f"\tPID {RIGHT_ARROW} {pid}", 

5307 f"\tExecutable {RIGHT_ARROW} {self.get_process_path_of(pid)}", 

5308 f"\tCommand line {RIGHT_ARROW} '{cmdline}'", sep="\n") 

5309 return 

5310 

5311 def show_ancestor(self) -> None: 

5312 info("Parent Process Information") 

5313 ppid = int(self.get_state_of(gef.session.pid)["PPid"]) 

5314 state = self.get_state_of(ppid) 

5315 cmdline = self.get_cmdline_of(ppid) 

5316 gef_print(f"\tParent PID {RIGHT_ARROW} {state['Pid']}", 

5317 f"\tCommand line {RIGHT_ARROW} '{cmdline}'", sep="\n") 

5318 return 

5319 

5320 def show_descendants(self) -> None: 

5321 info("Children Process Information") 

5322 children = self.get_children_pids(gef.session.pid) 

5323 if not children: 5323 ↛ 5327line 5323 didn't jump to line 5327 because the condition on line 5323 was always true

5324 gef_print("\tNo child process") 

5325 return 

5326 

5327 for child_pid in children: 

5328 state = self.get_state_of(child_pid) 

5329 pid = int(state["Pid"]) 

5330 gef_print(f"\tPID {RIGHT_ARROW} {pid} (Name: '{self.get_process_path_of(pid)}'," 

5331 f" CmdLine: '{self.get_cmdline_of(pid)}')") 

5332 return 

5333 

5334 def show_fds(self) -> None: 

5335 pid = gef.session.pid 

5336 path = f"/proc/{pid:d}/fd" 

5337 

5338 info("File Descriptors:") 

5339 items = os.listdir(path) 

5340 if not items: 5340 ↛ 5341line 5340 didn't jump to line 5341 because the condition on line 5340 was never true

5341 gef_print("\tNo FD opened") 

5342 return 

5343 

5344 for fname in items: 

5345 fullpath = os.path.join(path, fname) 

5346 if os.path.islink(fullpath): 5346 ↛ 5344line 5346 didn't jump to line 5344 because the condition on line 5346 was always true

5347 gef_print(f"\t{fullpath} {RIGHT_ARROW} {os.readlink(fullpath)}") 

5348 return 

5349 

5350 def list_sockets(self, pid: int) -> List[int]: 

5351 sockets = [] 

5352 path = f"/proc/{pid:d}/fd" 

5353 items = os.listdir(path) 

5354 for fname in items: 

5355 fullpath = os.path.join(path, fname) 

5356 if os.path.islink(fullpath) and os.readlink(fullpath).startswith("socket:"): 5356 ↛ 5357line 5356 didn't jump to line 5357 because the condition on line 5356 was never true

5357 p = os.readlink(fullpath).replace("socket:", "")[1:-1] 

5358 sockets.append(int(p)) 

5359 return sockets 

5360 

5361 def parse_ip_port(self, addr: str) -> Tuple[str, int]: 

5362 ip, port = addr.split(":") 

5363 return socket.inet_ntoa(struct.pack("<I", int(ip, 16))), int(port, 16) 

5364 

5365 def show_connections(self) -> None: 

5366 # https://github.com/torvalds/linux/blob/v4.7/include/net/tcp_states.h#L16 

5367 tcp_states_str = { 

5368 0x01: "TCP_ESTABLISHED", 

5369 0x02: "TCP_SYN_SENT", 

5370 0x03: "TCP_SYN_RECV", 

5371 0x04: "TCP_FIN_WAIT1", 

5372 0x05: "TCP_FIN_WAIT2", 

5373 0x06: "TCP_TIME_WAIT", 

5374 0x07: "TCP_CLOSE", 

5375 0x08: "TCP_CLOSE_WAIT", 

5376 0x09: "TCP_LAST_ACK", 

5377 0x0A: "TCP_LISTEN", 

5378 0x0B: "TCP_CLOSING", 

5379 0x0C: "TCP_NEW_SYN_RECV", 

5380 } 

5381 

5382 udp_states_str = { 

5383 0x07: "UDP_LISTEN", 

5384 } 

5385 

5386 info("Network Connections") 

5387 pid = gef.session.pid 

5388 sockets = self.list_sockets(pid) 

5389 if not sockets: 5389 ↛ 5393line 5389 didn't jump to line 5393 because the condition on line 5389 was always true

5390 gef_print("\tNo open connections") 

5391 return 

5392 

5393 entries = dict() 

5394 with open(f"/proc/{pid:d}/net/tcp", "r") as tcp: 

5395 entries["TCP"] = [x.split() for x in tcp.readlines()[1:]] 

5396 with open(f"/proc/{pid:d}/net/udp", "r") as udp: 

5397 entries["UDP"] = [x.split() for x in udp.readlines()[1:]] 

5398 

5399 for proto in entries: 

5400 for entry in entries[proto]: 

5401 local, remote, state = entry[1:4] 

5402 inode = int(entry[9]) 

5403 if inode in sockets: 

5404 local = self.parse_ip_port(local) 

5405 remote = self.parse_ip_port(remote) 

5406 state = int(state, 16) 

5407 state_str = tcp_states_str[state] if proto == "TCP" else udp_states_str[state] 

5408 

5409 gef_print(f"\t{local[0]}:{local[1]} {RIGHT_ARROW} {remote[0]}:{remote[1]} ({state_str})") 

5410 return 

5411 

5412 

5413@register 

5414class GefThemeCommand(GenericCommand): 

5415 """Customize GEF appearance.""" 

5416 

5417 _cmdline_ = "theme" 

5418 _syntax_ = f"{_cmdline_} [KEY [VALUE]]" 

5419 

5420 def __init__(self) -> None: 

5421 super().__init__(self._cmdline_) 

5422 self["context_title_line"] = ("gray", "Color of the borders in context window") 

5423 self["context_title_message"] = ("cyan", "Color of the title in context window") 

5424 self["default_title_line"] = ("gray", "Default color of borders") 

5425 self["default_title_message"] = ("cyan", "Default color of title") 

5426 self["table_heading"] = ("blue", "Color of the column headings to tables (e.g. vmmap)") 

5427 self["old_context"] = ("gray", "Color to use to show things such as code that is not immediately relevant") 

5428 self["disassemble_current_instruction"] = ("green", "Color to use to highlight the current $pc when disassembling") 

5429 self["dereference_string"] = ("yellow", "Color of dereferenced string") 

5430 self["dereference_code"] = ("gray", "Color of dereferenced code") 

5431 self["dereference_base_address"] = ("cyan", "Color of dereferenced address") 

5432 self["dereference_register_value"] = ("bold blue", "Color of dereferenced register") 

5433 self["registers_register_name"] = ("blue", "Color of the register name in the register window") 

5434 self["registers_value_changed"] = ("bold red", "Color of the changed register in the register window") 

5435 self["address_stack"] = ("pink", "Color to use when a stack address is found") 

5436 self["address_heap"] = ("green", "Color to use when a heap address is found") 

5437 self["address_code"] = ("red", "Color to use when a code address is found") 

5438 self["source_current_line"] = ("green", "Color to use for the current code line in the source window") 

5439 return 

5440 

5441 def do_invoke(self, args: List[str]) -> None: 

5442 self.dont_repeat() 

5443 argc = len(args) 

5444 

5445 if argc == 0: 

5446 for key in self.settings: 

5447 setting = self[key] 

5448 value = Color.colorify(setting, setting) 

5449 gef_print(f"{key:40s}: {value}") 

5450 return 

5451 

5452 setting_name = args[0] 

5453 if not setting_name in self: 

5454 err("Invalid key") 

5455 return 

5456 

5457 if argc == 1: 

5458 value = self[setting_name] 

5459 gef_print(f"{setting_name:40s}: {Color.colorify(value, value)}") 

5460 return 

5461 

5462 colors = [color for color in args[1:] if color in Color.colors] 

5463 self[setting_name] = " ".join(colors) 

5464 return 

5465 

5466 

5467class ExternalStructureManager: 

5468 class Structure: 

5469 def __init__(self, manager: "ExternalStructureManager", mod_path: pathlib.Path, struct_name: str) -> None: 

5470 self.manager = manager 

5471 self.module_path = mod_path 

5472 self.name = struct_name 

5473 self.class_type = self.__get_structure_class() 

5474 # if the symbol points to a class factory method and not a class 

5475 if not hasattr(self.class_type, "_fields_") and callable(self.class_type): 5475 ↛ 5476line 5475 didn't jump to line 5476 because the condition on line 5475 was never true

5476 self.class_type = self.class_type(gef) 

5477 return 

5478 

5479 def __str__(self) -> str: 

5480 return self.name 

5481 

5482 def pprint(self) -> None: 

5483 res: List[str] = [] 

5484 for _name, _type in self.class_type._fields_: # type: ignore 

5485 size = ctypes.sizeof(_type) 

5486 name = Color.colorify(_name, gef.config["pcustom.structure_name"]) 

5487 type = Color.colorify(_type.__name__, gef.config["pcustom.structure_type"]) 

5488 size = Color.colorify(hex(size), gef.config["pcustom.structure_size"]) 

5489 offset = Color.boldify(f"{getattr(self.class_type, _name).offset:04x}") 

5490 res.append(f"{offset} {name:32s} {type:16s} /* size={size} */") 

5491 gef_print("\n".join(res)) 

5492 return 

5493 

5494 def __get_structure_class(self) -> Type[ctypes.Structure]: 

5495 """Returns a tuple of (class, instance) if modname!classname exists""" 

5496 fpath = self.module_path 

5497 spec = importlib.util.spec_from_file_location(fpath.stem, fpath) 

5498 assert spec and spec.loader, "Failed to determine module specification" 

5499 module = importlib.util.module_from_spec(spec) 

5500 sys.modules[fpath.stem] = module 

5501 spec.loader.exec_module(module) 

5502 _class = getattr(module, self.name) 

5503 return _class 

5504 

5505 def apply_at(self, address: int, max_depth: int, depth: int = 0) -> None: 

5506 """Apply (recursively if possible) the structure format to the given address.""" 

5507 if depth >= max_depth: 5507 ↛ 5508line 5507 didn't jump to line 5508 because the condition on line 5507 was never true

5508 warn("maximum recursion level reached") 

5509 return 

5510 

5511 # read the data at the specified address 

5512 assert isinstance(self.class_type, type) 

5513 _structure = self.class_type() 

5514 _sizeof_structure = ctypes.sizeof(_structure) 

5515 

5516 try: 

5517 data = gef.memory.read(address, _sizeof_structure) 

5518 except gdb.MemoryError: 

5519 err(f"{' ' * depth}Cannot read memory {address:#x}") 

5520 return 

5521 

5522 # deserialize the data 

5523 length = min(len(data), _sizeof_structure) 

5524 ctypes.memmove(ctypes.addressof(_structure), data, length) 

5525 

5526 # pretty print all the fields (and call recursively if possible) 

5527 ptrsize = gef.arch.ptrsize 

5528 unpack = u32 if ptrsize == 4 else u64 

5529 for field in _structure._fields_: 

5530 _name, _type = field 

5531 _value = getattr(_structure, _name) 

5532 _offset = getattr(self.class_type, _name).offset 

5533 

5534 if ((ptrsize == 4 and _type is ctypes.c_uint32) 5534 ↛ 5538line 5534 didn't jump to line 5538 because the condition on line 5534 was never true

5535 or (ptrsize == 8 and _type is ctypes.c_uint64) 

5536 or (ptrsize == ctypes.sizeof(ctypes.c_void_p) and _type is ctypes.c_void_p)): 

5537 # try to dereference pointers 

5538 _value = RIGHT_ARROW.join(dereference_from(_value)) 

5539 

5540 line = f"{' ' * depth}" 

5541 line += f"{address:#x}+{_offset:#04x} {_name} : ".ljust(40) 

5542 line += f"{_value} ({_type.__name__})" 

5543 parsed_value = self.__get_ctypes_value(_structure, _name, _value) 

5544 if parsed_value: 5544 ↛ 5545line 5544 didn't jump to line 5545 because the condition on line 5544 was never true

5545 line += f"{RIGHT_ARROW} {parsed_value}" 

5546 gef_print(line) 

5547 

5548 if issubclass(_type, ctypes.Structure): 5548 ↛ 5549line 5548 didn't jump to line 5549 because the condition on line 5548 was never true

5549 self.apply_at(address + _offset, max_depth, depth + 1) 

5550 elif _type.__name__.startswith("LP_"): 

5551 # Pointer to a structure of a different type 

5552 __sub_type_name = _type.__name__.lstrip("LP_") 

5553 result = self.manager.find(__sub_type_name) 

5554 if result: 5554 ↛ 5529line 5554 didn't jump to line 5529 because the condition on line 5554 was always true

5555 _, __structure = result 

5556 __address = unpack(gef.memory.read(address + _offset, ptrsize)) 

5557 __structure.apply_at(__address, max_depth, depth + 1) 

5558 return 

5559 

5560 def __get_ctypes_value(self, struct, item, value) -> str: 

5561 if not hasattr(struct, "_values_"): return "" 5561 ↛ 5562line 5561 didn't jump to line 5562 because the condition on line 5561 was always true

5562 default = "" 

5563 for name, values in struct._values_: 

5564 if name != item: continue 

5565 if callable(values): 

5566 return values(value) 

5567 try: 

5568 for val, desc in values: 

5569 if value == val: return desc 

5570 if val is None: default = desc 

5571 except Exception as e: 

5572 err(f"Error parsing '{name}': {e}") 

5573 return default 

5574 

5575 class Module(dict): 

5576 def __init__(self, manager: "ExternalStructureManager", path: pathlib.Path) -> None: 

5577 self.manager = manager 

5578 self.path = path 

5579 self.name = path.stem 

5580 self.raw = self.__load() 

5581 

5582 for entry in self: 

5583 structure = ExternalStructureManager.Structure(manager, self.path, entry) 

5584 self[structure.name] = structure 

5585 return 

5586 

5587 def __load(self) -> ModuleType: 

5588 """Load a custom module, and return it.""" 

5589 fpath = self.path 

5590 spec = importlib.util.spec_from_file_location(fpath.stem, fpath) 

5591 assert spec and spec.loader 

5592 module = importlib.util.module_from_spec(spec) 

5593 sys.modules[fpath.stem] = module 

5594 spec.loader.exec_module(module) 

5595 return module 

5596 

5597 def __str__(self) -> str: 

5598 return self.name 

5599 

5600 def __iter__(self) -> Generator[str, None, None]: 

5601 _invalid = {"BigEndianStructure", "LittleEndianStructure", "Structure"} 

5602 for x in dir(self.raw): 

5603 if x in _invalid: continue 

5604 _attr = getattr(self.raw, x) 

5605 

5606 # if it's a ctypes.Structure class, add it 

5607 if inspect.isclass(_attr) and issubclass(_attr, ctypes.Structure): 

5608 yield x 

5609 continue 

5610 

5611 # also accept class factory functions 

5612 if callable(_attr) and _attr.__module__ == self.name and x.endswith("_t"): 5612 ↛ 5613line 5612 didn't jump to line 5613 because the condition on line 5612 was never true

5613 yield x 

5614 continue 

5615 return 

5616 

5617 class Modules(dict): 

5618 def __init__(self, manager: "ExternalStructureManager") -> None: 

5619 self.manager: "ExternalStructureManager" = manager 

5620 self.root: pathlib.Path = manager.path 

5621 

5622 for entry in self.root.iterdir(): 

5623 if not entry.is_file(): continue 

5624 if entry.suffix != ".py": continue 5624 ↛ 5622line 5624 didn't jump to line 5622 because the continue on line 5624 wasn't executed

5625 if entry.name == "__init__.py": continue 5625 ↛ 5622line 5625 didn't jump to line 5622 because the continue on line 5625 wasn't executed

5626 module = ExternalStructureManager.Module(manager, entry) 

5627 self[module.name] = module 

5628 return 

5629 

5630 def __contains__(self, structure_name: str) -> bool: 

5631 """Return True if the structure name is found in any of the modules""" 

5632 for module in self.values(): 

5633 if structure_name in module: 

5634 return True 

5635 return False 

5636 

5637 def __init__(self) -> None: 

5638 self.clear_caches() 

5639 return 

5640 

5641 def clear_caches(self) -> None: 

5642 self._path = None 

5643 self._modules = None 

5644 return 

5645 

5646 @property 

5647 def modules(self) -> "ExternalStructureManager.Modules": 

5648 if not self._modules: 

5649 self._modules = ExternalStructureManager.Modules(self) 

5650 return self._modules 

5651 

5652 @property 

5653 def path(self) -> pathlib.Path: 

5654 if not self._path: 

5655 self._path = gef.config["pcustom.struct_path"].expanduser().absolute() 

5656 return self._path 

5657 

5658 @property 

5659 def structures(self) -> Generator[Tuple["ExternalStructureManager.Module", "ExternalStructureManager.Structure"], None, None]: 

5660 for module in self.modules.values(): 

5661 for structure in module.values(): 

5662 yield module, structure 

5663 return 

5664 

5665 @lru_cache() 

5666 def find(self, structure_name: str) -> Optional[Tuple["ExternalStructureManager.Module", "ExternalStructureManager.Structure"]]: 

5667 """Return the module and structure for the given structure name; `None` if the structure name was not found.""" 

5668 for module in self.modules.values(): 

5669 if structure_name in module: 

5670 return module, module[structure_name] 

5671 return None 

5672 

5673 

5674@register 

5675class PCustomCommand(GenericCommand): 

5676 """Dump user defined structure. 

5677 This command attempts to reproduce WinDBG awesome `dt` command for GDB and allows 

5678 to apply structures (from symbols or custom) directly to an address. 

5679 Custom structures can be defined in pure Python using ctypes, and should be stored 

5680 in a specific directory, whose path must be stored in the `pcustom.struct_path` 

5681 configuration setting.""" 

5682 

5683 _cmdline_ = "pcustom" 

5684 _syntax_ = f"{_cmdline_} [list|edit <StructureName>|show <StructureName>]|<StructureName> 0xADDRESS]" 

5685 

5686 def __init__(self) -> None: 

5687 global gef 

5688 super().__init__(prefix=True) 

5689 self["max_depth"] = (4, "Maximum level of recursion supported") 

5690 self["structure_name"] = ("bold blue", "Color of the structure name") 

5691 self["structure_type"] = ("bold red", "Color of the attribute type") 

5692 self["structure_size"] = ("green", "Color of the attribute size") 

5693 gef.config[f"{self._cmdline_}.struct_path"] = GefSetting( gef.config["gef.tempdir"] / "structs", pathlib.Path, 

5694 "Path to store/load the structure ctypes files", 

5695 hooks={"on_write": [GefSetting.create_folder_tree,]}) 

5696 return 

5697 

5698 @parse_arguments({"type": "", "address": ""}, {}) 

5699 def do_invoke(self, *_: Any, **kwargs: Dict[str, Any]) -> None: 

5700 args = cast(argparse.Namespace, kwargs["arguments"]) 

5701 if not args.type: 5701 ↛ 5702line 5701 didn't jump to line 5702 because the condition on line 5701 was never true

5702 gdb.execute("pcustom list") 

5703 return 

5704 

5705 structname = self.explode_type(args.type)[1] 

5706 

5707 if not args.address: 

5708 gdb.execute(f"pcustom show {structname}") 

5709 return 

5710 

5711 if not is_alive(): 5711 ↛ 5712line 5711 didn't jump to line 5712 because the condition on line 5711 was never true

5712 err("Session is not active") 

5713 return 

5714 

5715 manager = ExternalStructureManager() 

5716 address = parse_address(args.address) 

5717 result = manager.find(structname) 

5718 if not result: 

5719 err(f"No structure named '{structname}' found") 

5720 return 

5721 

5722 structure = result[1] 

5723 structure.apply_at(address, self["max_depth"]) 

5724 return 

5725 

5726 def explode_type(self, arg: str) -> Tuple[str, str]: 

5727 modname, structname = arg.split(":", 1) if ":" in arg else (arg, arg) 

5728 structname = structname.split(".", 1)[0] if "." in structname else structname 

5729 return modname, structname 

5730 

5731 

5732@register 

5733class PCustomListCommand(PCustomCommand): 

5734 """PCustom: list available structures""" 

5735 

5736 _cmdline_ = "pcustom list" 

5737 _syntax_ = f"{_cmdline_}" 

5738 

5739 def __init__(self) -> None: 

5740 super().__init__() 

5741 return 

5742 

5743 def do_invoke(self, _: List) -> None: 

5744 """Dump the list of all the structures and their respective.""" 

5745 manager = ExternalStructureManager() 

5746 info(f"Listing custom structures from '{manager.path}'") 

5747 struct_color = gef.config["pcustom.structure_type"] 

5748 filename_color = gef.config["pcustom.structure_name"] 

5749 for module in manager.modules.values(): 

5750 __modules = ", ".join([Color.colorify(str(structure), struct_color) for structure in module.values()]) 

5751 __filename = Color.colorify(str(module.path), filename_color) 

5752 gef_print(f"{RIGHT_ARROW} {__filename} ({__modules})") 

5753 return 

5754 

5755 

5756@register 

5757class PCustomShowCommand(PCustomCommand): 

5758 """PCustom: show the content of a given structure""" 

5759 

5760 _cmdline_ = "pcustom show" 

5761 _syntax_ = f"{_cmdline_} StructureName" 

5762 _aliases_ = ["pcustom create", "pcustom update"] 

5763 

5764 def __init__(self) -> None: 

5765 super().__init__() 

5766 return 

5767 

5768 def do_invoke(self, argv: List[str]) -> None: 

5769 if len(argv) == 0: 5769 ↛ 5770line 5769 didn't jump to line 5770 because the condition on line 5769 was never true

5770 self.usage() 

5771 return 

5772 

5773 _, structname = self.explode_type(argv[0]) 

5774 manager = ExternalStructureManager() 

5775 result = manager.find(structname) 

5776 if result: 

5777 _, structure = result 

5778 structure.pprint() 

5779 else: 

5780 err(f"No structure named '{structname}' found") 

5781 return 

5782 

5783 

5784@register 

5785class PCustomEditCommand(PCustomCommand): 

5786 """PCustom: edit the content of a given structure""" 

5787 

5788 _cmdline_ = "pcustom edit" 

5789 _syntax_ = f"{_cmdline_} StructureName" 

5790 __aliases__ = ["pcustom create", "pcustom new", "pcustom update"] 

5791 

5792 def __init__(self) -> None: 

5793 super().__init__() 

5794 return 

5795 

5796 def do_invoke(self, argv: List[str]) -> None: 

5797 if len(argv) == 0: 

5798 self.usage() 

5799 return 

5800 

5801 modname, structname = self.explode_type(argv[0]) 

5802 self.__create_or_edit_structure(modname, structname) 

5803 return 

5804 

5805 def __create_or_edit_structure(self, mod_name: str, struct_name: str) -> int: 

5806 path = gef.config["pcustom.struct_path"].expanduser() / f"{mod_name}.py" 

5807 if path.is_file(): 

5808 info(f"Editing '{path}'") 

5809 else: 

5810 ok(f"Creating '{path}' from template") 

5811 self.__create_template(struct_name, path) 

5812 

5813 cmd = (os.getenv("EDITOR") or "nano").split() 

5814 cmd.append(str(path.absolute())) 

5815 return subprocess.call(cmd) 

5816 

5817 def __create_template(self, structname: str, fpath: pathlib.Path) -> None: 

5818 template = f"""from ctypes import * 

5819 

5820class {structname}(Structure): 

5821 _fields_ = [] 

5822 

5823 _values_ = [] 

5824""" 

5825 with fpath.open("w") as f: 

5826 f.write(template) 

5827 return 

5828 

5829 

5830@register 

5831class ChangeFdCommand(GenericCommand): 

5832 """ChangeFdCommand: redirect file descriptor during runtime.""" 

5833 

5834 _cmdline_ = "hijack-fd" 

5835 _syntax_ = f"{_cmdline_} FD_NUM NEW_OUTPUT" 

5836 _example_ = f"{_cmdline_} 2 /tmp/stderr_output.txt" 

5837 

5838 @only_if_gdb_running 

5839 @only_if_gdb_target_local 

5840 def do_invoke(self, argv: List[str]) -> None: 

5841 if len(argv) != 2: 

5842 self.usage() 

5843 return 

5844 

5845 if not os.access(f"/proc/{gef.session.pid:d}/fd/{argv[0]}", os.R_OK): 

5846 self.usage() 

5847 return 

5848 

5849 old_fd = int(argv[0]) 

5850 new_output = argv[1] 

5851 

5852 if ":" in new_output: 

5853 address = socket.gethostbyname(new_output.split(":")[0]) 

5854 port = int(new_output.split(":")[1]) 

5855 

5856 AF_INET = 2 

5857 SOCK_STREAM = 1 

5858 res = gdb.execute(f"call (int)socket({AF_INET}, {SOCK_STREAM}, 0)", to_string=True) or "" 

5859 new_fd = self.get_fd_from_result(res) 

5860 

5861 # fill in memory with sockaddr_in struct contents 

5862 # we will do this in the stack, since connect() wants a pointer to a struct 

5863 vmmap = gef.memory.maps 

5864 stack_addr = [entry.page_start for entry in vmmap if entry.path == "[stack]"][0] 

5865 original_contents = gef.memory.read(stack_addr, 8) 

5866 

5867 gef.memory.write(stack_addr, b"\x02\x00", 2) 

5868 gef.memory.write(stack_addr + 0x2, struct.pack("<H", socket.htons(port)), 2) 

5869 gef.memory.write(stack_addr + 0x4, socket.inet_aton(address), 4) 

5870 

5871 info(f"Trying to connect to {new_output}") 

5872 res = gdb.execute(f"""call (int)connect({new_fd}, {stack_addr}, {16})""", to_string=True) 

5873 if res is None: 

5874 err("Call to `connect` failed") 

5875 return 

5876 

5877 # recover stack state 

5878 gef.memory.write(stack_addr, original_contents, 8) 

5879 

5880 res = self.get_fd_from_result(res) 

5881 if res == -1: 

5882 err(f"Failed to connect to {address}:{port}") 

5883 return 

5884 

5885 info(f"Connected to {new_output}") 

5886 else: 

5887 res = gdb.execute(f"""call (int)open("{new_output}", 66, 0666)""", to_string=True) 

5888 if res is None: 

5889 err("Call to `open` failed") 

5890 return 

5891 new_fd = self.get_fd_from_result(res) 

5892 

5893 info(f"Opened '{new_output}' as fd #{new_fd:d}") 

5894 gdb.execute(f"""call (int)dup2({new_fd:d}, {old_fd:d})""", to_string=True) 

5895 info(f"Duplicated fd #{new_fd:d}{RIGHT_ARROW}#{old_fd:d}") 

5896 gdb.execute(f"""call (int)close({new_fd:d})""", to_string=True) 

5897 info(f"Closed extra fd #{new_fd:d}") 

5898 ok("Success") 

5899 return 

5900 

5901 def get_fd_from_result(self, res: str) -> int: 

5902 # Output example: $1 = 3 

5903 res = gdb.execute(f"p/d {int(res.split()[2], 0)}", to_string=True) or "" 

5904 return int(res.split()[2], 0) 

5905 

5906 

5907@register 

5908class ScanSectionCommand(GenericCommand): 

5909 """Search for addresses that are located in a memory mapping (haystack) that belonging 

5910 to another (needle).""" 

5911 

5912 _cmdline_ = "scan" 

5913 _syntax_ = f"{_cmdline_} HAYSTACK NEEDLE" 

5914 _aliases_ = ["lookup",] 

5915 _example_ = f"\n{_cmdline_} stack libc" 

5916 

5917 @only_if_gdb_running 

5918 def do_invoke(self, argv: List[str]) -> None: 

5919 if len(argv) != 2: 5919 ↛ 5920line 5919 didn't jump to line 5920 because the condition on line 5919 was never true

5920 self.usage() 

5921 return 

5922 

5923 haystack = argv[0] 

5924 needle = argv[1] 

5925 

5926 info(f"Searching for addresses in '{Color.yellowify(haystack)}' " 

5927 f"that point to '{Color.yellowify(needle)}'") 

5928 

5929 fpath = get_filepath() or "" 

5930 

5931 if haystack == "binary": 

5932 haystack = fpath 

5933 

5934 if needle == "binary": 5934 ↛ 5935line 5934 didn't jump to line 5935 because the condition on line 5934 was never true

5935 needle = fpath 

5936 

5937 needle_sections = [] 

5938 haystack_sections = [] 

5939 

5940 if "0x" in haystack: 5940 ↛ 5941line 5940 didn't jump to line 5941 because the condition on line 5940 was never true

5941 start, end = parse_string_range(haystack) 

5942 haystack_sections.append((start, end, "")) 

5943 

5944 if "0x" in needle: 5944 ↛ 5945line 5944 didn't jump to line 5945 because the condition on line 5944 was never true

5945 start, end = parse_string_range(needle) 

5946 needle_sections.append((start, end)) 

5947 

5948 for sect in gef.memory.maps: 

5949 if sect.path is None: 5949 ↛ 5950line 5949 didn't jump to line 5950 because the condition on line 5949 was never true

5950 continue 

5951 if haystack in sect.path: 

5952 haystack_sections.append((sect.page_start, sect.page_end, os.path.basename(sect.path))) 

5953 if needle in sect.path: 

5954 needle_sections.append((sect.page_start, sect.page_end)) 

5955 

5956 step = gef.arch.ptrsize 

5957 unpack = u32 if step == 4 else u64 

5958 

5959 dereference_cmd = gef.gdb.commands["dereference"] 

5960 assert isinstance(dereference_cmd, DereferenceCommand) 

5961 

5962 for hstart, hend, hname in haystack_sections: 

5963 try: 

5964 mem = gef.memory.read(hstart, hend - hstart) 

5965 except gdb.MemoryError: 

5966 continue 

5967 

5968 for i in range(0, len(mem), step): 

5969 target = unpack(mem[i:i+step]) 

5970 for nstart, nend in needle_sections: 

5971 if target >= nstart and target < nend: 

5972 deref = dereference_cmd.pprint_dereferenced(hstart, int(i / step)) 

5973 if hname != "": 5973 ↛ 5977line 5973 didn't jump to line 5977 because the condition on line 5973 was always true

5974 name = Color.colorify(hname, "yellow") 

5975 gef_print(f"{name}: {deref}") 

5976 else: 

5977 gef_print(f" {deref}") 

5978 

5979 return 

5980 

5981 

5982@register 

5983class SearchPatternCommand(GenericCommand): 

5984 """SearchPatternCommand: search a pattern in memory. If given an hex value (starting with 0x) 

5985 the command will also try to look for upwards cross-references to this address.""" 

5986 

5987 _cmdline_ = "search-pattern" 

5988 _syntax_ = f"{_cmdline_} PATTERN [little|big] [section]" 

5989 _aliases_ = ["grep", "xref"] 

5990 _example_ = [f"{_cmdline_} AAAAAAAA", 

5991 f"{_cmdline_} 0x555555554000 little stack", 

5992 f"{_cmdline_} AAAA 0x600000-0x601000", 

5993 f"{_cmdline_} --regex 0x401000 0x401500 ([\\\\x20-\\\\x7E]{{2,}})(?=\\\\x00) <-- It matchs null-end-printable(from x20-x7e) C strings (min size 2 bytes)"] 

5994 

5995 def __init__(self) -> None: 

5996 super().__init__() 

5997 self["max_size_preview"] = (10, "max size preview of bytes") 

5998 self["nr_pages_chunk"] = (0x400, "number of pages readed for each memory read chunk") 

5999 return 

6000 

6001 def print_section(self, section: Section) -> None: 

6002 title = "In " 

6003 if section.path: 6003 ↛ 6006line 6003 didn't jump to line 6006 because the condition on line 6003 was always true

6004 title += f"'{Color.blueify(section.path)}'" 

6005 

6006 title += f"({section.page_start:#x}-{section.page_end:#x})" 

6007 title += f", permission={section.permission}" 

6008 ok(title) 

6009 return 

6010 

6011 def print_loc(self, loc: Tuple[int, int, str]) -> None: 

6012 gef_print(f""" {loc[0]:#x} - {loc[1]:#x} {RIGHT_ARROW} "{Color.pinkify(loc[2])}" """) 

6013 return 

6014 

6015 def search_pattern_by_address(self, pattern: str, start_address: int, end_address: int) -> List[Tuple[int, int, str]]: 

6016 """Search a pattern within a range defined by arguments.""" 

6017 _pattern = gef_pybytes(pattern) 

6018 step = self["nr_pages_chunk"] * gef.session.pagesize 

6019 locations = [] 

6020 

6021 for chunk_addr in range(start_address, end_address, step): 

6022 if chunk_addr + step > end_address: 6022 ↛ 6025line 6022 didn't jump to line 6025 because the condition on line 6022 was always true

6023 chunk_size = end_address - chunk_addr 

6024 else: 

6025 chunk_size = step 

6026 

6027 try: 

6028 mem = gef.memory.read(chunk_addr, chunk_size) 

6029 except gdb.MemoryError: 

6030 return [] 

6031 

6032 for match in re.finditer(_pattern, mem): 

6033 start = chunk_addr + match.start() 

6034 ustr = "" 

6035 if is_ascii_string(start): 6035 ↛ 6039line 6035 didn't jump to line 6039 because the condition on line 6035 was always true

6036 ustr = gef.memory.read_ascii_string(start) or "" 

6037 end = start + len(ustr) 

6038 else: 

6039 ustr = gef_pystring(_pattern) + "[...]" 

6040 end = start + len(_pattern) 

6041 locations.append((start, end, ustr)) 

6042 

6043 del mem 

6044 

6045 return locations 

6046 

6047 def search_binpattern_by_address(self, binpattern: bytes, start_address: int, end_address: int) -> List[Tuple[int, int, str]]: 

6048 """Search a binary pattern within a range defined by arguments.""" 

6049 

6050 step = self["nr_pages_chunk"] * gef.session.pagesize 

6051 locations = [] 

6052 

6053 for chunk_addr in range(start_address, end_address, step): 

6054 if chunk_addr + step > end_address: 6054 ↛ 6057line 6054 didn't jump to line 6057 because the condition on line 6054 was always true

6055 chunk_size = end_address - chunk_addr 

6056 else: 

6057 chunk_size = step 

6058 

6059 try: 

6060 mem = gef.memory.read(chunk_addr, chunk_size) 

6061 except gdb.MemoryError: 

6062 return [] 

6063 preview_size = self["max_size_preview"] 

6064 preview = "" 

6065 for match in re.finditer(binpattern, mem): 

6066 start = chunk_addr + match.start() 

6067 preview = str(mem[slice(*match.span())][0:preview_size]) + "..." 

6068 size_match = match.span()[1] - match.span()[0] 

6069 if size_match > 0: 6069 ↛ 6071line 6069 didn't jump to line 6071 because the condition on line 6069 was always true

6070 size_match -= 1 

6071 end = start + size_match 

6072 locations.append((start, end, preview)) 

6073 

6074 del mem 

6075 

6076 return locations 

6077 

6078 def search_pattern(self, pattern: str, section_name: str) -> None: 

6079 """Search a pattern within the whole userland memory.""" 

6080 for section in gef.memory.maps: 

6081 if not section.permission & Permission.READ: continue 

6082 if section.path == "[vvar]": continue 

6083 if not section_name in section.path: continue 6083 ↛ 6080line 6083 didn't jump to line 6080 because the continue on line 6083 wasn't executed

6084 

6085 start = section.page_start 

6086 end = section.page_end - 1 

6087 old_section = None 

6088 

6089 for loc in self.search_pattern_by_address(pattern, start, end): 

6090 addr_loc_start = lookup_address(loc[0]) 

6091 if addr_loc_start and addr_loc_start.section: 6091 ↛ 6096line 6091 didn't jump to line 6096 because the condition on line 6091 was always true

6092 if old_section != addr_loc_start.section: 6092 ↛ 6096line 6092 didn't jump to line 6096 because the condition on line 6092 was always true

6093 self.print_section(addr_loc_start.section) 

6094 old_section = addr_loc_start.section 

6095 

6096 self.print_loc(loc) 

6097 return 

6098 

6099 @only_if_gdb_running 

6100 def do_invoke(self, argv: List[str]) -> None: 

6101 argc = len(argv) 

6102 if argc < 1: 6102 ↛ 6103line 6102 didn't jump to line 6103 because the condition on line 6102 was never true

6103 self.usage() 

6104 return 

6105 

6106 if argc > 3 and argv[0].startswith("--regex"): 

6107 pattern = ' '.join(argv[3:]) 

6108 pattern = ast.literal_eval("b'" + pattern + "'") 

6109 

6110 addr_start = parse_address(argv[1]) 

6111 addr_end = parse_address(argv[2]) 

6112 

6113 for loc in self.search_binpattern_by_address(pattern, addr_start, addr_end): 

6114 self.print_loc(loc) 

6115 

6116 return 

6117 

6118 pattern = argv[0] 

6119 endian = gef.arch.endianness 

6120 

6121 if argc >= 2: 6121 ↛ 6122line 6121 didn't jump to line 6122 because the condition on line 6121 was never true

6122 if argv[1].lower() == "big": endian = Endianness.BIG_ENDIAN 

6123 elif argv[1].lower() == "little": endian = Endianness.LITTLE_ENDIAN 

6124 

6125 if is_hex(pattern): 6125 ↛ 6126line 6125 didn't jump to line 6126 because the condition on line 6125 was never true

6126 if endian == Endianness.BIG_ENDIAN: 

6127 pattern = "".join(["\\x" + pattern[i:i + 2] for i in range(2, len(pattern), 2)]) 

6128 else: 

6129 pattern = "".join(["\\x" + pattern[i:i + 2] for i in range(len(pattern) - 2, 0, -2)]) 

6130 

6131 if argc == 3: 6131 ↛ 6132line 6131 didn't jump to line 6132 because the condition on line 6131 was never true

6132 info(f"Searching '{Color.yellowify(pattern)}' in {argv[2]}") 

6133 

6134 if "0x" in argv[2]: 

6135 start, end = parse_string_range(argv[2]) 

6136 

6137 loc = lookup_address(start) 

6138 if loc.valid: 

6139 self.print_section(loc.section) 

6140 

6141 for loc in self.search_pattern_by_address(pattern, start, end): 

6142 self.print_loc(loc) 

6143 else: 

6144 section_name = argv[2] 

6145 if section_name == "binary": 

6146 section_name = get_filepath() or "" 

6147 

6148 self.search_pattern(pattern, section_name) 

6149 else: 

6150 info(f"Searching '{Color.yellowify(pattern)}' in memory") 

6151 self.search_pattern(pattern, "") 

6152 return 

6153 

6154 

6155@register 

6156class FlagsCommand(GenericCommand): 

6157 """Edit flags in a human friendly way.""" 

6158 

6159 _cmdline_ = "edit-flags" 

6160 _syntax_ = f"{_cmdline_} [(+|-|~)FLAGNAME ...]" 

6161 _aliases_ = ["flags",] 

6162 _example_ = (f"\n{_cmdline_}" 

6163 f"\n{_cmdline_} +zero # sets ZERO flag") 

6164 

6165 def do_invoke(self, argv: List[str]) -> None: 

6166 if not gef.arch.flag_register: 6166 ↛ 6167line 6166 didn't jump to line 6167 because the condition on line 6166 was never true

6167 warn(f"The architecture {gef.arch.arch}:{gef.arch.mode} doesn't have flag register.") 

6168 return 

6169 

6170 for flag in argv: 

6171 if len(flag) < 2: 6171 ↛ 6172line 6171 didn't jump to line 6172 because the condition on line 6171 was never true

6172 continue 

6173 

6174 action = flag[0] 

6175 name = flag[1:].lower() 

6176 

6177 if action not in ("+", "-", "~"): 6177 ↛ 6178line 6177 didn't jump to line 6178 because the condition on line 6177 was never true

6178 err(f"Invalid action for flag '{flag}'") 

6179 continue 

6180 

6181 if name not in gef.arch.flags_table.values(): 6181 ↛ 6182line 6181 didn't jump to line 6182 because the condition on line 6181 was never true

6182 err(f"Invalid flag name '{flag[1:]}'") 

6183 continue 

6184 

6185 for off in gef.arch.flags_table: 

6186 if gef.arch.flags_table[off] != name: 

6187 continue 

6188 old_flag = gef.arch.register(gef.arch.flag_register) 

6189 if action == "+": 

6190 new_flags = old_flag | (1 << off) 

6191 elif action == "-": 

6192 new_flags = old_flag & ~(1 << off) 

6193 else: 

6194 new_flags = old_flag ^ (1 << off) 

6195 

6196 gdb.execute(f"set ({gef.arch.flag_register}) = {new_flags:#x}") 

6197 

6198 gef_print(gef.arch.flag_register_to_human()) 

6199 return 

6200 

6201 

6202@register 

6203class RemoteCommand(GenericCommand): 

6204 """GDB `target remote` command on steroids. This command will use the remote procfs to create 

6205 a local copy of the execution environment, including the target binary and its libraries 

6206 in the local temporary directory (the value by default is in `gef.config.tempdir`). Additionally, it 

6207 will fetch all the /proc/PID/maps and loads all its information. If procfs is not available remotely, the command 

6208 will likely fail. You can however still use the limited command provided by GDB `target remote`.""" 

6209 

6210 _cmdline_ = "gef-remote" 

6211 _syntax_ = f"{_cmdline_} [OPTIONS] TARGET" 

6212 _example_ = [f"{_cmdline_} localhost 1234", 

6213 f"{_cmdline_} --pid 6789 localhost 1234", 

6214 f"{_cmdline_} --qemu-user --qemu-binary /bin/debugme localhost 4444 "] 

6215 

6216 def __init__(self) -> None: 

6217 super().__init__(prefix=False) 

6218 return 

6219 

6220 @parse_arguments({"host": "", "port": 0}, {"--pid": -1, "--qemu-user": False, "--qemu-binary": ""}) 

6221 def do_invoke(self, _: List[str], **kwargs: Any) -> None: 

6222 if gef.session.remote is not None: 6222 ↛ 6223line 6222 didn't jump to line 6223 because the condition on line 6222 was never true

6223 err("You already are in remote session. Close it first before opening a new one...") 

6224 return 

6225 

6226 # argument check 

6227 args : argparse.Namespace = kwargs["arguments"] 

6228 if not args.host or not args.port: 6228 ↛ 6229line 6228 didn't jump to line 6229 because the condition on line 6228 was never true

6229 err("Missing parameters") 

6230 return 

6231 

6232 # qemu-user support 

6233 qemu_binary: Optional[pathlib.Path] = None 

6234 if args.qemu_user: 

6235 try: 

6236 qemu_binary = pathlib.Path(args.qemu_binary).expanduser().absolute() if args.qemu_binary else gef.session.file 

6237 if not qemu_binary or not qemu_binary.exists(): 6237 ↛ 6238line 6237 didn't jump to line 6238 because the condition on line 6237 was never true

6238 raise FileNotFoundError(f"{qemu_binary} does not exist") 

6239 except Exception as e: 

6240 err(f"Failed to initialize qemu-user mode, reason: {str(e)}") 

6241 return 

6242 

6243 # Try to establish the remote session, throw on error 

6244 # Set `.remote_initializing` to True here - `GefRemoteSessionManager` invokes code which 

6245 # calls `is_remote_debug` which checks if `remote_initializing` is True or `.remote` is None 

6246 # This prevents some spurious errors being thrown during startup 

6247 gef.session.remote_initializing = True 

6248 session = GefRemoteSessionManager(args.host, args.port, args.pid, qemu_binary) 

6249 

6250 dbg(f"[remote] initializing remote session with {session.target} under {session.root}") 

6251 if not session.connect(args.pid) or not session.setup(): 

6252 gef.session.remote = None 

6253 gef.session.remote_initializing = False 

6254 raise EnvironmentError("Failed to setup remote target") 

6255 

6256 gef.session.remote_initializing = False 

6257 gef.session.remote = session 

6258 reset_all_caches() 

6259 gdb.execute("context") 

6260 return 

6261 

6262 

6263@register 

6264class SkipiCommand(GenericCommand): 

6265 """Skip N instruction(s) execution""" 

6266 

6267 _cmdline_ = "skipi" 

6268 _syntax_ = (f"{_cmdline_} [LOCATION] [--n NUM_INSTRUCTIONS]" 

6269 "\n\tLOCATION\taddress/symbol from where to skip" 

6270 "\t--n NUM_INSTRUCTIONS\tSkip the specified number of instructions instead of the default 1.") 

6271 

6272 _example_ = [f"{_cmdline_}", 

6273 f"{_cmdline_} --n 3", 

6274 f"{_cmdline_} 0x69696969", 

6275 f"{_cmdline_} 0x69696969 --n 6",] 

6276 

6277 def __init__(self) -> None: 

6278 super().__init__(complete=gdb.COMPLETE_LOCATION) 

6279 return 

6280 

6281 @only_if_gdb_running 

6282 @parse_arguments({"address": "$pc"}, {"--n": 1}) 

6283 def do_invoke(self, _: List[str], **kwargs: Any) -> None: 

6284 args : argparse.Namespace = kwargs["arguments"] 

6285 address = parse_address(args.address) 

6286 num_instructions = args.n 

6287 

6288 last_insn = gef_instruction_n(address, num_instructions-1) 

6289 total_bytes = (last_insn.address - address) + last_insn.size() 

6290 target_addr = address + total_bytes 

6291 

6292 info(f"skipping {num_instructions} instructions ({total_bytes} bytes) from {address:#x} to {target_addr:#x}") 

6293 gdb.execute(f"set $pc = {target_addr:#x}") 

6294 return 

6295 

6296 

6297@register 

6298class StepoverCommand(GenericCommand): 

6299 """Breaks on the instruction immediately following this one. Ex: Step over call instruction""" 

6300 

6301 _cmdline_ = "stepover" 

6302 _syntax_ = (f"{_cmdline_}" 

6303 "\n\tBreaks on the instruction immediately following this one. Ex: Step over call instruction.") 

6304 _aliases_ = ["so",] 

6305 _example_ = [f"{_cmdline_}",] 

6306 

6307 def __init__(self) -> None: 

6308 super().__init__(complete=gdb.COMPLETE_LOCATION) 

6309 return 

6310 

6311 @only_if_gdb_running 

6312 def do_invoke(self, _: List[str]) -> None: 

6313 target_addr = gef_next_instruction(parse_address("$pc")).address 

6314 JustSilentStopBreakpoint("".join(["*", str(target_addr)])) 

6315 gdb.execute("continue") 

6316 return 

6317 

6318 

6319@register 

6320class NopCommand(GenericCommand): 

6321 """Patch the instruction(s) pointed by parameters with NOP. Note: this command is architecture 

6322 aware.""" 

6323 

6324 _cmdline_ = "nop" 

6325 _syntax_ = (f"{_cmdline_} [LOCATION] [--i ITEMS] [--f] [--n] [--b]" 

6326 "\n\tLOCATION\taddress/symbol to patch (by default this command replaces whole instructions)" 

6327 "\t--i ITEMS\tnumber of items to insert (default 1)" 

6328 "\t--f\tForce patch even when the selected settings could overwrite partial instructions" 

6329 "\t--n\tInstead of replacing whole instructions, insert ITEMS nop instructions, no matter how many instructions it overwrites" 

6330 "\t--b\tInstead of replacing whole instructions, fill ITEMS bytes with nops") 

6331 _example_ = [f"{_cmdline_}", 

6332 f"{_cmdline_} $pc+3", 

6333 f"{_cmdline_} --i 2 $pc+3", 

6334 f"{_cmdline_} --b", 

6335 f"{_cmdline_} --b $pc+3", 

6336 f"{_cmdline_} --f --b --i 2 $pc+3" 

6337 f"{_cmdline_} --n --i 2 $pc+3",] 

6338 

6339 def __init__(self) -> None: 

6340 super().__init__(complete=gdb.COMPLETE_LOCATION) 

6341 return 

6342 

6343 @only_if_gdb_running 

6344 @parse_arguments({"address": "$pc"}, {"--i": 1, "--b": False, "--f": False, "--n": False}) 

6345 def do_invoke(self, _: List[str], **kwargs: Any) -> None: 

6346 args : argparse.Namespace = kwargs["arguments"] 

6347 address = parse_address(args.address) 

6348 nop = gef.arch.nop_insn 

6349 num_items = int(args.i) or 1 

6350 fill_bytes = bool(args.b) 

6351 fill_nops = bool(args.n) 

6352 force_flag = bool(args.f) or False 

6353 

6354 if fill_nops and fill_bytes: 

6355 err("--b and --n cannot be specified at the same time.") 

6356 return 

6357 

6358 total_bytes = 0 

6359 if fill_bytes: 

6360 total_bytes = num_items 

6361 elif fill_nops: 

6362 total_bytes = num_items * len(nop) 

6363 else: 

6364 try: 

6365 last_insn = gef_instruction_n(address, num_items-1) 

6366 last_addr = last_insn.address 

6367 except: 

6368 err(f"Cannot patch instruction at {address:#x} reaching unmapped area") 

6369 return 

6370 total_bytes = (last_addr - address) + gef_get_instruction_at(last_addr).size() 

6371 

6372 if len(nop) > total_bytes or total_bytes % len(nop): 

6373 warn(f"Patching {total_bytes} bytes at {address:#x} will result in LAST-NOP " 

6374 f"(byte nr {total_bytes % len(nop):#x}) broken and may cause a crash or " 

6375 "break disassembly.") 

6376 if not force_flag: 

6377 warn("Use --f (force) to ignore this warning.") 

6378 return 

6379 

6380 target_end_address = address + total_bytes 

6381 curr_ins = gef_current_instruction(address) 

6382 while curr_ins.address + curr_ins.size() < target_end_address: 

6383 if not Address(value=curr_ins.address + 1).valid: 

6384 err(f"Cannot patch instruction at {address:#x}: reaching unmapped area") 

6385 return 

6386 curr_ins = gef_next_instruction(curr_ins.address) 

6387 

6388 final_ins_end_addr = curr_ins.address + curr_ins.size() 

6389 

6390 if final_ins_end_addr != target_end_address: 

6391 warn(f"Patching {total_bytes} bytes at {address:#x} will result in LAST-INSTRUCTION " 

6392 f"({curr_ins.address:#x}) being partial overwritten and may cause a crash or " 

6393 "break disassembly.") 

6394 if not force_flag: 

6395 warn("Use --f (force) to ignore this warning.") 

6396 return 

6397 

6398 nops = bytearray(nop * total_bytes) 

6399 end_address = Address(value=address + total_bytes - 1) 

6400 if not end_address.valid: 6400 ↛ 6401line 6400 didn't jump to line 6401 because the condition on line 6400 was never true

6401 err(f"Cannot patch instruction at {address:#x}: reaching unmapped " 

6402 f"area: {end_address:#x}") 

6403 return 

6404 

6405 ok(f"Patching {total_bytes} bytes from {address:#x}") 

6406 gef.memory.write(address, nops, total_bytes) 

6407 

6408 return 

6409 

6410 

6411@register 

6412class StubCommand(GenericCommand): 

6413 """Stub out the specified function. This function is useful when needing to skip one 

6414 function to be called and disrupt your runtime flow (ex. fork).""" 

6415 

6416 _cmdline_ = "stub" 

6417 _syntax_ = (f"{_cmdline_} [--retval RETVAL] [address]" 

6418 "\taddress\taddress/symbol to stub out" 

6419 "\t--retval RETVAL\tSet the return value") 

6420 _example_ = f"{_cmdline_} --retval 0 fork" 

6421 

6422 def __init__(self) -> None: 

6423 super().__init__(complete=gdb.COMPLETE_LOCATION) 

6424 return 

6425 

6426 @only_if_gdb_running 

6427 @parse_arguments({"address": ""}, {("-r", "--retval"): 0}) 

6428 def do_invoke(self, _: List[str], **kwargs: Any) -> None: 

6429 args : argparse.Namespace = kwargs["arguments"] 

6430 loc = args.address if args.address else f"*{gef.arch.pc:#x}" 

6431 StubBreakpoint(loc, args.retval) 

6432 return 

6433 

6434 

6435@register 

6436class GlibcHeapCommand(GenericCommand): 

6437 """Base command to get information about the Glibc heap structure.""" 

6438 

6439 _cmdline_ = "heap" 

6440 _syntax_ = f"{_cmdline_} (chunk|chunks|bins|arenas|set-arena)" 

6441 

6442 def __init__(self) -> None: 

6443 super().__init__(prefix=True) 

6444 return 

6445 

6446 @only_if_gdb_running 

6447 def do_invoke(self, _: List[str]) -> None: 

6448 self.usage() 

6449 return 

6450 

6451 

6452@register 

6453class GlibcHeapSetArenaCommand(GenericCommand): 

6454 """Set the address of the main_arena or the currently selected arena.""" 

6455 

6456 _cmdline_ = "heap set-arena" 

6457 _syntax_ = f"{_cmdline_} [address|&symbol]" 

6458 _example_ = f"{_cmdline_} 0x001337001337" 

6459 

6460 def __init__(self) -> None: 

6461 super().__init__(complete=gdb.COMPLETE_LOCATION) 

6462 return 

6463 

6464 @only_if_gdb_running 

6465 @parse_arguments({"addr": ""}, {"--reset": False}) 

6466 def do_invoke(self, _: List[str], **kwargs: Any) -> None: 

6467 global gef 

6468 

6469 args: argparse.Namespace = kwargs["arguments"] 

6470 

6471 if args.reset: 6471 ↛ 6472line 6471 didn't jump to line 6472 because the condition on line 6471 was never true

6472 gef.heap.reset_caches() 

6473 return 

6474 

6475 if not args.addr: 6475 ↛ 6476line 6475 didn't jump to line 6476 because the condition on line 6475 was never true

6476 ok(f"Current arena set to: '{gef.heap.selected_arena}'") 

6477 return 

6478 

6479 try: 

6480 new_arena_address = parse_address(args.addr) 

6481 except gdb.error: 

6482 err("Invalid symbol for arena") 

6483 return 

6484 

6485 new_arena = GlibcArena( f"*{new_arena_address:#x}") 

6486 if new_arena in gef.heap.arenas: 6486 ↛ 6491line 6486 didn't jump to line 6491 because the condition on line 6486 was always true

6487 # if entered arena is in arena list then just select it 

6488 gef.heap.selected_arena = new_arena 

6489 else: 

6490 # otherwise set the main arena to the entered arena 

6491 gef.heap.main_arena = new_arena 

6492 return 

6493 

6494 

6495@register 

6496class GlibcHeapArenaCommand(GenericCommand): 

6497 """Display information on a heap chunk.""" 

6498 

6499 _cmdline_ = "heap arenas" 

6500 _syntax_ = _cmdline_ 

6501 

6502 @only_if_gdb_running 

6503 def do_invoke(self, _: List[str]) -> None: 

6504 for arena in gef.heap.arenas: 

6505 gef_print(str(arena)) 

6506 return 

6507 

6508 

6509@register 

6510class GlibcHeapChunkCommand(GenericCommand): 

6511 """Display information on a heap chunk. 

6512 See https://github.com/sploitfun/lsploits/blob/master/glibc/malloc/malloc.c#L1123.""" 

6513 

6514 _cmdline_ = "heap chunk" 

6515 _syntax_ = f"{_cmdline_} [-h] [--allow-unaligned] [--number] address" 

6516 

6517 def __init__(self) -> None: 

6518 super().__init__(complete=gdb.COMPLETE_LOCATION) 

6519 return 

6520 

6521 @parse_arguments({"address": ""}, {"--allow-unaligned": False, "--number": 1}) 

6522 @only_if_gdb_running 

6523 def do_invoke(self, _: List[str], **kwargs: Any) -> None: 

6524 args : argparse.Namespace = kwargs["arguments"] 

6525 if not args.address: 6525 ↛ 6526line 6525 didn't jump to line 6526 because the condition on line 6525 was never true

6526 err("Missing chunk address") 

6527 self.usage() 

6528 return 

6529 

6530 addr = parse_address(args.address) 

6531 current_chunk = GlibcChunk(addr, allow_unaligned=args.allow_unaligned) 

6532 

6533 if args.number > 1: 

6534 for _i in range(args.number): 6534 ↛ 6550line 6534 didn't jump to line 6550 because the loop on line 6534 didn't complete

6535 if current_chunk.size == 0: 6535 ↛ 6536line 6535 didn't jump to line 6536 because the condition on line 6535 was never true

6536 break 

6537 

6538 gef_print(str(current_chunk)) 

6539 next_chunk_addr = current_chunk.get_next_chunk_addr() 

6540 if not Address(value=next_chunk_addr).valid: 

6541 break 

6542 

6543 next_chunk = current_chunk.get_next_chunk() 

6544 if next_chunk is None: 6544 ↛ 6545line 6544 didn't jump to line 6545 because the condition on line 6544 was never true

6545 break 

6546 

6547 current_chunk = next_chunk 

6548 else: 

6549 gef_print(current_chunk.psprint()) 

6550 return 

6551 

6552 

6553class GlibcHeapChunkSummary: 

6554 def __init__(self, desc = ""): 

6555 self.desc = desc 

6556 self.count = 0 

6557 self.total_bytes = 0 

6558 

6559 def process_chunk(self, chunk: GlibcChunk) -> None: 

6560 self.count += 1 

6561 self.total_bytes += chunk.size 

6562 

6563 

6564class GlibcHeapArenaSummary: 

6565 def __init__(self, resolve_type = False) -> None: 

6566 self.resolve_symbol = resolve_type 

6567 self.size_distribution = {} 

6568 self.flag_distribution = { 

6569 "PREV_INUSE": GlibcHeapChunkSummary(), 

6570 "IS_MMAPPED": GlibcHeapChunkSummary(), 

6571 "NON_MAIN_ARENA": GlibcHeapChunkSummary() 

6572 } 

6573 

6574 def process_chunk(self, chunk: GlibcChunk) -> None: 

6575 chunk_type = "" if not self.resolve_symbol else chunk.resolve_type() 

6576 

6577 per_size_summary = self.size_distribution.get((chunk.size, chunk_type), None) 

6578 if per_size_summary is None: 6578 ↛ 6581line 6578 didn't jump to line 6581 because the condition on line 6578 was always true

6579 per_size_summary = GlibcHeapChunkSummary(desc=chunk_type) 

6580 self.size_distribution[(chunk.size, chunk_type)] = per_size_summary 

6581 per_size_summary.process_chunk(chunk) 

6582 

6583 if chunk.has_p_bit(): 6583 ↛ 6585line 6583 didn't jump to line 6585 because the condition on line 6583 was always true

6584 self.flag_distribution["PREV_INUSE"].process_chunk(chunk) 

6585 if chunk.has_m_bit(): 6585 ↛ 6586line 6585 didn't jump to line 6586 because the condition on line 6585 was never true

6586 self.flag_distribution["IS_MAPPED"].process_chunk(chunk) 

6587 if chunk.has_n_bit(): 6587 ↛ 6588line 6587 didn't jump to line 6588 because the condition on line 6587 was never true

6588 self.flag_distribution["NON_MAIN_ARENA"].process_chunk(chunk) 

6589 

6590 def print(self) -> None: 

6591 gef_print("== Chunk distribution by size ==") 

6592 gef_print(f"{'ChunkBytes':<10s}\t{'Count':<10s}\t{'TotalBytes':15s}\t{'Description':s}") 

6593 for chunk_info, chunk_summary in sorted(self.size_distribution.items(), key=lambda x: x[1].total_bytes, reverse=True): 

6594 gef_print(f"{chunk_info[0]:<10d}\t{chunk_summary.count:<10d}\t{chunk_summary.total_bytes:<15d}\t{chunk_summary.desc:s}") 

6595 

6596 gef_print("\n== Chunk distribution by flag ==") 

6597 gef_print(f"{'Flag':<15s}\t{'TotalCount':<10s}\t{'TotalBytes':s}") 

6598 for chunk_flag, chunk_summary in self.flag_distribution.items(): 

6599 gef_print(f"{chunk_flag:<15s}\t{chunk_summary.count:<10d}\t{chunk_summary.total_bytes:<d}") 

6600 

6601class GlibcHeapWalkContext: 

6602 def __init__(self, print_arena: bool = False, allow_unaligned: bool = False, min_size: int = 0, max_size: int = 0, count: int = -1, resolve_type: bool = False, summary: bool = False) -> None: 

6603 self.print_arena = print_arena 

6604 self.allow_unaligned = allow_unaligned 

6605 self.min_size = min_size 

6606 self.max_size = max_size 

6607 self.remaining_chunk_count = count 

6608 self.summary = summary 

6609 self.resolve_type = resolve_type 

6610 

6611@register 

6612class GlibcHeapChunksCommand(GenericCommand): 

6613 """Display all heap chunks for the current arena. As an optional argument 

6614 the base address of a different arena can be passed""" 

6615 

6616 _cmdline_ = "heap chunks" 

6617 _syntax_ = f"{_cmdline_} [-h] [--all] [--allow-unaligned] [--summary] [--min-size MIN_SIZE] [--max-size MAX_SIZE] [--count COUNT] [--resolve] [arena_address]" 

6618 _example_ = (f"\n{_cmdline_}" 

6619 f"\n{_cmdline_} 0x555555775000") 

6620 

6621 def __init__(self) -> None: 

6622 super().__init__(complete=gdb.COMPLETE_LOCATION) 

6623 self["peek_nb_byte"] = (16, "Hexdump N first byte(s) inside the chunk data (0 to disable)") 

6624 return 

6625 

6626 @parse_arguments({"arena_address": ""}, {("--all", "-a"): False, "--allow-unaligned": False, "--min-size": 0, "--max-size": 0, ("--count", "-n"): -1, ("--summary", "-s"): False, "--resolve": False}) 

6627 @only_if_gdb_running 

6628 def do_invoke(self, _: List[str], **kwargs: Any) -> None: 

6629 args = kwargs["arguments"] 

6630 ctx = GlibcHeapWalkContext(print_arena=args.all, allow_unaligned=args.allow_unaligned, min_size=args.min_size, max_size=args.max_size, count=args.count, resolve_type=args.resolve, summary=args.summary) 

6631 if args.all or not args.arena_address: 

6632 for arena in gef.heap.arenas: 6632 ↛ 6636line 6632 didn't jump to line 6636 because the loop on line 6632 didn't complete

6633 self.dump_chunks_arena(arena, ctx) 

6634 if not args.all: 6634 ↛ 6632line 6634 didn't jump to line 6632 because the condition on line 6634 was always true

6635 return 

6636 try: 

6637 arena_addr = parse_address(args.arena_address) 

6638 arena = GlibcArena(f"*{arena_addr:#x}") 

6639 self.dump_chunks_arena(arena, ctx) 

6640 except gdb.error: 

6641 err("Invalid arena") 

6642 return 

6643 

6644 def dump_chunks_arena(self, arena: GlibcArena, ctx: GlibcHeapWalkContext) -> None: 

6645 heap_addr = arena.heap_addr(allow_unaligned=ctx.allow_unaligned) 

6646 if heap_addr is None: 6646 ↛ 6647line 6646 didn't jump to line 6647 because the condition on line 6646 was never true

6647 err("Could not find heap for arena") 

6648 return 

6649 if ctx.print_arena: 6649 ↛ 6650line 6649 didn't jump to line 6650 because the condition on line 6649 was never true

6650 gef_print(str(arena)) 

6651 if arena.is_main_arena(): 

6652 heap_end = arena.top + GlibcChunk(arena.top, from_base=True).size 

6653 self.dump_chunks_heap(heap_addr, heap_end, arena, ctx) 

6654 else: 

6655 heap_info_structs = arena.get_heap_info_list() or [] 

6656 for heap_info in heap_info_structs: 

6657 if not self.dump_chunks_heap(heap_info.heap_start, heap_info.heap_end, arena, ctx): 6657 ↛ 6658line 6657 didn't jump to line 6658 because the condition on line 6657 was never true

6658 break 

6659 return 

6660 

6661 def dump_chunks_heap(self, start: int, end: int, arena: GlibcArena, ctx: GlibcHeapWalkContext) -> bool: 

6662 nb = self["peek_nb_byte"] 

6663 chunk_iterator = GlibcChunk(start, from_base=True, allow_unaligned=ctx.allow_unaligned) 

6664 heap_summary = GlibcHeapArenaSummary(resolve_type=ctx.resolve_type) 

6665 for chunk in chunk_iterator: 

6666 heap_corrupted = chunk.base_address > end 

6667 should_process = self.should_process_chunk(chunk, ctx) 

6668 

6669 if not ctx.summary and chunk.base_address == arena.top: 

6670 if should_process: 

6671 gef_print( 

6672 f"{chunk!s} {LEFT_ARROW} {Color.greenify('top chunk')}") 

6673 break 

6674 

6675 if heap_corrupted: 6675 ↛ 6676line 6675 didn't jump to line 6676 because the condition on line 6675 was never true

6676 err("Corrupted heap, cannot continue.") 

6677 return False 

6678 

6679 if not should_process: 

6680 continue 

6681 

6682 if ctx.remaining_chunk_count == 0: 

6683 break 

6684 

6685 if ctx.summary: 

6686 heap_summary.process_chunk(chunk) 

6687 else: 

6688 line = str(chunk) 

6689 if nb: 6689 ↛ 6691line 6689 didn't jump to line 6691 because the condition on line 6689 was always true

6690 line += f"\n [{hexdump(gef.memory.read(chunk.data_address, nb), nb, base=chunk.data_address)}]" 

6691 gef_print(line) 

6692 

6693 ctx.remaining_chunk_count -= 1 

6694 

6695 if ctx.summary: 

6696 heap_summary.print() 

6697 

6698 return True 

6699 

6700 def should_process_chunk(self, chunk: GlibcChunk, ctx: GlibcHeapWalkContext) -> bool: 

6701 if chunk.size < ctx.min_size: 

6702 return False 

6703 

6704 if 0 < ctx.max_size < chunk.size: 

6705 return False 

6706 

6707 return True 

6708 

6709 

6710@register 

6711class GlibcHeapBinsCommand(GenericCommand): 

6712 """Display information on the bins on an arena (default: main_arena). 

6713 See https://github.com/sploitfun/lsploits/blob/master/glibc/malloc/malloc.c#L1123.""" 

6714 

6715 _bin_types_ = ("tcache", "fast", "unsorted", "small", "large") 

6716 _cmdline_ = "heap bins" 

6717 _syntax_ = f"{_cmdline_} [{'|'.join(_bin_types_)}]" 

6718 

6719 def __init__(self) -> None: 

6720 super().__init__(prefix=True, complete=gdb.COMPLETE_LOCATION) 

6721 return 

6722 

6723 @only_if_gdb_running 

6724 def do_invoke(self, argv: List[str]) -> None: 

6725 if not argv: 

6726 for bin_t in self._bin_types_: 

6727 gdb.execute(f"heap bins {bin_t}") 

6728 return 

6729 

6730 bin_t = argv[0] 

6731 if bin_t not in self._bin_types_: 

6732 self.usage() 

6733 return 

6734 

6735 gdb.execute(f"heap bins {bin_t}") 

6736 return 

6737 

6738 def pprint_bin(self, arena_addr: str, index: int, _type: str = "") -> int: 

6739 arena = GlibcArena(arena_addr) 

6740 

6741 fd, bk = arena.bin(index) 

6742 if (fd, bk) == (0x00, 0x00): 6742 ↛ 6743line 6742 didn't jump to line 6743 because the condition on line 6742 was never true

6743 warn("Invalid backward and forward bin pointers(fw==bk==NULL)") 

6744 return -1 

6745 

6746 if _type == "tcache": 6746 ↛ 6747line 6746 didn't jump to line 6747 because the condition on line 6746 was never true

6747 chunkClass = GlibcTcacheChunk 

6748 elif _type == "fast": 6748 ↛ 6749line 6748 didn't jump to line 6749 because the condition on line 6748 was never true

6749 chunkClass = GlibcFastChunk 

6750 else: 

6751 chunkClass = GlibcChunk 

6752 

6753 nb_chunk = 0 

6754 head = chunkClass(bk, from_base=True).fd 

6755 if fd == head: 

6756 return nb_chunk 

6757 

6758 ok(f"{_type}bins[{index:d}]: fw={fd:#x}, bk={bk:#x}") 

6759 

6760 m = [] 

6761 while fd != head: 

6762 chunk = chunkClass(fd, from_base=True) 

6763 m.append(f"{RIGHT_ARROW} {chunk!s}") 

6764 fd = chunk.fd 

6765 nb_chunk += 1 

6766 

6767 if m: 6767 ↛ 6769line 6767 didn't jump to line 6769 because the condition on line 6767 was always true

6768 gef_print(" ".join(m)) 

6769 return nb_chunk 

6770 

6771 

6772@register 

6773class GlibcHeapTcachebinsCommand(GenericCommand): 

6774 """Display information on the Tcachebins on an arena (default: main_arena). 

6775 See https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=d5c3fafc4307c9b7a4c7d5cb381fcdbfad340bcc.""" 

6776 

6777 _cmdline_ = "heap bins tcache" 

6778 _syntax_ = f"{_cmdline_} [all] [thread_ids...]" 

6779 

6780 TCACHE_MAX_BINS = 0x40 

6781 

6782 def __init__(self) -> None: 

6783 super().__init__(complete=gdb.COMPLETE_LOCATION) 

6784 return 

6785 

6786 @only_if_gdb_running 

6787 def do_invoke(self, argv: List[str]) -> None: 

6788 # Determine if we are using libc with tcache built in (2.26+) 

6789 if gef.libc.version and gef.libc.version < (2, 26): 6789 ↛ 6790line 6789 didn't jump to line 6790 because the condition on line 6789 was never true

6790 info("No Tcache in this version of libc") 

6791 return 

6792 

6793 current_thread = gdb.selected_thread() 

6794 if current_thread is None: 6794 ↛ 6795line 6794 didn't jump to line 6795 because the condition on line 6794 was never true

6795 err("Couldn't find current thread") 

6796 return 

6797 

6798 # As a nicety, we want to display threads in ascending order by gdb number 

6799 threads = sorted(gdb.selected_inferior().threads(), key=lambda t: t.num) 

6800 if argv: 

6801 if "all" in argv: 6801 ↛ 6804line 6801 didn't jump to line 6804 because the condition on line 6801 was always true

6802 tids = [t.num for t in threads] 

6803 else: 

6804 tids = self.check_thread_ids([int(a) for a in argv]) 

6805 else: 

6806 tids = [current_thread.num] 

6807 

6808 for thread in threads: 

6809 if thread.num not in tids: 

6810 continue 

6811 

6812 thread.switch() 

6813 

6814 tcache_addr = self.find_tcache() 

6815 if tcache_addr == 0: 6815 ↛ 6816line 6815 didn't jump to line 6816 because the condition on line 6815 was never true

6816 info(f"Uninitialized tcache for thread {thread.num:d}") 

6817 continue 

6818 

6819 gef_print(titlify(f"Tcachebins for thread {thread.num:d}")) 

6820 tcache_empty = True 

6821 for i in range(self.TCACHE_MAX_BINS): 

6822 chunk, count = self.tcachebin(tcache_addr, i) 

6823 chunks = set() 

6824 msg = [] 

6825 chunk_size = 0 

6826 

6827 # Only print the entry if there are valid chunks. Don't trust count 

6828 while True: 

6829 if chunk is None: 

6830 break 

6831 

6832 try: 

6833 msg.append(f"{LEFT_ARROW} {chunk!s} ") 

6834 if not chunk_size: 

6835 chunk_size = chunk.usable_size 

6836 

6837 if chunk.data_address in chunks: 6837 ↛ 6838line 6837 didn't jump to line 6838 because the condition on line 6837 was never true

6838 msg.append(f"{RIGHT_ARROW} [loop detected]") 

6839 break 

6840 

6841 chunks.add(chunk.data_address) 

6842 

6843 next_chunk = chunk.fd 

6844 if next_chunk == 0: 

6845 break 

6846 

6847 chunk = GlibcTcacheChunk(next_chunk) 

6848 except gdb.MemoryError: 

6849 msg.append(f"{LEFT_ARROW} [Corrupted chunk at {chunk.data_address:#x}]") 

6850 break 

6851 

6852 if msg: 

6853 tcache_empty = False 

6854 tidx = gef.heap.csize2tidx(chunk_size) 

6855 size = gef.heap.tidx2size(tidx) 

6856 count = len(chunks) 

6857 gef_print(f"Tcachebins[idx={tidx:d}, size={size:#x}, count={count}]", end="") 

6858 gef_print("".join(msg)) 

6859 

6860 if tcache_empty: 

6861 gef_print("All tcachebins are empty") 

6862 

6863 current_thread.switch() 

6864 return 

6865 

6866 def find_tcache(self) -> int: 

6867 """Return the location of the current thread's tcache.""" 

6868 try: 

6869 # For multithreaded binaries, the tcache symbol (in thread local 

6870 # storage) will give us the correct address. 

6871 tcache_addr = parse_address("(void *) tcache") 

6872 except gdb.error: 

6873 # In binaries not linked with pthread (and therefore there is only 

6874 # one thread), we can't use the tcache symbol, but we can guess the 

6875 # correct address because the tcache is consistently the first 

6876 # allocation in the main arena. 

6877 heap_base = gef.heap.base_address 

6878 if heap_base is None: 

6879 err("No heap section") 

6880 return 0x0 

6881 tcache_addr = heap_base + 0x10 

6882 return tcache_addr 

6883 

6884 def check_thread_ids(self, tids: List[int]) -> List[int]: 

6885 """Return the subset of tids that are currently valid.""" 

6886 existing_tids = set(t.num for t in gdb.selected_inferior().threads()) 

6887 return list(set(tids) & existing_tids) 

6888 

6889 def tcachebin(self, tcache_base: int, i: int) -> Tuple[Optional[GlibcTcacheChunk], int]: 

6890 """Return the head chunk in tcache[i] and the number of chunks in the bin.""" 

6891 if i >= self.TCACHE_MAX_BINS: 6891 ↛ 6892line 6891 didn't jump to line 6892 because the condition on line 6891 was never true

6892 err("Incorrect index value, index value must be between 0 and " 

6893 f"{self.TCACHE_MAX_BINS}-1, given {i}" 

6894 ) 

6895 return None, 0 

6896 

6897 tcache_chunk = GlibcTcacheChunk(tcache_base) 

6898 

6899 # Glibc changed the size of the tcache in version 2.30; this fix has 

6900 # been backported inconsistently between distributions. We detect the 

6901 # difference by checking the size of the allocated chunk for the 

6902 # tcache. 

6903 # Minimum usable size of allocated tcache chunk = ? 

6904 # For new tcache: 

6905 # TCACHE_MAX_BINS * _2_ + TCACHE_MAX_BINS * ptrsize 

6906 # For old tcache: 

6907 # TCACHE_MAX_BINS * _1_ + TCACHE_MAX_BINS * ptrsize 

6908 new_tcache_min_size = ( 

6909 self.TCACHE_MAX_BINS * 2 + 

6910 self.TCACHE_MAX_BINS * gef.arch.ptrsize) 

6911 

6912 if tcache_chunk.usable_size < new_tcache_min_size: 6912 ↛ 6913line 6912 didn't jump to line 6913 because the condition on line 6912 was never true

6913 tcache_count_size = 1 

6914 count = ord(gef.memory.read(tcache_base + tcache_count_size*i, 1)) 

6915 else: 

6916 tcache_count_size = 2 

6917 count = u16(gef.memory.read(tcache_base + tcache_count_size*i, 2)) 

6918 

6919 chunk = dereference(tcache_base + tcache_count_size*self.TCACHE_MAX_BINS + i*gef.arch.ptrsize) 

6920 chunk = GlibcTcacheChunk(int(chunk)) if chunk else None 

6921 return chunk, count 

6922 

6923 

6924@register 

6925class GlibcHeapFastbinsYCommand(GenericCommand): 

6926 """Display information on the fastbinsY on an arena (default: main_arena). 

6927 See https://github.com/sploitfun/lsploits/blob/master/glibc/malloc/malloc.c#L1123.""" 

6928 

6929 _cmdline_ = "heap bins fast" 

6930 _syntax_ = f"{_cmdline_} [ARENA_ADDRESS]" 

6931 

6932 def __init__(self) -> None: 

6933 super().__init__(complete=gdb.COMPLETE_LOCATION) 

6934 return 

6935 

6936 @parse_arguments({"arena_address": ""}, {}) 

6937 @only_if_gdb_running 

6938 def do_invoke(self, *_: Any, **kwargs: Any) -> None: 

6939 def fastbin_index(sz: int) -> int: 

6940 return (sz >> 4) - 2 if SIZE_SZ == 8 else (sz >> 3) - 2 

6941 

6942 args : argparse.Namespace = kwargs["arguments"] 

6943 if not gef.heap.main_arena: 6943 ↛ 6944line 6943 didn't jump to line 6944 because the condition on line 6943 was never true

6944 err("Heap not initialized") 

6945 return 

6946 

6947 SIZE_SZ = gef.arch.ptrsize 

6948 MAX_FAST_SIZE = 80 * SIZE_SZ // 4 

6949 NFASTBINS = fastbin_index(MAX_FAST_SIZE) - 1 

6950 

6951 arena = GlibcArena(f"*{args.arena_address}") if args.arena_address else gef.heap.selected_arena 

6952 if arena is None: 6952 ↛ 6953line 6952 didn't jump to line 6953 because the condition on line 6952 was never true

6953 err("Invalid Glibc arena") 

6954 return 

6955 

6956 gef_print(titlify(f"Fastbins for arena at {arena.addr:#x}")) 

6957 for i in range(NFASTBINS): 

6958 gef_print(f"Fastbins[idx={i:d}, size={(i+2)*SIZE_SZ*2:#x}] ", end="") 

6959 chunk = arena.fastbin(i) 

6960 chunks = set() 

6961 

6962 while True: 

6963 if chunk is None: 

6964 gef_print("0x00", end="") 

6965 break 

6966 

6967 try: 

6968 gef_print(f"{LEFT_ARROW} {chunk!s} ", end="") 

6969 if chunk.data_address in chunks: 6969 ↛ 6970line 6969 didn't jump to line 6970 because the condition on line 6969 was never true

6970 gef_print(f"{RIGHT_ARROW} [loop detected]", end="") 

6971 break 

6972 

6973 if fastbin_index(chunk.size) != i: 6973 ↛ 6974line 6973 didn't jump to line 6974 because the condition on line 6973 was never true

6974 gef_print("[incorrect fastbin_index] ", end="") 

6975 

6976 chunks.add(chunk.data_address) 

6977 

6978 next_chunk = chunk.fd 

6979 if next_chunk == 0: 6979 ↛ 6982line 6979 didn't jump to line 6982 because the condition on line 6979 was always true

6980 break 

6981 

6982 chunk = GlibcFastChunk(next_chunk, from_base=True) 

6983 except gdb.MemoryError: 

6984 gef_print(f"{LEFT_ARROW} [Corrupted chunk at {chunk.data_address:#x}]", end="") 

6985 break 

6986 gef_print() 

6987 return 

6988 

6989 

6990@register 

6991class GlibcHeapUnsortedBinsCommand(GenericCommand): 

6992 """Display information on the Unsorted Bins of an arena (default: main_arena). 

6993 See: https://github.com/sploitfun/lsploits/blob/master/glibc/malloc/malloc.c#L1689.""" 

6994 

6995 _cmdline_ = "heap bins unsorted" 

6996 _syntax_ = f"{_cmdline_} [ARENA_ADDRESS]" 

6997 

6998 def __init__(self) -> None: 

6999 super().__init__(complete=gdb.COMPLETE_LOCATION) 

7000 return 

7001 

7002 @parse_arguments({"arena_address": ""}, {}) 

7003 @only_if_gdb_running 

7004 def do_invoke(self, *_: Any, **kwargs: Any) -> None: 

7005 args : argparse.Namespace = kwargs["arguments"] 

7006 if not gef.heap.main_arena or not gef.heap.selected_arena: 7006 ↛ 7007line 7006 didn't jump to line 7007 because the condition on line 7006 was never true

7007 err("Heap not initialized") 

7008 return 

7009 

7010 arena_addr = args.arena_address if args.arena_address else f"{gef.heap.selected_arena.addr:#x}" 

7011 gef_print(titlify(f"Unsorted Bin for arena at {arena_addr}")) 

7012 heap_bins_cmd = gef.gdb.commands["heap bins"] 

7013 assert isinstance(heap_bins_cmd, GlibcHeapBinsCommand) 

7014 nb_chunk = heap_bins_cmd.pprint_bin(f"*{arena_addr}", 0, "unsorted_") 

7015 if nb_chunk >= 0: 7015 ↛ 7017line 7015 didn't jump to line 7017 because the condition on line 7015 was always true

7016 info(f"Found {nb_chunk:d} chunks in unsorted bin.") 

7017 return 

7018 

7019 

7020@register 

7021class GlibcHeapSmallBinsCommand(GenericCommand): 

7022 """Convenience command for viewing small bins.""" 

7023 

7024 _cmdline_ = "heap bins small" 

7025 _syntax_ = f"{_cmdline_} [ARENA_ADDRESS]" 

7026 

7027 def __init__(self) -> None: 

7028 super().__init__(complete=gdb.COMPLETE_LOCATION) 

7029 return 

7030 

7031 @parse_arguments({"arena_address": ""}, {}) 

7032 @only_if_gdb_running 

7033 def do_invoke(self, *_: Any, **kwargs: Any) -> None: 

7034 args : argparse.Namespace = kwargs["arguments"] 

7035 if not gef.heap.main_arena or not gef.heap.selected_arena: 7035 ↛ 7036line 7035 didn't jump to line 7036 because the condition on line 7035 was never true

7036 err("Heap not initialized") 

7037 return 

7038 

7039 arena_address = args.arena_address or f"{gef.heap.selected_arena.address:#x}" 

7040 gef_print(titlify(f"Small Bins for arena at {arena_address}")) 

7041 bins: Dict[int, int] = {} 

7042 heap_bins_cmd = gef.gdb.commands["heap bins"] 

7043 assert isinstance (heap_bins_cmd, GlibcHeapBinsCommand) 

7044 for i in range(1, 63): 

7045 nb_chunk = heap_bins_cmd.pprint_bin(f"*{arena_address}", i, "small_") 

7046 if nb_chunk < 0: 7046 ↛ 7047line 7046 didn't jump to line 7047 because the condition on line 7046 was never true

7047 break 

7048 if nb_chunk > 0: 

7049 bins[i] = nb_chunk 

7050 info(f"Found {sum(list(bins.values())):d} chunks in {len(bins):d} small non-empty bins.") 

7051 return 

7052 

7053 

7054@register 

7055class GlibcHeapLargeBinsCommand(GenericCommand): 

7056 """Convenience command for viewing large bins.""" 

7057 

7058 _cmdline_ = "heap bins large" 

7059 _syntax_ = f"{_cmdline_} [ARENA_ADDRESS]" 

7060 

7061 def __init__(self) -> None: 

7062 super().__init__(complete=gdb.COMPLETE_LOCATION) 

7063 return 

7064 

7065 @parse_arguments({"arena_address": ""}, {}) 

7066 @only_if_gdb_running 

7067 def do_invoke(self, *_: Any, **kwargs: Any) -> None: 

7068 args : argparse.Namespace = kwargs["arguments"] 

7069 if not gef.heap.main_arena or not gef.heap.selected_arena: 7069 ↛ 7070line 7069 didn't jump to line 7070 because the condition on line 7069 was never true

7070 err("Heap not initialized") 

7071 return 

7072 

7073 arena_addr = args.arena_address if args.arena_address else f"{gef.heap.selected_arena.addr:#x}" 

7074 gef_print(titlify(f"Large Bins for arena at {arena_addr}")) 

7075 bins = {} 

7076 heap_bins_cmd = gef.gdb.commands["heap bins"] 

7077 assert isinstance(heap_bins_cmd, GlibcHeapBinsCommand) 

7078 for i in range(63, 126): 

7079 nb_chunk = heap_bins_cmd.pprint_bin(f"*{arena_addr}", i, "large_") 

7080 if nb_chunk < 0: 7080 ↛ 7081line 7080 didn't jump to line 7081 because the condition on line 7080 was never true

7081 break 

7082 if nb_chunk > 0: 

7083 bins[i] = nb_chunk 

7084 info(f"Found {sum(bins.values()):d} chunks in {len(bins):d} large non-empty bins.") 

7085 return 

7086 

7087 

7088@register 

7089class DetailRegistersCommand(GenericCommand): 

7090 """Display full details on one, many or all registers value from current architecture.""" 

7091 

7092 _cmdline_ = "registers" 

7093 _syntax_ = f"{_cmdline_} [[Register1][Register2] ... [RegisterN]]" 

7094 _example_ = (f"\n{_cmdline_}" 

7095 f"\n{_cmdline_} $eax $eip $esp") 

7096 

7097 @only_if_gdb_running 

7098 @parse_arguments({"registers": [""]}, {}) 

7099 def do_invoke(self, _: List[str], **kwargs: Any) -> None: 

7100 unchanged_color = gef.config["theme.registers_register_name"] 

7101 changed_color = gef.config["theme.registers_value_changed"] 

7102 string_color = gef.config["theme.dereference_string"] 

7103 regs = gef.arch.all_registers 

7104 

7105 args : argparse.Namespace = kwargs["arguments"] 

7106 if args.registers and args.registers[0]: 

7107 all_regs = set(gef.arch.all_registers) 

7108 regs = [reg for reg in args.registers if reg in all_regs] 

7109 invalid_regs = [reg for reg in args.registers if reg not in all_regs] 

7110 if invalid_regs: 7110 ↛ 7111line 7110 didn't jump to line 7111 because the condition on line 7110 was never true

7111 err(f"invalid registers for architecture: {', '.join(invalid_regs)}") 

7112 

7113 memsize = gef.arch.ptrsize 

7114 endian = str(gef.arch.endianness) 

7115 charset = string.printable 

7116 widest = max(map(len, gef.arch.all_registers)) 

7117 special_line = "" 

7118 

7119 for regname in regs: 

7120 reg = gdb.parse_and_eval(regname) 

7121 if reg.type.code == gdb.TYPE_CODE_VOID: 7121 ↛ 7122line 7121 didn't jump to line 7122 because the condition on line 7121 was never true

7122 continue 

7123 

7124 padreg = regname.ljust(widest, " ") 

7125 

7126 if str(reg) == "<unavailable>": 7126 ↛ 7127line 7126 didn't jump to line 7127 because the condition on line 7126 was never true

7127 gef_print(f"{Color.colorify(padreg, unchanged_color)}: " 

7128 f"{Color.colorify('no value', 'yellow underline')}") 

7129 continue 

7130 

7131 value = align_address(int(reg)) 

7132 ctx_cmd = gef.gdb.commands["context"] 

7133 assert isinstance(ctx_cmd, ContextCommand) 

7134 old_value = ctx_cmd.old_registers.get(regname, 0) 

7135 if value == old_value: 

7136 color = unchanged_color 

7137 else: 

7138 color = changed_color 

7139 

7140 # Special (e.g. segment) registers go on their own line 

7141 if regname in gef.arch.special_registers: 

7142 special_line += f"{Color.colorify(regname, color)}: " 

7143 special_line += f"{gef.arch.register(regname):#04x} " 

7144 continue 

7145 

7146 line = f"{Color.colorify(padreg, color)}: " 

7147 

7148 if regname == gef.arch.flag_register: 

7149 line += gef.arch.flag_register_to_human() 

7150 gef_print(line) 

7151 continue 

7152 

7153 addr = lookup_address(align_address(int(value))) 

7154 if addr.valid: 

7155 line += str(addr) 

7156 else: 

7157 line += format_address_spaces(value) 

7158 addrs = dereference_from(value) 

7159 

7160 if len(addrs) > 1: 

7161 sep = f" {RIGHT_ARROW} " 

7162 line += sep 

7163 line += sep.join(addrs[1:]) 

7164 

7165 # check to see if reg value is ascii 

7166 try: 

7167 fmt = f"{endian}{'I' if memsize == 4 else 'Q'}" 

7168 last_addr = int(addrs[-1], 16) 

7169 val = gef_pystring(struct.pack(fmt, last_addr)) 

7170 if all([_ in charset for _ in val]): 

7171 line += f" (\"{Color.colorify(val, string_color)}\"?)" 

7172 except ValueError: 

7173 pass 

7174 

7175 gef_print(line) 

7176 

7177 if special_line: 7177 ↛ 7179line 7177 didn't jump to line 7179 because the condition on line 7177 was always true

7178 gef_print(special_line) 

7179 return 

7180 

7181 

7182@register 

7183class ShellcodeCommand(GenericCommand): 

7184 """ShellcodeCommand uses @JonathanSalwan simple-yet-awesome shellcode API to 

7185 download shellcodes.""" 

7186 

7187 _cmdline_ = "shellcode" 

7188 _syntax_ = f"{_cmdline_} (search|get)" 

7189 

7190 def __init__(self) -> None: 

7191 super().__init__(prefix=True) 

7192 return 

7193 

7194 def do_invoke(self, _: List[str]) -> None: 

7195 err("Missing sub-command (search|get)") 

7196 self.usage() 

7197 return 

7198 

7199 

7200@register 

7201class ShellcodeSearchCommand(GenericCommand): 

7202 """Search pattern in shell-storm's shellcode database.""" 

7203 

7204 _cmdline_ = "shellcode search" 

7205 _syntax_ = f"{_cmdline_} PATTERN1 PATTERN2" 

7206 _aliases_ = ["sc-search",] 

7207 

7208 api_base = "http://shell-storm.org" 

7209 search_url = f"{api_base}/api/?s=" 

7210 

7211 def do_invoke(self, argv: List[str]) -> None: 

7212 if not argv: 7212 ↛ 7213line 7212 didn't jump to line 7213 because the condition on line 7212 was never true

7213 err("Missing pattern to search") 

7214 self.usage() 

7215 return 

7216 

7217 self.search_shellcode(argv) 

7218 return 

7219 

7220 def search_shellcode(self, search_options: List) -> None: 

7221 # API : http://shell-storm.org/shellcode/ 

7222 args = "*".join(search_options) 

7223 

7224 res = http_get(self.search_url + args) 

7225 if res is None: 7225 ↛ 7226line 7225 didn't jump to line 7226 because the condition on line 7225 was never true

7226 err("Could not query search page") 

7227 return 

7228 

7229 ret = gef_pystring(res) 

7230 

7231 # format: [author, OS/arch, cmd, id, link] 

7232 lines = ret.split("\\n") 

7233 refs = [line.split("::::") for line in lines] 

7234 

7235 if refs: 7235 ↛ 7246line 7235 didn't jump to line 7246 because the condition on line 7235 was always true

7236 info("Showing matching shellcodes") 

7237 info("\t".join(["Id", "Platform", "Description"])) 

7238 for ref in refs: 

7239 try: 

7240 _, arch, cmd, sid, _ = ref 

7241 gef_print("\t".join([sid, arch, cmd])) 

7242 except ValueError: 

7243 continue 

7244 

7245 info("Use `shellcode get <id>` to fetch shellcode") 

7246 return 

7247 

7248 

7249@register 

7250class ShellcodeGetCommand(GenericCommand): 

7251 """Download shellcode from shell-storm's shellcode database.""" 

7252 

7253 _cmdline_ = "shellcode get" 

7254 _syntax_ = f"{_cmdline_} SHELLCODE_ID" 

7255 _aliases_ = ["sc-get",] 

7256 

7257 api_base = "http://shell-storm.org" 

7258 get_url = f"{api_base}/shellcode/files/shellcode-{{:d}}.html" 

7259 

7260 def do_invoke(self, argv: List[str]) -> None: 

7261 if len(argv) != 1: 7261 ↛ 7262line 7261 didn't jump to line 7262 because the condition on line 7261 was never true

7262 err("Missing ID to download") 

7263 self.usage() 

7264 return 

7265 

7266 if not argv[0].isdigit(): 7266 ↛ 7267line 7266 didn't jump to line 7267 because the condition on line 7266 was never true

7267 err("ID is not a number") 

7268 self.usage() 

7269 return 

7270 

7271 self.get_shellcode(int(argv[0])) 

7272 return 

7273 

7274 def get_shellcode(self, sid: int) -> None: 

7275 info(f"Downloading shellcode id={sid}") 

7276 res = http_get(self.get_url.format(sid)) 

7277 if res is None: 

7278 err(f"Failed to fetch shellcode #{sid}") 

7279 return 

7280 

7281 ok("Downloaded, written to disk...") 

7282 with tempfile.NamedTemporaryFile(prefix="sc-", suffix=".txt", mode='w+b', delete=False, dir=gef.config["gef.tempdir"]) as fd: 

7283 shellcode = res.split(b"<pre>")[1].split(b"</pre>")[0] 

7284 shellcode = shellcode.replace(b"&quot;", b'"') 

7285 fd.write(shellcode) 

7286 ok(f"Shellcode written to '{fd.name}'") 

7287 return 

7288 

7289 

7290@register 

7291class ProcessListingCommand(GenericCommand): 

7292 """List and filter process. If a PATTERN is given as argument, results shown will be grepped 

7293 by this pattern.""" 

7294 

7295 _cmdline_ = "process-search" 

7296 _syntax_ = f"{_cmdline_} [-h] [--attach] [--smart-scan] [REGEX_PATTERN]" 

7297 _aliases_ = ["ps"] 

7298 _example_ = f"{_cmdline_} gdb.*" 

7299 

7300 def __init__(self) -> None: 

7301 super().__init__(complete=gdb.COMPLETE_LOCATION) 

7302 self["ps_command"] = (f"{gef.session.constants['ps']} auxww", "`ps` command to get process information") 

7303 return 

7304 

7305 @parse_arguments({"pattern": ""}, {"--attach": False, "--smart-scan": False}) 

7306 def do_invoke(self, _: List, **kwargs: Any) -> None: 

7307 args : argparse.Namespace = kwargs["arguments"] 

7308 do_attach = args.attach 

7309 smart_scan = args.smart_scan 

7310 pattern = args.pattern 

7311 pattern = re.compile("^.*$") if not args else re.compile(pattern) 

7312 

7313 for process in self.get_processes(): 

7314 pid = int(process["pid"]) 

7315 command = process["command"] 

7316 

7317 if not re.search(pattern, command): 

7318 continue 

7319 

7320 if smart_scan: 7320 ↛ 7321line 7320 didn't jump to line 7321 because the condition on line 7320 was never true

7321 if command.startswith("[") and command.endswith("]"): continue 

7322 if command.startswith("socat "): continue 

7323 if command.startswith("grep "): continue 

7324 if command.startswith("gdb "): continue 

7325 

7326 if args and do_attach: 7326 ↛ 7327line 7326 didn't jump to line 7327 because the condition on line 7326 was never true

7327 ok(f"Attaching to process='{process['command']}' pid={pid:d}") 

7328 gdb.execute(f"attach {pid:d}") 

7329 return None 

7330 

7331 line = [process[i] for i in ("pid", "user", "cpu", "mem", "tty", "command")] 

7332 gef_print("\t\t".join(line)) 

7333 

7334 return None 

7335 

7336 def get_processes(self) -> Generator[Dict[str, str], None, None]: 

7337 output = gef_execute_external(self["ps_command"].split(), True) 

7338 names = [x.lower().replace("%", "") for x in output[0].split()] 

7339 

7340 for line in output[1:]: 

7341 fields = line.split() 

7342 t = {} 

7343 

7344 for i, name in enumerate(names): 

7345 if i == len(names) - 1: 

7346 t[name] = " ".join(fields[i:]) 

7347 else: 

7348 t[name] = fields[i] 

7349 

7350 yield t 

7351 

7352 return 

7353 

7354 

7355@register 

7356class ElfInfoCommand(GenericCommand): 

7357 """Display a limited subset of ELF header information. If no argument is provided, the command will 

7358 show information about the current ELF being debugged.""" 

7359 

7360 _cmdline_ = "elf-info" 

7361 _syntax_ = f"{_cmdline_} [FILE]" 

7362 _example_ = f"{_cmdline_} /bin/ls" 

7363 

7364 def __init__(self) -> None: 

7365 super().__init__(complete=gdb.COMPLETE_LOCATION) 

7366 return 

7367 

7368 @parse_arguments({}, {"--filename": ""}) 

7369 def do_invoke(self, _: List[str], **kwargs: Any) -> None: 

7370 args : argparse.Namespace = kwargs["arguments"] 

7371 

7372 if is_qemu_system(): 7372 ↛ 7373line 7372 didn't jump to line 7373 because the condition on line 7372 was never true

7373 err("Unsupported") 

7374 return 

7375 

7376 filename = args.filename or get_filepath() 

7377 if filename is None: 7377 ↛ 7378line 7377 didn't jump to line 7378 because the condition on line 7377 was never true

7378 return 

7379 

7380 try: 

7381 elf = Elf(filename) 

7382 except ValueError: 

7383 err(f"`{filename}` is an invalid value for ELF file") 

7384 return 

7385 

7386 data = [ 

7387 ("Magic", f"{hexdump(struct.pack('>I', elf.e_magic), show_raw=True)}"), 

7388 ("Class", f"{elf.e_class.value:#x} - {elf.e_class.name}"), 

7389 ("Endianness", f"{elf.e_endianness.value:#x} - {Endianness(elf.e_endianness).name}"), 

7390 ("Version", f"{elf.e_eiversion:#x}"), 

7391 ("OS ABI", f"{elf.e_osabi.value:#x} - {elf.e_osabi.name if elf.e_osabi else ''}"), 

7392 ("ABI Version", f"{elf.e_abiversion:#x}"), 

7393 ("Type", f"{elf.e_type.value:#x} - {elf.e_type.name}"), 

7394 ("Machine", f"{elf.e_machine.value:#x} - {elf.e_machine.name}"), 

7395 ("Program Header Table", f"{format_address(elf.e_phoff)}"), 

7396 ("Section Header Table", f"{format_address(elf.e_shoff)}"), 

7397 ("Header Table", f"{format_address(elf.e_phoff)}"), 

7398 ("ELF Version", f"{elf.e_version:#x}"), 

7399 ("Header size", f"{elf.e_ehsize} ({elf.e_ehsize:#x})"), 

7400 ("Entry point", f"{format_address(elf.e_entry)}"), 

7401 ] 

7402 

7403 for title, content in data: 

7404 gef_print(f"{Color.boldify(f'{title:<22}')}: {content}") 

7405 

7406 gef_print("") 

7407 gef_print(titlify("Program Header")) 

7408 

7409 gef_print(f" [{'#':>2s}] {'Type':12s} {'Offset':>8s} {'Virtaddr':>10s} {'Physaddr':>10s}" 

7410 f" {'FileSiz':>8s} {'MemSiz':>8s} {'Flags':5s} {'Align':>8s}") 

7411 

7412 for i, p in enumerate(elf.phdrs): 

7413 p_type = p.p_type.name if p.p_type else "" 

7414 p_flags = str(p.p_flags.name).lstrip("Flag.") if p.p_flags else "???" 

7415 

7416 gef_print(f" [{i:2d}] {p_type:12s} {p.p_offset:#8x} {p.p_vaddr:#10x} {p.p_paddr:#10x}" 

7417 f" {p.p_filesz:#8x} {p.p_memsz:#8x} {p_flags:5s} {p.p_align:#8x}") 

7418 

7419 gef_print("") 

7420 gef_print(titlify("Section Header")) 

7421 gef_print(f" [{'#':>2s}] {'Name':20s} {'Type':>15s} {'Address':>10s} {'Offset':>8s}" 

7422 f" {'Size':>8s} {'EntSiz':>8s} {'Flags':5s} {'Link':4s} {'Info':4s} {'Align':>8s}") 

7423 

7424 for i, s in enumerate(elf.shdrs): 

7425 sh_type = s.sh_type.name if s.sh_type else "UNKN" 

7426 sh_flags = str(s.sh_flags).lstrip("Flags.") if s.sh_flags else "UNKN" 

7427 

7428 gef_print(f" [{i:2d}] {s.name:20s} {sh_type:>15s} {s.sh_addr:#10x} {s.sh_offset:#8x} " 

7429 f"{s.sh_size:#8x} {s.sh_entsize:#8x} {sh_flags:5s} {s.sh_link:#4x} {s.sh_info:#4x} {s.sh_addralign:#8x}") 

7430 return 

7431 

7432 

7433@register 

7434class EntryPointBreakCommand(GenericCommand): 

7435 """Tries to find best entry point and sets a temporary breakpoint on it. The command will test for 

7436 well-known symbols for entry points, such as `main`, `_main`, `__libc_start_main`, etc. defined by 

7437 the setting `entrypoint_symbols`.""" 

7438 

7439 _cmdline_ = "entry-break" 

7440 _syntax_ = _cmdline_ 

7441 _aliases_ = ["start",] 

7442 

7443 def __init__(self) -> None: 

7444 super().__init__() 

7445 self["entrypoint_symbols"] = ("main _main __libc_start_main __uClibc_main start _start", "Possible symbols for entry points") 

7446 return 

7447 

7448 def do_invoke(self, argv: List[str]) -> None: 

7449 fpath = get_filepath() 

7450 if fpath is None: 7450 ↛ 7451line 7450 didn't jump to line 7451 because the condition on line 7450 was never true

7451 warn("No executable to debug, use `file` to load a binary") 

7452 return 

7453 

7454 if not os.access(fpath, os.X_OK): 7454 ↛ 7455line 7454 didn't jump to line 7455 because the condition on line 7454 was never true

7455 warn(f"The file '{fpath}' is not executable.") 

7456 return 

7457 

7458 if is_alive() and not gef.session.qemu_mode: 

7459 warn("gdb is already running") 

7460 return 

7461 

7462 bp = None 

7463 entrypoints = self["entrypoint_symbols"].split() 

7464 

7465 for sym in entrypoints: 

7466 try: 

7467 value = parse_address(sym) 

7468 info(f"Breaking at '{value:#x}'") 

7469 bp = EntryBreakBreakpoint(sym) 

7470 gdb.execute(f"run {' '.join(argv)}") 

7471 return 

7472 

7473 except gdb.error as gdb_error: 

7474 if 'The "remote" target does not support "run".' in str(gdb_error): 7474 ↛ 7476line 7474 didn't jump to line 7476 because the condition on line 7474 was never true

7475 # this case can happen when doing remote debugging 

7476 gdb.execute("continue") 

7477 return 

7478 continue 

7479 

7480 # if here, clear the breakpoint if any set 

7481 if bp: 7481 ↛ 7482line 7481 didn't jump to line 7482 because the condition on line 7481 was never true

7482 bp.delete() 

7483 

7484 assert gef.binary 

7485 # break at entry point 

7486 entry = gef.binary.entry_point 

7487 

7488 if is_pie(fpath): 7488 ↛ 7493line 7488 didn't jump to line 7493 because the condition on line 7488 was always true

7489 self.set_init_tbreak_pie(entry, argv) 

7490 gdb.execute("continue") 

7491 return 

7492 

7493 self.set_init_tbreak(entry) 

7494 gdb.execute(f"run {' '.join(argv)}") 

7495 return 

7496 

7497 def set_init_tbreak(self, addr: int) -> EntryBreakBreakpoint: 

7498 info(f"Breaking at entry-point: {addr:#x}") 

7499 bp = EntryBreakBreakpoint(f"*{addr:#x}") 

7500 return bp 

7501 

7502 def set_init_tbreak_pie(self, addr: int, argv: List[str]) -> EntryBreakBreakpoint: 

7503 warn("PIC binary detected, retrieving text base address") 

7504 gdb.execute("set stop-on-solib-events 1") 

7505 hide_context() 

7506 gdb.execute(f"run {' '.join(argv)}") 

7507 unhide_context() 

7508 gdb.execute("set stop-on-solib-events 0") 

7509 vmmap = gef.memory.maps 

7510 base_address = [x.page_start for x in vmmap if x.path == get_filepath()][0] 

7511 return self.set_init_tbreak(base_address + addr) 

7512 

7513 

7514@register 

7515class NamedBreakpointCommand(GenericCommand): 

7516 """Sets a breakpoint and assigns a name to it, which will be shown, when it's hit.""" 

7517 

7518 _cmdline_ = "name-break" 

7519 _syntax_ = f"{_cmdline_} name [address]" 

7520 _aliases_ = ["nb",] 

7521 _example = f"{_cmdline_} main *0x4008a9" 

7522 

7523 def __init__(self) -> None: 

7524 super().__init__() 

7525 return 

7526 

7527 @parse_arguments({"name": "", "address": "*$pc"}, {}) 

7528 def do_invoke(self, _: List[str], **kwargs: Any) -> None: 

7529 args : argparse.Namespace = kwargs["arguments"] 

7530 if not args.name: 7530 ↛ 7531line 7530 didn't jump to line 7531 because the condition on line 7530 was never true

7531 err("Missing name for breakpoint") 

7532 self.usage() 

7533 return 

7534 

7535 NamedBreakpoint(args.address, args.name) 

7536 return 

7537 

7538 

7539@register 

7540class ContextCommand(GenericCommand): 

7541 """Displays a comprehensive and modular summary of runtime context. Unless setting `enable` is 

7542 set to False, this command will be spawned automatically every time GDB hits a breakpoint, a 

7543 watchpoint, or any kind of interrupt. By default, it will show panes that contain the register 

7544 states, the stack, and the disassembly code around $pc.""" 

7545 

7546 _cmdline_ = "context" 

7547 _syntax_ = f"{_cmdline_} [legend|regs|stack|code|args|memory|source|trace|threads|extra]" 

7548 _aliases_ = ["ctx",] 

7549 

7550 old_registers: Dict[str, Optional[int]] = {} 

7551 

7552 def __init__(self) -> None: 

7553 super().__init__() 

7554 self["enable"] = (True, "Enable/disable printing the context when breaking") 

7555 self["show_source_code_variable_values"] = (True, "Show extra PC context info in the source code") 

7556 self["show_full_source_file_name_max_len"] = (30, "Show full source path name, if less than this value") 

7557 self["show_basename_source_file_name_max_len"] = (20, "Show the source basename in full, if less than this value") 

7558 self["show_prefix_source_path_name_len"] = (10, "When truncating source path, show this many path prefix characters") 

7559 self["show_stack_raw"] = (False, "Show the stack pane as raw hexdump (no dereference)") 

7560 self["show_registers_raw"] = (False, "Show the registers pane with raw values (no dereference)") 

7561 self["show_opcodes_size"] = (0, "Number of bytes of opcodes to display next to the disassembly") 

7562 self["peek_calls"] = (True, "Peek into calls") 

7563 self["peek_ret"] = (True, "Peek at return address") 

7564 self["nb_lines_stack"] = (8, "Number of line in the stack pane") 

7565 self["grow_stack_down"] = (False, "Order of stack downward starts at largest down to stack pointer") 

7566 self["nb_lines_backtrace"] = (10, "Number of line in the backtrace pane") 

7567 self["nb_lines_backtrace_before"] = (2, "Number of line in the backtrace pane before selected frame") 

7568 self["nb_lines_threads"] = (-1, "Number of line in the threads pane") 

7569 self["nb_lines_code"] = (6, "Number of instruction after $pc") 

7570 self["nb_lines_code_prev"] = (3, "Number of instruction before $pc") 

7571 self["ignore_registers"] = ("", "Space-separated list of registers not to display (e.g. '$cs $ds $gs')") 

7572 self["clear_screen"] = (True, "Clear the screen before printing the context") 

7573 self["layout"] = ("legend regs stack code args source memory threads trace extra", "Change the order/presence of the context sections") 

7574 self["redirect"] = ("", "Redirect the context information to another TTY") 

7575 self["libc_args"] = (False, "[DEPRECATED - Unused] Show libc function call args description") 

7576 self["libc_args_path"] = ("", "[DEPRECATED - Unused] Path to libc function call args json files, provided via gef-extras") 

7577 

7578 self.layout_mapping: Dict[str, Tuple[Callable, Optional[Callable], Optional[Callable]]] = { 

7579 "legend": (self.show_legend, None, None), 

7580 "regs": (self.context_regs, None, None), 

7581 "stack": (self.context_stack, None, None), 

7582 "code": (self.context_code, None, None), 

7583 "args": (self.context_args, None, None), 

7584 "memory": (self.context_memory, None, None), 

7585 "source": (self.context_source, None, None), 

7586 "trace": (self.context_trace, None, None), 

7587 "threads": (self.context_threads, None, None), 

7588 "extra": (self.context_additional_information, None, None), 

7589 } 

7590 

7591 self.instruction_iterator = gef_disassemble 

7592 return 

7593 

7594 def post_load(self) -> None: 

7595 gef_on_continue_hook(self.update_registers) 

7596 gef_on_continue_hook(self.empty_extra_messages) 

7597 return 

7598 

7599 def show_legend(self) -> None: 

7600 if gef.config["gef.disable_color"] is True: 7600 ↛ 7602line 7600 didn't jump to line 7602 because the condition on line 7600 was always true

7601 return 

7602 changed_register_title = Color.colorify("Modified register", gef.config["theme.registers_value_changed"]) 

7603 code_title = Color.colorify("Code", gef.config["theme.address_code"]) 

7604 heap_title = Color.colorify("Heap", gef.config["theme.address_heap"]) 

7605 stack_title = Color.colorify("Stack", gef.config["theme.address_stack"]) 

7606 str_title = Color.colorify("String", gef.config["theme.dereference_string"]) 

7607 gef_print(f"[ Legend: {changed_register_title} | {code_title} | {heap_title} | {stack_title} | {str_title} ]") 

7608 return 

7609 

7610 @only_if_gdb_running 

7611 def do_invoke(self, argv: List[str]) -> None: 

7612 if not self["enable"] or gef.ui.context_hidden: 

7613 return 

7614 

7615 if not all(_ in self.layout_mapping for _ in argv): 7615 ↛ 7616line 7615 didn't jump to line 7616 because the condition on line 7615 was never true

7616 self.usage() 

7617 return 

7618 

7619 if len(argv) > 0: 7619 ↛ 7620line 7619 didn't jump to line 7620 because the condition on line 7619 was never true

7620 current_layout = argv 

7621 else: 

7622 current_layout = self["layout"].strip().split() 

7623 

7624 if not current_layout: 7624 ↛ 7625line 7624 didn't jump to line 7625 because the condition on line 7624 was never true

7625 return 

7626 

7627 self.tty_rows, self.tty_columns = get_terminal_size() 

7628 

7629 redirect = self["redirect"] 

7630 if redirect and os.access(redirect, os.W_OK): 7630 ↛ 7631line 7630 didn't jump to line 7631 because the condition on line 7630 was never true

7631 enable_redirect_output(to_file=redirect) 

7632 

7633 if self["clear_screen"] and len(argv) == 0: 7633 ↛ 7636line 7633 didn't jump to line 7636 because the condition on line 7633 was always true

7634 clear_screen(redirect) 

7635 

7636 for section in current_layout: 

7637 if section[0] == "-": 7637 ↛ 7638line 7637 didn't jump to line 7638 because the condition on line 7637 was never true

7638 continue 

7639 

7640 try: 

7641 display_pane_function, pane_title_function, condition = self.layout_mapping[section] 

7642 if condition: 7642 ↛ 7643line 7642 didn't jump to line 7643 because the condition on line 7642 was never true

7643 if not condition(): 

7644 continue 

7645 if pane_title_function: 7645 ↛ 7646line 7645 didn't jump to line 7646 because the condition on line 7645 was never true

7646 self.context_title(pane_title_function()) 

7647 display_pane_function() 

7648 except gdb.MemoryError as e: 

7649 # a MemoryError will happen when $pc is corrupted (invalid address) 

7650 err(str(e)) 

7651 except IndexError: 

7652 # the `section` is not present, just skip 

7653 pass 

7654 

7655 self.context_title("") 

7656 

7657 if redirect and os.access(redirect, os.W_OK): 7657 ↛ 7658line 7657 didn't jump to line 7658 because the condition on line 7657 was never true

7658 disable_redirect_output() 

7659 return 

7660 

7661 def context_title(self, m: Optional[str]) -> None: 

7662 # allow for not displaying a title line 

7663 if m is None: 7663 ↛ 7664line 7663 didn't jump to line 7664 because the condition on line 7663 was never true

7664 return 

7665 

7666 line_color = gef.config["theme.context_title_line"] 

7667 msg_color = gef.config["theme.context_title_message"] 

7668 

7669 # print an empty line in case of "" 

7670 if not m: 

7671 gef_print(Color.colorify(HORIZONTAL_LINE * self.tty_columns, line_color)) 

7672 return 

7673 

7674 trail_len = len(m) + 6 

7675 title = "" 

7676 width = max(self.tty_columns - trail_len, 0) 

7677 padd = HORIZONTAL_LINE 

7678 title += Color.colorify(f"{'':{padd}<{width}} ", line_color) 

7679 title += Color.colorify(m, msg_color) 

7680 title += Color.colorify(f" {'':{padd}<4}", line_color) 

7681 gef_print(title) 

7682 return 

7683 

7684 def context_regs(self) -> None: 

7685 self.context_title("registers") 

7686 ignored_registers = set(self["ignore_registers"].split()) 

7687 

7688 # Defer to DetailRegisters by default 

7689 if self["show_registers_raw"] is False: 7689 ↛ 7695line 7689 didn't jump to line 7695 because the condition on line 7689 was always true

7690 regs = [reg for reg in gef.arch.all_registers if reg not in ignored_registers] 

7691 printable_registers = " ".join(regs) 

7692 gdb.execute(f"registers {printable_registers}") 

7693 return 

7694 

7695 widest = l = max(map(len, gef.arch.all_registers)) 

7696 l += 5 

7697 l += gef.arch.ptrsize * 2 

7698 nb = get_terminal_size()[1] // l 

7699 i = 1 

7700 line = "" 

7701 changed_color = gef.config["theme.registers_value_changed"] 

7702 regname_color = gef.config["theme.registers_register_name"] 

7703 

7704 for reg in gef.arch.all_registers: 

7705 if reg in ignored_registers: 

7706 continue 

7707 

7708 try: 

7709 r = gdb.parse_and_eval(reg) 

7710 if r.type.code == gdb.TYPE_CODE_VOID: 

7711 continue 

7712 

7713 new_value_type_flag = r.type.code == gdb.TYPE_CODE_FLAGS 

7714 new_value = int(r) 

7715 

7716 except (gdb.MemoryError, gdb.error): 

7717 # If this exception is triggered, it means that the current register 

7718 # is corrupted. Just use the register "raw" value (not eval-ed) 

7719 new_value = gef.arch.register(reg) 

7720 new_value_type_flag = False 

7721 

7722 except Exception: 

7723 new_value = 0 

7724 new_value_type_flag = False 

7725 

7726 old_value = self.old_registers.get(reg, 0) 

7727 

7728 padreg = reg.ljust(widest, " ") 

7729 value = align_address(new_value) 

7730 old_value = align_address(old_value or 0) 

7731 if value == old_value: 

7732 line += f"{Color.colorify(padreg, regname_color)}: " 

7733 else: 

7734 line += f"{Color.colorify(padreg, changed_color)}: " 

7735 if new_value_type_flag: 

7736 line += f"{format_address_spaces(value)} " 

7737 else: 

7738 addr = lookup_address(align_address(int(value))) 

7739 if addr.valid: 

7740 line += f"{addr!s} " 

7741 else: 

7742 line += f"{format_address_spaces(value)} " 

7743 

7744 if i % nb == 0: 

7745 gef_print(line) 

7746 line = "" 

7747 i += 1 

7748 

7749 if line: 

7750 gef_print(line) 

7751 

7752 gef_print(f"Flags: {gef.arch.flag_register_to_human()}") 

7753 return 

7754 

7755 def context_stack(self) -> None: 

7756 self.context_title("stack") 

7757 

7758 show_raw = self["show_stack_raw"] 

7759 nb_lines = self["nb_lines_stack"] 

7760 

7761 try: 

7762 sp = gef.arch.sp 

7763 if show_raw is True: 7763 ↛ 7764line 7763 didn't jump to line 7764 because the condition on line 7763 was never true

7764 mem = gef.memory.read(sp, 0x10 * nb_lines) 

7765 gef_print(hexdump(mem, base=sp)) 

7766 else: 

7767 gdb.execute(f"dereference -l {nb_lines:d} {sp:#x}") 

7768 

7769 except gdb.MemoryError: 

7770 err("Cannot read memory from $SP (corrupted stack pointer?)") 

7771 

7772 return 

7773 

7774 def addr_has_breakpoint(self, address: int, bp_locations: List[str]) -> bool: 

7775 return any(hex(address) in b for b in bp_locations) 

7776 

7777 def context_code(self) -> None: 

7778 nb_insn = self["nb_lines_code"] 

7779 nb_insn_prev = self["nb_lines_code_prev"] 

7780 show_opcodes_size = "show_opcodes_size" in self and self["show_opcodes_size"] 

7781 past_insns_color = gef.config["theme.old_context"] 

7782 cur_insn_color = gef.config["theme.disassemble_current_instruction"] 

7783 pc = gef.arch.pc 

7784 breakpoints = gdb.breakpoints() or [] 

7785 # breakpoint.locations was introduced in gdb 13.1 

7786 if len(breakpoints) and hasattr(breakpoints[-1], "locations"): 7786 ↛ 7787line 7786 didn't jump to line 7787 because the condition on line 7786 was never true

7787 bp_locations = [hex(location.address) for b in breakpoints for location in b.locations if location is not None] 

7788 else: 

7789 # location relies on the user setting the breakpoints with "b *{hex(address)}" 

7790 bp_locations = [b.location for b in breakpoints if b.location and b.location.startswith("*")] 

7791 

7792 frame = gdb.selected_frame() 

7793 arch_name = f"{gef.arch.arch.lower()}:{gef.arch.mode}" 

7794 

7795 self.context_title(f"code:{arch_name}") 

7796 

7797 try: 

7798 

7799 

7800 for insn in self.instruction_iterator(pc, nb_insn, nb_prev=nb_insn_prev): 

7801 line = [] 

7802 is_taken = False 

7803 target = None 

7804 bp_prefix = Color.redify(BP_GLYPH) if self.addr_has_breakpoint(insn.address, bp_locations) else " " 

7805 

7806 if show_opcodes_size == 0: 

7807 text = str(insn) 

7808 else: 

7809 insn_fmt = f"{{:{show_opcodes_size}o}}" 

7810 text = insn_fmt.format(insn) 

7811 

7812 if insn.address < pc: 

7813 line += f"{bp_prefix} {Color.colorify(text, past_insns_color)}" 

7814 

7815 elif insn.address == pc: 

7816 line += f"{bp_prefix}{Color.colorify(f'{RIGHT_ARROW[1:]}{text}', cur_insn_color)}" 

7817 

7818 if gef.arch.is_conditional_branch(insn): 7818 ↛ 7819line 7818 didn't jump to line 7819 because the condition on line 7818 was never true

7819 is_taken, reason = gef.arch.is_branch_taken(insn) 

7820 if is_taken: 

7821 target = insn.operands[-1].split()[0] 

7822 reason = f"[Reason: {reason}]" if reason else "" 

7823 line += Color.colorify(f"\tTAKEN {reason}", "bold green") 

7824 else: 

7825 reason = f"[Reason: !({reason})]" if reason else "" 

7826 line += Color.colorify(f"\tNOT taken {reason}", "bold red") 

7827 elif gef.arch.is_call(insn) and self["peek_calls"] is True: 7827 ↛ 7828line 7827 didn't jump to line 7828 because the condition on line 7827 was never true

7828 target = insn.operands[-1].split()[0] 

7829 elif gef.arch.is_ret(insn) and self["peek_ret"] is True: 

7830 target = gef.arch.get_ra(insn, frame) 

7831 

7832 else: 

7833 line += f"{bp_prefix} {text}" 

7834 

7835 gef_print("".join(line)) 

7836 

7837 if target: 

7838 try: 

7839 address = int(target, 0) if isinstance(target, str) else target 

7840 except ValueError: 

7841 # If the operand isn't an address right now we can't parse it 

7842 continue 

7843 for i, tinsn in enumerate(self.instruction_iterator(address, nb_insn)): 7843 ↛ 7844,   7843 ↛ 78462 missed branches: 1) line 7843 didn't jump to line 7844 because the loop on line 7843 never started, 2) line 7843 didn't jump to line 7846 because the loop on line 7843 didn't complete

7844 text= f" {DOWN_ARROW if i == 0 else ' '} {tinsn!s}" 

7845 gef_print(text) 

7846 break 

7847 

7848 except gdb.MemoryError: 

7849 err("Cannot disassemble from $PC") 

7850 return 

7851 

7852 def context_args(self) -> None: 

7853 insn = gef_current_instruction(gef.arch.pc) 

7854 if not gef.arch.is_call(insn): 7854 ↛ 7857line 7854 didn't jump to line 7857

7855 return 

7856 

7857 self.size2type = { 

7858 1: "BYTE", 

7859 2: "WORD", 

7860 4: "DWORD", 

7861 8: "QWORD", 

7862 } 

7863 

7864 if insn.operands[-1].startswith(self.size2type[gef.arch.ptrsize]+" PTR"): 

7865 target = "*" + insn.operands[-1].split()[-1] 

7866 elif "$"+insn.operands[0] in gef.arch.all_registers: 

7867 target = f"*{gef.arch.register('$' + insn.operands[0]):#x}" 

7868 else: 

7869 # is there a symbol? 

7870 ops = " ".join(insn.operands) 

7871 if "<" in ops and ">" in ops: 

7872 # extract it 

7873 target = re.sub(r".*<([^\(> ]*).*", r"\1", ops) 

7874 else: 

7875 # it's an address, just use as is 

7876 target = re.sub(r".*(0x[a-fA-F0-9]*).*", r"\1", ops) 

7877 

7878 sym = gdb.lookup_global_symbol(target) 

7879 if sym is None: 

7880 self.print_guessed_arguments(target) 

7881 return 

7882 

7883 if sym.type and sym.type.code != gdb.TYPE_CODE_FUNC: 

7884 err(f"Symbol '{target}' is not a function: type={sym.type.code}") 

7885 return 

7886 

7887 self.print_arguments_from_symbol(target, sym) 

7888 return 

7889 

7890 def print_arguments_from_symbol(self, function_name: str, symbol: "gdb.Symbol") -> None: 

7891 """If symbols were found, parse them and print the argument adequately.""" 

7892 args = [] 

7893 fields = symbol.type.fields() if symbol.type else [] 

7894 for i, f in enumerate(fields): 

7895 _value = gef.arch.get_ith_parameter(i, in_func=False)[1] 

7896 _value = RIGHT_ARROW.join(dereference_from(_value)) 

7897 _name = f.name or f"var_{i}" 

7898 _type = f.type.name or self.size2type[f.type.sizeof] 

7899 args.append(f"{_type} {_name} = {_value}") 

7900 

7901 self.context_title("arguments") 

7902 

7903 if not args: 

7904 gef_print(f"{function_name} (<void>)") 

7905 return 

7906 

7907 gef_print(f"{function_name} (\n "+",\n ".join(args)+"\n)") 

7908 return 

7909 

7910 def print_guessed_arguments(self, function_name: str) -> None: 

7911 """When no symbol, read the current basic block and look for "interesting" instructions.""" 

7912 

7913 def __get_current_block_start_address() -> Optional[int]: 

7914 pc = gef.arch.pc 

7915 try: 

7916 block = gdb.block_for_pc(pc) 

7917 block_start = block.start if block else gdb_get_nth_previous_instruction_address(pc, 5) 

7918 except RuntimeError: 

7919 block_start = gdb_get_nth_previous_instruction_address(pc, 5) 

7920 return block_start 

7921 

7922 parameter_set = set() 

7923 pc = gef.arch.pc 

7924 block_start = __get_current_block_start_address() 

7925 if not block_start: 

7926 return 

7927 

7928 function_parameters = gef.arch.function_parameters 

7929 arg_key_color = gef.config["theme.registers_register_name"] 

7930 

7931 for insn in self.instruction_iterator(block_start, pc - block_start): 

7932 if not insn.operands: 

7933 continue 

7934 

7935 if is_x86_32(): 

7936 if insn.mnemonic == "push": 

7937 parameter_set.add(insn.operands[0]) 

7938 else: 

7939 op = "$" + insn.operands[0] 

7940 if op in function_parameters: 

7941 parameter_set.add(op) 

7942 

7943 if is_x86_64(): 

7944 # also consider extended registers 

7945 extended_registers = {"$rdi": ["$edi", "$di"], 

7946 "$rsi": ["$esi", "$si"], 

7947 "$rdx": ["$edx", "$dx"], 

7948 "$rcx": ["$ecx", "$cx"], 

7949 } 

7950 for exreg in extended_registers: 

7951 if op in extended_registers[exreg]: 

7952 parameter_set.add(exreg) 

7953 

7954 if is_x86_32(): 

7955 nb_argument = len(parameter_set) 

7956 else: 

7957 nb_argument = max([function_parameters.index(p)+1 for p in parameter_set], default=0) 

7958 

7959 args = [] 

7960 for i in range(nb_argument): 

7961 _key, _values = gef.arch.get_ith_parameter(i, in_func=False) 

7962 _values = RIGHT_ARROW.join(dereference_from(_values)) 

7963 args.append(f"{Color.colorify(_key, arg_key_color)} = {_values}") 

7964 

7965 self.context_title("arguments (guessed)") 

7966 gef_print(f"{function_name} (") 

7967 if args: 

7968 gef_print(" " + ",\n ".join(args)) 

7969 gef_print(")") 

7970 return 

7971 

7972 def line_has_breakpoint(self, file_name: str, line_number: int, bp_locations: List[str]) -> bool: 

7973 filename_line = f"{file_name}:{line_number}" 

7974 return any(filename_line in loc for loc in bp_locations) 

7975 

7976 def context_source(self) -> None: 

7977 try: 

7978 pc = gef.arch.pc 

7979 symtabline = gdb.find_pc_line(pc) 

7980 symtab = symtabline.symtab 

7981 # we subtract one because the line number returned by gdb start at 1 

7982 line_num = symtabline.line - 1 

7983 if not symtab.is_valid(): 7983 ↛ 7984line 7983 didn't jump to line 7984 because the condition on line 7983 was never true

7984 return 

7985 

7986 fpath = symtab.fullname() 

7987 with open(fpath, "r") as f: 

7988 lines = [l.rstrip() for l in f.readlines()] 

7989 

7990 except Exception: 

7991 return 

7992 

7993 file_base_name = os.path.basename(symtab.filename) 

7994 breakpoints = gdb.breakpoints() or [] 

7995 bp_locations = [b.location for b in breakpoints if b.location and file_base_name in b.location] 

7996 past_lines_color = gef.config["theme.old_context"] 

7997 

7998 show_full_path_max = self["show_full_source_file_name_max_len"] 

7999 show_basename_path_max = self["show_basename_source_file_name_max_len"] 

8000 

8001 nb_line = self["nb_lines_code"] 

8002 fn = symtab.filename 

8003 if len(fn) > show_full_path_max: 8003 ↛ 8004line 8003 didn't jump to line 8004 because the condition on line 8003 was never true

8004 base = os.path.basename(fn) 

8005 if len(base) > show_basename_path_max: 

8006 base = base[-show_basename_path_max:] 

8007 fn = fn[:15] + "[...]" + base 

8008 title = f"source:{fn}+{line_num + 1}" 

8009 cur_line_color = gef.config["theme.source_current_line"] 

8010 self.context_title(title) 

8011 show_extra_info = self["show_source_code_variable_values"] 

8012 

8013 for i in range(line_num - nb_line + 1, line_num + nb_line): 

8014 if i < 0: 

8015 continue 

8016 

8017 bp_prefix = Color.redify(BP_GLYPH) if self.line_has_breakpoint(file_base_name, i + 1, bp_locations) else " " 

8018 

8019 if i < line_num: 

8020 gef_print("{}{}".format(bp_prefix, Color.colorify(f" {i + 1:4d}\t {lines[i]}", past_lines_color))) 

8021 

8022 if i == line_num: 

8023 prefix = f"{bp_prefix}{RIGHT_ARROW[1:]}{i + 1:4d}\t " 

8024 leading = len(lines[i]) - len(lines[i].lstrip()) 

8025 if show_extra_info: 8025 ↛ 8029line 8025 didn't jump to line 8029 because the condition on line 8025 was always true

8026 extra_info = self.get_pc_context_info(pc, lines[i]) 

8027 if extra_info: 

8028 gef_print(f"{' ' * (len(prefix) + leading)}{extra_info}") 

8029 gef_print(Color.colorify(f"{prefix}{lines[i]}", cur_line_color)) 

8030 

8031 if i > line_num: 

8032 try: 

8033 gef_print(f"{bp_prefix} {i + 1:4d}\t {lines[i]}") 

8034 except IndexError: 

8035 break 

8036 return 

8037 

8038 def get_pc_context_info(self, pc: int, line: str) -> str: 

8039 try: 

8040 current_block = gdb.block_for_pc(pc) 

8041 if not current_block or not current_block.is_valid(): return "" 8041 ↛ exitline 8041 didn't return from function 'get_pc_context_info' because the return on line 8041 wasn't executed

8042 m = collections.OrderedDict() 

8043 while current_block and not current_block.is_static: 

8044 for sym in list(current_block): 

8045 symbol = sym.name 

8046 if not sym.is_function and re.search(fr"\W{symbol}\W", line): 

8047 val = gdb.parse_and_eval(symbol) 

8048 if val.type.code in (gdb.TYPE_CODE_PTR, gdb.TYPE_CODE_ARRAY): 8048 ↛ 8049line 8048 didn't jump to line 8049 because the condition on line 8048 was never true

8049 addr = int(val.address) 

8050 addrs = dereference_from(addr) 

8051 if len(addrs) > 2: 

8052 addrs = [addrs[0], "[...]", addrs[-1]] 

8053 

8054 f = f" {RIGHT_ARROW} " 

8055 val = f.join(addrs) 

8056 elif val.type.code == gdb.TYPE_CODE_INT: 8056 ↛ 8059line 8056 didn't jump to line 8059 because the condition on line 8056 was always true

8057 val = hex(int(val)) 

8058 else: 

8059 continue 

8060 

8061 if symbol not in m: 8061 ↛ 8044line 8061 didn't jump to line 8044 because the condition on line 8061 was always true

8062 m[symbol] = val 

8063 current_block = current_block.superblock 

8064 

8065 if m: 

8066 return "// " + ", ".join([f"{Color.yellowify(a)}={b}" for a, b in m.items()]) 

8067 except Exception: 

8068 pass 

8069 return "" 

8070 

8071 def context_trace(self) -> None: 

8072 self.context_title("trace") 

8073 

8074 nb_backtrace = self["nb_lines_backtrace"] 

8075 if nb_backtrace <= 0: 8075 ↛ 8076line 8075 didn't jump to line 8076 because the condition on line 8075 was never true

8076 return 

8077 

8078 # backward compat for gdb (gdb < 7.10) 

8079 if not hasattr(gdb, "FrameDecorator"): 8079 ↛ 8080line 8079 didn't jump to line 8080 because the condition on line 8079 was never true

8080 gdb.execute(f"backtrace {nb_backtrace:d}") 

8081 return 

8082 

8083 orig_frame: gdb.Frame = gdb.selected_frame() 

8084 current_frame: gdb.Frame = gdb.newest_frame() 

8085 frames = [current_frame,] 

8086 while current_frame != orig_frame and current_frame: 8086 ↛ 8087line 8086 didn't jump to line 8087 because the condition on line 8086 was never true

8087 current_frame = current_frame.older() 

8088 if not current_frame: break 

8089 frames.append(current_frame) 

8090 

8091 nb_backtrace_before = self["nb_lines_backtrace_before"] 

8092 level = max(len(frames) - nb_backtrace_before - 1, 0) 

8093 current_frame: gdb.Frame = frames[level] 

8094 

8095 while current_frame: 8095 ↛ 8135line 8095 didn't jump to line 8135 because the condition on line 8095 was always true

8096 current_frame.select() 

8097 if not current_frame.is_valid(): 8097 ↛ 8098line 8097 didn't jump to line 8098 because the condition on line 8097 was never true

8098 continue 

8099 

8100 pc = int(current_frame.pc()) 

8101 name = current_frame.name() 

8102 items = [] 

8103 items.append(f"{pc:#x}") 

8104 if name: 

8105 frame_args = gdb.FrameDecorator.FrameDecorator(current_frame).frame_args() or [] # type: ignore 

8106 symstr= ", ".join([f"{Color.yellowify(x.sym)}={x.sym.value(current_frame)!s}" for x in frame_args]) 

8107 m = f"{Color.greenify(name)}({symstr})" 

8108 items.append(m) 

8109 else: 

8110 try: 

8111 insn = next(gef_disassemble(int(pc), 1)) 

8112 except gdb.MemoryError: 

8113 break 

8114 

8115 # check if the gdb symbol table may know the address 

8116 sym_found = gdb_get_location_from_symbol(pc) 

8117 symbol = "" 

8118 if sym_found: 8118 ↛ 8119line 8118 didn't jump to line 8119 because the condition on line 8118 was never true

8119 sym_name, offset = sym_found 

8120 symbol = f" <{sym_name}+{offset:x}> " 

8121 

8122 items.append(Color.redify(f"{symbol}{insn.mnemonic} {', '.join(insn.operands)}")) 

8123 

8124 title = Color.colorify(f"#{level}", "bold green" if current_frame == orig_frame else "bold pink") 

8125 gef_print(f"[{title}] {RIGHT_ARROW.join(items)}") 

8126 older = current_frame.older() 

8127 level += 1 

8128 nb_backtrace -= 1 

8129 if nb_backtrace == 0: 

8130 break 

8131 if not older: 

8132 break 

8133 current_frame = older 

8134 

8135 orig_frame.select() 

8136 return 

8137 

8138 def context_threads(self) -> None: 

8139 def reason() -> str: 

8140 res = gdb.execute("info program", to_string=True) 

8141 if not res: 8141 ↛ 8142line 8141 didn't jump to line 8142 because the condition on line 8141 was never true

8142 return "NOT RUNNING" 

8143 

8144 for line in res.splitlines(): 8144 ↛ 8157line 8144 didn't jump to line 8157 because the loop on line 8144 didn't complete

8145 line = line.strip() 

8146 if line.startswith("It stopped with signal "): 

8147 return line.replace("It stopped with signal ", "").split(",", 1)[0] 

8148 if line == "The program being debugged is not being run.": 8148 ↛ 8149line 8148 didn't jump to line 8149 because the condition on line 8148 was never true

8149 return "NOT RUNNING" 

8150 if line == "It stopped at a breakpoint that has since been deleted.": 8150 ↛ 8151line 8150 didn't jump to line 8151 because the condition on line 8150 was never true

8151 return "TEMPORARY BREAKPOINT" 

8152 if line.startswith("It stopped at breakpoint "): 

8153 return "BREAKPOINT" 

8154 if line == "It stopped after being stepped.": 8154 ↛ 8155line 8154 didn't jump to line 8155 because the condition on line 8154 was never true

8155 return "SINGLE STEP" 

8156 

8157 return "STOPPED" 

8158 

8159 self.context_title("threads") 

8160 

8161 threads = gdb.selected_inferior().threads()[::-1] 

8162 idx = self["nb_lines_threads"] 

8163 if idx > 0: 8163 ↛ 8164line 8163 didn't jump to line 8164 because the condition on line 8163 was never true

8164 threads = threads[0:idx] 

8165 

8166 if idx == 0: 8166 ↛ 8167line 8166 didn't jump to line 8167 because the condition on line 8166 was never true

8167 return 

8168 

8169 if not threads: 8169 ↛ 8170line 8169 didn't jump to line 8170 because the condition on line 8169 was never true

8170 err("No thread selected") 

8171 return 

8172 

8173 selected_thread = gdb.selected_thread() 

8174 selected_frame = gdb.selected_frame() 

8175 

8176 for i, thread in enumerate(threads): 

8177 line = f"[{Color.colorify(f'#{i:d}', 'bold green' if thread == selected_thread else 'bold pink')}] Id {thread.num:d}, " 

8178 if thread.name: 

8179 line += f"""Name: "{thread.name}", """ 

8180 if thread.is_running(): 8180 ↛ 8181line 8180 didn't jump to line 8181 because the condition on line 8180 was never true

8181 line += Color.colorify("running", "bold green") 

8182 elif thread.is_stopped(): 8182 ↛ 8198line 8182 didn't jump to line 8198 because the condition on line 8182 was always true

8183 line += Color.colorify("stopped", "bold red") 

8184 thread.switch() 

8185 frame = gdb.selected_frame() 

8186 frame_name = frame.name() 

8187 

8188 # check if the gdb symbol table may know the address 

8189 if not frame_name: 

8190 sym_found = gdb_get_location_from_symbol(int(frame.pc())) 

8191 if sym_found: 8191 ↛ 8192line 8191 didn't jump to line 8192 because the condition on line 8191 was never true

8192 sym_name, offset = sym_found 

8193 frame_name = f"<{sym_name}+{offset:x}>" 

8194 

8195 line += (f" {Color.colorify(f'{frame.pc():#x}', 'blue')} in " 

8196 f"{Color.colorify(frame_name or '??', 'bold yellow')} (), " 

8197 f"reason: {Color.colorify(reason(), 'bold pink')}") 

8198 elif thread.is_exited(): 

8199 line += Color.colorify("exited", "bold yellow") 

8200 gef_print(line) 

8201 i += 1 

8202 

8203 selected_thread.switch() 

8204 selected_frame.select() 

8205 return 

8206 

8207 def context_additional_information(self) -> None: 

8208 if not gef.ui.context_messages: 

8209 return 

8210 

8211 self.context_title("extra") 

8212 for level, text in gef.ui.context_messages: 

8213 if level == "error": err(text) 8213 ↛ 8212line 8213 didn't jump to line 8212 because

8214 elif level == "warn": warn(text) 8214 ↛ 8215line 8214 didn't jump to line 8215 because the condition on line 8214 was always true

8215 elif level == "success": ok(text) 

8216 else: info(text) 

8217 return 

8218 

8219 def context_memory(self) -> None: 

8220 for address, opt in sorted(gef.ui.watches.items()): 

8221 sz, fmt = opt[0:2] 

8222 self.context_title(f"memory:{address:#x}") 

8223 if fmt == "pointers": 8223 ↛ 8224line 8223 didn't jump to line 8224 because the condition on line 8223 was never true

8224 gdb.execute(f"dereference -l {sz:d} {address:#x}") 

8225 else: 

8226 gdb.execute(f"hexdump {fmt} -s {sz:d} {address:#x}") 

8227 

8228 @classmethod 

8229 def update_registers(cls, _) -> None: 

8230 for reg in gef.arch.all_registers: 

8231 try: 

8232 cls.old_registers[reg] = gef.arch.register(reg) 

8233 except Exception: 

8234 cls.old_registers[reg] = 0 

8235 return 

8236 

8237 def empty_extra_messages(self, _) -> None: 

8238 gef.ui.context_messages.clear() 

8239 return 

8240 

8241 

8242@register 

8243class MemoryCommand(GenericCommand): 

8244 """Add or remove address ranges to the memory view.""" 

8245 _cmdline_ = "memory" 

8246 _syntax_ = f"{_cmdline_} (watch|unwatch|reset|list)" 

8247 

8248 def __init__(self) -> None: 

8249 super().__init__(prefix=True) 

8250 return 

8251 

8252 @only_if_gdb_running 

8253 def do_invoke(self, argv: List[str]) -> None: 

8254 self.usage() 

8255 return 

8256 

8257 

8258@register 

8259class MemoryWatchCommand(GenericCommand): 

8260 """Adds address ranges to the memory view.""" 

8261 _cmdline_ = "memory watch" 

8262 _syntax_ = f"{_cmdline_} ADDRESS [SIZE] [(qword|dword|word|byte|pointers)]" 

8263 _example_ = (f"\n{_cmdline_} 0x603000 0x100 byte" 

8264 f"\n{_cmdline_} $sp") 

8265 

8266 def __init__(self) -> None: 

8267 super().__init__(complete=gdb.COMPLETE_LOCATION) 

8268 return 

8269 

8270 @only_if_gdb_running 

8271 def do_invoke(self, argv: List[str]) -> None: 

8272 if len(argv) not in (1, 2, 3): 8272 ↛ 8273line 8272 didn't jump to line 8273 because the condition on line 8272 was never true

8273 self.usage() 

8274 return 

8275 

8276 address = parse_address(argv[0]) 

8277 size = parse_address(argv[1]) if len(argv) > 1 else 0x10 

8278 group = "byte" 

8279 

8280 if len(argv) == 3: 

8281 group = argv[2].lower() 

8282 if group not in ("qword", "dword", "word", "byte", "pointers"): 8282 ↛ 8283line 8282 didn't jump to line 8283 because the condition on line 8282 was never true

8283 warn(f"Unexpected grouping '{group}'") 

8284 self.usage() 

8285 return 

8286 else: 

8287 if gef.arch.ptrsize == 4: 8287 ↛ 8288line 8287 didn't jump to line 8288 because the condition on line 8287 was never true

8288 group = "dword" 

8289 elif gef.arch.ptrsize == 8: 8289 ↛ 8292line 8289 didn't jump to line 8292 because the condition on line 8289 was always true

8290 group = "qword" 

8291 

8292 gef.ui.watches[address] = (size, group) 

8293 ok(f"Adding memwatch to {address:#x}") 

8294 return 

8295 

8296 

8297@register 

8298class MemoryUnwatchCommand(GenericCommand): 

8299 """Removes address ranges to the memory view.""" 

8300 _cmdline_ = "memory unwatch" 

8301 _syntax_ = f"{_cmdline_} ADDRESS" 

8302 _example_ = (f"\n{_cmdline_} 0x603000" 

8303 f"\n{_cmdline_} $sp") 

8304 

8305 def __init__(self) -> None: 

8306 super().__init__(complete=gdb.COMPLETE_LOCATION) 

8307 return 

8308 

8309 @only_if_gdb_running 

8310 def do_invoke(self, argv: List[str]) -> None: 

8311 if not argv: 8311 ↛ 8312line 8311 didn't jump to line 8312 because the condition on line 8311 was never true

8312 self.usage() 

8313 return 

8314 

8315 address = parse_address(argv[0]) 

8316 res = gef.ui.watches.pop(address, None) 

8317 if not res: 8317 ↛ 8320line 8317 didn't jump to line 8320 because the condition on line 8317 was always true

8318 warn(f"You weren't watching {address:#x}") 

8319 else: 

8320 ok(f"Removed memwatch of {address:#x}") 

8321 return 

8322 

8323 

8324@register 

8325class MemoryWatchResetCommand(GenericCommand): 

8326 """Removes all watchpoints.""" 

8327 _cmdline_ = "memory reset" 

8328 _syntax_ = f"{_cmdline_}" 

8329 

8330 @only_if_gdb_running 

8331 def do_invoke(self, _: List[str]) -> None: 

8332 gef.ui.watches.clear() 

8333 ok("Memory watches cleared") 

8334 return 

8335 

8336 

8337@register 

8338class MemoryWatchListCommand(GenericCommand): 

8339 """Lists all watchpoints to display in context layout.""" 

8340 _cmdline_ = "memory list" 

8341 _syntax_ = f"{_cmdline_}" 

8342 

8343 @only_if_gdb_running 

8344 def do_invoke(self, _: List[str]) -> None: 

8345 if not gef.ui.watches: 8345 ↛ 8349line 8345 didn't jump to line 8349 because the condition on line 8345 was always true

8346 info("No memory watches") 

8347 return 

8348 

8349 info("Memory watches:") 

8350 for address, opt in sorted(gef.ui.watches.items()): 

8351 gef_print(f"- {address:#x} ({opt[0]}, {opt[1]})") 

8352 return 

8353 

8354 

8355@register 

8356class HexdumpCommand(GenericCommand): 

8357 """Display SIZE lines of hexdump from the memory location pointed by LOCATION.""" 

8358 

8359 _cmdline_ = "hexdump" 

8360 _syntax_ = f"{_cmdline_} (qword|dword|word|byte) [LOCATION] [--size SIZE] [--reverse]" 

8361 _example_ = f"{_cmdline_} byte $rsp --size 16 --reverse" 

8362 

8363 def __init__(self) -> None: 

8364 super().__init__(complete=gdb.COMPLETE_LOCATION, prefix=True) 

8365 self["always_show_ascii"] = (False, "If true, hexdump will always display the ASCII dump") 

8366 self.format: Optional[str] = None 

8367 self.__last_target = "$sp" 

8368 return 

8369 

8370 @only_if_gdb_running 

8371 @parse_arguments({"address": "",}, {("--reverse", "-r"): False, ("--size", "-s"): 0}) 

8372 def do_invoke(self, _: List[str], **kwargs: Any) -> None: 

8373 valid_formats = ["byte", "word", "dword", "qword"] 

8374 if not self.format or self.format not in valid_formats: 8374 ↛ 8375line 8374 didn't jump to line 8375 because the condition on line 8374 was never true

8375 err("Invalid command") 

8376 return 

8377 

8378 args : argparse.Namespace = kwargs["arguments"] 

8379 target = args.address or self.__last_target 

8380 start_addr = parse_address(target) 

8381 read_from = align_address(start_addr) 

8382 

8383 if self.format == "byte": 

8384 read_len = args.size or 0x40 

8385 read_from += self.repeat_count * read_len 

8386 mem = gef.memory.read(read_from, read_len) 

8387 lines = hexdump(mem, base=read_from).splitlines() 

8388 else: 

8389 read_len = args.size or 0x10 

8390 lines = self._hexdump(read_from, read_len, self.format, self.repeat_count * read_len) 

8391 

8392 if args.reverse: 

8393 lines.reverse() 

8394 

8395 self.__last_target = target 

8396 gef_print("\n".join(lines)) 

8397 return 

8398 

8399 def _hexdump(self, start_addr: int, length: int, arrange_as: str, offset: int = 0) -> List[str]: 

8400 endianness = gef.arch.endianness 

8401 

8402 base_address_color = gef.config["theme.dereference_base_address"] 

8403 show_ascii = gef.config["hexdump.always_show_ascii"] 

8404 

8405 formats = { 

8406 "qword": ("Q", 8), 

8407 "dword": ("I", 4), 

8408 "word": ("H", 2), 

8409 } 

8410 

8411 r, l = formats[arrange_as] 

8412 fmt_str = f"{{base}}{VERTICAL_LINE}+{{offset:#06x}} {{sym}}{{val:#0{l*2+2}x}} {{text}}" 

8413 fmt_pack = f"{endianness!s}{r}" 

8414 lines = [] 

8415 

8416 i = 0 

8417 text = "" 

8418 while i < length: 

8419 cur_addr = start_addr + (i + offset) * l 

8420 sym = gdb_get_location_from_symbol(cur_addr) 

8421 sym = f"<{sym[0]:s}+{sym[1]:04x}> " if sym else "" 

8422 mem = gef.memory.read(cur_addr, l) 

8423 val = struct.unpack(fmt_pack, mem)[0] 

8424 if show_ascii: 8424 ↛ 8425line 8424 didn't jump to line 8425 because the condition on line 8424 was never true

8425 text = "".join([chr(b) if 0x20 <= b < 0x7F else "." for b in mem]) 

8426 lines.append(fmt_str.format(base=Color.colorify(format_address(cur_addr), base_address_color), 

8427 offset=(i + offset) * l, sym=sym, val=val, text=text)) 

8428 i += 1 

8429 

8430 return lines 

8431 

8432 

8433@register 

8434class HexdumpQwordCommand(HexdumpCommand): 

8435 """Display SIZE lines of hexdump as QWORD from the memory location pointed by ADDRESS.""" 

8436 

8437 _cmdline_ = "hexdump qword" 

8438 _syntax_ = f"{_cmdline_} [ADDRESS] [--size SIZE] [--reverse]" 

8439 _example_ = f"{_cmdline_} qword $rsp -s 16 --reverse" 

8440 

8441 def __init__(self) -> None: 

8442 super().__init__() 

8443 self.format = "qword" 

8444 return 

8445 

8446 

8447@register 

8448class HexdumpDwordCommand(HexdumpCommand): 

8449 """Display SIZE lines of hexdump as DWORD from the memory location pointed by ADDRESS.""" 

8450 

8451 _cmdline_ = "hexdump dword" 

8452 _syntax_ = f"{_cmdline_} [ADDRESS] [--size SIZE] [--reverse]" 

8453 _example_ = f"{_cmdline_} $esp -s 16 --reverse" 

8454 

8455 def __init__(self) -> None: 

8456 super().__init__() 

8457 self.format = "dword" 

8458 return 

8459 

8460 

8461@register 

8462class HexdumpWordCommand(HexdumpCommand): 

8463 """Display SIZE lines of hexdump as WORD from the memory location pointed by ADDRESS.""" 

8464 

8465 _cmdline_ = "hexdump word" 

8466 _syntax_ = f"{_cmdline_} [ADDRESS] [--size SIZE] [--reverse]" 

8467 _example_ = f"{_cmdline_} $esp -s 16 --reverse" 

8468 

8469 def __init__(self) -> None: 

8470 super().__init__() 

8471 self.format = "word" 

8472 return 

8473 

8474 

8475@register 

8476class HexdumpByteCommand(HexdumpCommand): 

8477 """Display SIZE lines of hexdump as BYTE from the memory location pointed by ADDRESS.""" 

8478 

8479 _cmdline_ = "hexdump byte" 

8480 _syntax_ = f"{_cmdline_} [ADDRESS] [--size SIZE] [--reverse]" 

8481 _example_ = f"{_cmdline_} $rsp -s 16" 

8482 

8483 def __init__(self) -> None: 

8484 super().__init__() 

8485 self.format = "byte" 

8486 return 

8487 

8488 

8489@register 

8490class PatchCommand(GenericCommand): 

8491 """Write specified values to the specified address.""" 

8492 

8493 _cmdline_ = "patch" 

8494 _syntax_ = (f"{_cmdline_} (qword|dword|word|byte) LOCATION VALUES\n" 

8495 f"{_cmdline_} string LOCATION \"double-escaped string\"") 

8496 SUPPORTED_SIZES = { 

8497 "qword": (8, "Q"), 

8498 "dword": (4, "L"), 

8499 "word": (2, "H"), 

8500 "byte": (1, "B"), 

8501 } 

8502 

8503 def __init__(self) -> None: 

8504 super().__init__(prefix=True, complete=gdb.COMPLETE_LOCATION) 

8505 self.format: Optional[str] = None 

8506 return 

8507 

8508 @only_if_gdb_running 

8509 @parse_arguments({"location": "", "values": ["", ]}, {}) 

8510 def do_invoke(self, _: List[str], **kwargs: Any) -> None: 

8511 args : argparse.Namespace = kwargs["arguments"] 

8512 if not self.format or self.format not in self.SUPPORTED_SIZES: 8512 ↛ 8513line 8512 didn't jump to line 8513 because the condition on line 8512 was never true

8513 self.usage() 

8514 return 

8515 

8516 if not args.location or not args.values: 8516 ↛ 8517line 8516 didn't jump to line 8517 because the condition on line 8516 was never true

8517 self.usage() 

8518 return 

8519 

8520 addr = align_address(parse_address(args.location)) 

8521 size, fcode = self.SUPPORTED_SIZES[self.format] 

8522 values = args.values 

8523 

8524 if size == 1: 

8525 if values[0].startswith("$_gef"): 

8526 var_name = values[0] 

8527 try: 

8528 values = str(gdb.parse_and_eval(var_name)).lstrip("{").rstrip("}").replace(",","").split(" ") 

8529 except: 

8530 gef_print(f"Bad variable specified, check value with command: p {var_name}") 

8531 return 

8532 

8533 d = str(gef.arch.endianness) 

8534 for value in values: 

8535 value = parse_address(value) & ((1 << size * 8) - 1) 

8536 vstr = struct.pack(d + fcode, value) 

8537 gef.memory.write(addr, vstr, length=size) 

8538 addr += size 

8539 return 

8540 

8541 

8542@register 

8543class PatchQwordCommand(PatchCommand): 

8544 """Write specified QWORD to the specified address.""" 

8545 

8546 _cmdline_ = "patch qword" 

8547 _syntax_ = f"{_cmdline_} LOCATION QWORD1 [QWORD2 [QWORD3..]]" 

8548 _example_ = f"{_cmdline_} $rip 0x4141414141414141" 

8549 

8550 def __init__(self) -> None: 

8551 super().__init__() 

8552 self.format = "qword" 

8553 return 

8554 

8555 

8556@register 

8557class PatchDwordCommand(PatchCommand): 

8558 """Write specified DWORD to the specified address.""" 

8559 

8560 _cmdline_ = "patch dword" 

8561 _syntax_ = f"{_cmdline_} LOCATION DWORD1 [DWORD2 [DWORD3..]]" 

8562 _example_ = f"{_cmdline_} $rip 0x41414141" 

8563 

8564 def __init__(self) -> None: 

8565 super().__init__() 

8566 self.format = "dword" 

8567 return 

8568 

8569 

8570@register 

8571class PatchWordCommand(PatchCommand): 

8572 """Write specified WORD to the specified address.""" 

8573 

8574 _cmdline_ = "patch word" 

8575 _syntax_ = f"{_cmdline_} LOCATION WORD1 [WORD2 [WORD3..]]" 

8576 _example_ = f"{_cmdline_} $rip 0x4141" 

8577 

8578 def __init__(self) -> None: 

8579 super().__init__() 

8580 self.format = "word" 

8581 return 

8582 

8583 

8584@register 

8585class PatchByteCommand(PatchCommand): 

8586 """Write specified BYTE to the specified address.""" 

8587 

8588 _cmdline_ = "patch byte" 

8589 _syntax_ = f"{_cmdline_} LOCATION BYTE1 [BYTE2 [BYTE3..]]" 

8590 _example_ = f"{_cmdline_} $pc 0x41 0x41 0x41 0x41 0x41" 

8591 

8592 def __init__(self) -> None: 

8593 super().__init__() 

8594 self.format = "byte" 

8595 return 

8596 

8597 

8598@register 

8599class PatchStringCommand(GenericCommand): 

8600 """Write specified string to the specified memory location pointed by ADDRESS.""" 

8601 

8602 _cmdline_ = "patch string" 

8603 _syntax_ = f"{_cmdline_} ADDRESS \"double backslash-escaped string\"" 

8604 _example_ = [ 

8605 f"{_cmdline_} $sp \"GEFROCKS\"", 

8606 f"{_cmdline_} $sp \"\\\\x41\\\\x41\\\\x41\\\\x41\"" 

8607 ] 

8608 

8609 @only_if_gdb_running 

8610 def do_invoke(self, argv: List[str]) -> None: 

8611 argc = len(argv) 

8612 if argc != 2: 8612 ↛ 8613line 8612 didn't jump to line 8613 because the condition on line 8612 was never true

8613 self.usage() 

8614 return 

8615 

8616 location, msg = argv[0:2] 

8617 addr = align_address(parse_address(location)) 

8618 

8619 try: 

8620 msg_as_bytes = codecs.escape_decode(msg, "utf-8")[0] 

8621 gef.memory.write(addr, msg_as_bytes, len(msg_as_bytes)) 

8622 except (binascii.Error, gdb.error): 

8623 err(f"Could not decode '\\xXX' encoded string \"{msg}\"") 

8624 return 

8625 

8626 

8627@lru_cache() 

8628def dereference_from(address: int) -> List[str]: 

8629 if not is_alive(): 8629 ↛ 8630line 8629 didn't jump to line 8630 because the condition on line 8629 was never true

8630 return [format_address(address),] 

8631 

8632 code_color = gef.config["theme.dereference_code"] 

8633 string_color = gef.config["theme.dereference_string"] 

8634 max_recursion = gef.config["dereference.max_recursion"] or 10 

8635 addr = lookup_address(align_address(address)) 

8636 msg = [format_address(addr.value),] 

8637 seen_addrs = set() 

8638 

8639 while addr.section and max_recursion: 

8640 if addr.value in seen_addrs: 

8641 msg.append("[loop detected]") 

8642 break 

8643 seen_addrs.add(addr.value) 

8644 

8645 max_recursion -= 1 

8646 

8647 # Is this value a pointer or a value? 

8648 # -- If it's a pointer, dereference 

8649 deref = addr.dereference() 

8650 if deref is None: 

8651 # if here, dereferencing addr has triggered a MemoryError, no need to go further 

8652 msg.append(str(addr)) 

8653 break 

8654 

8655 new_addr = lookup_address(deref) 

8656 if new_addr.valid: 

8657 addr = new_addr 

8658 msg.append(str(addr)) 

8659 continue 

8660 

8661 # -- Otherwise try to parse the value 

8662 if addr.section: 8662 ↛ 8683line 8662 didn't jump to line 8683 because the condition on line 8662 was always true

8663 if addr.section.is_executable() and addr.is_in_text_segment() and not is_ascii_string(addr.value): 

8664 insn = gef_current_instruction(addr.value) 

8665 insn_str = f"{insn.location} {insn.mnemonic} {', '.join(insn.operands)}" 

8666 msg.append(Color.colorify(insn_str, code_color)) 

8667 break 

8668 

8669 elif addr.section.permission & Permission.READ: 8669 ↛ 8683line 8669 didn't jump to line 8683 because the condition on line 8669 was always true

8670 if is_ascii_string(addr.value): 

8671 s = gef.memory.read_cstring(addr.value) 

8672 if len(s) < gef.arch.ptrsize: 

8673 txt = f'{format_address(deref)} ("{Color.colorify(s, string_color)}"?)' 

8674 elif len(s) > GEF_MAX_STRING_LENGTH: 

8675 txt = Color.colorify(f'"{s[:GEF_MAX_STRING_LENGTH]}[...]"', string_color) 

8676 else: 

8677 txt = Color.colorify(f'"{s}"', string_color) 

8678 

8679 msg.append(txt) 

8680 break 

8681 

8682 # if not able to parse cleanly, simply display and break 

8683 msg.append(format_address(deref)) 

8684 break 

8685 

8686 return msg 

8687 

8688 

8689@register 

8690class DereferenceCommand(GenericCommand): 

8691 """Dereference recursively from an address and display information. This acts like WinDBG `dps` 

8692 command.""" 

8693 

8694 _cmdline_ = "dereference" 

8695 _syntax_ = f"{_cmdline_} [-h] [--length LENGTH] [--reference REFERENCE] [address]" 

8696 _aliases_ = ["telescope", ] 

8697 _example_ = f"{_cmdline_} --length 20 --reference $sp+0x10 $sp" 

8698 

8699 def __init__(self) -> None: 

8700 super().__init__(complete=gdb.COMPLETE_LOCATION) 

8701 self["max_recursion"] = (7, "Maximum level of pointer recursion") 

8702 return 

8703 

8704 @staticmethod 

8705 def pprint_dereferenced(addr: int, idx: int, base_offset: int = 0) -> str: 

8706 base_address_color = gef.config["theme.dereference_base_address"] 

8707 registers_color = gef.config["theme.dereference_register_value"] 

8708 

8709 sep = f" {RIGHT_ARROW} " 

8710 memalign = gef.arch.ptrsize 

8711 

8712 offset = idx * memalign 

8713 current_address = align_address(addr + offset) 

8714 addrs = dereference_from(current_address) 

8715 addr_l = format_address(int(addrs[0], 16)) 

8716 ma = (memalign*2 + 2) 

8717 l = ( 

8718 f"{Color.colorify(addr_l, base_address_color)}{VERTICAL_LINE}" 

8719 f"{base_offset+offset:+#07x}: {sep.join(addrs[1:]):{ma}s}" 

8720 ) 

8721 

8722 register_hints = [] 

8723 

8724 for regname in gef.arch.all_registers: 

8725 regvalue = gef.arch.register(regname) 

8726 if current_address == regvalue: 

8727 register_hints.append(regname) 

8728 

8729 if register_hints: 

8730 m = f"\t{LEFT_ARROW}{', '.join(list(register_hints))}" 

8731 l += Color.colorify(m, registers_color) 

8732 

8733 offset += memalign 

8734 return l 

8735 

8736 @only_if_gdb_running 

8737 @parse_arguments({"address": "$sp"}, {("-r", "--reference"): "", ("-l", "--length"): 10}) 

8738 def do_invoke(self, _: List[str], **kwargs: Any) -> None: 

8739 args : argparse.Namespace = kwargs["arguments"] 

8740 nb = args.length 

8741 

8742 target = args.address 

8743 target_addr = parse_address(target) 

8744 

8745 reference = args.reference or target 

8746 ref_addr = parse_address(reference) 

8747 

8748 if process_lookup_address(target_addr) is None: 

8749 err(f"Unmapped address: '{target}'") 

8750 return 

8751 

8752 if process_lookup_address(ref_addr) is None: 8752 ↛ 8753line 8752 didn't jump to line 8753 because the condition on line 8752 was never true

8753 err(f"Unmapped address: '{reference}'") 

8754 return 

8755 

8756 if gef.config["context.grow_stack_down"] is True: 8756 ↛ 8757line 8756 didn't jump to line 8757 because the condition on line 8756 was never true

8757 insnum_step = -1 

8758 if nb > 0: 

8759 from_insnum = nb * (self.repeat_count + 1) - 1 

8760 to_insnum = self.repeat_count * nb - 1 

8761 else: 

8762 from_insnum = self.repeat_count * nb 

8763 to_insnum = nb * (self.repeat_count + 1) 

8764 else: 

8765 insnum_step = 1 

8766 if nb > 0: 

8767 from_insnum = self.repeat_count * nb 

8768 to_insnum = nb * (self.repeat_count + 1) 

8769 else: 

8770 from_insnum = nb * (self.repeat_count + 1) + 1 

8771 to_insnum = (self.repeat_count * nb) + 1 

8772 

8773 start_address = align_address(target_addr) 

8774 base_offset = start_address - align_address(ref_addr) 

8775 

8776 dereference_cmd = gef.gdb.commands["dereference"] 

8777 assert isinstance(dereference_cmd, DereferenceCommand) 

8778 for i in range(from_insnum, to_insnum, insnum_step): 

8779 gef_print(dereference_cmd.pprint_dereferenced(start_address, i, base_offset)) 

8780 

8781 return 

8782 

8783 

8784@register 

8785class ASLRCommand(GenericCommand): 

8786 """View/modify the ASLR setting of GDB. By default, GDB will disable ASLR when it starts the process. (i.e. not 

8787 attached). This command allows to change that setting.""" 

8788 

8789 _cmdline_ = "aslr" 

8790 _syntax_ = f"{_cmdline_} [(on|off)]" 

8791 

8792 def do_invoke(self, argv: List[str]) -> None: 

8793 argc = len(argv) 

8794 

8795 if argc == 0: 

8796 ret = gdb.execute("show disable-randomization", to_string=True) or "" 

8797 i = ret.find("virtual address space is ") 

8798 if i < 0: 8798 ↛ 8799line 8798 didn't jump to line 8799 because the condition on line 8798 was never true

8799 return 

8800 

8801 msg = "ASLR is currently " 

8802 if ret[i + 25:].strip() == "on.": 

8803 msg += Color.redify("disabled") 

8804 else: 

8805 msg += Color.greenify("enabled") 

8806 

8807 gef_print(msg) 

8808 return 

8809 

8810 elif argc == 1: 8810 ↛ 8822line 8810 didn't jump to line 8822 because the condition on line 8810 was always true

8811 if argv[0] == "on": 8811 ↛ 8815line 8811 didn't jump to line 8815 because the condition on line 8811 was always true

8812 info("Enabling ASLR") 

8813 gdb.execute("set disable-randomization off") 

8814 return 

8815 elif argv[0] == "off": 

8816 info("Disabling ASLR") 

8817 gdb.execute("set disable-randomization on") 

8818 return 

8819 

8820 warn("Invalid command") 

8821 

8822 self.usage() 

8823 return 

8824 

8825 

8826@register 

8827class ResetCacheCommand(GenericCommand): 

8828 """Reset cache of all stored data. This command is here for debugging and test purposes, GEF 

8829 handles properly the cache reset under "normal" scenario.""" 

8830 

8831 _cmdline_ = "reset-cache" 

8832 _syntax_ = _cmdline_ 

8833 

8834 def do_invoke(self, _: List[str]) -> None: 

8835 reset_all_caches() 

8836 return 

8837 

8838 

8839@register 

8840class VMMapCommand(GenericCommand): 

8841 """Display a comprehensive layout of the virtual memory mapping. If a filter argument, GEF will 

8842 filter out the mapping whose pathname do not match that filter.""" 

8843 

8844 _cmdline_ = "vmmap" 

8845 _syntax_ = f"{_cmdline_} [FILTER]" 

8846 _example_ = f"{_cmdline_} libc" 

8847 

8848 @only_if_gdb_running 

8849 def do_invoke(self, argv: List[str]) -> None: 

8850 vmmap = gef.memory.maps 

8851 if not vmmap: 8851 ↛ 8852line 8851 didn't jump to line 8852 because the condition on line 8851 was never true

8852 err("No address mapping information found") 

8853 return 

8854 

8855 if not gef.config["gef.disable_color"]: 8855 ↛ 8856line 8855 didn't jump to line 8856 because the condition on line 8855 was never true

8856 self.show_legend() 

8857 

8858 color = gef.config["theme.table_heading"] 

8859 

8860 headers = ["Start", "End", "Offset", "Perm", "Path"] 

8861 gef_print(Color.colorify("{:<{w}s}{:<{w}s}{:<{w}s}{:<4s} {:s}".format(*headers, w=gef.arch.ptrsize*2+3), color)) 

8862 

8863 for entry in vmmap: 

8864 if not argv: 

8865 self.print_entry(entry) 

8866 continue 

8867 if argv[0] in entry.path: 

8868 self.print_entry(entry) 

8869 elif self.is_integer(argv[0]): 8869 ↛ 8870line 8869 didn't jump to line 8870 because the condition on line 8869 was never true

8870 addr = int(argv[0], 0) 

8871 if addr >= entry.page_start and addr < entry.page_end: 

8872 self.print_entry(entry) 

8873 else: 

8874 addr = safe_parse_and_eval(argv[0]) 

8875 if addr is not None and addr >= entry.page_start and addr < entry.page_end: 

8876 self.print_entry(entry) 

8877 return 

8878 

8879 def print_entry(self, entry: Section) -> None: 

8880 line_color = "" 

8881 if entry.path == "[stack]": 

8882 line_color = gef.config["theme.address_stack"] 

8883 elif entry.path == "[heap]": 8883 ↛ 8884line 8883 didn't jump to line 8884 because the condition on line 8883 was never true

8884 line_color = gef.config["theme.address_heap"] 

8885 elif entry.permission & Permission.READ and entry.permission & Permission.EXECUTE: 

8886 line_color = gef.config["theme.address_code"] 

8887 

8888 l = [ 

8889 Color.colorify(format_address(entry.page_start), line_color), 

8890 Color.colorify(format_address(entry.page_end), line_color), 

8891 Color.colorify(format_address(entry.offset), line_color), 

8892 ] 

8893 if entry.permission == Permission.ALL: 8893 ↛ 8894line 8893 didn't jump to line 8894 because the condition on line 8893 was never true

8894 l.append(Color.colorify(str(entry.permission), "underline " + line_color)) 

8895 else: 

8896 l.append(Color.colorify(str(entry.permission), line_color)) 

8897 

8898 l.append(Color.colorify(entry.path, line_color)) 

8899 line = " ".join(l) 

8900 

8901 gef_print(line) 

8902 return 

8903 

8904 def show_legend(self) -> None: 

8905 code_title = Color.colorify("Code", gef.config["theme.address_code"]) 

8906 stack_title = Color.colorify("Heap", gef.config["theme.address_stack"]) 

8907 heap_title = Color.colorify("Stack", gef.config["theme.address_heap"]) 

8908 gef_print(f"[ Legend: {code_title} | {stack_title} | {heap_title} ]") 

8909 return 

8910 

8911 def is_integer(self, n: str) -> bool: 

8912 try: 

8913 int(n, 0) 

8914 except ValueError: 

8915 return False 

8916 return True 

8917 

8918 

8919@register 

8920class XFilesCommand(GenericCommand): 

8921 """Shows all libraries (and sections) loaded by binary. This command extends the GDB command 

8922 `info files`, by retrieving more information from extra sources, and providing a better 

8923 display. If an argument FILE is given, the output will grep information related to only that file. 

8924 If an argument name is also given, the output will grep to the name within FILE.""" 

8925 

8926 _cmdline_ = "xfiles" 

8927 _syntax_ = f"{_cmdline_} [FILE [NAME]]" 

8928 _example_ = f"\n{_cmdline_} libc\n{_cmdline_} libc IO_vtables" 

8929 

8930 @only_if_gdb_running 

8931 def do_invoke(self, argv: List[str]) -> None: 

8932 color = gef.config["theme.table_heading"] 

8933 headers = ["Start", "End", "Name", "File"] 

8934 gef_print(Color.colorify("{:<{w}s}{:<{w}s}{:<21s} {:s}".format(*headers, w=gef.arch.ptrsize*2+3), color)) 

8935 

8936 filter_by_file = argv[0] if argv and argv[0] else None 

8937 filter_by_name = argv[1] if len(argv) > 1 and argv[1] else None 

8938 

8939 for xfile in get_info_files(): 

8940 if filter_by_file: 8940 ↛ 8941line 8940 didn't jump to line 8941 because the condition on line 8940 was never true

8941 if filter_by_file not in xfile.filename: 

8942 continue 

8943 if filter_by_name and filter_by_name not in xfile.name: 

8944 continue 

8945 

8946 l = [ 

8947 format_address(xfile.zone_start), 

8948 format_address(xfile.zone_end), 

8949 f"{xfile.name:<21s}", 

8950 xfile.filename, 

8951 ] 

8952 gef_print(" ".join(l)) 

8953 return 

8954 

8955 

8956@register 

8957class XAddressInfoCommand(GenericCommand): 

8958 """Retrieve and display runtime information for the location(s) given as parameter.""" 

8959 

8960 _cmdline_ = "xinfo" 

8961 _syntax_ = f"{_cmdline_} LOCATION" 

8962 _example_ = f"{_cmdline_} $pc" 

8963 

8964 def __init__(self) -> None: 

8965 super().__init__(complete=gdb.COMPLETE_LOCATION) 

8966 return 

8967 

8968 @only_if_gdb_running 

8969 def do_invoke(self, argv: List[str]) -> None: 

8970 if not argv: 

8971 err("At least one valid address must be specified") 

8972 self.usage() 

8973 return 

8974 

8975 for sym in argv: 

8976 try: 

8977 addr = align_address(parse_address(sym)) 

8978 gef_print(titlify(f"xinfo: {addr:#x}")) 

8979 self.infos(addr) 

8980 

8981 except gdb.error as gdb_err: 

8982 err(f"{gdb_err}") 

8983 return 

8984 

8985 def infos(self, address: int) -> None: 

8986 addr = lookup_address(address) 

8987 if not addr.valid: 8987 ↛ 8988line 8987 didn't jump to line 8988 because the condition on line 8987 was never true

8988 warn(f"Cannot reach {address:#x} in memory space") 

8989 return 

8990 

8991 sect = addr.section 

8992 info = addr.info 

8993 

8994 if sect: 8994 ↛ 9002line 8994 didn't jump to line 9002 because the condition on line 8994 was always true

8995 gef_print(f"Page: {format_address(sect.page_start)} {RIGHT_ARROW} " 

8996 f"{format_address(sect.page_end)} (size={sect.page_end-sect.page_start:#x})" 

8997 f"\nPermissions: {sect.permission}" 

8998 f"\nPathname: {sect.path}" 

8999 f"\nOffset (from page): {addr.value-sect.page_start:#x}" 

9000 f"\nInode: {sect.inode}") 

9001 

9002 if info: 

9003 gef_print(f"Segment: {info.name} " 

9004 f"({format_address(info.zone_start)}-{format_address(info.zone_end)})" 

9005 f"\nOffset (from segment): {addr.value-info.zone_start:#x}") 

9006 

9007 sym = gdb_get_location_from_symbol(address) 

9008 if sym: 

9009 name, offset = sym 

9010 msg = f"Symbol: {name}" 

9011 if offset: 9011 ↛ 9013line 9011 didn't jump to line 9013 because the condition on line 9011 was always true

9012 msg += f"+{offset:d}" 

9013 gef_print(msg) 

9014 

9015 return 

9016 

9017 

9018@register 

9019class XorMemoryCommand(GenericCommand): 

9020 """XOR a block of memory. The command allows to simply display the result, or patch it 

9021 runtime at runtime.""" 

9022 

9023 _cmdline_ = "xor-memory" 

9024 _syntax_ = f"{_cmdline_} (display|patch) ADDRESS SIZE KEY" 

9025 

9026 def __init__(self) -> None: 

9027 super().__init__(prefix=True) 

9028 return 

9029 

9030 def do_invoke(self, _: List[str]) -> None: 

9031 self.usage() 

9032 return 

9033 

9034 

9035@register 

9036class XorMemoryDisplayCommand(GenericCommand): 

9037 """Display a block of memory pointed by ADDRESS by xor-ing each byte with KEY. The key must be 

9038 provided in hexadecimal format.""" 

9039 

9040 _cmdline_ = "xor-memory display" 

9041 _syntax_ = f"{_cmdline_} ADDRESS SIZE KEY" 

9042 _example_ = f"{_cmdline_} $sp 16 41414141" 

9043 

9044 @only_if_gdb_running 

9045 def do_invoke(self, argv: List[str]) -> None: 

9046 if len(argv) != 3: 9046 ↛ 9047line 9046 didn't jump to line 9047 because the condition on line 9046 was never true

9047 self.usage() 

9048 return 

9049 

9050 address = parse_address(argv[0]) 

9051 length = int(argv[1], 0) 

9052 key = argv[2] 

9053 block = gef.memory.read(address, length) 

9054 info(f"Displaying XOR-ing {address:#x}-{address + len(block):#x} with {key!r}") 

9055 

9056 gef_print(titlify("Original block")) 

9057 gef_print(hexdump(block, base=address)) 

9058 

9059 gef_print(titlify("XOR-ed block")) 

9060 gef_print(hexdump(xor(block, key), base=address)) 

9061 return 

9062 

9063 

9064@register 

9065class XorMemoryPatchCommand(GenericCommand): 

9066 """Patch a block of memory pointed by ADDRESS by xor-ing each byte with KEY. The key must be 

9067 provided in hexadecimal format.""" 

9068 

9069 _cmdline_ = "xor-memory patch" 

9070 _syntax_ = f"{_cmdline_} ADDRESS SIZE KEY" 

9071 _example_ = f"{_cmdline_} $sp 16 41414141" 

9072 

9073 @only_if_gdb_running 

9074 def do_invoke(self, argv: List[str]) -> None: 

9075 if len(argv) != 3: 9075 ↛ 9076line 9075 didn't jump to line 9076 because the condition on line 9075 was never true

9076 self.usage() 

9077 return 

9078 

9079 address = parse_address(argv[0]) 

9080 length = int(argv[1], 0) 

9081 key = argv[2] 

9082 block = gef.memory.read(address, length) 

9083 info(f"Patching XOR-ing {address:#x}-{address + len(block):#x} with {key!r}") 

9084 xored_block = xor(block, key) 

9085 gef.memory.write(address, xored_block, length) 

9086 return 

9087 

9088 

9089@register 

9090class TraceRunCommand(GenericCommand): 

9091 """Create a runtime trace of all instructions executed from $pc to LOCATION specified. The 

9092 trace is stored in a text file that can be next imported in IDA Pro to visualize the runtime 

9093 path.""" 

9094 

9095 _cmdline_ = "trace-run" 

9096 _syntax_ = f"{_cmdline_} LOCATION [MAX_CALL_DEPTH]" 

9097 _example_ = f"{_cmdline_} 0x555555554610" 

9098 

9099 def __init__(self) -> None: 

9100 super().__init__(self._cmdline_, complete=gdb.COMPLETE_LOCATION) 

9101 self["max_tracing_recursion"] = (1, "Maximum depth of tracing") 

9102 self["tracefile_prefix"] = ("./gef-trace-", "Specify the tracing output file prefix") 

9103 return 

9104 

9105 @only_if_gdb_running 

9106 def do_invoke(self, argv: List[str]) -> None: 

9107 if len(argv) not in (1, 2): 9107 ↛ 9108line 9107 didn't jump to line 9108 because the condition on line 9107 was never true

9108 self.usage() 

9109 return 

9110 

9111 if len(argv) == 2 and argv[1].isdigit(): 9111 ↛ 9112line 9111 didn't jump to line 9112 because the condition on line 9111 was never true

9112 depth = int(argv[1]) 

9113 else: 

9114 depth = 1 

9115 

9116 try: 

9117 loc_start = gef.arch.pc 

9118 loc_end = parse_address(argv[0]) 

9119 except gdb.error as e: 

9120 err(f"Invalid location: {e}") 

9121 return 

9122 

9123 self.trace(loc_start, loc_end, depth) 

9124 return 

9125 

9126 def get_frames_size(self) -> int: 

9127 n = 0 

9128 f = gdb.newest_frame() 

9129 while f: 

9130 n += 1 

9131 f = f.older() 

9132 return n 

9133 

9134 def trace(self, loc_start: int, loc_end: int, depth: int) -> None: 

9135 info(f"Tracing from {loc_start:#x} to {loc_end:#x} (max depth={depth:d})") 

9136 logfile = f"{self['tracefile_prefix']}{loc_start:#x}-{loc_end:#x}.txt" 

9137 with RedirectOutputContext(to_file=logfile): 

9138 hide_context() 

9139 self.start_tracing(loc_start, loc_end, depth) 

9140 unhide_context() 

9141 ok(f"Done, logfile stored as '{logfile}'") 

9142 info("Hint: import logfile with `ida_color_gdb_trace.py` script in IDA to visualize path") 

9143 return 

9144 

9145 def start_tracing(self, loc_start: int, loc_end: int, depth: int) -> None: 

9146 loc_cur = loc_start 

9147 frame_count_init = self.get_frames_size() 

9148 

9149 gef_print("#", 

9150 f"# Execution tracing of {get_filepath()}", 

9151 f"# Start address: {format_address(loc_start)}", 

9152 f"# End address: {format_address(loc_end)}", 

9153 f"# Recursion level: {depth:d}", 

9154 "# automatically generated by gef.py", 

9155 "#\n", sep="\n") 

9156 

9157 while loc_cur != loc_end: 9157 ↛ 9176line 9157 didn't jump to line 9176 because the condition on line 9157 was always true

9158 try: 

9159 delta = self.get_frames_size() - frame_count_init 

9160 

9161 if delta <= depth: 

9162 gdb.execute("stepi") 

9163 else: 

9164 gdb.execute("finish") 

9165 

9166 loc_cur = gef.arch.pc 

9167 gdb.flush() 

9168 

9169 except gdb.error as e: 

9170 gef_print("#", 

9171 f"# Execution interrupted at address {format_address(loc_cur)}", 

9172 f"# Exception: {e}", 

9173 "#\n", sep="\n") 

9174 break 

9175 

9176 return 

9177 

9178 

9179@register 

9180class PatternCommand(GenericCommand): 

9181 """Generate or Search a De Bruijn Sequence of unique substrings of length N 

9182 and a total length of LENGTH. The default value of N is set to match the 

9183 currently loaded architecture.""" 

9184 

9185 _cmdline_ = "pattern" 

9186 _syntax_ = f"{_cmdline_} (create|search) ARGS" 

9187 

9188 def __init__(self) -> None: 

9189 super().__init__(prefix=True) 

9190 self["length"] = (1024, "Default length of a cyclic buffer to generate") 

9191 return 

9192 

9193 def do_invoke(self, _: List[str]) -> None: 

9194 self.usage() 

9195 return 

9196 

9197 

9198@register 

9199class PatternCreateCommand(GenericCommand): 

9200 """Generate a De Bruijn Sequence of unique substrings of length N and a 

9201 total length of LENGTH. The default value of N is set to match the currently 

9202 loaded architecture.""" 

9203 

9204 _cmdline_ = "pattern create" 

9205 _syntax_ = f"{_cmdline_} [-h] [-n N] [length]" 

9206 _example_ = [ 

9207 f"{_cmdline_} 4096", 

9208 f"{_cmdline_} -n 4 128" 

9209 ] 

9210 

9211 @parse_arguments({"length": 0}, {"-n": 0,}) 

9212 def do_invoke(self, _: List[str], **kwargs: Any) -> None: 

9213 args : argparse.Namespace = kwargs["arguments"] 

9214 length = args.length or gef.config["pattern.length"] 

9215 n = args.n or gef.arch.ptrsize 

9216 info(f"Generating a pattern of {length:d} bytes (n={n:d})") 

9217 pattern_str = gef_pystring(generate_cyclic_pattern(length, n)) 

9218 gef_print(pattern_str) 

9219 ok(f"Saved as '{gef_convenience(pattern_str)}'") 

9220 return 

9221 

9222 

9223@register 

9224class PatternSearchCommand(GenericCommand): 

9225 """Search a De Bruijn Sequence of unique substrings of length N and a 

9226 maximum total length of MAX_LENGTH. The default value of N is set to match 

9227 the currently loaded architecture. The PATTERN argument can be a GDB symbol 

9228 (such as a register name), a string or a hexadecimal value""" 

9229 

9230 _cmdline_ = "pattern search" 

9231 _syntax_ = f"{_cmdline_} [-h] [-n N] [--max-length MAX_LENGTH] [pattern]" 

9232 _example_ = [f"{_cmdline_} $pc", 

9233 f"{_cmdline_} 0x61616164", 

9234 f"{_cmdline_} aaab"] 

9235 _aliases_ = ["pattern offset"] 

9236 

9237 @only_if_gdb_running 

9238 @parse_arguments({"pattern": ""}, {("--period", "-n"): 0, ("--max-length", "-l"): 0}) 

9239 def do_invoke(self, _: List[str], **kwargs: Any) -> None: 

9240 args = kwargs["arguments"] 

9241 if not args.pattern: 9241 ↛ 9242line 9241 didn't jump to line 9242 because the condition on line 9241 was never true

9242 warn("No pattern provided") 

9243 return 

9244 

9245 max_length = args.max_length or gef.config["pattern.length"] 

9246 n = args.period or gef.arch.ptrsize 

9247 if n not in (2, 4, 8) or n > gef.arch.ptrsize: 9247 ↛ 9248line 9247 didn't jump to line 9248 because the condition on line 9247 was never true

9248 err("Incorrect value for period") 

9249 return 

9250 self.search(args.pattern, max_length, n) 

9251 return 

9252 

9253 def search(self, pattern: str, size: int, period: int) -> None: 

9254 pattern_be, pattern_le = None, None 

9255 

9256 # 1. check if it's a symbol (like "$sp" or "0x1337") 

9257 symbol = safe_parse_and_eval(pattern) 

9258 if symbol: 

9259 addr = int(abs(to_unsigned_long(symbol))) 

9260 dereferenced_value = dereference(addr) 

9261 if dereferenced_value: 9261 ↛ 9262line 9261 didn't jump to line 9262 because the condition on line 9261 was never true

9262 addr = int(abs(to_unsigned_long(dereferenced_value))) 

9263 mask = (1<<(8 * period))-1 

9264 addr &= mask 

9265 pattern_le = addr.to_bytes(period, 'little') 

9266 pattern_be = addr.to_bytes(period, 'big') 

9267 else: 

9268 # 2. assume it's a plain string 

9269 pattern_be = gef_pybytes(pattern) 

9270 pattern_le = gef_pybytes(pattern[::-1]) 

9271 

9272 info(f"Searching for '{pattern_le.hex()}'/'{pattern_be.hex()}' with period={period}") 

9273 cyclic_pattern = generate_cyclic_pattern(size, period) 

9274 off = cyclic_pattern.find(pattern_le) 

9275 if off >= 0: 

9276 ok(f"Found at offset {off:d} (little-endian search) " 

9277 f"{Color.colorify('likely', 'bold red') if gef.arch.endianness == Endianness.LITTLE_ENDIAN else ''}") 

9278 return 

9279 

9280 off = cyclic_pattern.find(pattern_be) 

9281 if off >= 0: 9281 ↛ 9282line 9281 didn't jump to line 9282 because the condition on line 9281 was never true

9282 ok(f"Found at offset {off:d} (big-endian search) " 

9283 f"{Color.colorify('likely', 'bold green') if gef.arch.endianness == Endianness.BIG_ENDIAN else ''}") 

9284 return 

9285 

9286 err(f"Pattern '{pattern}' not found") 

9287 return 

9288 

9289 

9290@register 

9291class ChecksecCommand(GenericCommand): 

9292 """Checksec the security properties of the current executable or passed as argument. The 

9293 command checks for the following protections: 

9294 - PIE 

9295 - NX 

9296 - RelRO 

9297 - Glibc Stack Canaries 

9298 - Fortify Source""" 

9299 

9300 _cmdline_ = "checksec" 

9301 _syntax_ = f"{_cmdline_} [FILENAME]" 

9302 _example_ = f"{_cmdline_} /bin/ls" 

9303 

9304 def __init__(self) -> None: 

9305 super().__init__(complete=gdb.COMPLETE_FILENAME) 

9306 return 

9307 

9308 def do_invoke(self, argv: List[str]) -> None: 

9309 argc = len(argv) 

9310 

9311 if argc == 0: 9311 ↛ 9316line 9311 didn't jump to line 9316 because the condition on line 9311 was always true

9312 filename = get_filepath() 

9313 if filename is None: 9313 ↛ 9314line 9313 didn't jump to line 9314 because the condition on line 9313 was never true

9314 warn("No executable/library specified") 

9315 return 

9316 elif argc == 1: 

9317 filename = os.path.realpath(os.path.expanduser(argv[0])) 

9318 if not os.access(filename, os.R_OK): 

9319 err("Invalid filename") 

9320 return 

9321 else: 

9322 self.usage() 

9323 return 

9324 

9325 info(f"{self._cmdline_} for '{filename}'") 

9326 self.print_security_properties(filename) 

9327 return 

9328 

9329 def print_security_properties(self, filename: str) -> None: 

9330 sec = Elf(filename).checksec 

9331 for prop in sec: 

9332 if prop in ("Partial RelRO", "Full RelRO"): continue 

9333 val = sec[prop] 

9334 msg = Color.greenify(Color.boldify(TICK)) if val is True else Color.redify(Color.boldify(CROSS)) 

9335 if val and prop == "Canary" and is_alive(): 9335 ↛ 9336line 9335 didn't jump to line 9336 because the condition on line 9335 was never true

9336 canary = gef.session.canary[0] if gef.session.canary else 0 

9337 msg += f"(value: {canary:#x})" 

9338 

9339 gef_print(f"{prop:<30s}: {msg}") 

9340 

9341 if sec["Full RelRO"]: 

9342 gef_print(f"{'RelRO':<30s}: {Color.greenify('Full')}") 

9343 elif sec["Partial RelRO"]: 9343 ↛ 9346line 9343 didn't jump to line 9346 because the condition on line 9343 was always true

9344 gef_print(f"{'RelRO':<30s}: {Color.yellowify('Partial')}") 

9345 else: 

9346 gef_print(f"{'RelRO':<30s}: {Color.redify(Color.boldify(CROSS))}") 

9347 return 

9348 

9349 

9350@register 

9351class GotCommand(GenericCommand): 

9352 """Display current status of the got inside the process.""" 

9353 

9354 _cmdline_ = "got" 

9355 _syntax_ = f"{_cmdline_} [FUNCTION_NAME ...] " 

9356 _example_ = "got read printf exit" 

9357 

9358 def __init__(self): 

9359 super().__init__() 

9360 self["function_resolved"] = ("green", 

9361 "Line color of the got command output for resolved function") 

9362 self["function_not_resolved"] = ("yellow", 

9363 "Line color of the got command output for unresolved function") 

9364 return 

9365 

9366 def build_line(self, name: str, color: str, address_val: int, got_address: int) -> str: 

9367 line = f"[{hex(address_val)}] " 

9368 line += Color.colorify(f"{name} {RIGHT_ARROW} {hex(got_address)}", color) 

9369 return line 

9370 

9371 @only_if_gdb_running 

9372 @parse_arguments({"symbols": [""]}, {"--all": False}) 

9373 def do_invoke(self, _: List[str], **kwargs: Any) -> None: 

9374 args : argparse.Namespace = kwargs["arguments"] 

9375 if args.all: 

9376 vmmap = gef.memory.maps 

9377 mapfiles = set(mapfile.path for mapfile in vmmap if 

9378 pathlib.Path(mapfile.realpath).is_file() and 

9379 mapfile.permission & Permission.EXECUTE) 

9380 for mapfile in mapfiles: 

9381 self.print_got_for(mapfile, args.symbols) 

9382 else: 

9383 self.print_got_for(str(gef.session.file), args.symbols) 

9384 

9385 def print_got_for(self, file: str, argv: List[str]) -> None: 

9386 readelf = gef.session.constants["readelf"] 

9387 

9388 elf_file = file 

9389 elf_virtual_path = file 

9390 

9391 func_names_filter = argv if argv else [] 

9392 vmmap = gef.memory.maps 

9393 base_address = min(x.page_start for x in vmmap if x.path == elf_virtual_path) 

9394 end_address = max(x.page_end for x in vmmap if x.path == elf_virtual_path) 

9395 

9396 # get the checksec output. 

9397 checksec_status = Elf(elf_file).checksec 

9398 relro_status = "Full RelRO" 

9399 full_relro = checksec_status["Full RelRO"] 

9400 pie = checksec_status["PIE"] # if pie we will have offset instead of abs address. 

9401 

9402 if not full_relro: 

9403 relro_status = "Partial RelRO" 

9404 partial_relro = checksec_status["Partial RelRO"] 

9405 

9406 if not partial_relro: 9406 ↛ 9407line 9406 didn't jump to line 9407 because the condition on line 9406 was never true

9407 relro_status = "No RelRO" 

9408 

9409 # retrieve jump slots using readelf 

9410 lines = gef_execute_external([readelf, "--wide", "--relocs", elf_file], as_list=True) 

9411 jmpslots = [line for line in lines if "JUMP" in line] 

9412 

9413 gef_print(f"{titlify(file)}\n\nGOT protection: {relro_status} | GOT functions: {len(jmpslots)}\n ") 

9414 

9415 for line in jmpslots: 

9416 address, _, _, _, name = line.split()[:5] 

9417 

9418 # if we have a filter let's skip the entries that are not requested. 

9419 if func_names_filter: 9419 ↛ 9423line 9419 didn't jump to line 9423 because the condition on line 9419 was always true

9420 if not any(map(lambda x: x in name, func_names_filter)): 

9421 continue 

9422 

9423 address_val = int(address, 16) 

9424 

9425 # address_val is an offset from the base_address if we have PIE. 

9426 if pie or is_remote_debug(): 9426 ↛ 9430line 9426 didn't jump to line 9430 because the condition on line 9426 was always true

9427 address_val = base_address + address_val 

9428 

9429 # read the address of the function. 

9430 got_address = gef.memory.read_integer(address_val) 

9431 

9432 # for the swag: different colors if the function has been resolved or not. 

9433 if base_address < got_address < end_address: 

9434 color = self["function_not_resolved"] 

9435 else: 

9436 color = self["function_resolved"] 

9437 

9438 line = self.build_line(name, color, address_val, got_address) 

9439 gef_print(line) 

9440 return 

9441 

9442 

9443@register 

9444class HighlightCommand(GenericCommand): 

9445 """Highlight user-defined text matches in GEF output universally.""" 

9446 _cmdline_ = "highlight" 

9447 _syntax_ = f"{_cmdline_} (add|remove|list|clear)" 

9448 _aliases_ = ["hl"] 

9449 

9450 def __init__(self) -> None: 

9451 super().__init__(prefix=True) 

9452 self["regex"] = (False, "Enable regex highlighting") 

9453 

9454 def do_invoke(self, _: List[str]) -> None: 

9455 return self.usage() 

9456 

9457 

9458@register 

9459class HighlightListCommand(GenericCommand): 

9460 """Show the current highlight table with matches to colors.""" 

9461 _cmdline_ = "highlight list" 

9462 _aliases_ = ["highlight ls", "hll"] 

9463 _syntax_ = _cmdline_ 

9464 

9465 def print_highlight_table(self) -> None: 

9466 if not gef.ui.highlight_table: 

9467 err("no matches found") 

9468 return 

9469 

9470 left_pad = max(map(len, gef.ui.highlight_table.keys())) 

9471 for match, color in sorted(gef.ui.highlight_table.items()): 

9472 print(f"{Color.colorify(match.ljust(left_pad), color)} {VERTICAL_LINE} " 

9473 f"{Color.colorify(color, color)}") 

9474 return 

9475 

9476 def do_invoke(self, _: List[str]) -> None: 

9477 return self.print_highlight_table() 

9478 

9479 

9480@register 

9481class HighlightClearCommand(GenericCommand): 

9482 """Clear the highlight table, remove all matches.""" 

9483 _cmdline_ = "highlight clear" 

9484 _aliases_ = ["hlc"] 

9485 _syntax_ = _cmdline_ 

9486 

9487 def do_invoke(self, _: List[str]) -> None: 

9488 return gef.ui.highlight_table.clear() 

9489 

9490 

9491@register 

9492class HighlightAddCommand(GenericCommand): 

9493 """Add a match to the highlight table.""" 

9494 _cmdline_ = "highlight add" 

9495 _syntax_ = f"{_cmdline_} MATCH COLOR" 

9496 _aliases_ = ["highlight set", "hla"] 

9497 _example_ = f"{_cmdline_} 41414141 yellow" 

9498 

9499 def do_invoke(self, argv: List[str]) -> None: 

9500 if len(argv) < 2: 9500 ↛ 9501line 9500 didn't jump to line 9501 because the condition on line 9500 was never true

9501 return self.usage() 

9502 

9503 match, color = argv 

9504 gef.ui.highlight_table[match] = color 

9505 return 

9506 

9507 

9508@register 

9509class HighlightRemoveCommand(GenericCommand): 

9510 """Remove a match in the highlight table.""" 

9511 _cmdline_ = "highlight remove" 

9512 _syntax_ = f"{_cmdline_} MATCH" 

9513 _aliases_ = [ 

9514 "highlight delete", 

9515 "highlight del", 

9516 "highlight unset", 

9517 "highlight rm", 

9518 "hlr", 

9519 ] 

9520 _example_ = f"{_cmdline_} remove 41414141" 

9521 

9522 def do_invoke(self, argv: List[str]) -> None: 

9523 if not argv: 

9524 return self.usage() 

9525 

9526 gef.ui.highlight_table.pop(argv[0], None) 

9527 return 

9528 

9529 

9530@register 

9531class FormatStringSearchCommand(GenericCommand): 

9532 """Exploitable format-string helper: this command will set up specific breakpoints 

9533 at well-known dangerous functions (printf, snprintf, etc.), and check if the pointer 

9534 holding the format string is writable, and therefore susceptible to format string 

9535 attacks if an attacker can control its content.""" 

9536 _cmdline_ = "format-string-helper" 

9537 _syntax_ = _cmdline_ 

9538 _aliases_ = ["fmtstr-helper",] 

9539 

9540 def do_invoke(self, _: List[str]) -> None: 

9541 dangerous_functions = { 

9542 "printf": 0, 

9543 "sprintf": 1, 

9544 "fprintf": 1, 

9545 "snprintf": 2, 

9546 "vsnprintf": 2, 

9547 } 

9548 

9549 nb_installed_breaks = 0 

9550 

9551 with RedirectOutputContext(to_file="/dev/null"): 

9552 for function_name in dangerous_functions: 

9553 argument_number = dangerous_functions[function_name] 

9554 FormatStringBreakpoint(function_name, argument_number) 

9555 nb_installed_breaks += 1 

9556 

9557 ok(f"Enabled {nb_installed_breaks} FormatString " 

9558 f"breakpoint{'s' if nb_installed_breaks > 1 else ''}") 

9559 return 

9560 

9561 

9562@register 

9563class HeapAnalysisCommand(GenericCommand): 

9564 """Heap vulnerability analysis helper: this command aims to track dynamic heap allocation 

9565 done through malloc()/free() to provide some insights on possible heap vulnerabilities. The 

9566 following vulnerabilities are checked: 

9567 - NULL free 

9568 - Use-after-Free 

9569 - Double Free 

9570 - Heap overlap""" 

9571 _cmdline_ = "heap-analysis-helper" 

9572 _syntax_ = _cmdline_ 

9573 

9574 def __init__(self) -> None: 

9575 super().__init__(complete=gdb.COMPLETE_NONE) 

9576 self["check_free_null"] = (False, "Break execution when a free(NULL) is encountered") 

9577 self["check_double_free"] = (True, "Break execution when a double free is encountered") 

9578 self["check_weird_free"] = (True, "Break execution when free() is called against a non-tracked pointer") 

9579 self["check_uaf"] = (True, "Break execution when a possible Use-after-Free condition is found") 

9580 self["check_heap_overlap"] = (True, "Break execution when a possible overlap in allocation is found") 

9581 

9582 self.bp_malloc = None 

9583 self.bp_calloc = None 

9584 self.bp_free = None 

9585 self.bp_realloc = None 

9586 return 

9587 

9588 @only_if_gdb_running 

9589 @experimental_feature 

9590 def do_invoke(self, argv: List[str]) -> None: 

9591 if not argv: 9591 ↛ 9595line 9591 didn't jump to line 9595 because the condition on line 9591 was always true

9592 self.setup() 

9593 return 

9594 

9595 if argv[0] == "show": 

9596 self.dump_tracked_allocations() 

9597 return 

9598 

9599 def setup(self) -> None: 

9600 ok("Tracking malloc() & calloc()") 

9601 self.bp_malloc = TraceMallocBreakpoint("__libc_malloc") 

9602 self.bp_calloc = TraceMallocBreakpoint("__libc_calloc") 

9603 ok("Tracking free()") 

9604 self.bp_free = TraceFreeBreakpoint() 

9605 ok("Tracking realloc()") 

9606 self.bp_realloc = TraceReallocBreakpoint() 

9607 

9608 ok("Disabling hardware watchpoints (this may increase the latency)") 

9609 gdb.execute("set can-use-hw-watchpoints 0") 

9610 

9611 info("Dynamic breakpoints correctly setup, " 

9612 "GEF will break execution if a possible vulnerabity is found.") 

9613 warn(f"{Color.colorify('Note', 'bold underline yellow')}: " 

9614 "The heap analysis slows down the execution noticeably.") 

9615 

9616 # when inferior quits, we need to clean everything for a next execution 

9617 gef_on_exit_hook(self.clean) 

9618 return 

9619 

9620 def dump_tracked_allocations(self) -> None: 

9621 global gef 

9622 

9623 if gef.session.heap_allocated_chunks: 

9624 ok("Tracked as in-use chunks:") 

9625 for addr, sz in gef.session.heap_allocated_chunks: 

9626 gef_print(f"{CROSS} malloc({sz:d}) = {addr:#x}") 

9627 else: 

9628 ok("No malloc() chunk tracked") 

9629 

9630 if gef.session.heap_freed_chunks: 

9631 ok("Tracked as free-ed chunks:") 

9632 for addr, sz in gef.session.heap_freed_chunks: 

9633 gef_print(f"{TICK} free({sz:d}) = {addr:#x}") 

9634 else: 

9635 ok("No free() chunk tracked") 

9636 return 

9637 

9638 def clean(self, _: "gdb.events.ExitedEvent") -> None: 

9639 global gef 

9640 

9641 ok(f"{Color.colorify('Heap-Analysis', 'yellow bold')} - Cleaning up") 

9642 for bp in [self.bp_malloc, self.bp_calloc, self.bp_free, self.bp_realloc]: 

9643 if hasattr(bp, "retbp") and bp.retbp: 9643 ↛ 9651line 9643 didn't jump to line 9651 because the condition on line 9643 was always true

9644 try: 

9645 bp.retbp.delete() 

9646 except RuntimeError: 

9647 # in some cases, gdb was found failing to correctly remove the retbp 

9648 # but they can be safely ignored since the debugging session is over 

9649 pass 

9650 

9651 bp.delete() 

9652 

9653 for wp in gef.session.heap_uaf_watchpoints: 

9654 wp.delete() 

9655 

9656 gef.session.heap_allocated_chunks = [] 

9657 gef.session.heap_freed_chunks = [] 

9658 gef.session.heap_uaf_watchpoints = [] 

9659 

9660 ok(f"{Color.colorify('Heap-Analysis', 'yellow bold')} - Re-enabling hardware watchpoints") 

9661 gdb.execute("set can-use-hw-watchpoints 1") 

9662 

9663 gef_on_exit_unhook(self.clean) 

9664 return 

9665 

9666 

9667# 

9668# GDB Function declaration 

9669# 

9670@deprecated("") 

9671def register_function(cls: Type["GenericFunction"]) -> Type["GenericFunction"]: 

9672 """Decorator for registering a new convenience function to GDB.""" 

9673 return cls 

9674 

9675 

9676class GenericFunction(gdb.Function): 

9677 """This is an abstract class for invoking convenience functions, should not be instantiated.""" 

9678 

9679 _function_ : str 

9680 _syntax_: str = "" 

9681 _example_ : str = "" 

9682 

9683 def __init__(self) -> None: 

9684 super().__init__(self._function_) 

9685 

9686 def invoke(self, *args: Any) -> int: 

9687 if not is_alive(): 

9688 raise gdb.GdbError("No debugging session active") 

9689 return self.do_invoke(args) 

9690 

9691 def arg_to_long(self, args: List, index: int, default: int = 0) -> int: 

9692 try: 

9693 addr = args[index] 

9694 return int(addr) if addr.address is None else int(addr.address) 

9695 except IndexError: 

9696 return default 

9697 

9698 def do_invoke(self, args: Any) -> int: 

9699 raise NotImplementedError 

9700 

9701 

9702@register 

9703class StackOffsetFunction(GenericFunction): 

9704 """Return the current stack base address plus an optional offset.""" 

9705 _function_ = "_stack" 

9706 _syntax_ = f"${_function_}()" 

9707 

9708 def do_invoke(self, args: List) -> int: 

9709 base = get_section_base_address("[stack]") 

9710 if not base: 9710 ↛ 9711line 9710 didn't jump to line 9711 because the condition on line 9710 was never true

9711 raise gdb.GdbError("Stack not found") 

9712 

9713 return self.arg_to_long(args, 0) + base 

9714 

9715 

9716@register 

9717class HeapBaseFunction(GenericFunction): 

9718 """Return the current heap base address plus an optional offset.""" 

9719 _function_ = "_heap" 

9720 _syntax_ = f"${_function_}()" 

9721 

9722 def do_invoke(self, args: List) -> int: 

9723 base = gef.heap.base_address 

9724 if not base: 9724 ↛ 9725line 9724 didn't jump to line 9725 because the condition on line 9724 was never true

9725 base = get_section_base_address("[heap]") 

9726 if not base: 

9727 raise gdb.GdbError("Heap not found") 

9728 return self.arg_to_long(args, 0) + base 

9729 

9730 

9731@register 

9732class SectionBaseFunction(GenericFunction): 

9733 """Return the matching file's base address plus an optional offset. 

9734 Defaults to current file. Note that quotes need to be escaped""" 

9735 _function_ = "_base" 

9736 _syntax_ = "$_base([filepath])" 

9737 _example_ = "p $_base(\\\"/usr/lib/ld-2.33.so\\\")" 

9738 

9739 def do_invoke(self, args: List) -> int: 

9740 addr = 0 

9741 try: 

9742 name = args[0].string() 

9743 except IndexError: 9743 ↛ 9746line 9743 didn't jump to line 9746

9744 assert gef.session.file 

9745 name = gef.session.file.name 

9746 except gdb.error: 

9747 err(f"Invalid arg: {args[0]}") 

9748 return 0 

9749 

9750 try: 

9751 base = get_section_base_address(name) 

9752 if base: 9752 ↛ 9757line 9752 didn't jump to line 9757 because the condition on line 9752 was always true

9753 addr = int(base) 

9754 except TypeError: 

9755 err(f"Cannot find section {name}") 

9756 return 0 

9757 return addr 

9758 

9759 

9760@register 

9761class BssBaseFunction(GenericFunction): 

9762 """Return the current bss base address plus the given offset.""" 

9763 _function_ = "_bss" 

9764 _syntax_ = f"${_function_}([OFFSET])" 

9765 _example_ = "deref $_bss(0x20)" 

9766 

9767 def do_invoke(self, args: List) -> int: 

9768 base = get_zone_base_address(".bss") 

9769 if not base: 9769 ↛ 9770line 9769 didn't jump to line 9770 because the condition on line 9769 was never true

9770 raise gdb.GdbError("BSS not found") 

9771 return self.arg_to_long(args, 0) + base 

9772 

9773 

9774@register 

9775class GotBaseFunction(GenericFunction): 

9776 """Return the current GOT base address plus the given offset.""" 

9777 _function_ = "_got" 

9778 _syntax_ = f"${_function_}([OFFSET])" 

9779 _example_ = "deref $_got(0x20)" 

9780 

9781 def do_invoke(self, args: List) -> int: 

9782 base = get_zone_base_address(".got") 

9783 if not base: 9783 ↛ 9784line 9783 didn't jump to line 9784 because the condition on line 9783 was never true

9784 raise gdb.GdbError("GOT not found") 

9785 return base + self.arg_to_long(args, 0) 

9786 

9787 

9788@register 

9789class GefFunctionsCommand(GenericCommand): 

9790 """List the convenience functions provided by GEF.""" 

9791 _cmdline_ = "functions" 

9792 _syntax_ = _cmdline_ 

9793 

9794 def __init__(self) -> None: 

9795 super().__init__() 

9796 self.docs = [] 

9797 self.should_refresh = True 

9798 return 

9799 

9800 def __add__(self, function: GenericFunction): 

9801 """Add function to documentation.""" 

9802 doc = getattr(function, "__doc__", "").lstrip() 

9803 if not hasattr(function, "_syntax_"): 9803 ↛ 9804line 9803 didn't jump to line 9804 because the condition on line 9803 was never true

9804 raise ValueError("Function is invalid") 

9805 syntax = getattr(function, "_syntax_").lstrip() 

9806 msg = f"{Color.colorify(syntax, 'bold cyan')}\n {doc}" 

9807 example = getattr(function, "_example_", "").strip() 

9808 if example: 

9809 msg += f"\n {Color.yellowify('Example:')} {example}" 

9810 self.docs.append(msg) 

9811 return self 

9812 

9813 def __radd__(self, function: GenericFunction): 

9814 return self.__add__(function) 

9815 

9816 def __str__(self) -> str: 

9817 if self.should_refresh: 9817 ↛ 9819line 9817 didn't jump to line 9819 because the condition on line 9817 was always true

9818 self.__rebuild() 

9819 return self.__doc__ or "" 

9820 

9821 def __rebuild(self) -> None: 

9822 """Rebuild the documentation for functions.""" 

9823 for function in gef.gdb.functions.values(): 

9824 self += function 

9825 

9826 self.command_size = len(gef.gdb.commands) 

9827 _, cols = get_terminal_size() 

9828 separator = HORIZONTAL_LINE*cols 

9829 self.__doc__ = f"\n{separator}\n".join(sorted(self.docs)) 

9830 self.should_refresh = False 

9831 return 

9832 

9833 def do_invoke(self, argv) -> None: 

9834 self.dont_repeat() 

9835 gef_print(titlify("GEF - Convenience Functions")) 

9836 gef_print("These functions can be used as arguments to other " 

9837 "commands to dynamically calculate values\n") 

9838 gef_print(str(self)) 

9839 return 

9840 

9841 

9842# 

9843# GEF internal command classes 

9844# 

9845class GefCommand(gdb.Command): 

9846 """GEF main command: view all new commands by typing `gef`.""" 

9847 

9848 _cmdline_ = "gef" 

9849 _syntax_ = f"{_cmdline_} (missing|config|save|restore|set|run)" 

9850 

9851 def __init__(self) -> None: 

9852 super().__init__(self._cmdline_, gdb.COMMAND_SUPPORT, gdb.COMPLETE_NONE, True) 

9853 gef.config["gef.follow_child"] = GefSetting(True, bool, "Automatically set GDB to follow child when forking") 

9854 gef.config["gef.readline_compat"] = GefSetting(False, bool, "Workaround for readline SOH/ETX issue (SEGV)") 

9855 gef.config["gef.debug"] = GefSetting(False, bool, "Enable debug mode for gef") 

9856 gef.config["gef.autosave_breakpoints_file"] = GefSetting("", str, "Automatically save and restore breakpoints") 

9857 gef.config["gef.disable_target_remote_overwrite"] = GefSetting(False, bool, "Disable the overwrite of `target remote`") 

9858 plugins_dir = GefSetting("", str, "Autoload additional GEF commands from external directory", hooks={"on_write": [GefSetting.no_spaces, ]}) 

9859 plugins_dir.add_hook("on_changed", [lambda _, new_val: GefSetting.must_exist(new_val), lambda _, new_val: self.load_extra_plugins(new_val), ]) 9859 ↛ exitline 9859 didn't run the lambda on line 9859 or line 9859 didn't run the lambda on line 9859

9860 gef.config["gef.extra_plugins_dir"] = plugins_dir 

9861 gef.config["gef.disable_color"] = GefSetting(False, bool, "Disable all colors in GEF") 

9862 gef.config["gef.tempdir"] = GefSetting(GEF_TEMP_DIR, pathlib.Path, "Directory to use for temporary/cache content", hooks={"on_write": [GefSetting.no_spaces, GefSetting.create_folder_tree]}) 

9863 gef.config["gef.show_deprecation_warnings"] = GefSetting(True, bool, "Toggle the display of the `deprecated` warnings") 

9864 gef.config["gef.buffer"] = GefSetting(True, bool, "Internally buffer command output until completion") 

9865 gef.config["gef.bruteforce_main_arena"] = GefSetting(False, bool, "Allow bruteforcing main_arena symbol if everything else fails") 

9866 gef.config["gef.libc_version"] = GefSetting("", str, "Specify libc version when auto-detection fails") 

9867 gef.config["gef.main_arena_offset"] = GefSetting("", str, "Offset from libc base address to main_arena symbol (int or hex). Set to empty string to disable.") 

9868 gef.config["gef.propagate_debug_exception"] = GefSetting(False, bool, "If true, when debug mode is enabled, Python exceptions will be propagated all the way.") 

9869 

9870 self.commands : Dict[str, GenericCommand] = collections.OrderedDict() 

9871 self.functions : Dict[str, GenericFunction] = collections.OrderedDict() 

9872 self.missing: Dict[str, Exception] = {} 

9873 return 

9874 

9875 @property 

9876 @deprecated() 

9877 def loaded_commands(self) -> List[Tuple[str, Type[GenericCommand], Any]]: 

9878 raise ObsoleteException("Obsolete loaded_commands") 

9879 

9880 @property 

9881 @deprecated() 

9882 def loaded_functions(self) -> List[Type[GenericFunction]]: 

9883 raise ObsoleteException("Obsolete loaded_functions") 

9884 

9885 @property 

9886 @deprecated() 

9887 def missing_commands(self) -> Dict[str, Exception]: 

9888 raise ObsoleteException("Obsolete missing_commands") 

9889 

9890 def setup(self) -> None: 

9891 self.load() 

9892 

9893 GefHelpCommand() 

9894 GefConfigCommand() 

9895 GefSaveCommand() 

9896 GefMissingCommand() 

9897 GefSetCommand() 

9898 GefRunCommand() 

9899 GefInstallExtraScriptCommand() 

9900 

9901 # At this point, commands (incl. extras) are loaded with default settings. 

9902 # Load custom settings from config file if any 

9903 GefRestoreCommand() 

9904 return 

9905 

9906 def load_extra_plugins(self, extra_plugins_dir: Optional[pathlib.Path] = None) -> int: 

9907 """Load the plugins from the gef-extras setting. Returns the number of new plugins added.""" 

9908 def load_plugin(fpath: pathlib.Path) -> bool: 

9909 try: 

9910 dbg(f"Loading '{fpath}'") 

9911 gdb.execute(f"source {fpath}") 

9912 except AlreadyRegisteredException: 

9913 pass 

9914 except Exception as e: 

9915 warn(f"Exception while loading {fpath}: {str(e)}") 

9916 return False 

9917 return True 

9918 

9919 def load_plugins_from_directory(plugin_directory: pathlib.Path): 

9920 nb_added = -1 

9921 nb_inital = len(__registered_commands__) 

9922 start_time = time.perf_counter() 

9923 for entry in plugin_directory.glob("**/*.py"): 

9924 load_plugin(entry) 

9925 

9926 try: 

9927 nb_added = len(__registered_commands__) - nb_inital 

9928 if nb_added > 0: 

9929 self.load() 

9930 nb_failed = len(__registered_commands__) - len(self.commands) 

9931 load_time = time.perf_counter() - start_time 

9932 ok(f"{Color.colorify(str(nb_added), 'bold green')} extra commands added " \ 

9933 f"in {load_time:.2f} seconds") 

9934 if nb_failed != 0: 

9935 warn(f"{Color.colorify(str(nb_failed), 'bold light_gray')} extra commands/functions failed to be added. " 

9936 "Check `gef missing` to know why") 

9937 except gdb.error as e: 

9938 err(f"failed: {e}") 

9939 return nb_added 

9940 

9941 directory = extra_plugins_dir or gef.config["gef.extra_plugins_dir"] 

9942 if not directory: 9942 ↛ 9944line 9942 didn't jump to line 9944 because the condition on line 9942 was always true

9943 return 0 

9944 directory = pathlib.Path(directory).expanduser().absolute() 

9945 if not directory.exists(): 

9946 return 0 

9947 dbg(f"Loading extra plugins from directory={directory}") 

9948 return load_plugins_from_directory(directory) 

9949 

9950 @property 

9951 def loaded_command_names(self) -> Iterable[str]: 

9952 print("obsolete loaded_command_names") 

9953 return self.commands.keys() 

9954 

9955 def invoke(self, args: Any, from_tty: bool) -> None: 

9956 self.dont_repeat() 

9957 gdb.execute("gef help") 

9958 return 

9959 

9960 def add_context_layout_mapping(self, current_pane_name: str, display_pane_function: Callable, pane_title_function: Callable, condition: Optional[Callable]) -> None: 

9961 """Add a new context layout mapping.""" 

9962 context = self.commands["context"] 

9963 assert isinstance(context, ContextCommand) 

9964 

9965 # overload the printing of pane title 

9966 context.layout_mapping[current_pane_name] = (display_pane_function, pane_title_function, condition) 

9967 

9968 def add_context_pane(self, pane_name: str, display_pane_function: Callable, pane_title_function: Callable, condition: Optional[Callable]) -> None: 

9969 """Add a new context pane to ContextCommand.""" 

9970 context = self.commands["context"] 

9971 assert isinstance(context, ContextCommand) 

9972 

9973 # assure users can toggle the new context 

9974 corrected_settings_name: str = pane_name.replace(" ", "_") 

9975 gef.config["context.layout"] += f" {corrected_settings_name}" 

9976 

9977 self.add_context_layout_mapping(corrected_settings_name, display_pane_function, pane_title_function, condition) 

9978 

9979 def load(self) -> None: 

9980 """Load all the commands and functions defined by GEF into GDB.""" 

9981 current_commands = set(self.commands.keys()) 

9982 new_commands = set(x._cmdline_ for x in __registered_commands__) - current_commands 

9983 current_functions = set(self.functions.keys()) 

9984 new_functions = set(x._function_ for x in __registered_functions__) - current_functions 

9985 self.missing.clear() 

9986 self.__load_time_ms = time.time()* 1000 

9987 

9988 # load all new functions 

9989 for name in sorted(new_functions): 

9990 for function_cls in __registered_functions__: 9990 ↛ 9989line 9990 didn't jump to line 9989 because the loop on line 9990 didn't complete

9991 if function_cls._function_ == name: 

9992 assert issubclass(function_cls, GenericFunction) 

9993 self.functions[name] = function_cls() 

9994 break 

9995 

9996 # load all new commands 

9997 for name in sorted(new_commands): 

9998 try: 

9999 for command_cls in __registered_commands__: 9999 ↛ 9997line 9999 didn't jump to line 9997 because the loop on line 9999 didn't complete

10000 if command_cls._cmdline_ == name: 

10001 assert issubclass(command_cls, GenericCommand) 

10002 command_instance = command_cls() 

10003 

10004 # create the aliases if any 

10005 if hasattr(command_instance, "_aliases_"): 10005 ↛ 10010line 10005 didn't jump to line 10010 because the condition on line 10005 was always true

10006 aliases = getattr(command_instance, "_aliases_") 

10007 for alias in aliases: 

10008 GefAlias(alias, name) 

10009 

10010 self.commands[name] = command_instance 

10011 break 

10012 

10013 except Exception as reason: 

10014 self.missing[name] = reason 

10015 

10016 self.__load_time_ms = (time.time()* 1000) - self.__load_time_ms 

10017 return 

10018 

10019 

10020 def show_banner(self) -> None: 

10021 gef_print(f"{Color.greenify('GEF')} for {gef.session.os} ready, " 

10022 f"type `{Color.colorify('gef', 'underline yellow')}' to start, " 

10023 f"`{Color.colorify('gef config', 'underline pink')}' to configure") 

10024 

10025 ver = f"{sys.version_info.major:d}.{sys.version_info.minor:d}" 

10026 gef_print(f"{Color.colorify(str(len(self.commands)), 'bold green')} commands loaded " 

10027 f"and {Color.colorify(str(len(self.functions)), 'bold blue')} functions added for " 

10028 f"GDB {Color.colorify(gdb.VERSION, 'bold yellow')} in {self.__load_time_ms:.2f}ms " 

10029 f"using Python engine {Color.colorify(ver, 'bold red')}") 

10030 

10031 nb_missing = len(self.missing) 

10032 if nb_missing: 10032 ↛ 10033line 10032 didn't jump to line 10033 because the condition on line 10032 was never true

10033 warn(f"{Color.colorify(str(nb_missing), 'bold red')} " 

10034 f"command{'s' if nb_missing > 1 else ''} could not be loaded, " 

10035 f"run `{Color.colorify('gef missing', 'underline pink')}` to know why.") 

10036 return 

10037 

10038 

10039class GefHelpCommand(gdb.Command): 

10040 """GEF help sub-command.""" 

10041 _cmdline_ = "gef help" 

10042 _syntax_ = _cmdline_ 

10043 

10044 def __init__(self) -> None: 

10045 super().__init__(self._cmdline_, gdb.COMMAND_SUPPORT, gdb.COMPLETE_NONE, False) 

10046 self.docs = [] 

10047 self.should_refresh = True 

10048 self.command_size = 0 

10049 return 

10050 

10051 def invoke(self, args: Any, from_tty: bool) -> None: 

10052 self.dont_repeat() 

10053 gef_print(titlify("GEF - GDB Enhanced Features")) 

10054 gef_print(str(self)) 

10055 return 

10056 

10057 def __rebuild(self) -> None: 

10058 """Rebuild the documentation.""" 

10059 for name, cmd in gef.gdb.commands.items(): 

10060 self += (name, cmd) 

10061 

10062 self.command_size = len(gef.gdb.commands) 

10063 _, cols = get_terminal_size() 

10064 separator = HORIZONTAL_LINE*cols 

10065 self.__doc__ = f"\n{separator}\n".join(sorted(self.docs)) 

10066 self.should_refresh = False 

10067 return 

10068 

10069 def __add__(self, command: Tuple[str, GenericCommand]): 

10070 """Add command to GEF documentation.""" 

10071 cmd, class_obj = command 

10072 if " " in cmd: 

10073 # do not print subcommands in gef help 

10074 return self 

10075 doc = getattr(class_obj, "__doc__", "").lstrip() 

10076 aliases = f"Aliases: {', '.join(class_obj._aliases_)}" if hasattr(class_obj, "_aliases_") else "" 

10077 msg = f"{Color.colorify(cmd, 'bold red')}\n{doc}\n{aliases}" 

10078 self.docs.append(msg) 

10079 return self 

10080 

10081 def __radd__(self, command: Tuple[str, GenericCommand]): 

10082 return self.__add__(command) 

10083 

10084 def __str__(self) -> str: 

10085 """Lazily regenerate the `gef help` object if it was modified""" 

10086 # quick check in case the docs have changed 

10087 if self.should_refresh or self.command_size != len(gef.gdb.commands): 10087 ↛ 10089line 10087 didn't jump to line 10089 because the condition on line 10087 was always true

10088 self.__rebuild() 

10089 return self.__doc__ or "" 

10090 

10091 

10092class GefConfigCommand(gdb.Command): 

10093 """GEF configuration sub-command 

10094 This command will help set/view GEF settings for the current debugging session. 

10095 It is possible to make those changes permanent by running `gef save` (refer 

10096 to this command help), and/or restore previously saved settings by running 

10097 `gef restore` (refer help). 

10098 """ 

10099 _cmdline_ = "gef config" 

10100 _syntax_ = f"{_cmdline_} [setting_name] [setting_value]" 

10101 

10102 def __init__(self) -> None: 

10103 super().__init__(self._cmdline_, gdb.COMMAND_NONE, prefix=False) 

10104 return 

10105 

10106 def invoke(self, args: str, from_tty: bool) -> None: 

10107 self.dont_repeat() 

10108 argv = gdb.string_to_argv(args) 

10109 argc = len(argv) 

10110 

10111 if not (0 <= argc <= 2): 10111 ↛ 10112line 10111 didn't jump to line 10112 because the condition on line 10111 was never true

10112 err("Invalid number of arguments") 

10113 return 

10114 

10115 if argc == 0: 

10116 gef_print(titlify("GEF configuration settings")) 

10117 self.print_settings() 

10118 return 

10119 

10120 if argc == 1: 

10121 prefix = argv[0] 

10122 names = [x for x in gef.config.keys() if x.startswith(prefix)] 

10123 if names: 10123 ↛ 10130line 10123 didn't jump to line 10130 because the condition on line 10123 was always true

10124 if len(names) == 1: 10124 ↛ 10128line 10124 didn't jump to line 10128 because the condition on line 10124 was always true

10125 gef_print(titlify(f"GEF configuration setting: {names[0]}")) 

10126 self.print_setting(names[0], verbose=True) 

10127 else: 

10128 gef_print(titlify(f"GEF configuration settings matching '{argv[0]}'")) 

10129 for name in names: self.print_setting(name) 

10130 return 

10131 

10132 if not is_debug(): 

10133 try: 

10134 self.set_setting(argv) 

10135 except (ValueError, KeyError) as e: 

10136 err(str(e)) 

10137 else: 

10138 # Let exceptions (if any) propagate 

10139 self.set_setting(argv) 

10140 return 

10141 

10142 def print_setting(self, plugin_name: str, verbose: bool = False) -> None: 

10143 res = gef.config.raw_entry(plugin_name) 

10144 string_color = gef.config["theme.dereference_string"] 

10145 misc_color = gef.config["theme.dereference_base_address"] 

10146 

10147 if not res: 10147 ↛ 10148line 10147 didn't jump to line 10148 because the condition on line 10147 was never true

10148 return 

10149 

10150 _setting = Color.colorify(plugin_name, "green") 

10151 _type = res.type.__name__ 

10152 if _type == "str": 

10153 _value = f'"{Color.colorify(res.value, string_color)}"' 

10154 else: 

10155 _value = Color.colorify(res.value, misc_color) 

10156 

10157 gef_print(f"{_setting} ({_type}) = {_value}") 

10158 

10159 if verbose: 

10160 gef_print(Color.colorify("\nDescription:", "bold underline")) 

10161 gef_print(f"\t{res.description}") 

10162 return 

10163 

10164 def print_settings(self) -> None: 

10165 for x in sorted(gef.config): 

10166 self.print_setting(x) 

10167 return 

10168 

10169 def set_setting(self, argv: List[str]) -> bool: 

10170 global gef 

10171 key, new_value = argv 

10172 

10173 if "." not in key: 10173 ↛ 10174line 10173 didn't jump to line 10174 because the condition on line 10173 was never true

10174 err("Invalid command format") 

10175 return False 

10176 

10177 loaded_commands = list( gef.gdb.commands.keys()) + ["gef"] 

10178 plugin_name = key.split(".", 1)[0] 

10179 if plugin_name not in loaded_commands: 10179 ↛ 10180line 10179 didn't jump to line 10180 because the condition on line 10179 was never true

10180 err(f"Unknown plugin '{plugin_name}'") 

10181 return False 

10182 

10183 if key not in gef.config: 10183 ↛ 10184line 10183 didn't jump to line 10184 because the condition on line 10183 was never true

10184 dbg(f"'{key}' is not a valid configuration setting") 

10185 return False 

10186 

10187 _type = gef.config.raw_entry(key).type 

10188 

10189 # Attempt to parse specific values for known types 

10190 if _type == bool: 

10191 if new_value.upper() in ("TRUE", "T", "1"): 

10192 _newval = True 

10193 elif new_value.upper() in ("FALSE", "F", "0"): 

10194 _newval = False 

10195 else: 

10196 raise ValueError(f"Cannot parse '{new_value}' as bool") 

10197 else: 

10198 _newval = _type(new_value) 

10199 

10200 gef.config[key] = _newval 

10201 

10202 reset_all_caches() 

10203 return True 

10204 

10205 def complete(self, text: str, word: str) -> List[str]: 

10206 settings = sorted(gef.config) 

10207 

10208 if text == "": 

10209 # no prefix: example: `gef config TAB` 

10210 return [s for s in settings if word in s] 

10211 

10212 if "." not in text: 

10213 # if looking for possible prefix 

10214 return [s for s in settings if s.startswith(text.strip())] 

10215 

10216 # finally, look for possible values for given prefix 

10217 return [s.split(".", 1)[1] for s in settings if s.startswith(text.strip())] 

10218 

10219 

10220class GefSaveCommand(gdb.Command): 

10221 """GEF save sub-command. 

10222 Saves the current configuration of GEF to disk (by default in file '~/.gef.rc').""" 

10223 _cmdline_ = "gef save" 

10224 _syntax_ = _cmdline_ 

10225 

10226 def __init__(self) -> None: 

10227 super().__init__(self._cmdline_, gdb.COMMAND_SUPPORT, gdb.COMPLETE_NONE, False) 

10228 return 

10229 

10230 def invoke(self, args: Any, from_tty: bool) -> None: 

10231 self.dont_repeat() 

10232 cfg = configparser.RawConfigParser() 

10233 old_sect = None 

10234 

10235 # save the configuration 

10236 for key in sorted(gef.config): 

10237 sect, optname = key.split(".", 1) 

10238 value = gef.config[key] 

10239 

10240 if old_sect != sect: 

10241 cfg.add_section(sect) 

10242 old_sect = sect 

10243 

10244 cfg.set(sect, optname, value) 

10245 

10246 # save the aliases 

10247 cfg.add_section("aliases") 

10248 for alias in gef.session.aliases: 

10249 cfg.set("aliases", alias.alias, alias.command) 

10250 

10251 with GEF_RC.open("w") as fd: 

10252 cfg.write(fd) 

10253 

10254 ok(f"Configuration saved to '{GEF_RC}'") 

10255 return 

10256 

10257 

10258class GefRestoreCommand(gdb.Command): 

10259 """GEF restore sub-command. 

10260 Loads settings from file '~/.gef.rc' and apply them to the configuration of GEF.""" 

10261 _cmdline_ = "gef restore" 

10262 _syntax_ = _cmdline_ 

10263 

10264 def __init__(self) -> None: 

10265 super().__init__(self._cmdline_, gdb.COMMAND_SUPPORT, gdb.COMPLETE_NONE, False) 

10266 self.reload(True) 

10267 return 

10268 

10269 def invoke(self, args: str, from_tty: bool) -> None: 

10270 self.dont_repeat() 

10271 if GEF_RC.is_file(): 

10272 quiet = (args.lower() == "quiet") 

10273 self.reload(quiet) 

10274 return 

10275 

10276 def reload(self, quiet: bool): 

10277 cfg = configparser.ConfigParser() 

10278 cfg.read(GEF_RC) 

10279 

10280 for section in cfg.sections(): 

10281 if section == "aliases": 

10282 # load the aliases 

10283 for key in cfg.options(section): 

10284 try: 

10285 GefAlias(key, cfg.get(section, key)) 

10286 except Exception as e: 

10287 dbg(f"GefAlias() raised exception {e}") 

10288 continue 

10289 

10290 # load the other options 

10291 for optname in cfg.options(section): 

10292 key = f"{section}.{optname}" 

10293 try: 

10294 setting = gef.config.raw_entry(key) 

10295 except Exception: 

10296 continue 

10297 new_value = cfg.get(section, optname) 

10298 if setting.type == bool: 

10299 new_value = True if new_value.upper() in ("TRUE", "T", "1") else False 

10300 setting.value = setting.type(new_value) 

10301 

10302 if not quiet: 10302 ↛ 10303line 10302 didn't jump to line 10303 because the condition on line 10302 was never true

10303 ok(f"Configuration from '{Color.colorify(str(GEF_RC), 'bold blue')}' restored") 

10304 return 

10305 

10306 

10307class GefMissingCommand(gdb.Command): 

10308 """GEF missing sub-command 

10309 Display the GEF commands that could not be loaded, along with the reason of why 

10310 they could not be loaded. 

10311 """ 

10312 _cmdline_ = "gef missing" 

10313 _syntax_ = _cmdline_ 

10314 

10315 def __init__(self) -> None: 

10316 super().__init__(self._cmdline_, gdb.COMMAND_SUPPORT, gdb.COMPLETE_NONE, False) 

10317 return 

10318 

10319 def invoke(self, args: Any, from_tty: bool) -> None: 

10320 self.dont_repeat() 

10321 missing_commands: Dict[str, Exception] = gef.gdb.missing 

10322 if not missing_commands: 

10323 ok("No missing command") 

10324 return 

10325 

10326 for cmd, exc in missing_commands.items(): 

10327 warn(f"Missing `{cmd}`: reason: {str(exc)})") 

10328 return 

10329 

10330 

10331class GefSetCommand(gdb.Command): 

10332 """Override GDB set commands with the context from GEF.""" 

10333 _cmdline_ = "gef set" 

10334 _syntax_ = f"{_cmdline_} [GDB_SET_ARGUMENTS]" 

10335 

10336 def __init__(self) -> None: 

10337 super().__init__(self._cmdline_, gdb.COMMAND_SUPPORT, gdb.COMPLETE_SYMBOL, False) 

10338 return 

10339 

10340 def invoke(self, args: Any, from_tty: bool) -> None: 

10341 self.dont_repeat() 

10342 args = args.split() 

10343 cmd = ["set", args[0],] 

10344 for p in args[1:]: 

10345 if p.startswith("$_gef"): 10345 ↛ 10349line 10345 didn't jump to line 10349 because the condition on line 10345 was always true

10346 c = gdb.parse_and_eval(p) 

10347 cmd.append(c.string()) 

10348 else: 

10349 cmd.append(p) 

10350 

10351 gdb.execute(" ".join(cmd)) 

10352 return 

10353 

10354 

10355class GefRunCommand(gdb.Command): 

10356 """Override GDB run commands with the context from GEF. 

10357 Simple wrapper for GDB run command to use arguments set from `gef set args`.""" 

10358 _cmdline_ = "gef run" 

10359 _syntax_ = f"{_cmdline_} [GDB_RUN_ARGUMENTS]" 

10360 

10361 def __init__(self) -> None: 

10362 super().__init__(self._cmdline_, gdb.COMMAND_SUPPORT, gdb.COMPLETE_FILENAME, False) 

10363 return 

10364 

10365 def invoke(self, args: Any, from_tty: bool) -> None: 

10366 self.dont_repeat() 

10367 if is_alive(): 

10368 gdb.execute("continue") 

10369 return 

10370 

10371 argv = args.split() 

10372 gdb.execute(f"gef set args {' '.join(argv)}") 

10373 gdb.execute("run") 

10374 return 

10375 

10376 

10377class GefAlias(gdb.Command): 

10378 """Simple aliasing wrapper because GDB doesn't do what it should.""" 

10379 

10380 def __init__(self, alias: str, command: str, completer_class: int = gdb.COMPLETE_NONE, command_class: int = gdb.COMMAND_NONE) -> None: 

10381 p = command.split() 

10382 if not p: 10382 ↛ 10383line 10382 didn't jump to line 10383 because the condition on line 10382 was never true

10383 return 

10384 

10385 if any(x for x in gef.session.aliases if x.alias == alias): 

10386 return 

10387 

10388 self.command = command 

10389 self.alias = alias 

10390 c = command.split()[0] 

10391 r = self.lookup_command(c) 

10392 self.__doc__ = f"Alias for '{Color.greenify(command)}'" 

10393 if r is not None: 

10394 _instance = r[1] 

10395 self.__doc__ += f": {_instance.__doc__}" 

10396 

10397 if hasattr(_instance, "complete"): 10397 ↛ 10398line 10397 didn't jump to line 10398 because the condition on line 10397 was never true

10398 self.complete = _instance.complete 

10399 

10400 super().__init__(alias, command_class, completer_class=completer_class) 

10401 gef.session.aliases.append(self) 

10402 return 

10403 

10404 def __repr__(self) -> str: 

10405 return f"GefAlias(from={self.alias}, to={self.command})" 

10406 

10407 def __str__(self) -> str: 

10408 return f"GefAlias(from={self.alias}, to={self.command})" 

10409 

10410 def invoke(self, args: Any, from_tty: bool) -> None: 

10411 gdb.execute(f"{self.command} {args}", from_tty=from_tty) 

10412 return 

10413 

10414 def lookup_command(self, cmd: str) -> Optional[Tuple[str, GenericCommand]]: 

10415 global gef 

10416 for _name, _instance in gef.gdb.commands.items(): 

10417 if cmd == _name: 

10418 return _name, _instance 

10419 

10420 return None 

10421 

10422 

10423@register 

10424class AliasesCommand(GenericCommand): 

10425 """Base command to add, remove, or list aliases.""" 

10426 

10427 _cmdline_ = "aliases" 

10428 _syntax_ = f"{_cmdline_} (add|rm|ls)" 

10429 

10430 def __init__(self) -> None: 

10431 super().__init__(prefix=True) 

10432 return 

10433 

10434 def do_invoke(self, _: List[str]) -> None: 

10435 self.usage() 

10436 return 

10437 

10438 

10439@register 

10440class AliasesAddCommand(AliasesCommand): 

10441 """Command to add aliases.""" 

10442 

10443 _cmdline_ = "aliases add" 

10444 _syntax_ = f"{_cmdline_} [ALIAS] [COMMAND]" 

10445 _example_ = f"{_cmdline_} scope telescope" 

10446 

10447 def __init__(self) -> None: 

10448 super().__init__() 

10449 return 

10450 

10451 def do_invoke(self, argv: List[str]) -> None: 

10452 if len(argv) < 2: 10452 ↛ 10453line 10452 didn't jump to line 10453 because the condition on line 10452 was never true

10453 self.usage() 

10454 return 

10455 GefAlias(argv[0], " ".join(argv[1:])) 

10456 return 

10457 

10458 

10459@register 

10460class AliasesRmCommand(AliasesCommand): 

10461 """Command to remove aliases.""" 

10462 

10463 _cmdline_ = "aliases rm" 

10464 _syntax_ = f"{_cmdline_} [ALIAS]" 

10465 

10466 def __init__(self) -> None: 

10467 super().__init__() 

10468 return 

10469 

10470 def do_invoke(self, argv: List[str]) -> None: 

10471 global gef 

10472 if len(argv) != 1: 10472 ↛ 10473line 10472 didn't jump to line 10473 because the condition on line 10472 was never true

10473 self.usage() 

10474 return 

10475 try: 

10476 alias_to_remove = next(filter(lambda x: x.alias == argv[0], gef.session.aliases)) 

10477 gef.session.aliases.remove(alias_to_remove) 

10478 except (ValueError, StopIteration): 

10479 err(f"{argv[0]} not found in aliases.") 

10480 return 

10481 gef_print("You must reload GEF for alias removals to apply.") 

10482 return 

10483 

10484 

10485@register 

10486class AliasesListCommand(AliasesCommand): 

10487 """Command to list aliases.""" 

10488 

10489 _cmdline_ = "aliases ls" 

10490 _syntax_ = _cmdline_ 

10491 

10492 def __init__(self) -> None: 

10493 super().__init__() 

10494 return 

10495 

10496 def do_invoke(self, _: List[str]) -> None: 

10497 ok("Aliases defined:") 

10498 for a in gef.session.aliases: 

10499 gef_print(f"{a.alias:30s} {RIGHT_ARROW} {a.command}") 

10500 return 

10501 

10502 

10503class GefTmuxSetup(gdb.Command): 

10504 """Setup a comfortable tmux debugging environment.""" 

10505 

10506 def __init__(self) -> None: 

10507 super().__init__("tmux-setup", gdb.COMMAND_NONE, gdb.COMPLETE_NONE) 

10508 GefAlias("screen-setup", "tmux-setup") 

10509 return 

10510 

10511 def invoke(self, args: Any, from_tty: bool) -> None: 

10512 self.dont_repeat() 

10513 

10514 tmux = os.getenv("TMUX") 

10515 if tmux: 

10516 self.tmux_setup() 

10517 return 

10518 

10519 screen = os.getenv("TERM") 

10520 if screen is not None and screen == "screen": 

10521 self.screen_setup() 

10522 return 

10523 

10524 warn("Not in a tmux/screen session") 

10525 return 

10526 

10527 def tmux_setup(self) -> None: 

10528 """Prepare the tmux environment by vertically splitting the current pane, and 

10529 forcing the context to be redirected there.""" 

10530 tmux = which("tmux") 

10531 ok("tmux session found, splitting window...") 

10532 

10533 pane, pty = subprocess.check_output([tmux, "splitw", "-h", '-F#{session_name}:#{window_index}.#{pane_index}-#{pane_tty}', "-P"]).decode().strip().split("-") 

10534 atexit.register(lambda : subprocess.run([tmux, "kill-pane", "-t", pane])) 

10535 # clear the screen and let it wait for input forever 

10536 gdb.execute(f"!'{tmux}' send-keys -t {pane} 'clear ; cat' C-m") 

10537 gdb.execute(f"!'{tmux}' select-pane -L") 

10538 

10539 ok(f"Setting `context.redirect` to '{pty}'...") 

10540 gdb.execute(f"gef config context.redirect {pty}") 

10541 ok("Done!") 

10542 return 

10543 

10544 def screen_setup(self) -> None: 

10545 """Hackish equivalent of the tmux_setup() function for screen.""" 

10546 screen = which("screen") 

10547 sty = os.getenv("STY") 

10548 ok("screen session found, splitting window...") 

10549 fd_script, script_path = tempfile.mkstemp() 

10550 fd_tty, tty_path = tempfile.mkstemp() 

10551 os.close(fd_tty) 

10552 

10553 with os.fdopen(fd_script, "w") as f: 

10554 f.write("startup_message off\n") 

10555 f.write("split -v\n") 

10556 f.write("focus right\n") 

10557 f.write(f"screen bash -c 'tty > {tty_path}; clear; cat'\n") 

10558 f.write("focus left\n") 

10559 

10560 gdb.execute(f"!'{screen}' -r '{sty}' -m -d -X source {script_path}") 

10561 # artificial delay to make sure `tty_path` is populated 

10562 time.sleep(0.25) 

10563 with open(tty_path, "r") as f: 

10564 pty = f.read().strip() 

10565 ok(f"Setting `context.redirect` to '{pty}'...") 

10566 gdb.execute(f"gef config context.redirect {pty}") 

10567 ok("Done!") 

10568 os.unlink(script_path) 

10569 os.unlink(tty_path) 

10570 return 

10571 

10572 

10573class GefInstallExtraScriptCommand(gdb.Command): 

10574 """`gef install` command: installs one or more scripts from the `gef-extras` script repo. Note that the command 

10575 doesn't check for external dependencies the script(s) might require.""" 

10576 _cmdline_ = "gef install" 

10577 _syntax_ = f"{_cmdline_} SCRIPTNAME [SCRIPTNAME [SCRIPTNAME...]]" 

10578 

10579 def __init__(self) -> None: 

10580 super().__init__(self._cmdline_, gdb.COMMAND_SUPPORT, gdb.COMPLETE_NONE, False) 

10581 self.branch = gef.config.get("gef.extras_default_branch", GEF_EXTRAS_DEFAULT_BRANCH) 

10582 return 

10583 

10584 def invoke(self, argv: str, from_tty: bool) -> None: 

10585 self.dont_repeat() 

10586 if not argv: 10586 ↛ 10587line 10586 didn't jump to line 10587 because the condition on line 10586 was never true

10587 err("No script name provided") 

10588 return 

10589 

10590 args = argv.split() 

10591 

10592 if "--list" in args or "-l" in args: 10592 ↛ 10593line 10592 didn't jump to line 10593 because the condition on line 10592 was never true

10593 subprocess.run(["xdg-open", f"https://github.com/hugsy/gef-extras/{self.branch}/"]) 

10594 return 

10595 

10596 self.dirpath = gef.config["gef.tempdir"].expanduser().absolute() 

10597 if not self.dirpath.is_dir(): 10597 ↛ 10598line 10597 didn't jump to line 10598 because the condition on line 10597 was never true

10598 err("'gef.tempdir' is not a valid directory") 

10599 return 

10600 

10601 for script in args: 

10602 script = script.lower() 

10603 if not self.__install_extras_script(script): 10603 ↛ 10604line 10603 didn't jump to line 10604 because the condition on line 10603 was never true

10604 warn(f"Failed to install '{script}', skipping...") 

10605 return 

10606 

10607 

10608 def __install_extras_script(self, script: str) -> bool: 

10609 fpath = self.dirpath / f"{script}.py" 

10610 if not fpath.exists(): 10610 ↛ 10622line 10610 didn't jump to line 10622 because the condition on line 10610 was always true

10611 url = f"https://raw.githubusercontent.com/hugsy/gef-extras/{self.branch}/scripts/{script}.py" 

10612 info(f"Searching for '{script}.py' in `gef-extras@{self.branch}`...") 

10613 data = http_get(url) 

10614 if not data: 10614 ↛ 10615line 10614 didn't jump to line 10615 because the condition on line 10614 was never true

10615 warn("Not found") 

10616 return False 

10617 

10618 with fpath.open("wb") as fd: 

10619 fd.write(data) 

10620 fd.flush() 

10621 

10622 old_command_set = set(gef.gdb.commands) 

10623 gdb.execute(f"source {fpath}") 

10624 new_command_set = set(gef.gdb.commands) 

10625 new_commands = [f"`{c[0]}`" for c in (new_command_set - old_command_set)] 

10626 ok(f"Installed file '{fpath}', new command(s) available: {', '.join(new_commands)}") 

10627 return True 

10628 

10629 

10630# 

10631# GEF internal classes 

10632# 

10633 

10634def __gef_prompt__(current_prompt: Callable[[Callable], str]) -> str: 

10635 """GEF custom prompt function.""" 

10636 if gef.config["gef.readline_compat"] is True: return GEF_PROMPT 

10637 if gef.config["gef.disable_color"] is True: return GEF_PROMPT 

10638 prompt = gef.session.remote.mode.prompt_string() if gef.session.remote else "" 

10639 prompt += GEF_PROMPT_ON if is_alive() else GEF_PROMPT_OFF 

10640 return prompt 

10641 

10642 

10643class GefManager(metaclass=abc.ABCMeta): 

10644 def reset_caches(self) -> None: 

10645 """Reset the LRU-cached attributes""" 

10646 for attr in dir(self): 

10647 try: 

10648 obj = getattr(self, attr) 

10649 if not hasattr(obj, "cache_clear"): 10649 ↛ 10651line 10649 didn't jump to line 10651 because the condition on line 10649 was always true

10650 continue 

10651 obj.cache_clear() 

10652 except: # we're reseting the cache here, we don't care if (or which) exception triggers 

10653 continue 

10654 return 

10655 

10656 

10657class GefMemoryManager(GefManager): 

10658 """Class that manages memory access for gef.""" 

10659 def __init__(self) -> None: 

10660 self.reset_caches() 

10661 return 

10662 

10663 def reset_caches(self) -> None: 

10664 super().reset_caches() 

10665 self.__maps: Optional[List[Section]] = None 

10666 return 

10667 

10668 def write(self, address: int, buffer: ByteString, length: Optional[int] = None) -> None: 

10669 """Write `buffer` at address `address`.""" 

10670 length = length or len(buffer) 

10671 gdb.selected_inferior().write_memory(address, buffer, length) 

10672 

10673 def read(self, addr: int, length: int = 0x10) -> bytes: 

10674 """Return a `length` long byte array with the copy of the process memory at `addr`.""" 

10675 return gdb.selected_inferior().read_memory(addr, length).tobytes() 

10676 

10677 def read_integer(self, addr: int) -> int: 

10678 """Return an integer read from memory.""" 

10679 sz = gef.arch.ptrsize 

10680 mem = self.read(addr, sz) 

10681 unpack = u32 if sz == 4 else u64 

10682 return unpack(mem) 

10683 

10684 def read_cstring(self, 

10685 address: int, 

10686 max_length: int = GEF_MAX_STRING_LENGTH, 

10687 encoding: Optional[str] = None) -> str: 

10688 """Return a C-string read from memory.""" 

10689 encoding = encoding or "unicode-escape" 

10690 length = min(address | (DEFAULT_PAGE_SIZE-1), max_length+1) 

10691 

10692 try: 

10693 res_bytes = self.read(address, length) 

10694 except gdb.error: 

10695 err(f"Can't read memory at '{address}'") 

10696 return "" 

10697 try: 

10698 with warnings.catch_warnings(): 

10699 # ignore DeprecationWarnings (see #735) 

10700 warnings.simplefilter("ignore") 

10701 res = res_bytes.decode(encoding, "strict") 

10702 except UnicodeDecodeError: 

10703 # latin-1 as fallback due to its single-byte to glyph mapping 

10704 res = res_bytes.decode("latin-1", "replace") 

10705 

10706 res = res.split("\x00", 1)[0] 

10707 ustr = res.replace("\n", "\\n").replace("\r", "\\r").replace("\t", "\\t") 

10708 if max_length and len(res) > max_length: 

10709 return f"{ustr[:max_length]}[...]" 

10710 return ustr 

10711 

10712 def read_ascii_string(self, address: int) -> Optional[str]: 

10713 """Read an ASCII string from memory""" 

10714 cstr = self.read_cstring(address) 

10715 if isinstance(cstr, str) and cstr and all(x in string.printable for x in cstr): 

10716 return cstr 

10717 return None 

10718 

10719 @property 

10720 def maps(self) -> List[Section]: 

10721 if not self.__maps: 

10722 self.__maps = self.__parse_maps() 

10723 return self.__maps 

10724 

10725 def __parse_maps(self) -> Optional[List[Section]]: 

10726 """Return the mapped memory sections. If the current arch has its maps 

10727 method defined, then defer to that to generated maps, otherwise, try to 

10728 figure it out from procfs, then info sections, then monitor info 

10729 mem.""" 

10730 if gef.arch.maps is not None: 10730 ↛ 10731line 10730 didn't jump to line 10731 because the condition on line 10730 was never true

10731 return list(gef.arch.maps()) 

10732 

10733 try: 

10734 return list(self.parse_gdb_info_proc_maps()) 

10735 except: 

10736 pass 

10737 

10738 try: 

10739 return list(self.parse_procfs_maps()) 

10740 except: 

10741 pass 

10742 

10743 try: 

10744 return list(self.parse_monitor_info_mem()) 

10745 except: 

10746 pass 

10747 

10748 raise RuntimeError("Failed to get memory layout") 

10749 

10750 @classmethod 

10751 def parse_procfs_maps(cls) -> Generator[Section, None, None]: 

10752 """Get the memory mapping from procfs.""" 

10753 procfs_mapfile = gef.session.maps 

10754 if not procfs_mapfile: 

10755 is_remote = gef.session.remote is not None 

10756 raise FileNotFoundError(f"Missing {'remote ' if is_remote else ''}procfs map file") 

10757 

10758 with procfs_mapfile.open("r") as fd: 

10759 for line in fd: 

10760 line = line.strip() 

10761 addr, perm, off, _, rest = line.split(" ", 4) 

10762 rest = rest.split(" ", 1) 

10763 if len(rest) == 1: 

10764 inode = rest[0] 

10765 pathname = "" 

10766 else: 

10767 inode = rest[0] 

10768 pathname = rest[1].lstrip() 

10769 

10770 addr_start, addr_end = parse_string_range(addr) 

10771 off = int(off, 16) 

10772 perm = Permission.from_process_maps(perm) 

10773 inode = int(inode) 

10774 yield Section(page_start=addr_start, 

10775 page_end=addr_end, 

10776 offset=off, 

10777 permission=perm, 

10778 inode=inode, 

10779 path=pathname) 

10780 return 

10781 

10782 @classmethod 

10783 def parse_gdb_info_proc_maps(cls) -> Generator[Section, None, None]: 

10784 """Get the memory mapping from GDB's command `maintenance info sections` (limited info).""" 

10785 if GDB_VERSION < (11, 0): 10785 ↛ 10786line 10785 didn't jump to line 10786 because the condition on line 10785 was never true

10786 raise AttributeError("Disregarding old format") 

10787 

10788 output = (gdb.execute("info proc mappings", to_string=True) or "") 

10789 if not output: 10789 ↛ 10790line 10789 didn't jump to line 10790 because the condition on line 10789 was never true

10790 raise AttributeError 

10791 

10792 start_idx = output.find("Start Addr") 

10793 if start_idx == -1: 

10794 raise AttributeError 

10795 

10796 output = output[start_idx:] 

10797 lines = output.splitlines() 

10798 if len(lines) < 2: 10798 ↛ 10799line 10798 didn't jump to line 10799 because the condition on line 10798 was never true

10799 raise AttributeError 

10800 

10801 # The function assumes the following output format (as of GDB 11+) for `info proc mappings`: 

10802 # - live process (incl. remote) 

10803 # ``` 

10804 # Start Addr End Addr Size Offset Perms objfile 

10805 # 0x555555554000 0x555555558000 0x4000 0x0 r--p /usr/bin/ls 

10806 # 0x555555558000 0x55555556c000 0x14000 0x4000 r-xp /usr/bin/ls 

10807 # [...] 

10808 # ``` 

10809 # or 

10810 # - coredump & rr 

10811 # ``` 

10812 # Start Addr End Addr Size Offset objfile 

10813 # 0x555555554000 0x555555558000 0x4000 0x0 /usr/bin/ls 

10814 # 0x555555558000 0x55555556c000 0x14000 0x4000 /usr/bin/ls 

10815 # ``` 

10816 # In the latter case the 'Perms' header is missing, so mock the Permission to `rwx` so 

10817 # `dereference` will still work. 

10818 

10819 mock_permission = all(map(lambda x: x.strip() != "Perms", lines[0].split())) 

10820 for line in lines[1:]: 

10821 if not line: 10821 ↛ 10822line 10821 didn't jump to line 10822 because the condition on line 10821 was never true

10822 break 

10823 

10824 parts = [x.strip() for x in line.split()] 

10825 addr_start, addr_end, _, offset = [int(x, 16) for x in parts[0:4]] 

10826 if mock_permission: 10826 ↛ 10827line 10826 didn't jump to line 10827 because the condition on line 10826 was never true

10827 perm = Permission(7) 

10828 path = " ".join(parts[4:]) if len(parts) >= 4 else "" 

10829 else: 

10830 perm = Permission.from_process_maps(parts[4]) 

10831 path = " ".join(parts[5:]) if len(parts) >= 5 else "" 

10832 yield Section( 

10833 page_start=addr_start, 

10834 page_end=addr_end, 

10835 offset=offset, 

10836 permission=perm, 

10837 path=path, 

10838 ) 

10839 return 

10840 

10841 @classmethod 

10842 def parse_monitor_info_mem(cls) -> Generator[Section, None, None]: 

10843 """Get the memory mapping from GDB's command `monitor info mem` 

10844 This can raise an exception, which the memory manager takes to mean 

10845 that this method does not work to get a map. 

10846 """ 

10847 stream = StringIO(gdb.execute("monitor info mem", to_string=True)) 

10848 

10849 for line in stream: 

10850 try: 

10851 ranges, off, perms = line.split() 

10852 off = int(off, 16) 

10853 start, end = [int(s, 16) for s in ranges.split("-")] 

10854 except ValueError: 

10855 continue 

10856 

10857 perm = Permission.from_monitor_info_mem(perms) 

10858 yield Section(page_start=start, 

10859 page_end=end, 

10860 offset=off, 

10861 permission=perm) 

10862 

10863 @staticmethod 

10864 def parse_info_mem(): 

10865 """Get the memory mapping from GDB's command `info mem`. This can be 

10866 provided by certain gdbserver implementations.""" 

10867 for line in StringIO(gdb.execute("info mem", to_string=True)): 

10868 # Using memory regions provided by the target. 

10869 # Num Enb Low Addr High Addr Attrs 

10870 # 0 y 0x10000000 0x10200000 flash blocksize 0x1000 nocache 

10871 # 1 y 0x20000000 0x20042000 rw nocache 

10872 _, en, start, end, *attrs = line.split() 

10873 if en != "y": 

10874 continue 

10875 

10876 if "flash" in attrs: 

10877 perm = Permission.from_info_mem("r") 

10878 else: 

10879 perm = Permission.from_info_mem("rw") 

10880 yield Section(page_start=int(start, 0), 

10881 page_end=int(end, 0), 

10882 permission=perm) 

10883 

10884 def append(self, section: Section): 

10885 if not self.maps: 

10886 raise AttributeError("No mapping defined") 

10887 if not isinstance(section, Section): 

10888 raise TypeError("section has an invalid type") 

10889 

10890 assert self.__maps 

10891 for s in self.__maps: 

10892 if section.overlaps(s): 

10893 raise RuntimeError(f"{section} overlaps {s}") 

10894 self.__maps.append(section) 

10895 return self 

10896 

10897 def __iadd__(self, section: Section): 

10898 return self.append(section) 

10899 

10900 

10901class GefHeapManager(GefManager): 

10902 """Class managing session heap.""" 

10903 def __init__(self) -> None: 

10904 self.reset_caches() 

10905 return 

10906 

10907 def reset_caches(self) -> None: 

10908 self.__libc_main_arena: Optional[GlibcArena] = None 

10909 self.__libc_selected_arena: Optional[GlibcArena] = None 

10910 self.__heap_base = None 

10911 return 

10912 

10913 @property 

10914 def main_arena(self) -> Optional[GlibcArena]: 

10915 if not self.__libc_main_arena: 

10916 try: 

10917 __main_arena_addr = GefHeapManager.find_main_arena_addr() 

10918 self.__libc_main_arena = GlibcArena(f"*{__main_arena_addr:#x}") 

10919 # the initialization of `main_arena` also defined `selected_arena`, so 

10920 # by default, `main_arena` == `selected_arena` 

10921 self.selected_arena = self.__libc_main_arena 

10922 except: 

10923 # the search for arena can fail when the session is not started 

10924 pass 

10925 return self.__libc_main_arena 

10926 

10927 @main_arena.setter 

10928 def main_arena(self, value: GlibcArena) -> None: 

10929 self.__libc_main_arena = value 

10930 return 

10931 

10932 @staticmethod 

10933 @lru_cache() 

10934 def find_main_arena_addr() -> int: 

10935 assert gef.libc.version 

10936 """A helper function to find the glibc `main_arena` address, either from 

10937 symbol, from its offset from `__malloc_hook` or by brute force.""" 

10938 # Before anything else, use libc offset from config if available 

10939 if gef.config["gef.main_arena_offset"]: 10939 ↛ 10940line 10939 didn't jump to line 10940 because the condition on line 10939 was never true

10940 try: 

10941 libc_base = get_section_base_address("libc") 

10942 offset: int = gef.config["gef.main_arena_offset"] 

10943 if libc_base: 

10944 dbg(f"Using main_arena_offset={offset:#x} from config") 

10945 addr = libc_base + offset 

10946 

10947 # Verify the found address before returning 

10948 if GlibcArena.verify(addr): 

10949 return addr 

10950 except gdb.error: 

10951 pass 

10952 

10953 # First, try to find `main_arena` symbol directly 

10954 try: 

10955 return parse_address(f"&{LIBC_HEAP_MAIN_ARENA_DEFAULT_NAME}") 

10956 except gdb.error: 

10957 pass 

10958 

10959 # Second, try to find it by offset from `__malloc_hook` 

10960 if gef.libc.version < (2, 34): 

10961 try: 

10962 malloc_hook_addr = parse_address("(void *)&__malloc_hook") 

10963 

10964 struct_size = ctypes.sizeof(GlibcArena.malloc_state_t()) 

10965 

10966 if is_x86(): 

10967 addr = align_address_to_size(malloc_hook_addr + gef.arch.ptrsize, 0x20) 

10968 elif is_arch(Elf.Abi.AARCH64): 

10969 addr = malloc_hook_addr - gef.arch.ptrsize*2 - struct_size 

10970 elif is_arch(Elf.Abi.ARM): 

10971 addr = malloc_hook_addr - gef.arch.ptrsize - struct_size 

10972 else: 

10973 addr = None 

10974 

10975 # Verify the found address before returning 

10976 if addr and GlibcArena.verify(addr): 

10977 return addr 

10978 

10979 except gdb.error: 

10980 pass 

10981 

10982 # Last resort, try to find it via brute force if enabled in settings 

10983 if gef.config["gef.bruteforce_main_arena"]: 

10984 alignment = 0x8 

10985 try: 

10986 dbg("Trying to bruteforce main_arena address") 

10987 # setup search_range for `main_arena` to `.data` of glibc 

10988 search_filter = lambda f: "libc" in pathlib.Path(f.filename).name and f.name == ".data" 

10989 

10990 for dotdata in list(filter(search_filter, get_info_files())): 

10991 search_range = range(dotdata.zone_start, dotdata.zone_end, alignment) 

10992 # find first possible candidate 

10993 for addr in search_range: 

10994 if GlibcArena.verify(addr): 

10995 dbg(f"Found candidate at {addr:#x}") 

10996 return addr 

10997 dbg("Bruteforce not successful") 

10998 except Exception: 

10999 pass 

11000 

11001 # Nothing helped 

11002 err_msg = f"Cannot find main_arena for {gef.arch.arch}. You might want to set a manually found libc offset " 

11003 if not gef.config["gef.bruteforce_main_arena"]: 

11004 err_msg += "or allow bruteforcing " 

11005 err_msg += "through the GEF config." 

11006 raise OSError(err_msg) 

11007 

11008 @property 

11009 def selected_arena(self) -> Optional[GlibcArena]: 

11010 if not self.__libc_selected_arena: 11010 ↛ 11012line 11010 didn't jump to line 11012 because the condition on line 11010 was never true

11011 # `selected_arena` must default to `main_arena` 

11012 self.__libc_selected_arena = self.main_arena 

11013 return self.__libc_selected_arena 

11014 

11015 @selected_arena.setter 

11016 def selected_arena(self, value: GlibcArena) -> None: 

11017 self.__libc_selected_arena = value 

11018 return 

11019 

11020 @property 

11021 def arenas(self) -> Union[List, Iterator[GlibcArena]]: 

11022 if not self.main_arena: 11022 ↛ 11023line 11022 didn't jump to line 11023 because the condition on line 11022 was never true

11023 return [] 

11024 return iter(self.main_arena) 

11025 

11026 @property 

11027 def base_address(self) -> Optional[int]: 

11028 if not self.__heap_base: 

11029 base = 0 

11030 try: 

11031 base = parse_address("mp_->sbrk_base") 

11032 base = self.malloc_align_address(base) 

11033 except gdb.error: 

11034 # missing symbol, try again 

11035 base = 0 

11036 if not base: 11036 ↛ 11037line 11036 didn't jump to line 11037 because the condition on line 11036 was never true

11037 base = get_section_base_address("[heap]") 

11038 self.__heap_base = base 

11039 return self.__heap_base 

11040 

11041 @property 

11042 def chunks(self) -> Union[List, Iterator]: 

11043 if not self.base_address: 

11044 return [] 

11045 return iter(GlibcChunk(self.base_address, from_base=True)) 

11046 

11047 @property 

11048 def min_chunk_size(self) -> int: 

11049 return 4 * gef.arch.ptrsize 

11050 

11051 @property 

11052 def malloc_alignment(self) -> int: 

11053 assert gef.libc.version 

11054 __default_malloc_alignment = 0x10 

11055 if gef.libc.version >= (2, 26) and is_x86_32(): 11055 ↛ 11058line 11055 didn't jump to line 11058 because the condition on line 11055 was never true

11056 # Special case introduced in Glibc 2.26: 

11057 # https://elixir.bootlin.com/glibc/glibc-2.26/source/sysdeps/i386/malloc-alignment.h#L22 

11058 return __default_malloc_alignment 

11059 # Generic case: 

11060 # https://elixir.bootlin.com/glibc/glibc-2.26/source/sysdeps/generic/malloc-alignment.h#L22 

11061 return 2 * gef.arch.ptrsize 

11062 

11063 def csize2tidx(self, size: int) -> int: 

11064 return abs((size - self.min_chunk_size + self.malloc_alignment - 1)) // self.malloc_alignment 

11065 

11066 def tidx2size(self, idx: int) -> int: 

11067 return idx * self.malloc_alignment + self.min_chunk_size 

11068 

11069 def malloc_align_address(self, address: int) -> int: 

11070 """Align addresses according to glibc's MALLOC_ALIGNMENT. See also Issue #689 on Github""" 

11071 malloc_alignment = self.malloc_alignment 

11072 ceil = lambda n: int(-1 * n // 1 * -1) 

11073 return malloc_alignment * ceil((address / malloc_alignment)) 

11074 

11075 

11076class GefSetting: 

11077 """Basic class for storing gef settings as objects""" 

11078 

11079 def __init__(self, value: Any, cls: Optional[type] = None, description: Optional[str] = None, hooks: Optional[Dict[str, List[Callable]]] = None) -> None: 

11080 self.value = value 

11081 self.type = cls or type(value) 

11082 self.description = description or "" 

11083 self.hooks: Dict[str, List[Callable]] = collections.defaultdict(list) 

11084 if not hooks: 

11085 hooks = {"on_read": [], "on_write": [], "on_changed": []} 

11086 

11087 for access, funcs in hooks.items(): 

11088 self.add_hook(access, funcs) 

11089 return 

11090 

11091 def __str__(self) -> str: 

11092 return f"Setting(type={self.type.__name__}, value='{self.value}', desc='{self.description[:10]}...', " \ 

11093 f"read_hooks={len(self.hooks['on_read'])}, write_hooks={len(self.hooks['on_write'])}, "\ 

11094 f"changed_hooks={len(self.hooks['on_changed'])})" 

11095 

11096 def add_hook(self, access: str, funcs: List[Callable]): 

11097 if access not in ("on_read", "on_write", "on_changed"): 11097 ↛ 11098line 11097 didn't jump to line 11098 because the condition on line 11097 was never true

11098 raise ValueError("invalid access type") 

11099 for func in funcs: 

11100 if not callable(func): 11100 ↛ 11101line 11100 didn't jump to line 11101 because the condition on line 11100 was never true

11101 raise ValueError("hook is not callable") 

11102 self.hooks[access].append(func) 

11103 return self 

11104 

11105 @staticmethod 

11106 def no_spaces(value: pathlib.Path): 

11107 if " " in str(value): 

11108 raise ValidationError("setting cannot contain spaces") 

11109 

11110 @staticmethod 

11111 def must_exist(value: pathlib.Path): 

11112 if not value or not pathlib.Path(value).expanduser().absolute().exists(): 

11113 raise ValidationError("specified path must exist") 

11114 

11115 @staticmethod 

11116 def create_folder_tree(value: pathlib.Path): 

11117 value.mkdir(0o755, exist_ok=True, parents=True) 

11118 

11119 

11120class GefSettingsManager(dict): 

11121 """ 

11122 GefSettings acts as a dict where the global settings are stored and can be read, written or deleted as any other dict. 

11123 For instance, to read a specific command setting: `gef.config[mycommand.mysetting]` 

11124 """ 

11125 def __getitem__(self, name: str) -> Any: 

11126 setting : GefSetting = super().__getitem__(name) 

11127 self.__invoke_read_hooks(setting) 

11128 return setting.value 

11129 

11130 def __setitem__(self, name: str, value: Any) -> None: 

11131 # check if the key exists 

11132 if super().__contains__(name): 

11133 # if so, update its value directly 

11134 setting = super().__getitem__(name) 

11135 if not isinstance(setting, GefSetting): raise TypeError 11135 ↛ exitline 11135 didn't except from function '__setitem__' because the raise on line 11135 wasn't executed

11136 new_value = setting.type(value) 

11137 dbg(f"in __invoke_changed_hooks(\"{name}\"), setting.value={setting.value} -> new_value={new_value}, changing={bool(setting.value != new_value)}") 

11138 self.__invoke_changed_hooks(setting, new_value) 

11139 self.__invoke_write_hooks(setting, new_value) 

11140 setting.value = new_value 

11141 return 

11142 

11143 # if not, assert `value` is a GefSetting, then insert it 

11144 if not isinstance(value, GefSetting): raise TypeError("Invalid argument") 11144 ↛ exitline 11144 didn't except from function '__setitem__' because the raise on line 11144 wasn't executed

11145 if not value.type: raise TypeError("Invalid type") 11145 ↛ exitline 11145 didn't except from function '__setitem__' because the raise on line 11145 wasn't executed

11146 if not value.description: raise AttributeError("Invalid description") 11146 ↛ exitline 11146 didn't except from function '__setitem__' because the raise on line 11146 wasn't executed

11147 setting = value 

11148 value = setting.value 

11149 self.__invoke_write_hooks(setting, value) 

11150 super().__setitem__(name, setting) 

11151 return 

11152 

11153 def __delitem__(self, name: str) -> None: 

11154 return super().__delitem__(name) 

11155 

11156 def raw_entry(self, name: str) -> GefSetting: 

11157 return super().__getitem__(name) 

11158 

11159 def __invoke_read_hooks(self, setting: GefSetting) -> None: 

11160 for callback in setting.hooks["on_read"]: 11160 ↛ 11161line 11160 didn't jump to line 11161 because the loop on line 11160 never started

11161 callback() 

11162 return 

11163 

11164 def __invoke_changed_hooks(self, setting: GefSetting, new_value: Any) -> None: 

11165 old_value = setting.value 

11166 if old_value == new_value: 

11167 return 

11168 for callback in setting.hooks["on_changed"]: 11168 ↛ 11169line 11168 didn't jump to line 11169 because the loop on line 11168 never started

11169 callback(old_value, new_value) 

11170 

11171 def __invoke_write_hooks(self, setting: GefSetting, new_value: Any) -> None: 

11172 for callback in setting.hooks["on_write"]: 

11173 callback(new_value) 

11174 

11175 

11176class GefSessionManager(GefManager): 

11177 """Class managing the runtime properties of GEF. """ 

11178 def __init__(self) -> None: 

11179 self.reset_caches() 

11180 self.remote: Optional["GefRemoteSessionManager"] = None 

11181 self.remote_initializing: bool = False 

11182 self.qemu_mode: bool = False 

11183 self.convenience_vars_index: int = 0 

11184 self.heap_allocated_chunks: List[Tuple[int, int]] = [] 

11185 self.heap_freed_chunks: List[Tuple[int, int]] = [] 

11186 self.heap_uaf_watchpoints: List[UafWatchpoint] = [] 

11187 self.pie_breakpoints: Dict[int, PieVirtualBreakpoint] = {} 

11188 self.pie_counter: int = 1 

11189 self.aliases: List[GefAlias] = [] 

11190 self.modules: List[FileFormat] = [] 

11191 self.constants = {} # a dict for runtime constants (like 3rd party file paths) 

11192 for constant in ("python3", "readelf", "nm", "file", "ps"): 

11193 self.constants[constant] = which(constant) 

11194 return 

11195 

11196 def reset_caches(self) -> None: 

11197 super().reset_caches() 

11198 self._auxiliary_vector = None 

11199 self._pagesize = None 

11200 self._os = None 

11201 self._pid = None 

11202 self._file = None 

11203 self._maps: Optional[pathlib.Path] = None 

11204 self._root: Optional[pathlib.Path] = None 

11205 return 

11206 

11207 def __str__(self) -> str: 

11208 _type = "Local" if self.remote is None else f"Remote/{self.remote.mode}" 

11209 return f"Session(type={_type}, pid={self.pid or 'Not running'}, os='{self.os}')" 

11210 

11211 def __repr__(self) -> str: 

11212 return str(self) 

11213 

11214 @property 

11215 def auxiliary_vector(self) -> Optional[Dict[str, int]]: 

11216 if not is_alive(): 

11217 return None 

11218 if is_qemu_system(): 11218 ↛ 11219line 11218 didn't jump to line 11219 because the condition on line 11218 was never true

11219 return None 

11220 if not self._auxiliary_vector: 

11221 auxiliary_vector = {} 

11222 auxv_info = gdb.execute("info auxv", to_string=True) or "" 

11223 if not auxv_info or "failed" in auxv_info: 11223 ↛ 11224line 11223 didn't jump to line 11224 because the condition on line 11223 was never true

11224 err("Failed to query auxiliary variables") 

11225 return None 

11226 for line in auxv_info.splitlines(): 

11227 line = line.split('"')[0].strip() # remove the ending string (if any) 

11228 line = line.split() # split the string by whitespace(s) 

11229 if len(line) < 4: 

11230 continue 

11231 __av_type = line[1] 

11232 __av_value = line[-1] 

11233 auxiliary_vector[__av_type] = int(__av_value, base=0) 

11234 self._auxiliary_vector = auxiliary_vector 

11235 return self._auxiliary_vector 

11236 

11237 @property 

11238 def os(self) -> str: 

11239 """Return the current OS.""" 

11240 if not self._os: 

11241 self._os = platform.system().lower() 

11242 return self._os 

11243 

11244 @property 

11245 def pid(self) -> int: 

11246 """Return the PID of the target process.""" 

11247 if not self._pid: 

11248 pid = gdb.selected_inferior().pid if not self.qemu_mode else gdb.selected_thread().ptid[1] 

11249 if not pid: 

11250 raise RuntimeError("cannot retrieve PID for target process") 

11251 self._pid = pid 

11252 return self._pid 

11253 

11254 @property 

11255 def file(self) -> Optional[pathlib.Path]: 

11256 """Return a Path object of the target process.""" 

11257 if self.remote is not None: 

11258 return self.remote.file 

11259 progspace = gdb.current_progspace() 

11260 assert progspace 

11261 fpath: str = progspace.filename 

11262 if fpath and not self._file: 

11263 self._file = pathlib.Path(fpath).expanduser() 

11264 return self._file 

11265 

11266 @property 

11267 def cwd(self) -> Optional[pathlib.Path]: 

11268 if self.remote is not None: 

11269 return self.remote.root 

11270 return self.file.parent if self.file else None 

11271 

11272 @property 

11273 def pagesize(self) -> int: 

11274 """Get the system page size""" 

11275 auxval = self.auxiliary_vector 

11276 if not auxval: 

11277 return DEFAULT_PAGE_SIZE 

11278 self._pagesize = auxval["AT_PAGESZ"] 

11279 return self._pagesize 

11280 

11281 @property 

11282 def canary(self) -> Optional[Tuple[int, int]]: 

11283 """Return a tuple of the canary address and value, read from the canonical 

11284 location if supported by the architecture. Otherwise, read from the auxiliary 

11285 vector.""" 

11286 try: 

11287 canary_location = gef.arch.canary_address() 

11288 canary = gef.memory.read_integer(canary_location) 

11289 except (NotImplementedError, gdb.error): 

11290 # Fall back to `AT_RANDOM`, which is the original source 

11291 # of the canary value but not the canonical location 

11292 return self.original_canary 

11293 return canary, canary_location 

11294 

11295 @property 

11296 def original_canary(self) -> Optional[Tuple[int, int]]: 

11297 """Return a tuple of the initial canary address and value, read from the 

11298 auxiliary vector.""" 

11299 auxval = self.auxiliary_vector 

11300 if not auxval: 

11301 return None 

11302 canary_location = auxval["AT_RANDOM"] 

11303 canary = gef.memory.read_integer(canary_location) 

11304 canary &= ~0xFF 

11305 return canary, canary_location 

11306 

11307 @property 

11308 def maps(self) -> Optional[pathlib.Path]: 

11309 """Returns the Path to the procfs entry for the memory mapping.""" 

11310 if not is_alive(): 

11311 return None 

11312 if not self._maps: 

11313 if self.remote is not None: 

11314 self._maps = self.remote.maps 

11315 else: 

11316 self._maps = pathlib.Path(f"/proc/{self.pid}/maps") 

11317 return self._maps 

11318 

11319 @property 

11320 def root(self) -> Optional[pathlib.Path]: 

11321 """Returns the path to the process's root directory.""" 

11322 if not is_alive(): 

11323 return None 

11324 if not self._root: 

11325 self._root = pathlib.Path(f"/proc/{self.pid}/root") 

11326 return self._root 

11327 

11328 

11329class GefRemoteSessionManager(GefSessionManager): 

11330 """Class for managing remote sessions with GEF. It will create a temporary environment 

11331 designed to clone the remote one.""" 

11332 

11333 class RemoteMode(enum.IntEnum): 

11334 GDBSERVER = 0 

11335 QEMU = 1 

11336 RR = 2 

11337 

11338 def __str__(self): 

11339 return self.name 

11340 

11341 def __repr__(self): 

11342 return f"RemoteMode = {str(self)} ({int(self)})" 

11343 

11344 def prompt_string(self) -> str: 

11345 if self == GefRemoteSessionManager.RemoteMode.QEMU: 

11346 return Color.boldify("(qemu) ") 

11347 if self == GefRemoteSessionManager.RemoteMode.RR: 

11348 return Color.boldify("(rr) ") 

11349 if self == GefRemoteSessionManager.RemoteMode.GDBSERVER: 

11350 return Color.boldify("(remote) ") 

11351 raise AttributeError("Unknown value") 

11352 

11353 def __init__(self, host: str, port: int, pid: int =-1, qemu: Optional[pathlib.Path] = None) -> None: 

11354 super().__init__() 

11355 self.__host = host 

11356 self.__port = port 

11357 self.__local_root_fd = tempfile.TemporaryDirectory() 

11358 self.__local_root_path = pathlib.Path(self.__local_root_fd.name) 

11359 self.__qemu = qemu 

11360 

11361 if self.__qemu is not None: 

11362 self._mode = GefRemoteSessionManager.RemoteMode.QEMU 

11363 elif os.environ.get("GDB_UNDER_RR", None) == "1": 11363 ↛ 11364line 11363 didn't jump to line 11364 because the condition on line 11363 was never true

11364 self._mode = GefRemoteSessionManager.RemoteMode.RR 

11365 else: 

11366 self._mode = GefRemoteSessionManager.RemoteMode.GDBSERVER 

11367 

11368 def close(self) -> None: 

11369 self.__local_root_fd.cleanup() 

11370 try: 

11371 gef_on_new_unhook(self.remote_objfile_event_handler) 

11372 gef_on_new_hook(new_objfile_handler) 

11373 except Exception as e: 

11374 warn(f"Exception while restoring local context: {str(e)}") 

11375 return 

11376 

11377 def __str__(self) -> str: 

11378 return f"RemoteSession(target='{self.target}', local='{self.root}', pid={self.pid}, mode={self.mode})" 

11379 

11380 def __repr__(self) -> str: 

11381 return str(self) 

11382 

11383 @property 

11384 def target(self) -> str: 

11385 return f"{self.__host}:{self.__port}" 

11386 

11387 @property 

11388 def root(self) -> pathlib.Path: 

11389 return self.__local_root_path.absolute() 

11390 

11391 @property 

11392 def file(self) -> pathlib.Path: 

11393 """Path to the file being debugged as seen by the remote endpoint.""" 

11394 if not self._file: 

11395 progspace = gdb.current_progspace() 

11396 if not progspace: 11396 ↛ 11397line 11396 didn't jump to line 11397 because the condition on line 11396 was never true

11397 raise RuntimeError("No session started") 

11398 filename = progspace.filename 

11399 if not filename: 11399 ↛ 11400line 11399 didn't jump to line 11400 because the condition on line 11399 was never true

11400 raise RuntimeError("No session started") 

11401 start_idx = len("target:") if filename.startswith("target:") else 0 

11402 self._file = pathlib.Path(progspace.filename[start_idx:]) 

11403 return self._file 

11404 

11405 @property 

11406 def lfile(self) -> pathlib.Path: 

11407 """Local path to the file being debugged.""" 

11408 return self.root / str(self.file).lstrip("/") 

11409 

11410 @property 

11411 def maps(self) -> pathlib.Path: 

11412 if not self._maps: 

11413 self._maps = self.root / f"proc/{self.pid}/maps" 

11414 return self._maps 

11415 

11416 @property 

11417 def mode(self) -> RemoteMode: 

11418 return self._mode 

11419 

11420 def sync(self, src: str, dst: Optional[str] = None) -> bool: 

11421 """Copy the `src` into the temporary chroot. If `dst` is provided, that path will be 

11422 used instead of `src`.""" 

11423 if not dst: 

11424 dst = src 

11425 tgt = self.root / dst.lstrip("/") 

11426 if tgt.exists(): 11426 ↛ 11427line 11426 didn't jump to line 11427 because the condition on line 11426 was never true

11427 return True 

11428 tgt.parent.mkdir(parents=True, exist_ok=True) 

11429 dbg(f"[remote] downloading '{src}' -> '{tgt}'") 

11430 gdb.execute(f"remote get '{src}' '{tgt.absolute()}'") 

11431 return tgt.exists() 

11432 

11433 def connect(self, pid: int) -> bool: 

11434 """Connect to remote target. If in extended mode, also attach to the given PID.""" 

11435 # before anything, register our new hook to download files from the remote target 

11436 dbg(f"[remote] Installing new objfile handlers") 

11437 try: 

11438 gef_on_new_unhook(new_objfile_handler) 

11439 except SystemError: 

11440 # the default objfile handler might already have been removed, ignore failure 

11441 pass 

11442 

11443 gef_on_new_hook(self.remote_objfile_event_handler) 

11444 

11445 # then attempt to connect 

11446 is_extended_mode = (pid > -1) 

11447 dbg(f"[remote] Enabling extended remote: {bool(is_extended_mode)}") 

11448 try: 

11449 with DisableContextOutputContext(): 

11450 cmd = f"target {'extended-' if is_extended_mode else ''}remote {self.target}" 

11451 dbg(f"[remote] Executing '{cmd}'") 

11452 gdb.execute(cmd) 

11453 if is_extended_mode: 11453 ↛ 11454line 11453 didn't jump to line 11454 because the condition on line 11453 was never true

11454 gdb.execute(f"attach {pid:d}") 

11455 return True 

11456 except Exception as e: 

11457 err(f"Failed to connect to {self.target}: {e}") 

11458 

11459 # a failure will trigger the cleanup, deleting our hook anyway 

11460 return False 

11461 

11462 def setup(self) -> bool: 

11463 # setup remote adequately depending on remote or qemu mode 

11464 if self.mode == GefRemoteSessionManager.RemoteMode.QEMU: 

11465 dbg(f"Setting up as qemu session, target={self.__qemu}") 

11466 self.__setup_qemu() 

11467 elif self.mode == GefRemoteSessionManager.RemoteMode.RR: 11467 ↛ 11468line 11467 didn't jump to line 11468 because the condition on line 11467 was never true

11468 dbg(f"Setting up as rr session") 

11469 self.__setup_rr() 

11470 elif self.mode == GefRemoteSessionManager.RemoteMode.GDBSERVER: 11470 ↛ 11474line 11470 didn't jump to line 11474 because the condition on line 11470 was always true

11471 dbg(f"Setting up as remote session") 

11472 self.__setup_remote() 

11473 else: 

11474 raise Exception 

11475 # refresh gef to consider the binary 

11476 reset_all_caches() 

11477 gef.binary = Elf(self.lfile) 

11478 reset_architecture() 

11479 return True 

11480 

11481 def __setup_qemu(self) -> bool: 

11482 # setup emulated file in the chroot 

11483 assert self.__qemu 

11484 target = self.root / str(self.__qemu.parent).lstrip("/") 

11485 target.mkdir(parents=True, exist_ok=False) 

11486 shutil.copy2(self.__qemu, target) 

11487 self._file = self.__qemu 

11488 assert self.lfile.exists() 

11489 

11490 # create a procfs 

11491 procfs = self.root / f"proc/{self.pid}/" 

11492 procfs.mkdir(parents=True, exist_ok=True) 

11493 

11494 ## /proc/pid/cmdline 

11495 cmdline = procfs / "cmdline" 

11496 if not cmdline.exists(): 11496 ↛ 11501line 11496 didn't jump to line 11501 because the condition on line 11496 was always true

11497 with cmdline.open("w") as fd: 

11498 fd.write("") 

11499 

11500 ## /proc/pid/environ 

11501 environ = procfs / "environ" 

11502 if not environ.exists(): 11502 ↛ 11507line 11502 didn't jump to line 11507 because the condition on line 11502 was always true

11503 with environ.open("wb") as fd: 

11504 fd.write(b"PATH=/bin\x00HOME=/tmp\x00") 

11505 

11506 ## /proc/pid/maps 

11507 maps = procfs / "maps" 

11508 if not maps.exists(): 11508 ↛ 11513line 11508 didn't jump to line 11513 because the condition on line 11508 was always true

11509 with maps.open("w") as fd: 

11510 fname = self.file.absolute() 

11511 mem_range = "00000000-ffffffff" if is_32bit() else "0000000000000000-ffffffffffffffff" 

11512 fd.write(f"{mem_range} rwxp 00000000 00:00 0 {fname}\n") 

11513 return True 

11514 

11515 def __setup_remote(self) -> bool: 

11516 # get the file 

11517 fpath = f"/proc/{self.pid}/exe" 

11518 if not self.sync(fpath, str(self.file)): 11518 ↛ 11519line 11518 didn't jump to line 11519 because the condition on line 11518 was never true

11519 err(f"'{fpath}' could not be fetched on the remote system.") 

11520 return False 

11521 

11522 # pseudo procfs 

11523 for _file in ("maps", "environ", "cmdline"): 

11524 fpath = f"/proc/{self.pid}/{_file}" 

11525 if not self.sync(fpath): 11525 ↛ 11526line 11525 didn't jump to line 11526 because the condition on line 11525 was never true

11526 err(f"'{fpath}' could not be fetched on the remote system.") 

11527 return False 

11528 

11529 return True 

11530 

11531 def __setup_rr(self) -> bool: 

11532 # 

11533 # Simply override the local root path, the binary must exist 

11534 # on the host. 

11535 # 

11536 self.__local_root_path = pathlib.Path("/") 

11537 return True 

11538 

11539 def remote_objfile_event_handler(self, evt: "gdb.events.NewObjFileEvent") -> None: 

11540 dbg(f"[remote] in remote_objfile_handler({evt.new_objfile.filename if evt else 'None'}))") 

11541 if not evt or not evt.new_objfile.filename: 11541 ↛ 11542line 11541 didn't jump to line 11542 because the condition on line 11541 was never true

11542 return 

11543 if not evt.new_objfile.filename.startswith("target:") and not evt.new_objfile.filename.startswith("/"): 

11544 warn(f"[remote] skipping '{evt.new_objfile.filename}'") 

11545 return 

11546 if evt.new_objfile.filename.startswith("target:"): 

11547 src: str = evt.new_objfile.filename[len("target:"):] 

11548 if not self.sync(src): 11548 ↛ 11549line 11548 didn't jump to line 11549 because the condition on line 11548 was never true

11549 raise FileNotFoundError(f"Failed to sync '{src}'") 

11550 return 

11551 

11552 

11553class GefUiManager(GefManager): 

11554 """Class managing UI settings.""" 

11555 def __init__(self) -> None: 

11556 self.redirect_fd : Optional[TextIOWrapper] = None 

11557 self.context_hidden = False 

11558 self.stream_buffer : Optional[StringIO] = None 

11559 self.highlight_table: Dict[str, str] = {} 

11560 self.watches: Dict[int, Tuple[int, str]] = {} 

11561 self.context_messages: List[Tuple[str, str]] = [] 

11562 return 

11563 

11564 

11565class GefLibcManager(GefManager): 

11566 """Class managing everything libc-related (except heap).""" 

11567 PATTERN_LIBC_VERSION_MEMORY = re.compile(rb"glibc (\d+)\.(\d+)") 

11568 PATTERN_LIBC_VERSION_FILENAME = re.compile(r"libc6?[-_](\d+)\.(\d+)\.so") 

11569 

11570 def __init__(self) -> None: 

11571 self._version : Optional[Tuple[int, int]] = None 

11572 self._patch: Optional[int] = None 

11573 self._release: Optional[str] = None 

11574 return 

11575 

11576 def __str__(self) -> str: 

11577 return f"Libc(version='{self.version}')" 

11578 

11579 @property 

11580 def version(self) -> Optional[Tuple[int, int]]: 

11581 if not is_alive(): 11581 ↛ 11582line 11581 didn't jump to line 11582 because the condition on line 11581 was never true

11582 return None 

11583 

11584 if not self._version: 

11585 self._version = GefLibcManager.find_libc_version() 

11586 

11587 # Whenever auto-detection fails, try use the user-provided version. 

11588 if self._version == (0, 0): 11588 ↛ 11589line 11588 didn't jump to line 11589 because the condition on line 11588 was never true

11589 if gef.config["gef.libc_version"]: 

11590 ver = [int(v) for v in gef.config["gef.libc_version"].split(".", 1)] 

11591 assert len(ver) >= 2 

11592 self._version = ver[0], ver[1] 

11593 

11594 return self._version 

11595 

11596 @staticmethod 

11597 @lru_cache() 

11598 def find_libc_version() -> Tuple[int, int]: 

11599 """Attempt to determine the libc version. This operation can be long.""" 

11600 libc_sections = (m for m in gef.memory.maps if "libc" in m.path and m.permission & Permission.READ) 11600 ↛ exitline 11600 didn't finish the generator expression on line 11600

11601 for section in libc_sections: 11601 ↛ 11616line 11601 didn't jump to line 11616 because the loop on line 11601 didn't complete

11602 # Try to determine from the filepath 

11603 match = re.search(GefLibcManager.PATTERN_LIBC_VERSION_FILENAME, section.path) 

11604 if match: 11604 ↛ 11605line 11604 didn't jump to line 11605 because the condition on line 11604 was never true

11605 return int(match.group(1)), int(match.group(2)) 

11606 

11607 # Try to determine from memory 

11608 try: 

11609 mem = gef.memory.read(section.page_start, section.size) 

11610 match = re.search(GefLibcManager.PATTERN_LIBC_VERSION_MEMORY, mem) 

11611 if match: 

11612 return int(match.group(1)), int(match.group(2)) 

11613 except gdb.MemoryError: 

11614 continue 

11615 

11616 return 0, 0 

11617 

11618 

11619class Gef: 

11620 """The GEF root class, which serves as a entrypoint for all the debugging session attributes (architecture, 

11621 memory, settings, etc.).""" 

11622 binary: Optional[FileFormat] 

11623 arch: Architecture 

11624 config : GefSettingsManager 

11625 ui: GefUiManager 

11626 libc: GefLibcManager 

11627 memory : GefMemoryManager 

11628 heap : GefHeapManager 

11629 session : GefSessionManager 

11630 gdb: GefCommand 

11631 

11632 def __init__(self) -> None: 

11633 self.binary: Optional[FileFormat] = None 

11634 self.arch: Architecture = GenericArchitecture() # see PR #516, will be reset by `new_objfile_handler` 

11635 self.arch_reason: str = "This is the default architecture" 

11636 self.config = GefSettingsManager() 

11637 self.ui = GefUiManager() 

11638 self.libc = GefLibcManager() 

11639 return 

11640 

11641 def __str__(self) -> str: 

11642 return f"Gef(binary='{self.binary or 'None'}', arch={self.arch})" 

11643 

11644 def reinitialize_managers(self) -> None: 

11645 """Reinitialize the managers. Avoid calling this function directly, using `pi reset()` is preferred""" 

11646 self.memory = GefMemoryManager() 

11647 self.heap = GefHeapManager() 

11648 self.session = GefSessionManager() 

11649 return 

11650 

11651 def setup(self) -> None: 

11652 """Setup initialize the runtime setup, which may require for the `gef` to be not None.""" 

11653 self.reinitialize_managers() 

11654 self.gdb = GefCommand() 

11655 self.gdb.setup() 

11656 gdb.execute(f"save gdb-index '{self.config['gef.tempdir']}'") 

11657 return 

11658 

11659 def reset_caches(self) -> None: 

11660 """Recursively clean the cache of all the managers. Avoid calling this function directly, using `reset-cache` 

11661 is preferred""" 

11662 for mgr in (self.memory, self.heap, self.session, self.arch): 

11663 mgr.reset_caches() 

11664 return 

11665 

11666 

11667def target_remote_posthook(): 

11668 if gef.session.remote_initializing: 

11669 return 

11670 

11671 gef.session.remote = GefRemoteSessionManager("", 0) 

11672 if not gef.session.remote.setup(): 11672 ↛ 11673line 11672 didn't jump to line 11673 because the condition on line 11672 was never true

11673 raise EnvironmentError(f"Failed to create a proper environment for {gef.session.remote}") 

11674 

11675if __name__ == "__main__": 11675 ↛ exitline 11675 didn't jump to the function exit

11676 if sys.version_info[0] == 2: 11676 ↛ 11677line 11676 didn't jump to line 11677 because the condition on line 11676 was never true

11677 err("GEF has dropped Python2 support for GDB when it reached EOL on 2020/01/01.") 

11678 err("If you require GEF for GDB+Python2, use https://github.com/hugsy/gef-legacy.") 

11679 exit(1) 

11680 

11681 if GDB_VERSION < GDB_MIN_VERSION or PYTHON_VERSION < PYTHON_MIN_VERSION: 11681 ↛ 11682line 11681 didn't jump to line 11682 because the condition on line 11681 was never true

11682 err("You're using an old version of GDB. GEF will not work correctly. " 

11683 f"Consider updating to GDB {'.'.join(map(str, GDB_MIN_VERSION))} or higher " 

11684 f"(with Python {'.'.join(map(str, PYTHON_MIN_VERSION))} or higher).") 

11685 exit(1) 

11686 

11687 # When using a Python virtual environment, GDB still loads the system-installed Python 

11688 # so GEF doesn't load site-packages dir from environment 

11689 # In order to fix it, from the shell with venv activated we run the python binary, 

11690 # take and parse its path, add the path to the current python process using sys.path.extend 

11691 PYTHONBIN = which("python3") 

11692 PREFIX = gef_pystring(subprocess.check_output([PYTHONBIN, '-c', 'import os, sys;print((sys.prefix))'])).strip("\\n") 

11693 if PREFIX != sys.base_prefix: 11693 ↛ 11694line 11693 didn't jump to line 11694 because the condition on line 11693 was never true

11694 SITE_PACKAGES_DIRS = subprocess.check_output( 

11695 [PYTHONBIN, "-c", "import os, sys;print(os.linesep.join(sys.path).strip())"]).decode("utf-8").split() 

11696 sys.path.extend(SITE_PACKAGES_DIRS) 

11697 

11698 # setup config 

11699 gdb_initial_settings = ( 

11700 "set confirm off", 

11701 "set verbose off", 

11702 "set pagination off", 

11703 "set print elements 0", 

11704 "set history save on", 

11705 f"set history filename {os.getenv('GDBHISTFILE', '~/.gdb_history')}", 

11706 "set output-radix 0x10", 

11707 "set print pretty on", 

11708 "set disassembly-flavor intel", 

11709 "handle SIGALRM print nopass", 

11710 ) 

11711 for cmd in gdb_initial_settings: 

11712 try: 

11713 gdb.execute(cmd) 

11714 except gdb.error: 

11715 pass 

11716 

11717 # load GEF, set up the managers and load the plugins, functions, 

11718 gef = Gef() 

11719 reset() 

11720 assert isinstance(gef, Gef) 

11721 gef.gdb.load() 

11722 gef.gdb.show_banner() 

11723 

11724 # load config 

11725 if gef.gdb.load_extra_plugins(): 11725 ↛ 11727line 11725 didn't jump to line 11727 because the condition on line 11725 was never true

11726 # reload settings 

11727 gdb.execute("gef restore") 

11728 

11729 # setup gdb prompt 

11730 gdb.prompt_hook = __gef_prompt__ 

11731 

11732 # gdb events configuration 

11733 gef_on_continue_hook(continue_handler) 

11734 gef_on_stop_hook(hook_stop_handler) 

11735 gef_on_new_hook(new_objfile_handler) 

11736 gef_on_exit_hook(exit_handler) 

11737 gef_on_memchanged_hook(memchanged_handler) 

11738 gef_on_regchanged_hook(regchanged_handler) 

11739 

11740 progspace = gdb.current_progspace() 

11741 if progspace and progspace.filename: 11741 ↛ 11745line 11741 didn't jump to line 11745 because the condition on line 11741 was always true

11742 # if here, we are sourcing gef from a gdb session already attached, force call to new_objfile (see issue #278) 

11743 new_objfile_handler(None) 

11744 

11745 GefTmuxSetup() 

11746 

11747 if GDB_VERSION > (9, 0): 11747 ↛ 11775line 11747 didn't jump to line 11775 because the condition on line 11747 was always true

11748 disable_tr_overwrite_setting = "gef.disable_target_remote_overwrite" 

11749 

11750 if not gef.config[disable_tr_overwrite_setting]: 11750 ↛ 11767line 11750 didn't jump to line 11767 because the condition on line 11750 was always true

11751 warnmsg = ("Using `target remote` with GEF should work in most cases, " 

11752 "but use `gef-remote` if you can. You can disable the " 

11753 "overwrite of the `target remote` command by toggling " 

11754 f"`{disable_tr_overwrite_setting}` in the config.") 

11755 hook = f""" 

11756 define target hookpost-{{}} 

11757 pi target_remote_posthook() 

11758 context 

11759 pi if calling_function() != "connect": warn("{warnmsg}") 

11760 end 

11761 """ 

11762 

11763 # Register a post-hook for `target remote` that initialize the remote session 

11764 gdb.execute(hook.format("remote")) 

11765 gdb.execute(hook.format("extended-remote")) 

11766 else: 

11767 errmsg = ("Using `target remote` does not work, use `gef-remote` " 

11768 f"instead. You can toggle `{disable_tr_overwrite_setting}` " 

11769 "if this is not desired.") 

11770 hook = f"""pi if calling_function() != "connect": err("{errmsg}")""" 

11771 gdb.execute(f"define target hook-remote\n{hook}\nend") 

11772 gdb.execute(f"define target hook-extended-remote\n{hook}\nend") 

11773 

11774 # restore saved breakpoints (if any) 

11775 bkp_fpath = pathlib.Path(gef.config["gef.autosave_breakpoints_file"]).expanduser().absolute() 

11776 if bkp_fpath.is_file(): 11776 ↛ 11777line 11776 didn't jump to line 11777 because the condition on line 11776 was never true

11777 gdb.execute(f"source {bkp_fpath}") 

11778 

11779 # Add a `source` post hook to force gef to recheck the registered plugins and 

11780 # eventually load the missing one(s) 

11781 gdb.execute("define hookpost-source\npi gef.gdb.load()\nend")