Coverage for gef.py: 71.0775%

8062 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-04-15 16:07 +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-2024 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, Generator, Iterable, Iterator, 

86 NoReturn, Sequence, Type, TypeVar, cast) 

87from urllib.request import urlopen 

88 

89 

90GEF_DEFAULT_BRANCH = "main" 

91GEF_EXTRAS_DEFAULT_BRANCH = "main" 

92 

93def http_get(url: str) -> bytes | None: 

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

95 otherwise return None.""" 

96 try: 

97 http = urlopen(url) 

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

99 except Exception: 

100 return None 

101 

102 

103def update_gef(argv: list[str]) -> int: 

104 """Obsolete. Use `gef.sh`.""" 

105 return -1 

106 

107 

108try: 

109 import gdb # type:ignore 

110except ImportError: 

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

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

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

114 sys.exit(1) 

115 

116 

117GDB_MIN_VERSION: tuple[int, int] = (10, 0) 

118PYTHON_MIN_VERSION: tuple[int, int] = (3, 10) 

119PYTHON_VERSION: tuple[int, int] = sys.version_info[0:2] 

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

121 

122DEFAULT_PAGE_ALIGN_SHIFT = 12 

123DEFAULT_PAGE_SIZE = 1 << DEFAULT_PAGE_ALIGN_SHIFT 

124 

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

126 if os.getenv("GEF_RC") 

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

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

129GEF_MAX_STRING_LENGTH = 50 

130 

131LIBC_HEAP_MAIN_ARENA_DEFAULT_NAME = "main_arena" 

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

133 

134LEFT_ARROW = " ← " 

135RIGHT_ARROW = " → " 

136DOWN_ARROW = "↳" 

137HORIZONTAL_LINE = "─" 

138VERTICAL_LINE = "│" 

139CROSS = "✘ " 

140TICK = "✓ " 

141BP_GLYPH = "●" 

142GEF_PROMPT = "gef➤ " 

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

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

145 

146__registered_commands__ : set[Type["GenericCommand"]] = set() 

147__registered_functions__ : set[Type["GenericFunction"]] = set() 

148__registered_architectures__ : dict["Elf.Abi | str", Type["Architecture"]] = {} 

149__registered_file_formats__ : set[ Type["FileFormat"] ] = set() 

150 

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

152 

153 

154def reset_all_caches() -> None: 

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

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

157 

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

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

160 if hasattr(obj, "cache_clear"): 

161 obj.cache_clear() 

162 

163 gef.reset_caches() 

164 return 

165 

166 

167def reset() -> None: 

168 global gef 

169 

170 arch = None 

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

172 reset_all_caches() 

173 arch = gef.arch 

174 del gef 

175 

176 gef = Gef() 

177 gef.setup() 

178 

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

180 gef.arch = arch 

181 return 

182 

183 

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

185 """ 

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

187 

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

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

190 around those matches. 

191 

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

193 within the specified string. 

194 """ 

195 global gef 

196 

197 if not gef.ui.highlight_table: 

198 return text 

199 

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

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

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

203 return text 

204 

205 ansiSplit = re.split(ANSI_SPLIT_RE, text) 

206 

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

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

209 found = val.find(match) 

210 if found > -1: 

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

212 break 

213 text = "".join(ansiSplit) 

214 ansiSplit = re.split(ANSI_SPLIT_RE, text) 

215 

216 return "".join(ansiSplit) 

217 

218 

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

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

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

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

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

224 return 

225 

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

227 return 

228 

229 

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

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

232 

233 @functools.wraps(f) 

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

235 global gef 

236 

237 if gef.ui.stream_buffer: 

238 return f(*args, **kwargs) 

239 

240 gef.ui.stream_buffer = StringIO() 

241 try: 

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

243 finally: 

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

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

246 if not gef.ui.redirect_fd: 

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

248 fd = open(redirect, "wt") 

249 gef.ui.redirect_fd = fd 

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

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

252 gef.ui.redirect_fd.close() 

253 fd = open(redirect, "wt") 

254 gef.ui.redirect_fd = fd 

255 else: 

256 # otherwise, keep using it 

257 fd = gef.ui.redirect_fd 

258 else: 

259 fd = sys.stdout 

260 gef.ui.redirect_fd = None 

261 

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

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

264 fd = sys.stdout 

265 gef.ui.redirect_fd = None 

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

267 

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

269 fd.flush() 

270 gef.ui.stream_buffer = None 

271 return rv 

272 

273 return wrapper 

274 

275 

276class ValidationError(Exception): pass 

277 

278# 

279# Helpers 

280# 

281 

282class ObsoleteException(Exception): pass 

283 

284class AlreadyRegisteredException(Exception): pass 

285 

286def p8(x: int, s: bool = False, e: "Endianness | None" = None) -> bytes: 

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

288 endian = e or gef.arch.endianness 

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

290 

291 

292def p16(x: int, s: bool = False, e: "Endianness | None" = None) -> bytes: 

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

294 endian = e or gef.arch.endianness 

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

296 

297 

298def p32(x: int, s: bool = False, e: "Endianness | None" = None) -> bytes: 

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

300 endian = e or gef.arch.endianness 

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

302 

303 

304def p64(x: int, s: bool = False, e: "Endianness | None" = None) -> bytes: 

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

306 endian = e or gef.arch.endianness 

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

308 

309 

310def u8(x: bytes, s: bool = False, e: "Endianness | None" = None) -> int: 

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

312 endian = e or gef.arch.endianness 

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

314 

315 

316def u16(x: bytes, s: bool = False, e: "Endianness | None" = None) -> int: 

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

318 endian = e or gef.arch.endianness 

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

320 

321 

322def u32(x: bytes, s: bool = False, e: "Endianness | None" = None) -> int: 

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

324 endian = e or gef.arch.endianness 

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

326 

327 

328def u64(x: bytes, s: bool = False, e: "Endianness | None" = None) -> int: 

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

330 endian = e or gef.arch.endianness 

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

332 

333 

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

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

336 try: 

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

338 except Exception: 

339 return False 

340 

341 

342def is_alive() -> bool: 

343 """Check if GDB is running.""" 

344 try: 

345 return gdb.selected_inferior().pid > 0 

346 except Exception: 

347 return False 

348 

349 

350def calling_function() -> str | None: 

351 """Return the name of the calling function""" 

352 try: 

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

354 return stack_info.name 

355 except Exception as e: 

356 dbg(f"traceback failed with {str(e)}") 

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 @functools.wraps(f) 

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

367 if is_alive(): 

368 return f(*args, **kwargs) 

369 else: 

370 warn("No debugging session active") 

371 

372 return wrapper 

373 

374 

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

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

377 

378 @functools.wraps(f) 

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

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

381 return f(*args, **kwargs) 

382 else: 

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

384 

385 return wrapper 

386 

387 

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

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

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

391 @functools.wraps(f) 

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

393 caller = inspect.stack()[1] 

394 caller_file = pathlib.Path(caller.filename) 

395 caller_loc = caller.lineno 

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

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

398 print(msg) 

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

400 if solution: 

401 msg += solution 

402 warn(msg) 

403 return f(*args, **kwargs) 

404 

405 if not wrapper.__doc__: 

406 wrapper.__doc__ = "" 

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

408 return wrapper 

409 return decorator 

410 

411 

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

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

414 

415 @functools.wraps(f) 

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

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

418 return f(*args, **kwargs) 

419 

420 return wrapper 

421 

422 

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

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

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

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

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

428 return f(*args, **kwargs) 

429 warn("GDB events cannot be set") 

430 return wrapped_f 

431 return wrap 

432 

433 

434class classproperty(property): 

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

436 def __get__(self, cls, owner): 

437 assert self.fget 

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

439 

440 

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

442 raise RuntimeWarning 

443 

444 

445sys.exit = FakeExit 

446 

447 

448def parse_arguments(required_arguments: dict[str | tuple[str, str], Any], 

449 optional_arguments: dict[str | tuple[str, str], Any]) -> Callable: 

450 """Argument parsing decorator.""" 

451 

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

453 

454 def decorator(f: Callable) -> Callable | None: 

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

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

457 for argname in required_arguments: 

458 argvalue = required_arguments[argname] 

459 argtype = type(argvalue) 

460 if argtype is int: 

461 argtype = int_wrapper 

462 

463 argname_is_list = not isinstance(argname, str) 

464 assert not argname_is_list and isinstance(argname, str) 

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

466 # optional args 

467 if argtype is bool: 

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

469 else: 

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

471 else: 

472 if argtype in (list, tuple): 

473 nargs = "*" 

474 argtype = type(argvalue[0]) 

475 else: 

476 nargs = "?" 

477 # positional args 

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

479 

480 for argname in optional_arguments: 

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

482 # refuse positional arguments 

483 continue 

484 argvalue = optional_arguments[argname] 

485 argtype = type(argvalue) 

486 if isinstance(argname, str): 

487 argname = [argname,] 

488 if argtype is int: 

489 argtype = int_wrapper 

490 elif argtype is bool: 

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

492 continue 

493 elif argtype in (list, tuple): 

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

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

496 continue 

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

498 

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

500 kwargs["arguments"] = parsed_args 

501 return f(*args, **kwargs) 

502 return wrapper 

503 return decorator 

504 

505 

506class Color: 

507 """Used to colorify terminal output.""" 

508 

509 ### Special chars: 

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

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

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

513 # started with \001 

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

515 colors = { 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

532 } 

533 

534 @staticmethod 

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

536 @staticmethod 

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

538 @staticmethod 

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

540 @staticmethod 

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

542 @staticmethod 

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

544 @staticmethod 

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

546 @staticmethod 

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

548 @staticmethod 

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

550 @staticmethod 

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

552 @staticmethod 

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

554 @staticmethod 

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

556 @staticmethod 

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

558 

559 @staticmethod 

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

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

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

563 

564 colors = Color.colors 

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

566 msg.append(str(text)) 

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

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

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

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

571 return "".join(msg) 

572 

573 

574class Address: 

575 """GEF representation of memory addresses.""" 

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

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

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

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

580 return 

581 

582 def __str__(self) -> str: 

583 value = format_address(self.value) 

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

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

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

587 if self.is_in_text_segment(): 

588 return Color.colorify(value, code_color) 

589 if self.is_in_heap_segment(): 

590 return Color.colorify(value, heap_color) 

591 if self.is_in_stack_segment(): 

592 return Color.colorify(value, stack_color) 

593 return value 

594 

595 def __int__(self) -> int: 

596 return self.value 

597 

598 def is_in_text_segment(self) -> bool: 

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

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

601 

602 def is_in_stack_segment(self) -> bool: 

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

604 

605 def is_in_heap_segment(self) -> bool: 

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

607 

608 def dereference(self) -> int | None: 

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

610 derefed = dereference(addr) 

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

612 

613 @property 

614 def valid(self) -> bool: 

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

616 

617 

618class Permission(enum.Flag): 

619 """GEF representation of Linux permission.""" 

620 NONE = 0 

621 EXECUTE = 1 

622 WRITE = 2 

623 READ = 4 

624 ALL = 7 

625 

626 def __str__(self) -> str: 

627 perm_str = "" 

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

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

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

631 return perm_str 

632 

633 @classmethod 

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

635 perm = cls(0) 

636 for arg in args: 

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

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

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

640 return perm 

641 

642 @classmethod 

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

644 perm = cls(0) 

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

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

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

648 return perm 

649 

650 @classmethod 

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

652 perm = cls(0) 

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

654 # we don't track 

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

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

657 return perm 

658 

659 @classmethod 

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

661 perm = cls(0) 

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

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

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

665 return perm 

666 

667 

668class Section: 

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

670 

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

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

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

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

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

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

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

678 return 

679 

680 def is_readable(self) -> bool: 

681 return bool(self.permission & Permission.READ) 

682 

683 def is_writable(self) -> bool: 

684 return bool(self.permission & Permission.WRITE) 

685 

686 def is_executable(self) -> bool: 

687 return bool(self.permission & Permission.EXECUTE) 

688 

689 @property 

690 def size(self) -> int: 

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

692 raise AttributeError 

693 return self.page_end - self.page_start 

694 

695 def _search_for_realpath_without_versions(self, path: pathlib.Path) -> str | None: 

696 """Given a path, search for a file that exists without numeric suffixes.""" 

697 

698 # Match the path string against a regex that will remove a suffix 

699 # consisting of a dot followed by numbers. 

700 candidate = re.match(r"^(.*)\.(\d*)$", str(path)) 

701 while candidate: 

702 candidate = candidate.group(1) 

703 # If the prefix from the regex match is a file, return that path. 

704 if pathlib.Path(candidate).is_file(): 704 ↛ 705line 704 didn't jump to line 705 because the condition on line 704 was never true

705 return candidate 

706 # Otherwise, try to match again. 

707 candidate = re.match(r"^(.*)\.(\d*)$", candidate) 

708 return None 

709 

710 def _search_for_realpath(self) -> str | None: 

711 """This function is a workaround for gdb bug #23764 

712 

713 path might be wrong for remote sessions, so try a simple search for files 

714 that aren't found at the path indicated, which should be canonical. 

715 """ 

716 

717 assert gef.session.remote 

718 remote_path = pathlib.Path(self.path) 

719 # First, try the canonical path in the remote session root. 

720 candidate1 = gef.session.remote.root / remote_path.relative_to(remote_path.anchor) 

721 if candidate1.is_file(): 721 ↛ 722line 721 didn't jump to line 722 because the condition on line 721 was never true

722 return str(candidate1) 

723 # Also try that path without version suffixes. 

724 candidate = self._search_for_realpath_without_versions(candidate1) 

725 if candidate: 725 ↛ 726line 725 didn't jump to line 726 because the condition on line 725 was never true

726 return candidate 

727 

728 # On some systems, /lib(64) might be a symlink to /usr/lib(64), so try removing 

729 # the /usr prefix. 

730 if self.path.startswith("/usr"): 730 ↛ 740line 730 didn't jump to line 740 because the condition on line 730 was always true

731 candidate = gef.session.remote.root / remote_path.relative_to("/usr") 

732 if candidate.is_file(): 732 ↛ 735line 732 didn't jump to line 735 because the condition on line 732 was always true

733 return str(candidate) 

734 # Also try that path without version suffixes. 

735 candidate = self._search_for_realpath_without_versions(candidate) 

736 if candidate: 

737 return candidate 

738 

739 # Base case, return the original realpath 

740 return str(candidate1) 

741 

742 @property 

743 def realpath(self) -> str: 

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

745 if gef.session.remote is None: 

746 return self.path 

747 default = self._search_for_realpath() 

748 if default: 748 ↛ 750line 748 didn't jump to line 750 because the condition on line 748 was always true

749 return default 

750 raise FileNotFoundError 

751 

752 def __str__(self) -> str: 

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

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

755 

756 def __repr__(self) -> str: 

757 return str(self) 

758 

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

760 return other and \ 

761 self.page_start == other.page_start and \ 

762 self.size == other.size and \ 

763 self.permission == other.permission and \ 

764 self.path == other.path 

765 

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

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

768 

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

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

771 

772 

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

774 

775 

776class Endianness(enum.Enum): 

777 LITTLE_ENDIAN = 1 

778 BIG_ENDIAN = 2 

779 

780 def __str__(self) -> str: 

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

782 

783 def __repr__(self) -> str: 

784 return self.name 

785 

786 def __int__(self) -> int: 

787 return self.value 

788 

789 

790class FileFormatSection: 

791 misc: Any 

792 

793 

794class FileFormat: 

795 name: str 

796 path: pathlib.Path 

797 entry_point: int 

798 checksec: dict[str, bool] 

799 sections: list[FileFormatSection] 

800 

801 def __init__(self, path: str | pathlib.Path) -> None: 

802 raise NotImplementedError 

803 

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

805 global __registered_file_formats__ 

806 super().__init_subclass__(**kwargs) 

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

808 for attr in required_attributes: 

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

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

811 __registered_file_formats__.add(cls) 

812 return 

813 

814 @classmethod 

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

816 raise NotImplementedError 

817 

818 def __str__(self) -> str: 

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

820 

821 

822class Elf(FileFormat): 

823 """Basic ELF parsing. 

824 Ref: 

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

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

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

828 """ 

829 class Class(enum.Enum): 

830 ELF_32_BITS = 0x01 

831 ELF_64_BITS = 0x02 

832 

833 ELF_MAGIC = 0x7f454c46 

834 

835 class Abi(enum.Enum): 

836 X86_64 = 0x3e 

837 X86_32 = 0x03 

838 ARM = 0x28 

839 MIPS = 0x08 

840 POWERPC = 0x14 

841 POWERPC64 = 0x15 

842 SPARC = 0x02 

843 SPARC64 = 0x2b 

844 AARCH64 = 0xb7 

845 RISCV = 0xf3 

846 IA64 = 0x32 

847 M68K = 0x04 

848 

849 class Type(enum.Enum): 

850 ET_RELOC = 1 

851 ET_EXEC = 2 

852 ET_DYN = 3 

853 ET_CORE = 4 

854 

855 class OsAbi(enum.Enum): 

856 SYSTEMV = 0x00 

857 HPUX = 0x01 

858 NETBSD = 0x02 

859 LINUX = 0x03 

860 SOLARIS = 0x06 

861 AIX = 0x07 

862 IRIX = 0x08 

863 FREEBSD = 0x09 

864 OPENBSD = 0x0C 

865 

866 e_magic: int = ELF_MAGIC 

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

868 e_endianness: Endianness = Endianness.LITTLE_ENDIAN 

869 e_eiversion: int 

870 e_osabi: "Elf.OsAbi" 

871 e_abiversion: int 

872 e_pad: bytes 

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

874 e_machine: Abi = Abi.X86_32 

875 e_version: int 

876 e_entry: int 

877 e_phoff: int 

878 e_shoff: int 

879 e_flags: int 

880 e_ehsize: int 

881 e_phentsize: int 

882 e_phnum: int 

883 e_shentsize: int 

884 e_shnum: int 

885 e_shstrndx: int 

886 

887 path: pathlib.Path 

888 phdrs : list["Phdr"] 

889 shdrs : list["Shdr"] 

890 name: str = "ELF" 

891 

892 __checksec : dict[str, bool] 

893 

894 def __init__(self, path: str | pathlib.Path) -> None: 

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

896 

897 if isinstance(path, str): 

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

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

900 self.path = path 

901 else: 

902 raise TypeError 

903 

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

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

906 

907 self.__checksec = {} 

908 

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

910 # off 0x0 

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

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

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

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

915 

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

917 

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

919 warn("Unexpected endianness for architecture") 

920 

921 endian = self.e_endianness 

922 

923 # off 0x7 

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

925 self.e_osabi = Elf.OsAbi(e_osabi) 

926 

927 # off 0x9 

928 self.e_pad = self.read(7) 

929 

930 # off 0x10 

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

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

933 

934 # off 0x18 

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

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

937 else: 

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

939 

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

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

942 

943 self.phdrs = [] 

944 for i in range(self.e_phnum): 

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

946 

947 self.shdrs = [] 

948 for i in range(self.e_shnum): 

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

950 return 

951 

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

953 return self.fd.read(size) 

954 

955 def read_and_unpack(self, fmt: str) -> tuple[Any, ...]: 

956 size = struct.calcsize(fmt) 

957 data = self.fd.read(size) 

958 return struct.unpack(fmt, data) 

959 

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

961 self.fd.seek(off, 0) 

962 

963 def __str__(self) -> str: 

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

965 

966 def __repr__(self) -> str: 

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

968 

969 @property 

970 def entry_point(self) -> int: 

971 return self.e_entry 

972 

973 @classmethod 

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

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

976 

977 @property 

978 def checksec(self) -> dict[str, bool]: 

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

980 - Canary 

981 - NX 

982 - PIE 

983 - Fortify 

984 - Partial/Full RelRO. 

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

986 associated whether the protection was found.""" 

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

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

989 cmd = [readelf,] 

990 cmd += opt.split() 

991 cmd += [filename,] 

992 lines = gef_execute_external(cmd, as_list=True) 

993 for line in lines: 

994 if re.search(pattern, line): 

995 return True 

996 return False 

997 

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

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

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

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

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

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

1004 else: 

1005 self.__checksec["NX"] = False 

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

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

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

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

1010 return self.__checksec 

1011 

1012 @classproperty 

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

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

1015 

1016 @classproperty 

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

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

1019 

1020 @classproperty 

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

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

1023 

1024 @classproperty 

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

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

1027 

1028 @classproperty 

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

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

1031 

1032 @classproperty 

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

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

1035 

1036 @classproperty 

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

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

1039 

1040 @classproperty 

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

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

1043 

1044 @classproperty 

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

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

1047 

1048 @classproperty 

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

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

1051 

1052 

1053class Phdr: 

1054 class Type(enum.IntEnum): 

1055 PT_NULL = 0 

1056 PT_LOAD = 1 

1057 PT_DYNAMIC = 2 

1058 PT_INTERP = 3 

1059 PT_NOTE = 4 

1060 PT_SHLIB = 5 

1061 PT_PHDR = 6 

1062 PT_TLS = 7 

1063 PT_LOOS = 0x60000000 

1064 PT_GNU_EH_FRAME = 0x6474e550 

1065 PT_GNU_STACK = 0x6474e551 

1066 PT_GNU_RELRO = 0x6474e552 

1067 PT_GNU_PROPERTY = 0x6474e553 

1068 PT_LOSUNW = 0x6ffffffa 

1069 PT_SUNWBSS = 0x6ffffffa 

1070 PT_SUNWSTACK = 0x6ffffffb 

1071 PT_HISUNW = PT_HIOS = 0x6fffffff 

1072 PT_LOPROC = 0x70000000 

1073 PT_ARM_EIDX = 0x70000001 

1074 PT_MIPS_ABIFLAGS= 0x70000003 

1075 PT_HIPROC = 0x7fffffff 

1076 UNKNOWN_PHDR = 0xffffffff 

1077 

1078 @classmethod 

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

1080 return cls.UNKNOWN_PHDR 

1081 

1082 class Flags(enum.IntFlag): 

1083 PF_X = 1 

1084 PF_W = 2 

1085 PF_R = 4 

1086 

1087 p_type: "Phdr.Type" 

1088 p_flags: "Phdr.Flags" 

1089 p_offset: int 

1090 p_vaddr: int 

1091 p_paddr: int 

1092 p_filesz: int 

1093 p_memsz: int 

1094 p_align: int 

1095 

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

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

1098 return 

1099 elf.seek(off) 

1100 self.offset = off 

1101 endian = elf.e_endianness 

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

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

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

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

1106 else: 

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

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

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

1110 

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

1112 return 

1113 

1114 def __str__(self) -> str: 

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

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

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

1118 

1119 

1120class Shdr: 

1121 class Type(enum.IntEnum): 

1122 SHT_NULL = 0 

1123 SHT_PROGBITS = 1 

1124 SHT_SYMTAB = 2 

1125 SHT_STRTAB = 3 

1126 SHT_RELA = 4 

1127 SHT_HASH = 5 

1128 SHT_DYNAMIC = 6 

1129 SHT_NOTE = 7 

1130 SHT_NOBITS = 8 

1131 SHT_REL = 9 

1132 SHT_SHLIB = 10 

1133 SHT_DYNSYM = 11 

1134 SHT_NUM = 12 

1135 SHT_INIT_ARRAY = 14 

1136 SHT_FINI_ARRAY = 15 

1137 SHT_PREINIT_ARRAY = 16 

1138 SHT_GROUP = 17 

1139 SHT_SYMTAB_SHNDX = 18 

1140 SHT_LOOS = 0x60000000 

1141 SHT_GNU_ATTRIBUTES = 0x6ffffff5 

1142 SHT_GNU_HASH = 0x6ffffff6 

1143 SHT_GNU_LIBLIST = 0x6ffffff7 

1144 SHT_CHECKSUM = 0x6ffffff8 

1145 SHT_LOSUNW = 0x6ffffffa 

1146 SHT_SUNW_move = 0x6ffffffa 

1147 SHT_SUNW_COMDAT = 0x6ffffffb 

1148 SHT_SUNW_syminfo = 0x6ffffffc 

1149 SHT_GNU_verdef = 0x6ffffffd 

1150 SHT_GNU_verneed = 0x6ffffffe 

1151 SHT_GNU_versym = 0x6fffffff 

1152 SHT_LOPROC = 0x70000000 

1153 SHT_ARM_EXIDX = 0x70000001 

1154 SHT_X86_64_UNWIND = 0x70000001 

1155 SHT_ARM_ATTRIBUTES = 0x70000003 

1156 SHT_MIPS_OPTIONS = 0x7000000d 

1157 DT_MIPS_INTERFACE = 0x7000002a 

1158 SHT_HIPROC = 0x7fffffff 

1159 SHT_LOUSER = 0x80000000 

1160 SHT_HIUSER = 0x8fffffff 

1161 UNKNOWN_SHDR = 0xffffffff 

1162 

1163 @classmethod 

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

1165 return cls.UNKNOWN_SHDR 

1166 

1167 class Flags(enum.IntFlag): 

1168 WRITE = 1 

1169 ALLOC = 2 

1170 EXECINSTR = 4 

1171 MERGE = 0x10 

1172 STRINGS = 0x20 

1173 INFO_LINK = 0x40 

1174 LINK_ORDER = 0x80 

1175 OS_NONCONFORMING = 0x100 

1176 GROUP = 0x200 

1177 TLS = 0x400 

1178 COMPRESSED = 0x800 

1179 RELA_LIVEPATCH = 0x00100000 

1180 RO_AFTER_INIT = 0x00200000 

1181 ORDERED = 0x40000000 

1182 EXCLUDE = 0x80000000 

1183 UNKNOWN_FLAG = 0xffffffff 

1184 

1185 @classmethod 

1186 def _missing_(cls, _:int): 

1187 return cls.UNKNOWN_FLAG 

1188 

1189 sh_name: int 

1190 sh_type: "Shdr.Type" 

1191 sh_flags: "Shdr.Flags" 

1192 sh_addr: int 

1193 sh_offset: int 

1194 sh_size: int 

1195 sh_link: int 

1196 sh_info: int 

1197 sh_addralign: int 

1198 sh_entsize: int 

1199 name: str 

1200 

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

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

1203 return 

1204 elf.seek(off) 

1205 endian = elf.e_endianness 

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

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

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

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

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

1211 else: 

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

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

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

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

1216 

1217 self.sh_type = Shdr.Type(sh_type) 

1218 self.sh_flags = Shdr.Flags(sh_flags) 

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

1220 

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

1222 elf.seek(stroff + 16 + 8) 

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

1224 else: 

1225 elf.seek(stroff + 12 + 4) 

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

1227 elf.seek(offset + self.sh_name) 

1228 self.name = "" 

1229 while True: 

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

1231 if c == 0: 

1232 break 

1233 self.name += chr(c) 

1234 return 

1235 

1236 def __str__(self) -> str: 

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

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

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

1240 

1241 

1242class Instruction: 

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

1244 

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

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

1247 address, location, mnemo, operands, opcodes 

1248 return 

1249 

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

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

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

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

1254 return str(self) 

1255 

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

1257 opcodes_len = len(self.opcodes) 

1258 else: 

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

1260 

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

1262 if opcodes_len < len(self.opcodes): 

1263 opcodes_text += "..." 

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

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

1266 

1267 def __str__(self) -> str: 

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

1269 

1270 def is_valid(self) -> bool: 

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

1272 

1273 def size(self) -> int: 

1274 return len(self.opcodes) 

1275 

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

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

1278 return gef_get_instruction_at(address) 

1279 

1280 

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

1282def search_for_main_arena() -> int: 

1283 return GefHeapManager.find_main_arena_addr() 

1284 

1285class GlibcHeapInfo: 

1286 """Glibc heap_info struct""" 

1287 

1288 @staticmethod 

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

1290 assert gef.libc.version 

1291 class heap_info_cls(ctypes.Structure): 

1292 pass 

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

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

1295 fields = [ 

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

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

1298 ("size", pointer) 

1299 ] 

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

1301 fields += [ 

1302 ("mprotect_size", pointer) 

1303 ] 

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

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

1306 fields += [ 

1307 ("pagesize", pointer) 

1308 ] 

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

1310 fields += [ 

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

1312 ] 

1313 heap_info_cls._fields_ = fields 

1314 return heap_info_cls 

1315 

1316 def __init__(self, addr: str | int) -> None: 

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

1318 self.reset() 

1319 return 

1320 

1321 def reset(self): 

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

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

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

1325 return 

1326 

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

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

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

1330 return getattr(self, item) 

1331 

1332 def __abs__(self) -> int: 

1333 return self.__address 

1334 

1335 def __int__(self) -> int: 

1336 return self.__address 

1337 

1338 @property 

1339 def address(self) -> int: 

1340 return self.__address 

1341 

1342 @property 

1343 def sizeof(self) -> int: 

1344 return self._sizeof 

1345 

1346 @property 

1347 def addr(self) -> int: 

1348 return int(self) 

1349 

1350 @property 

1351 def heap_start(self) -> int: 

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

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

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

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

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

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

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

1359 heap_addr = arena.heap_addr() 

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

1361 return heap_addr 

1362 else: 

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

1364 return 0 

1365 return self.address + self.sizeof 

1366 

1367 @property 

1368 def heap_end(self) -> int: 

1369 return self.address + self.size 

1370 

1371 

1372class GlibcArena: 

1373 """Glibc arena class""" 

1374 

1375 NFASTBINS = 10 

1376 NBINS = 128 

1377 NSMALLBINS = 64 

1378 BINMAPSHIFT = 5 

1379 BITSPERMAP = 1 << BINMAPSHIFT 

1380 BINMAPSIZE = NBINS // BITSPERMAP 

1381 

1382 @staticmethod 

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

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

1385 fields = [ 

1386 ("mutex", ctypes.c_uint32), 

1387 ("flags", ctypes.c_uint32), 

1388 ] 

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

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

1391 fields += [ 

1392 ("have_fastchunks", ctypes.c_uint32), 

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

1394 ] 

1395 fields += [ 

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

1397 ("top", pointer), 

1398 ("last_remainder", pointer), 

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

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

1401 ("next", pointer), 

1402 ("next_free", pointer) 

1403 ] 

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

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

1406 fields += [ 

1407 ("attached_threads", pointer) 

1408 ] 

1409 fields += [ 

1410 ("system_mem", pointer), 

1411 ("max_system_mem", pointer), 

1412 ] 

1413 class malloc_state_cls(ctypes.Structure): 

1414 _fields_ = fields 

1415 return malloc_state_cls 

1416 

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

1418 try: 

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

1420 except gdb.error: 

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

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

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

1424 self.reset() 

1425 return 

1426 

1427 def reset(self): 

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

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

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

1431 return 

1432 

1433 def __abs__(self) -> int: 

1434 return self.__address 

1435 

1436 def __int__(self) -> int: 

1437 return self.__address 

1438 

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

1440 assert gef.heap.main_arena 

1441 main_arena = int(gef.heap.main_arena) 

1442 

1443 current_arena = self 

1444 yield current_arena 

1445 

1446 while True: 

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

1448 break 

1449 

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

1451 yield current_arena 

1452 return 

1453 

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

1455 return self.__address == int(other) 

1456 

1457 def __str__(self) -> str: 

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

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

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

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

1462 

1463 def __repr__(self) -> str: 

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

1465 

1466 @property 

1467 def address(self) -> int: 

1468 return self.__address 

1469 

1470 @property 

1471 def sizeof(self) -> int: 

1472 return self._sizeof 

1473 

1474 @property 

1475 def addr(self) -> int: 

1476 return int(self) 

1477 

1478 @property 

1479 def top(self) -> int: 

1480 return self.__arena.top 

1481 

1482 @property 

1483 def last_remainder(self) -> int: 

1484 return self.__arena.last_remainder 

1485 

1486 @property 

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

1488 return self.__arena.fastbinsY 

1489 

1490 @property 

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

1492 return self.__arena.bins 

1493 

1494 @property 

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

1496 return self.__arena.binmap 

1497 

1498 @property 

1499 def next(self) -> int: 

1500 return self.__arena.next 

1501 

1502 @property 

1503 def next_free(self) -> int: 

1504 return self.__arena.next_free 

1505 

1506 @property 

1507 def attached_threads(self) -> int: 

1508 return self.__arena.attached_threads 

1509 

1510 @property 

1511 def system_mem(self) -> int: 

1512 return self.__arena.system_mem 

1513 

1514 @property 

1515 def max_system_mem(self) -> int: 

1516 return self.__arena.max_system_mem 

1517 

1518 def fastbin(self, i: int) -> "GlibcFastChunk | None": 

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

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

1521 if addr == 0: 

1522 return None 

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

1524 

1525 def bin(self, i: int) -> tuple[int, int]: 

1526 idx = i * 2 

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

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

1529 return fd, bk 

1530 

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

1532 header_sz = 2 * gef.arch.ptrsize 

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

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

1535 

1536 def is_main_arena(self) -> bool: 

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

1538 

1539 def heap_addr(self, allow_unaligned: bool = False) -> int | None: 

1540 if self.is_main_arena(): 

1541 heap_section = gef.heap.base_address 

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

1543 return None 

1544 return heap_section 

1545 _addr = int(self) + self.sizeof 

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

1547 return _addr 

1548 return gef.heap.malloc_align_address(_addr) 

1549 

1550 def get_heap_info_list(self) -> list[GlibcHeapInfo] | None: 

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

1552 return None 

1553 heap_addr = self.get_heap_for_ptr(self.top) 

1554 heap_infos = [GlibcHeapInfo(heap_addr)] 

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

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

1557 heap_info = GlibcHeapInfo(prev) 

1558 heap_infos.append(heap_info) 

1559 return heap_infos[::-1] 

1560 

1561 @staticmethod 

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

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

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

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

1566 default_mmap_threshold_max = 512 * 1024 

1567 else: # 64bit 

1568 val = cached_lookup_type("long") 

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

1570 default_mmap_threshold_max = 4 * 1024 * 1024 * sz 

1571 heap_max_size = 2 * default_mmap_threshold_max 

1572 return ptr & ~(heap_max_size - 1) 

1573 

1574 @staticmethod 

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

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

1577 try: 

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

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

1580 while cur_arena != test_arena: 

1581 if cur_arena == 0: 

1582 return False 

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

1584 except Exception as e: 

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

1586 return False 

1587 return True 

1588 

1589 

1590class GlibcChunk: 

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

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

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

1594 

1595 class ChunkFlags(enum.IntFlag): 

1596 PREV_INUSE = 1 

1597 IS_MMAPPED = 2 

1598 NON_MAIN_ARENA = 4 

1599 

1600 def __str__(self) -> str: 

1601 return " | ".join([ 

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

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

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

1605 ]) 

1606 

1607 @staticmethod 

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

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

1610 class malloc_chunk_cls(ctypes.Structure): 

1611 pass 

1612 

1613 malloc_chunk_cls._fields_ = [ 

1614 ("prev_size", pointer), 

1615 ("size", pointer), 

1616 ("fd", pointer), 

1617 ("bk", pointer), 

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

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

1620 ] 

1621 return malloc_chunk_cls 

1622 

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

1624 ptrsize = gef.arch.ptrsize 

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

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

1627 if not allow_unaligned: 

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

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

1630 self.prev_size_addr = self.base_address 

1631 self.reset() 

1632 return 

1633 

1634 def reset(self): 

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

1636 self._data = gef.memory.read( 

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

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

1639 return 

1640 

1641 @property 

1642 def prev_size(self) -> int: 

1643 return self._chunk.prev_size 

1644 

1645 @property 

1646 def size(self) -> int: 

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

1648 

1649 @property 

1650 def flags(self) -> ChunkFlags: 

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

1652 

1653 @property 

1654 def fd(self) -> int: 

1655 return self._chunk.fd 

1656 

1657 @property 

1658 def bk(self) -> int: 

1659 return self._chunk.bk 

1660 

1661 @property 

1662 def fd_nextsize(self) -> int: 

1663 return self._chunk.fd_nextsize 

1664 

1665 @property 

1666 def bk_nextsize(self) -> int: 

1667 return self._chunk.bk_nextsize 

1668 

1669 def get_usable_size(self) -> int: 

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

1671 ptrsz = gef.arch.ptrsize 

1672 cursz = self.size 

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

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

1675 return cursz - ptrsz 

1676 

1677 @property 

1678 def usable_size(self) -> int: 

1679 return self.get_usable_size() 

1680 

1681 def get_prev_chunk_size(self) -> int: 

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

1683 

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

1685 assert gef.heap.main_arena 

1686 current_chunk = self 

1687 top = gef.heap.main_arena.top 

1688 

1689 while True: 

1690 yield current_chunk 

1691 

1692 if current_chunk.base_address == top: 

1693 break 

1694 

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

1696 break 

1697 

1698 next_chunk_addr = current_chunk.get_next_chunk_addr() 

1699 

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

1701 break 

1702 

1703 next_chunk = current_chunk.get_next_chunk() 

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

1705 break 

1706 

1707 current_chunk = next_chunk 

1708 return 

1709 

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

1711 addr = self.get_next_chunk_addr() 

1712 return GlibcChunk(addr, allow_unaligned=allow_unaligned) 

1713 

1714 def get_next_chunk_addr(self) -> int: 

1715 return self.data_address + self.size 

1716 

1717 def has_p_bit(self) -> bool: 

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

1719 

1720 def has_m_bit(self) -> bool: 

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

1722 

1723 def has_n_bit(self) -> bool: 

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

1725 

1726 def is_used(self) -> bool: 

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

1728 - checking the M bit is true 

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

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

1731 return True 

1732 

1733 next_chunk = self.get_next_chunk() 

1734 return True if next_chunk.has_p_bit() else False 

1735 

1736 def __str_sizes(self) -> str: 

1737 msg = [] 

1738 failed = False 

1739 

1740 try: 

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

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

1743 failed = True 

1744 except gdb.MemoryError: 

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

1746 

1747 try: 

1748 prev_chunk_sz = self.get_prev_chunk_size() 

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

1750 failed = True 

1751 except gdb.MemoryError: 

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

1753 

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

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

1756 

1757 return "\n".join(msg) 

1758 

1759 def _str_pointers(self) -> str: 

1760 fwd = self.data_address 

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

1762 

1763 msg = [] 

1764 try: 

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

1766 except gdb.MemoryError: 

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

1768 

1769 try: 

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

1771 except gdb.MemoryError: 

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

1773 

1774 return "\n".join(msg) 

1775 

1776 def __str__(self) -> str: 

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

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

1779 

1780 def psprint(self) -> str: 

1781 msg = [ 

1782 str(self), 

1783 self.__str_sizes(), 

1784 ] 

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

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

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

1788 

1789 def resolve_type(self) -> str: 

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

1791 if ptr_data != 0: 

1792 sym = gdb_get_location_from_symbol(ptr_data) 

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

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

1795 

1796 return "" 

1797 

1798 

1799class GlibcFastChunk(GlibcChunk): 

1800 

1801 @property 

1802 def fd(self) -> int: 

1803 assert(gef and gef.libc.version) 

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

1805 return self._chunk.fd 

1806 return self.reveal_ptr(self.data_address) 

1807 

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

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

1810 assert(gef and gef.libc.version) 

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

1812 return pointer 

1813 return (pos >> 12) ^ pointer 

1814 

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

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

1817 assert(gef and gef.libc.version) 

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

1819 return pointer 

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

1821 

1822class GlibcTcacheChunk(GlibcFastChunk): 

1823 pass 

1824 

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

1826def get_libc_version() -> tuple[int, ...]: 

1827 return GefLibcManager.find_libc_version() 

1828 

1829def titlify(text: str, color: str | None = None, msg_color: str | None = None) -> str: 

1830 """Print a centered title.""" 

1831 _, cols = get_terminal_size() 

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

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

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

1835 

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

1837 Color.colorify(text, text_color), 

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

1839 return "".join(msg) 

1840 

1841 

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

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

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

1845 return 

1846 

1847 

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

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

1850 return 

1851 

1852 

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

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

1855 return 

1856 

1857 

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

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

1860 return 

1861 

1862 

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

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

1865 return 

1866 

1867 

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

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

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

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

1872 return 

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

1874 return 

1875 

1876 

1877def show_last_exception() -> None: 

1878 """Display the last Python exception.""" 

1879 

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

1881 fpath = pathlib.Path(os.path.expanduser(os.path.expandvars(fname))) 

1882 _data = fpath.read_text().splitlines() 

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

1884 

1885 gef_print("") 

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

1887 exc_name = exc_type.__name__ if exc_type else "Unknown" 

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

1889 gef_print(f"{Color.colorify(exc_name, 'bold underline red')}: {exc_value}") 

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

1891 

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

1893 filename, lineno, method, code = fs 

1894 

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

1896 code = _show_code_line(filename, lineno) 

1897 

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

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

1900 

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

1902 gdb.execute("version full") 

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

1904 gdb.execute("show commands") 

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

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

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

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

1909 

1910 try: 

1911 lsb_release = which("lsb_release") 

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

1913 except FileNotFoundError: 

1914 pass 

1915 

1916 gef_print(HORIZONTAL_LINE*80) 

1917 gef_print("") 

1918 return 

1919 

1920 

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

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

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

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

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

1926 return res 

1927 

1928 

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

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

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

1932 

1933 

1934@lru_cache() 

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

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

1937 res = shutil.which(program) 

1938 if not res: 

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

1940 return pathlib.Path(res) 

1941 

1942 

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

1944 style = { 

1945 "nonprintable": "yellow", 

1946 "printable": "white", 

1947 "00": "gray", 

1948 "0a": "blue", 

1949 "ff": "green", 

1950 } 

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

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

1953 return sbyte 

1954 

1955 if sbyte in style: 

1956 st = style[sbyte] 

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

1958 st = style.get("printable") 

1959 else: 

1960 st = style.get("nonprintable") 

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

1962 sbyte = Color.colorify(sbyte, st) 

1963 return sbyte 

1964 

1965 

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

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

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

1969 @param length is the length of items per line 

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

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

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

1973 @return a string with the hexdump""" 

1974 result = [] 

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

1976 

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

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

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

1980 

1981 if show_raw: 

1982 result.append(hexa) 

1983 continue 

1984 

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

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

1987 sym = gdb_get_location_from_symbol(base + i) 

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

1989 else: 

1990 sym = "" 

1991 

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

1993 return "\n".join(result) 

1994 

1995 

1996def is_debug() -> bool: 

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

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

1999 

2000 

2001def buffer_output() -> bool: 

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

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

2004 

2005 

2006def hide_context() -> bool: 

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

2008 gef.ui.context_hidden = True 

2009 return True 

2010 

2011 

2012def unhide_context() -> bool: 

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

2014 gef.ui.context_hidden = False 

2015 return True 

2016 

2017 

2018class DisableContextOutputContext: 

2019 def __enter__(self) -> None: 

2020 hide_context() 

2021 return 

2022 

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

2024 unhide_context() 

2025 return 

2026 

2027 

2028class RedirectOutputContext: 

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

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

2031 self.redirection_target_file = to_file 

2032 return 

2033 

2034 def __enter__(self) -> None: 

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

2036 gdb.execute("set logging overwrite") 

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

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

2039 

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

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

2042 else: 

2043 gdb.execute("set logging on") 

2044 return 

2045 

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

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

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

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

2050 else: 

2051 gdb.execute("set logging off") 

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

2053 return 

2054 

2055 

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

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

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

2059 gdb.execute("set logging overwrite") 

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

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

2062 

2063 if GDB_VERSION >= (12, 0): 

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

2065 else: 

2066 gdb.execute("set logging on") 

2067 return 

2068 

2069 

2070def disable_redirect_output() -> None: 

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

2072 if GDB_VERSION >= (12, 0): 

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

2074 else: 

2075 gdb.execute("set logging off") 

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

2077 return 

2078 

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

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

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

2082 fpath = pathlib.Path(path) 

2083 if not fpath.is_dir(): 

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

2085 return fpath.absolute() 

2086 

2087 

2088@lru_cache() 

2089def gdb_lookup_symbol(sym: str) -> tuple[gdb.Symtab_and_line, ...] | None: 

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

2091 try: 

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

2093 return res 

2094 except gdb.error: 

2095 return None 

2096 

2097@lru_cache(maxsize=512) 

2098def gdb_get_location_from_symbol(address: int) -> tuple[str, int] | None: 

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

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

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

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

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

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

2105 return None 

2106 

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

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

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

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

2111 name, offset = sym[0], 0 

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

2113 offset = int(sym[1]) 

2114 return name, offset 

2115 

2116 

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

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

2119 parameters: 

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

2121 start_pc to end_pc are returned. 

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

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

2124 Return an iterator of Instruction objects 

2125 """ 

2126 frame = gdb.selected_frame() 

2127 arch = frame.architecture() 

2128 

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

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

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

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

2133 address = insn["addr"] 

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

2135 if len(asm) > 1: 

2136 mnemo, operands = asm 

2137 operands = operands.split(",") 

2138 else: 

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

2140 

2141 loc = gdb_get_location_from_symbol(address) 

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

2143 

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

2145 

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

2147 

2148 

2149def gdb_get_nth_previous_instruction_address(addr: int, n: int) -> int | None: 

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

2151 # fixed-length ABI 

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

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

2154 

2155 # variable-length ABI 

2156 cur_insn_addr = gef_current_instruction(addr).address 

2157 

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

2159 # the 15 comes from the longest instruction valid size 

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

2161 try: 

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

2163 except gdb.MemoryError: 

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

2165 break 

2166 

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

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

2169 continue 

2170 

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

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

2173 insns = insns[-n - 1 :] 

2174 

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

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

2177 continue 

2178 

2179 # 3. check all instructions are valid 

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

2181 return insns[0].address 

2182 

2183 return None 

2184 

2185 

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

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

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

2189 return gef_instruction_n(addr, n).address 

2190 

2191 

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

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

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

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

2196 

2197 

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

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

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

2201 return insn 

2202 

2203 

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

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

2206 return gef_instruction_n(addr, 0) 

2207 

2208 

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

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

2211 return gef_instruction_n(addr, 1) 

2212 

2213 

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

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

2216 Return an iterator of Instruction objects.""" 

2217 nb_insn = max(1, nb_insn) 

2218 

2219 if nb_prev: 

2220 try: 

2221 start_addr = gdb_get_nth_previous_instruction_address(addr, nb_prev) 

2222 if start_addr: 

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

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

2225 yield insn 

2226 except gdb.MemoryError: 

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

2228 pass 

2229 

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

2231 yield insn 

2232 

2233 

2234def gef_execute_external(command: Sequence[str], as_list: bool = False, **kwargs: Any) -> str | list[str]: 

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

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

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

2238 

2239 

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

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

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

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

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

2245 f.write(commands) 

2246 f.flush() 

2247 

2248 fname = pathlib.Path(fname) 

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

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

2251 fname.unlink() 

2252 return 

2253 

2254 

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

2256def checksec(filename: str) -> dict[str, bool]: 

2257 return Elf(filename).checksec 

2258 

2259 

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

2261def get_arch() -> str: 

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

2263 if is_alive(): 

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

2265 return arch.name() 

2266 

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

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

2269 if arch_str.startswith(pat): 

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

2271 return arch_str 

2272 

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

2274 if arch_str.startswith(pat): 

2275 return arch_str[len(pat):] 

2276 

2277 pat = "The target architecture is set to " 

2278 if arch_str.startswith(pat): 

2279 # GDB version >= 10.1 

2280 if '"auto"' in arch_str: 

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

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

2283 

2284 # Unknown, we throw an exception to be safe 

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

2286 

2287 

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

2289def get_entry_point() -> int | None: 

2290 """Return the binary entry point.""" 

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

2292 

2293 

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

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

2296 

2297 

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

2299def is_big_endian() -> bool: 

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

2301 

2302 

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

2304def is_little_endian() -> bool: 

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

2306 

2307 

2308def flags_to_human(reg_value: int, value_table: dict[int, str]) -> str: 

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

2310 flags = [] 

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

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

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

2314 

2315 

2316@lru_cache() 

2317def get_section_base_address(name: str) -> int | None: 

2318 section = process_lookup_path(name) 

2319 return section.page_start if section else None 

2320 

2321 

2322@lru_cache() 

2323def get_zone_base_address(name: str) -> int | None: 

2324 zone = file_lookup_name_path(name, get_filepath()) 

2325 return zone.zone_start if zone else None 

2326 

2327 

2328# 

2329# Architecture classes 

2330# 

2331 

2332@deprecated("Using the decorator `register_architecture` is unnecessary") 

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

2334 return cls 

2335 

2336class ArchitectureBase: 

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

2338 aliases: tuple[str | Elf.Abi, ...] = () 

2339 

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

2341 global __registered_architectures__ 

2342 super().__init_subclass__(**kwargs) 

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

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

2345 if isinstance(key, str): 

2346 __registered_architectures__[key.lower()] = cls 

2347 else: 

2348 __registered_architectures__[key] = cls 

2349 return 

2350 

2351 

2352class Architecture(ArchitectureBase): 

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

2354 

2355 # Mandatory defined attributes by inheriting classes 

2356 arch: str 

2357 mode: str 

2358 all_registers: tuple[str, ...] 

2359 nop_insn: bytes 

2360 return_register: str 

2361 flag_register: str | None 

2362 instruction_length: int | None 

2363 flags_table: dict[int, str] 

2364 syscall_register: str | None 

2365 syscall_instructions: tuple[str, ...] 

2366 function_parameters: tuple[str, ...] 

2367 

2368 # Optionally defined attributes 

2369 _ptrsize: int | None = None 

2370 _endianness: Endianness | None = None 

2371 special_registers: tuple[()] | tuple[str, ...] = () 

2372 maps: GefMemoryMapProvider | None = None 

2373 

2374 def __init_subclass__(cls, **kwargs): 

2375 super().__init_subclass__(**kwargs) 

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

2377 "return_register", "flag_register", "instruction_length", "flags_table", 

2378 "function_parameters",) 

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

2380 raise NotImplementedError 

2381 

2382 def __str__(self) -> str: 

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

2384 

2385 def __repr__(self) -> str: 

2386 return self.__str__() 

2387 

2388 @staticmethod 

2389 def supports_gdb_arch(gdb_arch: str) -> bool | None: 

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

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

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

2393 return None 

2394 

2395 def flag_register_to_human(self, val: int | None = None) -> str: 

2396 raise NotImplementedError 

2397 

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

2399 raise NotImplementedError 

2400 

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

2402 raise NotImplementedError 

2403 

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

2405 raise NotImplementedError 

2406 

2407 def is_branch_taken(self, insn: Instruction) -> tuple[bool, str]: 

2408 raise NotImplementedError 

2409 

2410 def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> int | None: 

2411 raise NotImplementedError 

2412 

2413 def canary_address(self) -> int: 

2414 raise NotImplementedError 

2415 

2416 @classmethod 

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

2418 raise NotImplementedError 

2419 

2420 def reset_caches(self) -> None: 

2421 self.__get_register_for_selected_frame.cache_clear() 

2422 return 

2423 

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

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

2426 curframe = gdb.selected_frame() 

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

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

2429 

2430 @lru_cache() 

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

2432 # 1st chance 

2433 try: 

2434 return parse_address(regname) 

2435 except gdb.error: 

2436 pass 

2437 

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

2439 regname = regname.lstrip("$") 

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

2441 return int(value) 

2442 

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

2444 if not is_alive(): 

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

2446 return self.__get_register(name) 

2447 

2448 @property 

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

2450 yield from self.all_registers 

2451 

2452 @property 

2453 def pc(self) -> int: 

2454 return self.register("$pc") 

2455 

2456 @property 

2457 def sp(self) -> int: 

2458 return self.register("$sp") 

2459 

2460 @property 

2461 def fp(self) -> int: 

2462 return self.register("$fp") 

2463 

2464 @property 

2465 def ptrsize(self) -> int: 

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

2467 res = cached_lookup_type("size_t") 

2468 if res is not None: 

2469 self._ptrsize = res.sizeof 

2470 else: 

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

2472 return self._ptrsize 

2473 

2474 @property 

2475 def endianness(self) -> Endianness: 

2476 if not self._endianness: 

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

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

2479 self._endianness = Endianness.LITTLE_ENDIAN 

2480 elif "big endian" in output: 

2481 self._endianness = Endianness.BIG_ENDIAN 

2482 else: 

2483 raise OSError(f"No valid endianness found in '{output}'") 

2484 return self._endianness 

2485 

2486 def get_ith_parameter(self, i: int, in_func: bool = True) -> tuple[str, int | None]: 

2487 """Retrieves the correct parameter used for the current function call.""" 

2488 reg = self.function_parameters[i] 

2489 val = self.register(reg) 

2490 key = reg 

2491 return key, val 

2492 

2493 

2494class GenericArchitecture(Architecture): 

2495 arch = "Generic" 

2496 mode = "" 

2497 aliases = ("GenericArchitecture",) 

2498 all_registers = () 

2499 instruction_length = 0 

2500 return_register = "" 

2501 function_parameters = () 

2502 syscall_register = "" 

2503 syscall_instructions = () 

2504 nop_insn = b"" 

2505 flag_register = None 

2506 flags_table = {} 

2507 

2508 

2509class RISCV(Architecture): 

2510 arch = "RISCV" 

2511 mode = "RISCV" 

2512 aliases = ("RISCV", Elf.Abi.RISCV) 

2513 all_registers = ("$zero", "$ra", "$sp", "$gp", "$tp", "$t0", "$t1", 

2514 "$t2", "$fp", "$s1", "$a0", "$a1", "$a2", "$a3", 

2515 "$a4", "$a5", "$a6", "$a7", "$s2", "$s3", "$s4", 

2516 "$s5", "$s6", "$s7", "$s8", "$s9", "$s10", "$s11", 

2517 "$t3", "$t4", "$t5", "$t6",) 

2518 return_register = "$a0" 

2519 function_parameters = ("$a0", "$a1", "$a2", "$a3", "$a4", "$a5", "$a6", "$a7") 

2520 syscall_register = "$a7" 

2521 syscall_instructions = ("ecall",) 

2522 nop_insn = b"\x00\x00\x00\x13" 

2523 # RISC-V has no flags registers 

2524 flag_register = None 

2525 flags_table = {} 

2526 

2527 @property 

2528 def instruction_length(self) -> int: 

2529 return 4 

2530 

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

2532 return insn.mnemonic == "call" 

2533 

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

2535 mnemo = insn.mnemonic 

2536 if mnemo == "ret": 

2537 return True 

2538 elif (mnemo == "jalr" and insn.operands[0] == "zero" and 

2539 insn.operands[1] == "ra" and insn.operands[2] == 0): 

2540 return True 

2541 elif (mnemo == "c.jalr" and insn.operands[0] == "ra"): 

2542 return True 

2543 return False 

2544 

2545 @classmethod 

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

2547 raise OSError(f"Architecture {cls.arch} not supported yet") 

2548 

2549 @property 

2550 def ptrsize(self) -> int: 

2551 if self._ptrsize is not None: 

2552 return self._ptrsize 

2553 if is_alive(): 

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

2555 return self._ptrsize 

2556 return 4 

2557 

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

2559 return insn.mnemonic.startswith("b") 

2560 

2561 def is_branch_taken(self, insn: Instruction) -> tuple[bool, str]: 

2562 def long_to_twos_complement(v: int) -> int: 

2563 """Convert a python long value to its two's complement.""" 

2564 if is_32bit(): 

2565 if v & 0x80000000: 

2566 return v - 0x100000000 

2567 elif is_64bit(): 

2568 if v & 0x8000000000000000: 

2569 return v - 0x10000000000000000 

2570 else: 

2571 raise OSError("RISC-V: ELF file is not ELF32 or ELF64. This is not currently supported") 

2572 return v 

2573 

2574 mnemo = insn.mnemonic 

2575 condition = mnemo[1:] 

2576 

2577 if condition.endswith("z"): 

2578 # r2 is the zero register if we are comparing to 0 

2579 rs1 = gef.arch.register(insn.operands[0]) 

2580 rs2 = gef.arch.register("$zero") 

2581 condition = condition[:-1] 

2582 elif len(insn.operands) > 2: 

2583 # r2 is populated with the second operand 

2584 rs1 = gef.arch.register(insn.operands[0]) 

2585 rs2 = gef.arch.register(insn.operands[1]) 

2586 else: 

2587 raise OSError(f"RISC-V: Failed to get rs1 and rs2 for instruction: `{insn}`") 

2588 

2589 # If the conditional operation is not unsigned, convert the python long into 

2590 # its two's complement 

2591 if not condition.endswith("u"): 

2592 rs2 = long_to_twos_complement(rs2) 

2593 rs1 = long_to_twos_complement(rs1) 

2594 else: 

2595 condition = condition[:-1] 

2596 

2597 if condition == "eq": 

2598 if rs1 == rs2: taken, reason = True, f"{rs1}={rs2}" 

2599 else: taken, reason = False, f"{rs1}!={rs2}" 

2600 elif condition == "ne": 

2601 if rs1 != rs2: taken, reason = True, f"{rs1}!={rs2}" 

2602 else: taken, reason = False, f"{rs1}={rs2}" 

2603 elif condition == "lt": 

2604 if rs1 < rs2: taken, reason = True, f"{rs1}<{rs2}" 

2605 else: taken, reason = False, f"{rs1}>={rs2}" 

2606 elif condition == "le": 

2607 if rs1 <= rs2: taken, reason = True, f"{rs1}<={rs2}" 

2608 else: taken, reason = False, f"{rs1}>{rs2}" 

2609 elif condition == "ge": 

2610 if rs1 >= rs2: taken, reason = True, f"{rs1}>={rs2}" 

2611 else: taken, reason = False, f"{rs1}<{rs2}" 

2612 else: 

2613 raise OSError(f"RISC-V: Conditional instruction `{insn}` not supported yet") 

2614 

2615 return taken, reason 

2616 

2617 def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> int | None: 

2618 ra = None 

2619 if self.is_ret(insn): 

2620 ra = gef.arch.register("$ra") 

2621 else: 

2622 older = frame.older() 

2623 if older: 

2624 ra = to_unsigned_long(older.pc()) 

2625 return ra 

2626 

2627 def flag_register_to_human(self, val: int | None = None) -> str: 

2628 # RISC-V has no flags registers, return an empty string to 

2629 # preserve the Architecture API 

2630 return "" 

2631 

2632class ARM(Architecture): 

2633 aliases = ("ARM", Elf.Abi.ARM) 

2634 arch = "ARM" 

2635 all_registers = ("$r0", "$r1", "$r2", "$r3", "$r4", "$r5", "$r6", 

2636 "$r7", "$r8", "$r9", "$r10", "$r11", "$r12", "$sp", 

2637 "$lr", "$pc", "$cpsr",) 

2638 

2639 nop_insn = b"\x00\xf0\x20\xe3" # hint #0 

2640 return_register = "$r0" 

2641 flag_register: str = "$cpsr" 

2642 flags_table = { 

2643 31: "negative", 

2644 30: "zero", 

2645 29: "carry", 

2646 28: "overflow", 

2647 7: "interrupt", 

2648 6: "fast", 

2649 5: "thumb", 

2650 } 

2651 function_parameters = ("$r0", "$r1", "$r2", "$r3") 

2652 syscall_register = "$r7" 

2653 syscall_instructions = ("swi 0x0", "swi NR") 

2654 _endianness = Endianness.LITTLE_ENDIAN 

2655 

2656 def is_thumb(self) -> bool: 

2657 """Determine if the machine is currently in THUMB mode.""" 

2658 return is_alive() and (self.cpsr & (1 << 5) == 1) 

2659 

2660 @property 

2661 def pc(self) -> int | None: 

2662 pc = gef.arch.register("$pc") 

2663 if self.is_thumb(): 

2664 pc += 1 

2665 return pc 

2666 

2667 @property 

2668 def cpsr(self) -> int: 

2669 if not is_alive(): 

2670 raise RuntimeError("Cannot get CPSR, program not started?") 

2671 return gef.arch.register(self.flag_register) 

2672 

2673 @property 

2674 def mode(self) -> str: 

2675 return "THUMB" if self.is_thumb() else "ARM" 

2676 

2677 @property 

2678 def instruction_length(self) -> int | None: 

2679 # Thumb instructions have variable-length (2 or 4-byte) 

2680 return None if self.is_thumb() else 4 

2681 

2682 @property 

2683 def ptrsize(self) -> int: 

2684 return 4 

2685 

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

2687 mnemo = insn.mnemonic 

2688 call_mnemos = {"bl", "blx"} 

2689 return mnemo in call_mnemos 

2690 

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

2692 pop_mnemos = {"pop"} 

2693 branch_mnemos = {"bl", "bx"} 

2694 write_mnemos = {"ldr", "add"} 

2695 if insn.mnemonic in pop_mnemos: 

2696 return insn.operands[-1] == " pc}" 

2697 if insn.mnemonic in branch_mnemos: 

2698 return insn.operands[-1] == "lr" 

2699 if insn.mnemonic in write_mnemos: 

2700 return insn.operands[0] == "pc" 

2701 return False 

2702 

2703 def flag_register_to_human(self, val: int | None = None) -> str: 

2704 # https://www.botskool.com/user-pages/tutorials/electronics/arm-7-tutorial-part-1 

2705 if val is None: 

2706 reg = self.flag_register 

2707 val = gef.arch.register(reg) 

2708 return flags_to_human(val, self.flags_table) 

2709 

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

2711 conditions = {"eq", "ne", "lt", "le", "gt", "ge", "vs", "vc", "mi", "pl", "hi", "ls", "cc", "cs"} 

2712 return insn.mnemonic[-2:] in conditions 

2713 

2714 def is_branch_taken(self, insn: Instruction) -> tuple[bool, str]: 

2715 mnemo = insn.mnemonic 

2716 # ref: https://www.davespace.co.uk/arm/introduction-to-arm/conditional.html 

2717 flags = dict((self.flags_table[k], k) for k in self.flags_table) 

2718 val = gef.arch.register(self.flag_register) 

2719 taken, reason = False, "" 

2720 

2721 if mnemo.endswith("eq"): taken, reason = bool(val&(1<<flags["zero"])), "Z" 

2722 elif mnemo.endswith("ne"): taken, reason = not bool(val&(1<<flags["zero"])), "!Z" 

2723 elif mnemo.endswith("lt"): 

2724 taken, reason = bool(val&(1<<flags["negative"])) != bool(val&(1<<flags["overflow"])), "N!=V" 

2725 elif mnemo.endswith("le"): 

2726 taken, reason = bool(val&(1<<flags["zero"])) or \ 

2727 bool(val&(1<<flags["negative"])) != bool(val&(1<<flags["overflow"])), "Z || N!=V" 

2728 elif mnemo.endswith("gt"): 

2729 taken, reason = bool(val&(1<<flags["zero"])) == 0 and \ 

2730 bool(val&(1<<flags["negative"])) == bool(val&(1<<flags["overflow"])), "!Z && N==V" 

2731 elif mnemo.endswith("ge"): 

2732 taken, reason = bool(val&(1<<flags["negative"])) == bool(val&(1<<flags["overflow"])), "N==V" 

2733 elif mnemo.endswith("vs"): taken, reason = bool(val&(1<<flags["overflow"])), "V" 

2734 elif mnemo.endswith("vc"): taken, reason = not val&(1<<flags["overflow"]), "!V" 

2735 elif mnemo.endswith("mi"): 

2736 taken, reason = bool(val&(1<<flags["negative"])), "N" 

2737 elif mnemo.endswith("pl"): 

2738 taken, reason = not val&(1<<flags["negative"]), "N==0" 

2739 elif mnemo.endswith("hi"): 

2740 taken, reason = bool(val&(1<<flags["carry"])) and not bool(val&(1<<flags["zero"])), "C && !Z" 

2741 elif mnemo.endswith("ls"): 

2742 taken, reason = not val&(1<<flags["carry"]) or bool(val&(1<<flags["zero"])), "!C || Z" 

2743 elif mnemo.endswith("cs"): taken, reason = bool(val&(1<<flags["carry"])), "C" 

2744 elif mnemo.endswith("cc"): taken, reason = not val&(1<<flags["carry"]), "!C" 

2745 return taken, reason 

2746 

2747 def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> int | None: 

2748 if not self.is_ret(insn): 

2749 older = frame.older() 

2750 if not older: 

2751 return None 

2752 return int(older.pc()) 

2753 

2754 # If it's a pop, we have to peek into the stack, otherwise use lr 

2755 if insn.mnemonic == "pop": 

2756 ra_addr = gef.arch.sp + (len(insn.operands)-1) * self.ptrsize 

2757 if not ra_addr: 

2758 return None 

2759 ra = dereference(ra_addr) 

2760 if ra is None: 

2761 return None 

2762 return to_unsigned_long(ra) 

2763 elif insn.mnemonic == "ldr": 

2764 ra = dereference(gef.arch.sp) 

2765 if ra is None: 

2766 return None 

2767 return to_unsigned_long(ra) 

2768 else: # 'bx lr' or 'add pc, lr, #0' 

2769 return gef.arch.register("$lr") 

2770 

2771 @classmethod 

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

2773 _NR_mprotect = 125 

2774 insns = [ 

2775 "push {r0-r2, r7}", 

2776 f"mov r1, {addr & 0xffff:d}", 

2777 f"mov r0, {(addr & 0xffff0000) >> 16:d}", 

2778 "lsl r0, r0, 16", 

2779 "add r0, r0, r1", 

2780 f"mov r1, {size & 0xffff:d}", 

2781 f"mov r2, {perm.value & 0xff:d}", 

2782 f"mov r7, {_NR_mprotect:d}", 

2783 "svc 0", 

2784 "pop {r0-r2, r7}", 

2785 ] 

2786 return "; ".join(insns) 

2787 

2788 

2789class AARCH64(ARM): 

2790 aliases = ("ARM64", "AARCH64", Elf.Abi.AARCH64) 

2791 arch = "ARM64" 

2792 mode: str = "" 

2793 

2794 all_registers = ( 

2795 "$x0", "$x1", "$x2", "$x3", "$x4", "$x5", "$x6", "$x7", 

2796 "$x8", "$x9", "$x10", "$x11", "$x12", "$x13", "$x14","$x15", 

2797 "$x16", "$x17", "$x18", "$x19", "$x20", "$x21", "$x22", "$x23", 

2798 "$x24", "$x25", "$x26", "$x27", "$x28", "$x29", "$x30", "$sp", 

2799 "$pc", "$cpsr", "$fpsr", "$fpcr",) 

2800 return_register = "$x0" 

2801 flag_register = "$cpsr" 

2802 flags_table = { 

2803 31: "negative", 

2804 30: "zero", 

2805 29: "carry", 

2806 28: "overflow", 

2807 7: "interrupt", 

2808 9: "endian", 

2809 6: "fast", 

2810 5: "t32", 

2811 4: "m[4]", 

2812 } 

2813 nop_insn = b"\x1f\x20\x03\xd5" # hint #0 

2814 function_parameters = ("$x0", "$x1", "$x2", "$x3", "$x4", "$x5", "$x6", "$x7",) 

2815 syscall_register = "$x8" 

2816 syscall_instructions = ("svc $x0",) 

2817 

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

2819 mnemo = insn.mnemonic 

2820 call_mnemos = {"bl", "blr"} 

2821 return mnemo in call_mnemos 

2822 

2823 def flag_register_to_human(self, val: int | None = None) -> str: 

2824 # https://events.linuxfoundation.org/sites/events/files/slides/KoreaLinuxForum-2014.pdf 

2825 reg = self.flag_register 

2826 if not val: 

2827 val = gef.arch.register(reg) 

2828 return flags_to_human(val, self.flags_table) 

2829 

2830 def is_aarch32(self) -> bool: 

2831 """Determine if the CPU is currently in AARCH32 mode from runtime.""" 

2832 return (self.cpsr & (1 << 4) != 0) and (self.cpsr & (1 << 5) == 0) 

2833 

2834 def is_thumb32(self) -> bool: 

2835 """Determine if the CPU is currently in THUMB32 mode from runtime.""" 

2836 return (self.cpsr & (1 << 4) == 1) and (self.cpsr & (1 << 5) == 1) 

2837 

2838 @property 

2839 def ptrsize(self) -> int: 

2840 """Determine the size of pointer from the current CPU mode""" 

2841 if not is_alive(): 

2842 return 8 

2843 if self.is_aarch32(): 

2844 return 4 

2845 if self.is_thumb32(): 

2846 return 2 

2847 return 8 

2848 

2849 @classmethod 

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

2851 _NR_mprotect = 226 

2852 insns = [ 

2853 "str x8, [sp, -16]!", 

2854 "str x0, [sp, -16]!", 

2855 "str x1, [sp, -16]!", 

2856 "str x2, [sp, -16]!", 

2857 f"mov x8, {_NR_mprotect:d}", 

2858 f"movz x0, {addr & 0xFFFF:#x}", 

2859 f"movk x0, {(addr >> 16) & 0xFFFF:#x}, lsl 16", 

2860 f"movk x0, {(addr >> 32) & 0xFFFF:#x}, lsl 32", 

2861 f"movk x0, {(addr >> 48) & 0xFFFF:#x}, lsl 48", 

2862 f"movz x1, {size & 0xFFFF:#x}", 

2863 f"movk x1, {(size >> 16) & 0xFFFF:#x}, lsl 16", 

2864 f"mov x2, {perm.value:d}", 

2865 "svc 0", 

2866 "ldr x2, [sp], 16", 

2867 "ldr x1, [sp], 16", 

2868 "ldr x0, [sp], 16", 

2869 "ldr x8, [sp], 16", 

2870 ] 

2871 return "; ".join(insns) 

2872 

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

2874 # https://www.element14.com/community/servlet/JiveServlet/previewBody/41836-102-1-229511/ARM.Reference_Manual.pdf 

2875 # sect. 5.1.1 

2876 mnemo = insn.mnemonic 

2877 branch_mnemos = {"cbnz", "cbz", "tbnz", "tbz"} 

2878 return mnemo.startswith("b.") or mnemo in branch_mnemos 

2879 

2880 def is_branch_taken(self, insn: Instruction) -> tuple[bool, str]: 

2881 mnemo, operands = insn.mnemonic, insn.operands 

2882 taken, reason = False, "" 

2883 

2884 if mnemo in {"cbnz", "cbz", "tbnz", "tbz"}: 

2885 reg = f"${operands[0]}" 

2886 op = gef.arch.register(reg) 

2887 if mnemo == "cbnz": 

2888 if op!=0: taken, reason = True, f"{reg}!=0" 

2889 else: taken, reason = False, f"{reg}==0" 

2890 elif mnemo == "cbz": 

2891 if op == 0: taken, reason = True, f"{reg}==0" 

2892 else: taken, reason = False, f"{reg}!=0" 

2893 elif mnemo == "tbnz": 

2894 # operands[1] has one or more white spaces in front, then a #, then the number 

2895 # so we need to eliminate them 

2896 i = int(operands[1].strip().lstrip("#")) 

2897 if (op & 1<<i) != 0: taken, reason = True, f"{reg}&1<<{i}!=0" 

2898 else: taken, reason = False, f"{reg}&1<<{i}==0" 

2899 elif mnemo == "tbz": 

2900 # operands[1] has one or more white spaces in front, then a #, then the number 

2901 # so we need to eliminate them 

2902 i = int(operands[1].strip().lstrip("#")) 

2903 if (op & 1<<i) == 0: taken, reason = True, f"{reg}&1<<{i}==0" 

2904 else: taken, reason = False, f"{reg}&1<<{i}!=0" 

2905 

2906 if not reason: 

2907 taken, reason = super().is_branch_taken(insn) 

2908 return taken, reason 

2909 

2910 

2911class X86(Architecture): 

2912 aliases = ("X86", Elf.Abi.X86_32) 

2913 arch = "X86" 

2914 mode = "32" 

2915 

2916 nop_insn = b"\x90" 

2917 flag_register: str = "$eflags" 

2918 special_registers = ("$cs", "$ss", "$ds", "$es", "$fs", "$gs", ) 

2919 gpr_registers = ("$eax", "$ebx", "$ecx", "$edx", "$esp", "$ebp", "$esi", "$edi", "$eip", ) 

2920 all_registers = gpr_registers + ( flag_register,) + special_registers 

2921 instruction_length = None 

2922 return_register = "$eax" 

2923 function_parameters = ("$esp", ) 

2924 flags_table = { 

2925 6: "zero", 

2926 0: "carry", 

2927 2: "parity", 

2928 4: "adjust", 

2929 7: "sign", 

2930 8: "trap", 

2931 9: "interrupt", 

2932 10: "direction", 

2933 11: "overflow", 

2934 16: "resume", 

2935 17: "virtualx86", 

2936 21: "identification", 

2937 } 

2938 syscall_register = "$eax" 

2939 syscall_instructions = ("sysenter", "int 0x80") 

2940 _ptrsize = 4 

2941 _endianness = Endianness.LITTLE_ENDIAN 

2942 

2943 def flag_register_to_human(self, val: int | None = None) -> str: 

2944 reg = self.flag_register 

2945 if val is None: 

2946 val = gef.arch.register(reg) 

2947 return flags_to_human(val, self.flags_table) 

2948 

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

2950 mnemo = insn.mnemonic 

2951 call_mnemos = {"call", "callq"} 

2952 return mnemo in call_mnemos 

2953 

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

2955 return insn.mnemonic == "ret" 

2956 

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

2958 mnemo = insn.mnemonic 

2959 branch_mnemos = { 

2960 "ja", "jnbe", "jae", "jnb", "jnc", "jb", "jc", "jnae", "jbe", "jna", 

2961 "jcxz", "jecxz", "jrcxz", "je", "jz", "jg", "jnle", "jge", "jnl", 

2962 "jl", "jnge", "jle", "jng", "jne", "jnz", "jno", "jnp", "jpo", "jns", 

2963 "jo", "jp", "jpe", "js" 

2964 } 

2965 return mnemo in branch_mnemos 

2966 

2967 def is_branch_taken(self, insn: Instruction) -> tuple[bool, str]: 

2968 mnemo = insn.mnemonic 

2969 # all kudos to fG! (https://github.com/gdbinit/Gdbinit/blob/master/gdbinit#L1654) 

2970 flags = dict((self.flags_table[k], k) for k in self.flags_table) 

2971 val = gef.arch.register(self.flag_register) 

2972 

2973 taken, reason = False, "" 

2974 

2975 if mnemo in ("ja", "jnbe"): 

2976 taken, reason = not val&(1<<flags["carry"]) and not bool(val&(1<<flags["zero"])), "!C && !Z" 

2977 elif mnemo in ("jae", "jnb", "jnc"): 

2978 taken, reason = not val&(1<<flags["carry"]), "!C" 

2979 elif mnemo in ("jb", "jc", "jnae"): 

2980 taken, reason = bool(val&(1<<flags["carry"])) != 0, "C" 

2981 elif mnemo in ("jbe", "jna"): 

2982 taken, reason = bool(val&(1<<flags["carry"])) or bool(val&(1<<flags["zero"])), "C || Z" 

2983 elif mnemo in ("jcxz", "jecxz", "jrcxz"): 

2984 cx = gef.arch.register("$rcx") if is_x86_64() else gef.arch.register("$ecx") 

2985 taken, reason = cx == 0, "!$CX" 

2986 elif mnemo in ("je", "jz"): 

2987 taken, reason = bool(val&(1<<flags["zero"])), "Z" 

2988 elif mnemo in ("jne", "jnz"): 

2989 taken, reason = not bool(val&(1<<flags["zero"])), "!Z" 

2990 elif mnemo in ("jg", "jnle"): 

2991 taken, reason = not bool(val&(1<<flags["zero"])) and bool(val&(1<<flags["overflow"])) == bool(val&(1<<flags["sign"])), "!Z && S==O" 

2992 elif mnemo in ("jge", "jnl"): 

2993 taken, reason = bool(val&(1<<flags["sign"])) == bool(val&(1<<flags["overflow"])), "S==O" 

2994 elif mnemo in ("jl", "jnge"): 

2995 taken, reason = bool(val&(1<<flags["overflow"]) != val&(1<<flags["sign"])), "S!=O" 

2996 elif mnemo in ("jle", "jng"): 

2997 taken, reason = bool(val&(1<<flags["zero"])) or bool(val&(1<<flags["overflow"])) != bool(val&(1<<flags["sign"])), "Z || S!=O" 

2998 elif mnemo in ("jo",): 

2999 taken, reason = bool(val&(1<<flags["overflow"])), "O" 

3000 elif mnemo in ("jno",): 

3001 taken, reason = not val&(1<<flags["overflow"]), "!O" 

3002 elif mnemo in ("jpe", "jp"): 

3003 taken, reason = bool(val&(1<<flags["parity"])), "P" 

3004 elif mnemo in ("jnp", "jpo"): 

3005 taken, reason = not val&(1<<flags["parity"]), "!P" 

3006 elif mnemo in ("js",): 

3007 taken, reason = bool(val&(1<<flags["sign"])) != 0, "S" 

3008 elif mnemo in ("jns",): 

3009 taken, reason = not val&(1<<flags["sign"]), "!S" 

3010 return taken, reason 

3011 

3012 def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> int | None: 

3013 ra = None 

3014 if self.is_ret(insn): 3014 ↛ 3017line 3014 didn't jump to line 3017 because the condition on line 3014 was always true

3015 ra = dereference(gef.arch.sp) 

3016 else: 

3017 older = frame.older() 

3018 if older: 

3019 ra = older.pc() 

3020 if ra is None: 3020 ↛ 3021line 3020 didn't jump to line 3021 because the condition on line 3020 was never true

3021 return None 

3022 return to_unsigned_long(ra) 

3023 

3024 @classmethod 

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

3026 _NR_mprotect = 125 

3027 insns = [ 

3028 "pushad", 

3029 "pushfd", 

3030 f"mov eax, {_NR_mprotect:d}", 

3031 f"mov ebx, {addr:d}", 

3032 f"mov ecx, {size:d}", 

3033 f"mov edx, {perm.value:d}", 

3034 "int 0x80", 

3035 "popfd", 

3036 "popad", 

3037 ] 

3038 return "; ".join(insns) 

3039 

3040 def get_ith_parameter(self, i: int, in_func: bool = True) -> tuple[str, int | None]: 

3041 if in_func: 

3042 i += 1 # Account for RA being at the top of the stack 

3043 sp = gef.arch.sp 

3044 sz = gef.arch.ptrsize 

3045 loc = sp + (i * sz) 

3046 val = gef.memory.read_integer(loc) 

3047 key = f"[sp + {i * sz:#x}]" 

3048 return key, val 

3049 

3050 

3051class X86_64(X86): 

3052 aliases = ("X86_64", Elf.Abi.X86_64, "i386:x86-64") 

3053 arch = "X86" 

3054 mode = "64" 

3055 

3056 gpr_registers = ( 

3057 "$rax", "$rbx", "$rcx", "$rdx", "$rsp", "$rbp", "$rsi", "$rdi", "$rip", 

3058 "$r8", "$r9", "$r10", "$r11", "$r12", "$r13", "$r14", "$r15", ) 

3059 all_registers = gpr_registers + ( X86.flag_register, ) + X86.special_registers 

3060 return_register = "$rax" 

3061 function_parameters = ["$rdi", "$rsi", "$rdx", "$rcx", "$r8", "$r9"] 

3062 syscall_register = "$rax" 

3063 syscall_instructions = ["syscall"] 

3064 # We don't want to inherit x86's stack based param getter 

3065 get_ith_parameter = Architecture.get_ith_parameter 

3066 _ptrsize = 8 

3067 

3068 @classmethod 

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

3070 _NR_mprotect = 10 

3071 insns = [ 

3072 "pushfq", 

3073 "push rax", 

3074 "push rdi", 

3075 "push rsi", 

3076 "push rdx", 

3077 "push rcx", 

3078 "push r11", 

3079 f"mov rax, {_NR_mprotect:d}", 

3080 f"mov rdi, {addr:d}", 

3081 f"mov rsi, {size:d}", 

3082 f"mov rdx, {perm.value:d}", 

3083 "syscall", 

3084 "pop r11", 

3085 "pop rcx", 

3086 "pop rdx", 

3087 "pop rsi", 

3088 "pop rdi", 

3089 "pop rax", 

3090 "popfq", 

3091 ] 

3092 return "; ".join(insns) 

3093 

3094 def canary_address(self) -> int: 

3095 return self.register("fs_base") + 0x28 

3096 

3097class PowerPC(Architecture): 

3098 aliases = ("PowerPC", Elf.Abi.POWERPC, "PPC") 

3099 arch = "PPC" 

3100 mode = "PPC32" 

3101 

3102 all_registers = ( 

3103 "$r0", "$r1", "$r2", "$r3", "$r4", "$r5", "$r6", "$r7", 

3104 "$r8", "$r9", "$r10", "$r11", "$r12", "$r13", "$r14", "$r15", 

3105 "$r16", "$r17", "$r18", "$r19", "$r20", "$r21", "$r22", "$r23", 

3106 "$r24", "$r25", "$r26", "$r27", "$r28", "$r29", "$r30", "$r31", 

3107 "$pc", "$msr", "$cr", "$lr", "$ctr", "$xer", "$trap",) 

3108 instruction_length = 4 

3109 nop_insn = b"\x60\x00\x00\x00" # https://developer.ibm.com/articles/l-ppc/ 

3110 return_register = "$r0" 

3111 flag_register: str = "$cr" 

3112 flags_table = { 

3113 3: "negative[0]", 

3114 2: "positive[0]", 

3115 1: "equal[0]", 

3116 0: "overflow[0]", 

3117 # cr7 

3118 31: "less[7]", 

3119 30: "greater[7]", 

3120 29: "equal[7]", 

3121 28: "overflow[7]", 

3122 } 

3123 function_parameters = ("$i0", "$i1", "$i2", "$i3", "$i4", "$i5") 

3124 syscall_register = "$r0" 

3125 syscall_instructions = ("sc",) 

3126 _ptrsize = 4 

3127 

3128 

3129 def flag_register_to_human(self, val: int | None = None) -> str: 

3130 # https://www.cebix.net/downloads/bebox/pem32b.pdf (% 2.1.3) 

3131 if val is None: 

3132 reg = self.flag_register 

3133 val = gef.arch.register(reg) 

3134 return flags_to_human(val, self.flags_table) 

3135 

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

3137 return False 

3138 

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

3140 return insn.mnemonic == "blr" 

3141 

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

3143 mnemo = insn.mnemonic 

3144 branch_mnemos = {"beq", "bne", "ble", "blt", "bgt", "bge"} 

3145 return mnemo in branch_mnemos 

3146 

3147 def is_branch_taken(self, insn: Instruction) -> tuple[bool, str]: 

3148 mnemo = insn.mnemonic 

3149 flags = dict((self.flags_table[k], k) for k in self.flags_table) 

3150 val = gef.arch.register(self.flag_register) 

3151 taken, reason = False, "" 

3152 if mnemo == "beq": taken, reason = bool(val&(1<<flags["equal[7]"])), "E" 

3153 elif mnemo == "bne": taken, reason = val&(1<<flags["equal[7]"]) == 0, "!E" 

3154 elif mnemo == "ble": taken, reason = bool(val&(1<<flags["equal[7]"])) or bool(val&(1<<flags["less[7]"])), "E || L" 

3155 elif mnemo == "blt": taken, reason = bool(val&(1<<flags["less[7]"])), "L" 

3156 elif mnemo == "bge": taken, reason = bool(val&(1<<flags["equal[7]"])) or bool(val&(1<<flags["greater[7]"])), "E || G" 

3157 elif mnemo == "bgt": taken, reason = bool(val&(1<<flags["greater[7]"])), "G" 

3158 return taken, reason 

3159 

3160 def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> int | None: 

3161 ra = None 

3162 if self.is_ret(insn): 

3163 ra = gef.arch.register("$lr") 

3164 else: 

3165 older = frame.older() 

3166 if older: 

3167 ra = to_unsigned_long(older.pc()) 

3168 return ra 

3169 

3170 @classmethod 

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

3172 # Ref: https://developer.ibm.com/articles/l-ppc/ 

3173 _NR_mprotect = 125 

3174 insns = [ 

3175 "addi 1, 1, -16", # 1 = r1 = sp 

3176 "stw 0, 0(1)", 

3177 "stw 3, 4(1)", # r0 = syscall_code | r3, r4, r5 = args 

3178 "stw 4, 8(1)", 

3179 "stw 5, 12(1)", 

3180 f"li 0, {_NR_mprotect:d}", 

3181 f"lis 3, {addr:#x}@h", 

3182 f"ori 3, 3, {addr:#x}@l", 

3183 f"lis 4, {size:#x}@h", 

3184 f"ori 4, 4, {size:#x}@l", 

3185 f"li 5, {perm.value:d}", 

3186 "sc", 

3187 "lwz 0, 0(1)", 

3188 "lwz 3, 4(1)", 

3189 "lwz 4, 8(1)", 

3190 "lwz 5, 12(1)", 

3191 "addi 1, 1, 16", 

3192 ] 

3193 return ";".join(insns) 

3194 

3195 

3196class PowerPC64(PowerPC): 

3197 aliases = ("PowerPC64", Elf.Abi.POWERPC64, "PPC64") 

3198 arch = "PPC" 

3199 mode = "PPC64" 

3200 _ptrsize = 8 

3201 

3202 

3203class SPARC(Architecture): 

3204 """ Refs: 

3205 - https://www.cse.scu.edu/~atkinson/teaching/sp05/259/sparc.pdf 

3206 """ 

3207 aliases = ("SPARC", Elf.Abi.SPARC) 

3208 arch = "SPARC" 

3209 mode = "" 

3210 

3211 all_registers = ( 

3212 "$g0", "$g1", "$g2", "$g3", "$g4", "$g5", "$g6", "$g7", 

3213 "$o0", "$o1", "$o2", "$o3", "$o4", "$o5", "$o7", 

3214 "$l0", "$l1", "$l2", "$l3", "$l4", "$l5", "$l6", "$l7", 

3215 "$i0", "$i1", "$i2", "$i3", "$i4", "$i5", "$i7", 

3216 "$pc", "$npc", "$sp ", "$fp ", "$psr",) 

3217 instruction_length = 4 

3218 nop_insn = b"\x00\x00\x00\x00" # sethi 0, %g0 

3219 return_register = "$i0" 

3220 flag_register: str = "$psr" 

3221 flags_table = { 

3222 23: "negative", 

3223 22: "zero", 

3224 21: "overflow", 

3225 20: "carry", 

3226 7: "supervisor", 

3227 5: "trap", 

3228 } 

3229 function_parameters = ("$o0 ", "$o1 ", "$o2 ", "$o3 ", "$o4 ", "$o5 ", "$o7 ",) 

3230 syscall_register = "%g1" 

3231 syscall_instructions = ("t 0x10",) 

3232 

3233 def flag_register_to_human(self, val: int | None = None) -> str: 

3234 # https://www.gaisler.com/doc/sparcv8.pdf 

3235 reg = self.flag_register 

3236 if val is None: 

3237 val = gef.arch.register(reg) 

3238 return flags_to_human(val, self.flags_table) 

3239 

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

3241 return False 

3242 

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

3244 return insn.mnemonic == "ret" 

3245 

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

3247 mnemo = insn.mnemonic 

3248 # http://moss.csc.ncsu.edu/~mueller/codeopt/codeopt00/notes/condbranch.html 

3249 branch_mnemos = { 

3250 "be", "bne", "bg", "bge", "bgeu", "bgu", "bl", "ble", "blu", "bleu", 

3251 "bneg", "bpos", "bvs", "bvc", "bcs", "bcc" 

3252 } 

3253 return mnemo in branch_mnemos 

3254 

3255 def is_branch_taken(self, insn: Instruction) -> tuple[bool, str]: 

3256 mnemo = insn.mnemonic 

3257 flags = dict((self.flags_table[k], k) for k in self.flags_table) 

3258 val = gef.arch.register(self.flag_register) 

3259 taken, reason = False, "" 

3260 

3261 if mnemo == "be": taken, reason = bool(val&(1<<flags["zero"])), "Z" 

3262 elif mnemo == "bne": taken, reason = bool(val&(1<<flags["zero"])) == 0, "!Z" 

3263 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)" 

3264 elif mnemo == "bge": taken, reason = val&(1<<flags["negative"]) == 0 or val&(1<<flags["overflow"]) == 0, "!N || !O" 

3265 elif mnemo == "bgu": taken, reason = val&(1<<flags["carry"]) == 0 and val&(1<<flags["zero"]) == 0, "!C && !Z" 

3266 elif mnemo == "bgeu": taken, reason = val&(1<<flags["carry"]) == 0, "!C" 

3267 elif mnemo == "bl": taken, reason = bool(val&(1<<flags["negative"])) and bool(val&(1<<flags["overflow"])), "N && O" 

3268 elif mnemo == "blu": taken, reason = bool(val&(1<<flags["carry"])), "C" 

3269 elif mnemo == "ble": taken, reason = bool(val&(1<<flags["zero"])) or bool(val&(1<<flags["negative"]) or val&(1<<flags["overflow"])), "Z || (N || O)" 

3270 elif mnemo == "bleu": taken, reason = bool(val&(1<<flags["carry"])) or bool(val&(1<<flags["zero"])), "C || Z" 

3271 elif mnemo == "bneg": taken, reason = bool(val&(1<<flags["negative"])), "N" 

3272 elif mnemo == "bpos": taken, reason = val&(1<<flags["negative"]) == 0, "!N" 

3273 elif mnemo == "bvs": taken, reason = bool(val&(1<<flags["overflow"])), "O" 

3274 elif mnemo == "bvc": taken, reason = val&(1<<flags["overflow"]) == 0, "!O" 

3275 elif mnemo == "bcs": taken, reason = bool(val&(1<<flags["carry"])), "C" 

3276 elif mnemo == "bcc": taken, reason = val&(1<<flags["carry"]) == 0, "!C" 

3277 return taken, reason 

3278 

3279 def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> int | None: 

3280 ra = None 

3281 if self.is_ret(insn): 

3282 ra = gef.arch.register("$o7") 

3283 else: 

3284 older = frame.older() 

3285 if older: 

3286 ra = to_unsigned_long(older.pc()) 

3287 return ra 

3288 

3289 @classmethod 

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

3291 hi = (addr & 0xffff0000) >> 16 

3292 lo = (addr & 0x0000ffff) 

3293 _NR_mprotect = 125 

3294 insns = ["add %sp, -16, %sp", 

3295 "st %g1, [ %sp ]", "st %o0, [ %sp + 4 ]", 

3296 "st %o1, [ %sp + 8 ]", "st %o2, [ %sp + 12 ]", 

3297 f"sethi %hi({hi}), %o0", 

3298 f"or %o0, {lo}, %o0", 

3299 "clr %o1", 

3300 "clr %o2", 

3301 f"mov {_NR_mprotect}, %g1", 

3302 "t 0x10", 

3303 "ld [ %sp ], %g1", "ld [ %sp + 4 ], %o0", 

3304 "ld [ %sp + 8 ], %o1", "ld [ %sp + 12 ], %o2", 

3305 "add %sp, 16, %sp",] 

3306 return "; ".join(insns) 

3307 

3308 

3309class SPARC64(SPARC): 

3310 """Refs: 

3311 - http://math-atlas.sourceforge.net/devel/assembly/abi_sysV_sparc.pdf 

3312 - https://cr.yp.to/2005-590/sparcv9.pdf 

3313 """ 

3314 aliases = ("SPARC64", Elf.Abi.SPARC64) 

3315 arch = "SPARC" 

3316 mode = "V9" 

3317 

3318 all_registers = [ 

3319 "$g0", "$g1", "$g2", "$g3", "$g4", "$g5", "$g6", "$g7", 

3320 "$o0", "$o1", "$o2", "$o3", "$o4", "$o5", "$o7", 

3321 "$l0", "$l1", "$l2", "$l3", "$l4", "$l5", "$l6", "$l7", 

3322 "$i0", "$i1", "$i2", "$i3", "$i4", "$i5", "$i7", 

3323 "$pc", "$npc", "$sp", "$fp", "$state", ] 

3324 

3325 flag_register = "$state" # sparcv9.pdf, 5.1.5.1 (ccr) 

3326 flags_table = { 

3327 35: "negative", 

3328 34: "zero", 

3329 33: "overflow", 

3330 32: "carry", 

3331 } 

3332 

3333 syscall_instructions = ["t 0x6d"] 

3334 

3335 @classmethod 

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

3337 hi = (addr & 0xffff0000) >> 16 

3338 lo = (addr & 0x0000ffff) 

3339 _NR_mprotect = 125 

3340 insns = ["add %sp, -16, %sp", 

3341 "st %g1, [ %sp ]", "st %o0, [ %sp + 4 ]", 

3342 "st %o1, [ %sp + 8 ]", "st %o2, [ %sp + 12 ]", 

3343 f"sethi %hi({hi}), %o0", 

3344 f"or %o0, {lo}, %o0", 

3345 "clr %o1", 

3346 "clr %o2", 

3347 f"mov {_NR_mprotect}, %g1", 

3348 "t 0x6d", 

3349 "ld [ %sp ], %g1", "ld [ %sp + 4 ], %o0", 

3350 "ld [ %sp + 8 ], %o1", "ld [ %sp + 12 ], %o2", 

3351 "add %sp, 16, %sp",] 

3352 return "; ".join(insns) 

3353 

3354 

3355class MIPS(Architecture): 

3356 aliases = ("MIPS", Elf.Abi.MIPS) 

3357 arch = "MIPS" 

3358 mode = "MIPS32" 

3359 

3360 # https://vhouten.home.xs4all.nl/mipsel/r3000-isa.html 

3361 all_registers = ( 

3362 "$zero", "$at", "$v0", "$v1", "$a0", "$a1", "$a2", "$a3", 

3363 "$t0", "$t1", "$t2", "$t3", "$t4", "$t5", "$t6", "$t7", 

3364 "$s0", "$s1", "$s2", "$s3", "$s4", "$s5", "$s6", "$s7", 

3365 "$t8", "$t9", "$k0", "$k1", "$s8", "$pc", "$sp", "$hi", 

3366 "$lo", "$fir", "$ra", "$gp", ) 

3367 instruction_length = 4 

3368 _ptrsize = 4 

3369 nop_insn = b"\x00\x00\x00\x00" # sll $0,$0,0 

3370 return_register = "$v0" 

3371 flag_register = "$fcsr" 

3372 flags_table = {} 

3373 function_parameters = ("$a0", "$a1", "$a2", "$a3") 

3374 syscall_register = "$v0" 

3375 syscall_instructions = ("syscall",) 

3376 

3377 def flag_register_to_human(self, val: int | None = None) -> str: 

3378 return Color.colorify("No flag register", "yellow underline") 

3379 

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

3381 return False 

3382 

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

3384 return insn.mnemonic == "jr" and insn.operands[0] == "ra" 

3385 

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

3387 mnemo = insn.mnemonic 

3388 branch_mnemos = {"beq", "bne", "beqz", "bnez", "bgtz", "bgez", "bltz", "blez"} 

3389 return mnemo in branch_mnemos 

3390 

3391 def is_branch_taken(self, insn: Instruction) -> tuple[bool, str]: 

3392 mnemo, ops = insn.mnemonic, insn.operands 

3393 taken, reason = False, "" 

3394 

3395 if mnemo == "beq": 

3396 taken, reason = gef.arch.register(ops[0]) == gef.arch.register(ops[1]), f"{ops[0]} == {ops[1]}" 

3397 elif mnemo == "bne": 

3398 taken, reason = gef.arch.register(ops[0]) != gef.arch.register(ops[1]), f"{ops[0]} != {ops[1]}" 

3399 elif mnemo == "beqz": 

3400 taken, reason = gef.arch.register(ops[0]) == 0, f"{ops[0]} == 0" 

3401 elif mnemo == "bnez": 

3402 taken, reason = gef.arch.register(ops[0]) != 0, f"{ops[0]} != 0" 

3403 elif mnemo == "bgtz": 

3404 taken, reason = gef.arch.register(ops[0]) > 0, f"{ops[0]} > 0" 

3405 elif mnemo == "bgez": 

3406 taken, reason = gef.arch.register(ops[0]) >= 0, f"{ops[0]} >= 0" 

3407 elif mnemo == "bltz": 

3408 taken, reason = gef.arch.register(ops[0]) < 0, f"{ops[0]} < 0" 

3409 elif mnemo == "blez": 

3410 taken, reason = gef.arch.register(ops[0]) <= 0, f"{ops[0]} <= 0" 

3411 return taken, reason 

3412 

3413 def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> int | None: 

3414 ra = None 

3415 if self.is_ret(insn): 

3416 ra = gef.arch.register("$ra") 

3417 else: 

3418 older = frame.older() 

3419 if older: 

3420 ra = to_unsigned_long(older.pc()) 

3421 return ra 

3422 

3423 @classmethod 

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

3425 _NR_mprotect = 4125 

3426 insns = ["addi $sp, $sp, -16", 

3427 "sw $v0, 0($sp)", "sw $a0, 4($sp)", 

3428 "sw $a3, 8($sp)", "sw $a3, 12($sp)", 

3429 f"li $v0, {_NR_mprotect:d}", 

3430 f"li $a0, {addr:d}", 

3431 f"li $a1, {size:d}", 

3432 f"li $a2, {perm.value:d}", 

3433 "syscall", 

3434 "lw $v0, 0($sp)", "lw $a1, 4($sp)", 

3435 "lw $a3, 8($sp)", "lw $a3, 12($sp)", 

3436 "addi $sp, $sp, 16",] 

3437 return "; ".join(insns) 

3438 

3439 

3440class MIPS64(MIPS): 

3441 aliases = ("MIPS64",) 

3442 arch = "MIPS" 

3443 mode = "MIPS64" 

3444 _ptrsize = 8 

3445 

3446 @staticmethod 

3447 def supports_gdb_arch(gdb_arch: str) -> bool | None: 

3448 if not gef.binary or not isinstance(gef.binary, Elf): 3448 ↛ 3449line 3448 didn't jump to line 3449 because the condition on line 3448 was never true

3449 return False 

3450 return gdb_arch.startswith("mips") and gef.binary.e_class == Elf.Class.ELF_64_BITS 

3451 

3452 

3453def copy_to_clipboard(data: bytes) -> None: 

3454 """Helper function to submit data to the clipboard""" 

3455 if sys.platform == "linux": 

3456 xclip = which("xclip") 

3457 prog = [xclip, "-selection", "clipboard", "-i"] 

3458 elif sys.platform == "darwin": 

3459 pbcopy = which("pbcopy") 

3460 prog = [pbcopy] 

3461 else: 

3462 raise NotImplementedError("copy: Unsupported OS") 

3463 

3464 with subprocess.Popen(prog, stdin=subprocess.PIPE) as p: 

3465 assert p.stdin 

3466 p.stdin.write(data) 

3467 p.stdin.close() 

3468 p.wait() 

3469 return 

3470 

3471 

3472def use_stdtype() -> str: 

3473 if is_32bit(): return "uint32_t" 3473 ↛ exitline 3473 didn't return from function 'use_stdtype' because the return on line 3473 wasn't executed

3474 elif is_64bit(): return "uint64_t" 3474 ↛ 3475line 3474 didn't jump to line 3475 because the condition on line 3474 was always true

3475 return "uint16_t" 

3476 

3477 

3478def use_default_type() -> str: 

3479 if is_32bit(): return "unsigned int" 3479 ↛ exitline 3479 didn't return from function 'use_default_type' because the return on line 3479 wasn't executed

3480 elif is_64bit(): return "unsigned long" 3480 ↛ 3481line 3480 didn't jump to line 3481 because the condition on line 3480 was always true

3481 return "unsigned short" 

3482 

3483 

3484def use_golang_type() -> str: 

3485 if is_32bit(): return "uint32" 

3486 elif is_64bit(): return "uint64" 

3487 return "uint16" 

3488 

3489 

3490def use_rust_type() -> str: 

3491 if is_32bit(): return "u32" 

3492 elif is_64bit(): return "u64" 

3493 return "u16" 

3494 

3495 

3496def to_unsigned_long(v: gdb.Value) -> int: 

3497 """Cast a gdb.Value to unsigned long.""" 

3498 mask = (1 << (gef.arch.ptrsize*8)) - 1 

3499 return int(v.cast(gdb.Value(mask).type)) & mask 

3500 

3501 

3502def get_path_from_info_proc() -> str | None: 

3503 for x in (gdb.execute("info proc", to_string=True) or "").splitlines(): 

3504 if x.startswith("exe = "): 

3505 return x.split(" = ")[1].replace("'", "") 

3506 return None 

3507 

3508 

3509@deprecated("Use `gef.session.os`") 

3510def get_os() -> str: 

3511 return gef.session.os 

3512 

3513 

3514@lru_cache() 

3515def is_qemu() -> bool: 

3516 if not is_remote_debug(): 

3517 return False 

3518 response = gdb.execute("maintenance packet Qqemu.sstepbits", to_string=True, from_tty=False) or "" 

3519 return "ENABLE=" in response 

3520 

3521 

3522@lru_cache() 

3523def is_qemu_usermode() -> bool: 

3524 if not is_qemu(): 

3525 return False 

3526 response = gdb.execute("maintenance packet qOffsets", to_string=True, from_tty=False) or "" 

3527 return "Text=" in response 

3528 

3529 

3530@lru_cache() 

3531def is_qemu_system() -> bool: 

3532 if not is_qemu(): 

3533 return False 

3534 response = gdb.execute("maintenance packet qOffsets", to_string=True, from_tty=False) or "" 

3535 return "received: \"\"" in response 

3536 

3537 

3538def is_target_coredump() -> bool: 

3539 global gef 

3540 if gef.session.coredump_mode is not None: 

3541 return gef.session.coredump_mode 

3542 lines = (gdb.execute("maintenance info section", to_string=True) or "").splitlines() 

3543 is_coredump_mode = any(map(lambda line: line.startswith("Core file: "), lines)) 

3544 gef.session.coredump_mode = is_coredump_mode 

3545 return is_coredump_mode 

3546 

3547 

3548def get_filepath() -> str | None: 

3549 """Return the local absolute path of the file currently debugged.""" 

3550 if gef.session.remote: 

3551 return str(gef.session.remote.lfile.absolute()) 

3552 if gef.session.file: 3552 ↛ 3554line 3552 didn't jump to line 3554 because the condition on line 3552 was always true

3553 return str(gef.session.file.absolute()) 

3554 return None 

3555 

3556 

3557def get_function_length(sym: str) -> int: 

3558 """Attempt to get the length of the raw bytes of a function.""" 

3559 dis = (gdb.execute(f"disassemble '{sym}'", to_string=True) or "").splitlines() 

3560 start_addr = int(dis[1].split()[0], 16) 

3561 end_addr = int(dis[-2].split()[0], 16) 

3562 return end_addr - start_addr 

3563 

3564 

3565@lru_cache() 

3566def get_info_files() -> list[Zone]: 

3567 """Retrieve all the files loaded by debuggee.""" 

3568 lines = (gdb.execute("info files", to_string=True) or "").splitlines() 

3569 infos = [] 

3570 for line in lines: 

3571 line = line.strip() 

3572 if not line: 3572 ↛ 3573line 3572 didn't jump to line 3573 because the condition on line 3572 was never true

3573 break 

3574 

3575 if not line.startswith("0x"): 

3576 continue 

3577 

3578 blobs = [x.strip() for x in line.split(" ")] 

3579 addr_start = int(blobs[0], 16) 

3580 addr_end = int(blobs[2], 16) 

3581 section_name = blobs[4] 

3582 

3583 if len(blobs) == 7: 

3584 filename = blobs[6] 

3585 else: 

3586 filename = get_filepath() 

3587 

3588 infos.append(Zone(section_name, addr_start, addr_end, filename)) 

3589 return infos 

3590 

3591 

3592def process_lookup_address(address: int) -> Section | None: 

3593 """Look up for an address in memory. 

3594 Return an Address object if found, None otherwise.""" 

3595 if not is_alive(): 3595 ↛ 3596line 3595 didn't jump to line 3596 because the condition on line 3595 was never true

3596 err("Process is not running") 

3597 return None 

3598 

3599 if is_x86(): 3599 ↛ 3603line 3599 didn't jump to line 3603 because the condition on line 3599 was always true

3600 if is_in_x86_kernel(address): 3600 ↛ 3601line 3600 didn't jump to line 3601 because the condition on line 3600 was never true

3601 return None 

3602 

3603 for sect in gef.memory.maps: 

3604 if sect.page_start <= address < sect.page_end: 

3605 return sect 

3606 

3607 return None 

3608 

3609 

3610@lru_cache() 

3611def process_lookup_path(name: str, perm: Permission = Permission.ALL) -> Section | None: 

3612 """Look up for a path in the process memory mapping. 

3613 Return a Section object if found, None otherwise.""" 

3614 if not is_alive(): 3614 ↛ 3615line 3614 didn't jump to line 3615 because the condition on line 3614 was never true

3615 err("Process is not running") 

3616 return None 

3617 

3618 matches: dict[str, Section] = dict() 

3619 for sect in gef.memory.maps: 

3620 filename = pathlib.Path(sect.path).name 

3621 

3622 if name in filename and sect.permission & perm: 

3623 if sect.path not in matches.keys(): 

3624 matches[sect.path] = sect 

3625 

3626 matches_count = len(matches) 

3627 

3628 if matches_count == 0: 

3629 return None 

3630 

3631 if matches_count > 1: 3631 ↛ 3632line 3631 didn't jump to line 3632 because the condition on line 3631 was never true

3632 warn(f"{matches_count} matches! You should probably refine your search!") 

3633 

3634 for x in matches.keys(): 

3635 warn(f"- '{x}'") 

3636 

3637 warn("Returning the first match") 

3638 

3639 return list(matches.values())[0] 

3640 

3641 

3642@lru_cache() 

3643def file_lookup_name_path(name: str, path: str) -> Zone | None: 

3644 """Look up a file by name and path. 

3645 Return a Zone object if found, None otherwise.""" 

3646 for xfile in get_info_files(): 3646 ↛ 3649line 3646 didn't jump to line 3649 because the loop on line 3646 didn't complete

3647 if path == xfile.filename and name == xfile.name: 

3648 return xfile 

3649 return None 

3650 

3651 

3652@lru_cache() 

3653def file_lookup_address(address: int) -> Zone | None: 

3654 """Look up for a file by its address. 

3655 Return a Zone object if found, None otherwise.""" 

3656 for info in get_info_files(): 

3657 if info.zone_start <= address < info.zone_end: 

3658 return info 

3659 return None 

3660 

3661 

3662@lru_cache() 

3663def lookup_address(address: int) -> Address: 

3664 """Try to find the address in the process address space. 

3665 Return an Address object, with validity flag set based on success.""" 

3666 sect = process_lookup_address(address) 

3667 info = file_lookup_address(address) 

3668 if sect is None and info is None: 

3669 # i.e. there is no info on this address 

3670 return Address(value=address, valid=False) 

3671 return Address(value=address, section=sect, info=info) 

3672 

3673 

3674def xor(data: ByteString, key: str) -> bytearray: 

3675 """Return `data` xor-ed with `key`.""" 

3676 key_raw = binascii.unhexlify(key.lstrip("0x")) 

3677 return bytearray(x ^ y for x, y in zip(data, itertools.cycle(key_raw))) 

3678 

3679 

3680def is_hex(pattern: str) -> bool: 

3681 """Return whether provided string is a hexadecimal value.""" 

3682 if not pattern.lower().startswith("0x"): 

3683 return False 

3684 return len(pattern) % 2 == 0 and all(c in string.hexdigits for c in pattern[2:]) 

3685 

3686 

3687def continue_handler(_: "gdb.ContinueEvent") -> None: 

3688 """GDB event handler for new object continue cases.""" 

3689 return 

3690 

3691 

3692def hook_stop_handler(_: "gdb.StopEvent") -> None: 

3693 """GDB event handler for stop cases.""" 

3694 reset_all_caches() 

3695 gdb.execute("context") 

3696 return 

3697 

3698 

3699def new_objfile_handler(evt: "gdb.NewObjFileEvent | None") -> None: 

3700 """GDB event handler for new object file cases.""" 

3701 reset_all_caches() 

3702 progspace = gdb.current_progspace() 

3703 if evt: 

3704 path = evt.new_objfile.filename or "" 

3705 elif progspace: 3705 ↛ 3708line 3705 didn't jump to line 3708 because the condition on line 3705 was always true

3706 path = progspace.filename or "" 

3707 else: 

3708 raise RuntimeError("Cannot determine file path") 

3709 try: 

3710 if gef.session.root and path.startswith("target:"): 

3711 # If the process is in a container, replace the "target:" prefix 

3712 # with the actual root directory of the process. 

3713 path = path.replace("target:", str(gef.session.root), 1) 

3714 target = pathlib.Path(path) 

3715 FileFormatClasses = list(filter(lambda fmtcls: fmtcls.is_valid(target), __registered_file_formats__)) 

3716 GuessedFileFormatClass : Type[FileFormat] = FileFormatClasses.pop() if len(FileFormatClasses) else Elf 

3717 binary = GuessedFileFormatClass(target) 

3718 if not gef.binary: 

3719 gef.binary = binary 

3720 reset_architecture() 

3721 else: 

3722 gef.session.modules.append(binary) 

3723 except FileNotFoundError as fne: 

3724 # Linux automatically maps the vDSO into our process, and GDB 

3725 # will give us the string 'system-supplied DSO' as a path. 

3726 # This is normal, so we shouldn't warn the user about it 

3727 if "system-supplied DSO" not in path: 3727 ↛ 3728line 3727 didn't jump to line 3728 because the condition on line 3727 was never true

3728 warn(f"Failed to find objfile or not a valid file format: {str(fne)}") 

3729 except RuntimeError as re: 

3730 warn(f"Not a valid file format: {str(re)}") 

3731 return 

3732 

3733 

3734def exit_handler(_: "gdb.ExitedEvent") -> None: 

3735 """GDB event handler for exit cases.""" 

3736 global gef 

3737 # flush the caches 

3738 reset_all_caches() 

3739 

3740 # disconnect properly the remote session 

3741 gef.session.qemu_mode = False 

3742 if gef.session.remote: 

3743 gef.session.remote.close() 

3744 del gef.session.remote 

3745 gef.session.remote = None 

3746 gef.session.remote_initializing = False 

3747 

3748 # if `autosave_breakpoints_file` setting is configured, save the breakpoints to disk 

3749 setting = (gef.config["gef.autosave_breakpoints_file"] or "").strip() 

3750 if not setting: 3750 ↛ 3753line 3750 didn't jump to line 3753 because the condition on line 3750 was always true

3751 return 

3752 

3753 bkp_fpath = pathlib.Path(setting).expanduser().absolute() 

3754 if bkp_fpath.exists(): 

3755 warn(f"{bkp_fpath} exists, content will be overwritten") 

3756 

3757 with bkp_fpath.open("w") as fd: 

3758 for bp in list(gdb.breakpoints()): 

3759 if not bp.enabled or not bp.is_valid: 

3760 continue 

3761 fd.write(f"{'t' if bp.temporary else ''}break {bp.location}\n") 

3762 return 

3763 

3764 

3765def memchanged_handler(_: "gdb.MemoryChangedEvent") -> None: 

3766 """GDB event handler for mem changes cases.""" 

3767 reset_all_caches() 

3768 return 

3769 

3770 

3771def regchanged_handler(_: "gdb.RegisterChangedEvent") -> None: 

3772 """GDB event handler for reg changes cases.""" 

3773 reset_all_caches() 

3774 return 

3775 

3776 

3777def get_terminal_size() -> tuple[int, int]: 

3778 """Return the current terminal size.""" 

3779 if is_debug(): 3779 ↛ 3782line 3779 didn't jump to line 3782 because the condition on line 3779 was always true

3780 return 600, 100 

3781 

3782 if platform.system() == "Windows": 

3783 from ctypes import create_string_buffer, windll # type: ignore 

3784 hStdErr = -12 

3785 herr = windll.kernel32.GetStdHandle(hStdErr) 

3786 csbi = create_string_buffer(22) 

3787 res = windll.kernel32.GetConsoleScreenBufferInfo(herr, csbi) 

3788 if res: 

3789 _, _, _, _, _, left, top, right, bottom, _, _ = struct.unpack("hhhhHhhhhhh", csbi.raw) 

3790 tty_columns = right - left + 1 

3791 tty_rows = bottom - top + 1 

3792 return tty_rows, tty_columns 

3793 else: 

3794 return 600, 100 

3795 else: 

3796 import fcntl 

3797 import termios 

3798 try: 

3799 tty_rows, tty_columns = struct.unpack("hh", fcntl.ioctl(1, termios.TIOCGWINSZ, "1234")) # type: ignore 

3800 return tty_rows, tty_columns 

3801 except OSError: 

3802 return 600, 100 

3803 

3804 

3805@lru_cache() 

3806def is_64bit() -> bool: 

3807 """Checks if current target is 64bit.""" 

3808 return gef.arch.ptrsize == 8 

3809 

3810 

3811@lru_cache() 

3812def is_32bit() -> bool: 

3813 """Checks if current target is 32bit.""" 

3814 return gef.arch.ptrsize == 4 

3815 

3816 

3817@lru_cache() 

3818def is_x86_64() -> bool: 

3819 """Checks if current target is x86-64""" 

3820 return Elf.Abi.X86_64 in gef.arch.aliases 

3821 

3822 

3823@lru_cache() 

3824def is_x86_32(): 

3825 """Checks if current target is an x86-32""" 

3826 return Elf.Abi.X86_32 in gef.arch.aliases 

3827 

3828 

3829@lru_cache() 

3830def is_x86() -> bool: 

3831 return is_x86_32() or is_x86_64() 

3832 

3833 

3834@lru_cache() 

3835def is_arch(arch: Elf.Abi) -> bool: 

3836 return arch in gef.arch.aliases 

3837 

3838 

3839def reset_architecture(arch: str | None = None) -> None: 

3840 """Sets the current architecture. 

3841 If an architecture is explicitly specified by parameter, try to use that one. If this fails, an `OSError` 

3842 exception will occur. 

3843 If no architecture is specified, then GEF will attempt to determine automatically based on the current 

3844 ELF target. If this fails, an `OSError` exception will occur. 

3845 """ 

3846 global gef 

3847 arches = __registered_architectures__ 

3848 

3849 # check if the architecture is forced by parameter 

3850 if arch: 

3851 try: 

3852 gef.arch = arches[arch.lower()]() 

3853 gef.arch_reason = "The architecture has been set manually" 

3854 except KeyError: 

3855 raise OSError(f"Specified arch {arch.upper()} is not supported") 

3856 return 

3857 

3858 # check for bin running 

3859 if is_alive(): 

3860 gdb_arch = gdb.selected_frame().architecture().name() 

3861 preciser_arch = next((a for a in arches.values() if a.supports_gdb_arch(gdb_arch)), None) 

3862 if preciser_arch: 3862 ↛ 3863line 3862 didn't jump to line 3863 because the condition on line 3862 was never true

3863 gef.arch = preciser_arch() 

3864 gef.arch_reason = "The architecture has been detected by GDB" 

3865 return 

3866 

3867 # last resort, use the info from elf header to find it from the known architectures 

3868 if gef.binary and isinstance(gef.binary, Elf): 3868 ↛ 3876line 3868 didn't jump to line 3876 because the condition on line 3868 was always true

3869 try: 

3870 gef.arch = arches[gef.binary.e_machine]() 

3871 gef.arch_reason = "The architecture has been detected via the ELF headers" 

3872 except KeyError: 

3873 raise OSError(f"CPU type is currently not supported: {gef.binary.e_machine}") 

3874 return 

3875 

3876 warn("Did not find any way to guess the correct architecture :(") 

3877 

3878 

3879@lru_cache() 

3880def cached_lookup_type(_type: str) -> gdb.Type | None: 

3881 try: 

3882 return gdb.lookup_type(_type).strip_typedefs() 

3883 except RuntimeError: 

3884 return None 

3885 

3886 

3887@deprecated("Use `gef.arch.ptrsize` instead") 

3888def get_memory_alignment(in_bits: bool = False) -> int: 

3889 """Try to determine the size of a pointer on this system. 

3890 First, try to parse it out of the ELF header. 

3891 Next, use the size of `size_t`. 

3892 Finally, try the size of $pc. 

3893 If `in_bits` is set to True, the result is returned in bits, otherwise in 

3894 bytes.""" 

3895 res = cached_lookup_type("size_t") 

3896 if res is not None: 

3897 return res.sizeof if not in_bits else res.sizeof * 8 

3898 

3899 try: 

3900 return gdb.parse_and_eval("$pc").type.sizeof 

3901 except Exception: 

3902 pass 

3903 

3904 raise OSError("GEF is running under an unsupported mode") 

3905 

3906 

3907def clear_screen(tty: str = "") -> None: 

3908 """Clear the screen.""" 

3909 clean_sequence = "\x1b[H\x1b[J" 

3910 if tty: 3910 ↛ 3911line 3910 didn't jump to line 3911 because the condition on line 3910 was never true

3911 pathlib.Path(tty).write_text(clean_sequence) 

3912 else: 

3913 sys.stdout.write(clean_sequence) 

3914 

3915 return 

3916 

3917 

3918def format_address(addr: int) -> str: 

3919 """Format the address according to its size.""" 

3920 memalign_size = gef.arch.ptrsize 

3921 addr = align_address(addr) 

3922 return f"0x{addr:016x}" if memalign_size == 8 else f"0x{addr:08x}" 

3923 

3924 

3925def format_address_spaces(addr: int, left: bool = True) -> str: 

3926 """Format the address according to its size, but with spaces instead of zeroes.""" 

3927 width = gef.arch.ptrsize * 2 + 2 

3928 addr = align_address(addr) 

3929 

3930 if not left: 3930 ↛ 3931line 3930 didn't jump to line 3931 because the condition on line 3930 was never true

3931 return f"{addr:#x}".rjust(width) 

3932 

3933 return f"{addr:#x}".ljust(width) 

3934 

3935 

3936def align_address(address: int) -> int: 

3937 """Align the provided address to the process's native length.""" 

3938 return address & 0xFFFFFFFFFFFFFFFF if gef.arch.ptrsize == 8 else address & 0xFFFFFFFF 

3939 

3940 

3941def align_address_to_size(address: int, align: int) -> int: 

3942 """Align the address to the given size.""" 

3943 return address + ((align - (address % align)) % align) 

3944 

3945 

3946def align_address_to_page(address: int) -> int: 

3947 """Align the address to a page.""" 

3948 a = align_address(address) >> DEFAULT_PAGE_ALIGN_SHIFT 

3949 return a << DEFAULT_PAGE_ALIGN_SHIFT 

3950 

3951 

3952def parse_address(address: str) -> int: 

3953 """Parse an address and return it as an Integer.""" 

3954 if is_hex(address): 

3955 return int(address, 16) 

3956 return int(gdb.parse_and_eval(address)) 

3957 

3958 

3959def is_in_x86_kernel(address: int) -> bool: 

3960 address = align_address(address) 

3961 memalign = gef.arch.ptrsize*8 - 1 

3962 return (address >> memalign) == 0xF 

3963 

3964 

3965def is_remote_debug() -> bool: 

3966 """"Return True is the current debugging session is running through GDB remote session.""" 

3967 return gef.session.remote_initializing or gef.session.remote is not None 

3968 

3969 

3970def de_bruijn(alphabet: bytes, n: int) -> Generator[int, None, None]: 

3971 """De Bruijn sequence for alphabet and subsequences of length n (for compat. w/ pwnlib).""" 

3972 k = len(alphabet) 

3973 a = [0] * k * n 

3974 

3975 def db(t: int, p: int) -> Generator[int, None, None]: 

3976 if t > n: 

3977 if n % p == 0: 

3978 for j in range(1, p + 1): 

3979 yield alphabet[a[j]] 

3980 else: 

3981 a[t] = a[t - p] 

3982 yield from db(t + 1, p) 

3983 

3984 for j in range(a[t - p] + 1, k): 

3985 a[t] = j 

3986 yield from db(t + 1, t) 

3987 

3988 return db(1, 1) 

3989 

3990 

3991def generate_cyclic_pattern(length: int, cycle: int = 4) -> bytearray: 

3992 """Create a `length` byte bytearray of a de Bruijn cyclic pattern.""" 

3993 charset = bytearray(b"abcdefghijklmnopqrstuvwxyz") 

3994 return bytearray(itertools.islice(de_bruijn(charset, cycle), length)) 

3995 

3996 

3997def safe_parse_and_eval(value: str) -> "gdb.Value | None": 

3998 """GEF wrapper for gdb.parse_and_eval(): this function returns None instead of raising 

3999 gdb.error if the eval failed.""" 

4000 try: 

4001 return gdb.parse_and_eval(value) 

4002 except gdb.error as e: 

4003 dbg(f"gdb.parse_and_eval() failed, reason: {str(e)}") 

4004 return None 

4005 

4006 

4007@lru_cache() 

4008def dereference(addr: int) -> "gdb.Value | None": 

4009 """GEF wrapper for gdb dereference function.""" 

4010 try: 

4011 ulong_t = cached_lookup_type(use_stdtype()) or \ 

4012 cached_lookup_type(use_default_type()) or \ 

4013 cached_lookup_type(use_golang_type()) or \ 

4014 cached_lookup_type(use_rust_type()) 

4015 if not ulong_t: 4015 ↛ 4016line 4015 didn't jump to line 4016 because the condition on line 4015 was never true

4016 raise gdb.MemoryError("Failed to determine unsigned long type") 

4017 unsigned_long_type = ulong_t.pointer() 

4018 res = gdb.Value(addr).cast(unsigned_long_type).dereference() 

4019 # GDB does lazy fetch by default so we need to force access to the value 

4020 res.fetch_lazy() 

4021 return res 

4022 except gdb.MemoryError as e: 

4023 dbg(str(e)) 

4024 return None 

4025 

4026 

4027def gef_convenience(value: str | bytes) -> str: 

4028 """Defines a new convenience value.""" 

4029 global gef 

4030 var_name = f"$_gef{gef.session.convenience_vars_index:d}" 

4031 gef.session.convenience_vars_index += 1 

4032 if isinstance(value, str): 

4033 gdb.execute(f"""set {var_name} = "{value}" """) 

4034 elif isinstance(value, bytes): 4034 ↛ 4038line 4034 didn't jump to line 4038 because the condition on line 4034 was always true

4035 value_as_array = "{" + ", ".join([f"0x{b:02x}" for b in value]) + "}" 

4036 gdb.execute(f"""set {var_name} = {value_as_array} """) 

4037 else: 

4038 raise TypeError 

4039 return var_name 

4040 

4041 

4042def parse_string_range(s: str) -> Iterator[int]: 

4043 """Parses an address range (e.g. 0x400000-0x401000)""" 

4044 addrs = s.split("-") 

4045 return map(lambda x: int(x, 16), addrs) 

4046 

4047 

4048@lru_cache() 

4049def is_syscall(instruction: Instruction | int) -> bool: 

4050 """Checks whether an instruction or address points to a system call.""" 

4051 if isinstance(instruction, int): 

4052 instruction = gef_current_instruction(instruction) 

4053 insn_str = instruction.mnemonic 

4054 if len(instruction.operands): 

4055 insn_str += f" {', '.join(instruction.operands)}" 

4056 return insn_str in gef.arch.syscall_instructions 

4057 

4058 

4059# 

4060# Deprecated API 

4061# 

4062 

4063@deprecated("Use `gef.session.pie_breakpoints[num]`") 

4064def gef_get_pie_breakpoint(num: int) -> "PieVirtualBreakpoint": 

4065 return gef.session.pie_breakpoints[num] 

4066 

4067 

4068@deprecated("Use `str(gef.arch.endianness)` instead") 

4069def endian_str() -> str: 

4070 return str(gef.arch.endianness) 

4071 

4072 

4073@deprecated("Use `gef.config[key]`") 

4074def get_gef_setting(name: str) -> Any: 

4075 return gef.config[name] 

4076 

4077 

4078@deprecated("Use `gef.config[key] = value`") 

4079def set_gef_setting(name: str, value: Any) -> None: 

4080 gef.config[name] = value 

4081 return 

4082 

4083 

4084@deprecated("Use `gef.session.pagesize`") 

4085def gef_getpagesize() -> int: 

4086 return gef.session.pagesize 

4087 

4088 

4089@deprecated("Use `gef.session.canary`") 

4090def gef_read_canary() -> tuple[int, int] | None: 

4091 return gef.session.canary 

4092 

4093 

4094@deprecated("Use `gef.session.pid`") 

4095def get_pid() -> int: 

4096 return gef.session.pid 

4097 

4098 

4099@deprecated("Use `gef.session.file.name`") 

4100def get_filename() -> str: 

4101 assert gef.session.file 

4102 return gef.session.file.name 

4103 

4104 

4105@deprecated("Use `gef.heap.main_arena`") 

4106def get_glibc_arena() -> GlibcArena | None: 

4107 return gef.heap.main_arena 

4108 

4109 

4110@deprecated("Use `gef.arch.register(regname)`") 

4111def get_register(regname) -> int | None: 

4112 return gef.arch.register(regname) 

4113 

4114 

4115@deprecated("Use `gef.memory.maps`") 

4116def get_process_maps() -> list[Section]: 

4117 return gef.memory.maps 

4118 

4119 

4120@deprecated("Use `reset_architecture`") 

4121def set_arch(arch: str | None = None, _: str | None = None) -> None: 

4122 return reset_architecture(arch) 

4123 

4124# 

4125# GDB event hooking 

4126# 

4127 

4128@only_if_events_supported("cont") 

4129def gef_on_continue_hook(func: Callable[["gdb.ContinueEvent"], None]) -> None: 

4130 gdb.events.cont.connect(func) 

4131 

4132 

4133@only_if_events_supported("cont") 

4134def gef_on_continue_unhook(func: Callable[["gdb.ThreadEvent"], None]) -> None: 

4135 gdb.events.cont.disconnect(func) 

4136 

4137 

4138@only_if_events_supported("stop") 

4139def gef_on_stop_hook(func: Callable[["gdb.StopEvent"], None]) -> None: 

4140 gdb.events.stop.connect(func) 

4141 

4142 

4143@only_if_events_supported("stop") 

4144def gef_on_stop_unhook(func: Callable[["gdb.StopEvent"], None]) -> None: 

4145 gdb.events.stop.disconnect(func) 

4146 

4147 

4148@only_if_events_supported("exited") 

4149def gef_on_exit_hook(func: Callable[["gdb.ExitedEvent"], None]) -> None: 

4150 gdb.events.exited.connect(func) 

4151 

4152 

4153@only_if_events_supported("exited") 

4154def gef_on_exit_unhook(func: Callable[["gdb.ExitedEvent"], None]) -> None: 

4155 gdb.events.exited.disconnect(func) 

4156 

4157 

4158@only_if_events_supported("new_objfile") 

4159def gef_on_new_hook(func: Callable[["gdb.NewObjFileEvent"], None]) -> None: 

4160 gdb.events.new_objfile.connect(func) 

4161 

4162 

4163@only_if_events_supported("new_objfile") 

4164def gef_on_new_unhook(func: Callable[["gdb.NewObjFileEvent"], None]) -> None: 

4165 gdb.events.new_objfile.disconnect(func) 

4166 

4167 

4168@only_if_events_supported("clear_objfiles") 

4169def gef_on_unload_objfile_hook(func: Callable[["gdb.ClearObjFilesEvent"], None]) -> None: 

4170 gdb.events.clear_objfiles.connect(func) 

4171 

4172 

4173@only_if_events_supported("clear_objfiles") 

4174def gef_on_unload_objfile_unhook(func: Callable[["gdb.ClearObjFilesEvent"], None]) -> None: 

4175 gdb.events.clear_objfiles.disconnect(func) 

4176 

4177 

4178@only_if_events_supported("memory_changed") 

4179def gef_on_memchanged_hook(func: Callable[["gdb.MemoryChangedEvent"], None]) -> None: 

4180 gdb.events.memory_changed.connect(func) 

4181 

4182 

4183@only_if_events_supported("memory_changed") 

4184def gef_on_memchanged_unhook(func: Callable[["gdb.MemoryChangedEvent"], None]) -> None: 

4185 gdb.events.memory_changed.disconnect(func) 

4186 

4187 

4188@only_if_events_supported("register_changed") 

4189def gef_on_regchanged_hook(func: Callable[["gdb.RegisterChangedEvent"], None]) -> None: 

4190 gdb.events.register_changed.connect(func) 

4191 

4192 

4193@only_if_events_supported("register_changed") 

4194def gef_on_regchanged_unhook(func: Callable[["gdb.RegisterChangedEvent"], None]) -> None: 

4195 gdb.events.register_changed.disconnect(func) 

4196 

4197 

4198# 

4199# Virtual breakpoints 

4200# 

4201 

4202class PieVirtualBreakpoint: 

4203 """PIE virtual breakpoint (not real breakpoint).""" 

4204 

4205 def __init__(self, set_func: Callable[[int], str], vbp_num: int, addr: int) -> None: 

4206 # set_func(base): given a base address return a 

4207 # "set breakpoint" gdb command string 

4208 self.set_func = set_func 

4209 self.vbp_num = vbp_num 

4210 # breakpoint num, 0 represents not instantiated yet 

4211 self.bp_num = 0 

4212 self.bp_addr = 0 

4213 # this address might be a symbol, just to know where to break 

4214 if isinstance(addr, int): 4214 ↛ 4217line 4214 didn't jump to line 4217 because the condition on line 4214 was always true

4215 self.addr: int | str = hex(addr) 

4216 else: 

4217 self.addr = addr 

4218 return 

4219 

4220 def instantiate(self, base: int) -> None: 

4221 if self.bp_num: 4221 ↛ 4222line 4221 didn't jump to line 4222 because the condition on line 4221 was never true

4222 self.destroy() 

4223 

4224 try: 

4225 res = gdb.execute(self.set_func(base), to_string=True) or "" 

4226 if not res: return 4226 ↛ exitline 4226 didn't return from function 'instantiate' because the return on line 4226 wasn't executed

4227 except gdb.error as e: 

4228 err(str(e)) 

4229 return 

4230 

4231 if "Breakpoint" not in res: 4231 ↛ 4232line 4231 didn't jump to line 4232 because the condition on line 4231 was never true

4232 err(res) 

4233 return 

4234 

4235 res_list = res.split() 

4236 self.bp_num = res_list[1] 

4237 self.bp_addr = res_list[3] 

4238 return 

4239 

4240 def destroy(self) -> None: 

4241 if not self.bp_num: 

4242 err("Destroy PIE breakpoint not even set") 

4243 return 

4244 gdb.execute(f"delete {self.bp_num}") 

4245 self.bp_num = 0 

4246 return 

4247 

4248 

4249# 

4250# Breakpoints 

4251# 

4252 

4253class FormatStringBreakpoint(gdb.Breakpoint): 

4254 """Inspect stack for format string.""" 

4255 def __init__(self, spec: str, num_args: int) -> None: 

4256 super().__init__(spec, type=gdb.BP_BREAKPOINT, internal=False) 

4257 self.num_args = num_args 

4258 self.enabled = True 

4259 return 

4260 

4261 def stop(self) -> bool: 

4262 reset_all_caches() 

4263 msg = [] 

4264 ptr, addr = gef.arch.get_ith_parameter(self.num_args) 

4265 addr = lookup_address(addr) 

4266 

4267 if not addr.valid: 4267 ↛ 4268line 4267 didn't jump to line 4268 because the condition on line 4267 was never true

4268 return False 

4269 

4270 if addr.section.is_writable(): 

4271 content = gef.memory.read_cstring(addr.value) 

4272 name = addr.info.name if addr.info else addr.section.path 

4273 msg.append(Color.colorify("Format string helper", "yellow bold")) 

4274 msg.append(f"Possible insecure format string: {self.location}('{ptr}' {RIGHT_ARROW} {addr.value:#x}: '{content}')") 

4275 msg.append(f"Reason: Call to '{self.location}()' with format string argument in position " 

4276 f"#{self.num_args:d} is in page {addr.section.page_start:#x} ({name}) that has write permission") 

4277 push_context_message("warn", "\n".join(msg)) 

4278 return True 

4279 

4280 return False 

4281 

4282 

4283class StubBreakpoint(gdb.Breakpoint): 

4284 """Create a breakpoint to permanently disable a call (fork/alarm/signal/etc.).""" 

4285 

4286 def __init__(self, func: str, retval: int | None) -> None: 

4287 super().__init__(func, gdb.BP_BREAKPOINT, internal=False) 

4288 self.func = func 

4289 self.retval = retval 

4290 

4291 m = f"All calls to '{self.func}' will be skipped" 

4292 if self.retval is not None: 4292 ↛ 4294line 4292 didn't jump to line 4294 because the condition on line 4292 was always true

4293 m += f" (with return value set to {self.retval:#x})" 

4294 info(m) 

4295 return 

4296 

4297 def stop(self) -> bool: 

4298 size = "long" if gef.arch.ptrsize == 8 else "int" 

4299 gdb.execute(f"return (unsigned {size}){self.retval:#x}") 

4300 ok(f"Ignoring call to '{self.func}' " 

4301 f"(setting return value to {self.retval:#x})") 

4302 return False 

4303 

4304 

4305class ChangePermissionBreakpoint(gdb.Breakpoint): 

4306 """When hit, this temporary breakpoint will restore the original code, and position 

4307 $pc correctly.""" 

4308 

4309 def __init__(self, loc: str, code: ByteString, pc: int) -> None: 

4310 super().__init__(loc, gdb.BP_BREAKPOINT, internal=False) 

4311 self.original_code = code 

4312 self.original_pc = pc 

4313 return 

4314 

4315 def stop(self) -> bool: 

4316 info("Restoring original context") 

4317 gef.memory.write(self.original_pc, self.original_code, len(self.original_code)) 

4318 info("Restoring $pc") 

4319 gdb.execute(f"set $pc = {self.original_pc:#x}") 

4320 return True 

4321 

4322 

4323class TraceMallocBreakpoint(gdb.Breakpoint): 

4324 """Track allocations done with malloc() or calloc().""" 

4325 

4326 def __init__(self, name: str) -> None: 

4327 super().__init__(name, gdb.BP_BREAKPOINT, internal=True) 

4328 self.silent = True 

4329 self.name = name 

4330 return 

4331 

4332 def stop(self) -> bool: 

4333 reset_all_caches() 

4334 _, size = gef.arch.get_ith_parameter(0) 

4335 assert size 

4336 self.retbp = TraceMallocRetBreakpoint(size, self.name) 

4337 return False 

4338 

4339 

4340class TraceMallocRetBreakpoint(gdb.FinishBreakpoint): 

4341 """Internal temporary breakpoint to retrieve the return value of malloc().""" 

4342 

4343 def __init__(self, size: int, name: str) -> None: 

4344 super().__init__(gdb.newest_frame(), internal=True) 

4345 self.size = size 

4346 self.name = name 

4347 self.silent = True 

4348 return 

4349 

4350 def stop(self) -> bool: 

4351 if self.return_value: 4351 ↛ 4354line 4351 didn't jump to line 4354 because the condition on line 4351 was always true

4352 loc = int(self.return_value) 

4353 else: 

4354 loc = parse_address(gef.arch.return_register) 

4355 

4356 size = self.size 

4357 ok(f"{Color.colorify('Heap-Analysis', 'yellow bold')} - {self.name}({size})={loc:#x}") 

4358 check_heap_overlap = gef.config["heap-analysis-helper.check_heap_overlap"] 

4359 

4360 # pop from free-ed list if it was in it 

4361 if gef.session.heap_freed_chunks: 4361 ↛ 4362line 4361 didn't jump to line 4362 because the condition on line 4361 was never true

4362 idx = 0 

4363 for item in gef.session.heap_freed_chunks: 

4364 addr = item[0] 

4365 if addr == loc: 

4366 gef.session.heap_freed_chunks.remove(item) 

4367 continue 

4368 idx += 1 

4369 

4370 # pop from uaf watchlist 

4371 if gef.session.heap_uaf_watchpoints: 4371 ↛ 4372line 4371 didn't jump to line 4372 because the condition on line 4371 was never true

4372 idx = 0 

4373 for wp in gef.session.heap_uaf_watchpoints: 

4374 wp_addr = wp.address 

4375 if loc <= wp_addr < loc + size: 

4376 gef.session.heap_uaf_watchpoints.remove(wp) 

4377 wp.enabled = False 

4378 continue 

4379 idx += 1 

4380 

4381 item = (loc, size) 

4382 

4383 if check_heap_overlap: 4383 ↛ 4405line 4383 didn't jump to line 4405 because the condition on line 4383 was always true

4384 # seek all the currently allocated chunks, read their effective size and check for overlap 

4385 msg = [] 

4386 align = gef.arch.ptrsize 

4387 for chunk_addr, _ in gef.session.heap_allocated_chunks: 

4388 current_chunk = GlibcChunk(chunk_addr) 

4389 current_chunk_size = current_chunk.size 

4390 

4391 if chunk_addr <= loc < chunk_addr + current_chunk_size: 4391 ↛ 4392line 4391 didn't jump to line 4392 because the condition on line 4391 was never true

4392 offset = loc - chunk_addr - 2*align 

4393 if offset < 0: continue # false positive, discard 

4394 

4395 msg.append(Color.colorify("Heap-Analysis", "yellow bold")) 

4396 msg.append("Possible heap overlap detected") 

4397 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})") 

4398 msg.append(f"Writing {offset:d} bytes from {chunk_addr:#x} will reach chunk {loc:#x}") 

4399 msg.append(f"Payload example for chunk {chunk_addr:#x} (to overwrite {loc:#x} headers):") 

4400 msg.append(f" data = 'A'*{offset:d} + 'B'*{align:d} + 'C'*{align:d}") 

4401 push_context_message("warn", "\n".join(msg)) 

4402 return True 

4403 

4404 # add it to alloc-ed list 

4405 gef.session.heap_allocated_chunks.append(item) 

4406 return False 

4407 

4408 

4409class TraceReallocBreakpoint(gdb.Breakpoint): 

4410 """Track re-allocations done with realloc().""" 

4411 

4412 def __init__(self) -> None: 

4413 super().__init__("__libc_realloc", gdb.BP_BREAKPOINT, internal=True) 

4414 self.silent = True 

4415 return 

4416 

4417 def stop(self) -> bool: 

4418 _, ptr = gef.arch.get_ith_parameter(0) 

4419 _, size = gef.arch.get_ith_parameter(1) 

4420 assert ptr is not None and size is not None 

4421 self.retbp = TraceReallocRetBreakpoint(ptr, size) 

4422 return False 

4423 

4424 

4425class TraceReallocRetBreakpoint(gdb.FinishBreakpoint): 

4426 """Internal temporary breakpoint to retrieve the return value of realloc().""" 

4427 

4428 def __init__(self, ptr: int, size: int) -> None: 

4429 super().__init__(gdb.newest_frame(), internal=True) 

4430 self.ptr = ptr 

4431 self.size = size 

4432 self.silent = True 

4433 return 

4434 

4435 def stop(self) -> bool: 

4436 if self.return_value: 4436 ↛ 4439line 4436 didn't jump to line 4439 because the condition on line 4436 was always true

4437 newloc = int(self.return_value) 

4438 else: 

4439 newloc = parse_address(gef.arch.return_register) 

4440 

4441 title = Color.colorify("Heap-Analysis", "yellow bold") 

4442 if newloc != self: 4442 ↛ 4446line 4442 didn't jump to line 4446 because the condition on line 4442 was always true

4443 loc = Color.colorify(f"{newloc:#x}", "green") 

4444 ok(f"{title} - realloc({self.ptr:#x}, {self.size})={loc}") 

4445 else: 

4446 loc = Color.colorify(f"{newloc:#x}", "red") 

4447 ok(f"{title} - realloc({self.ptr:#x}, {self.size})={loc}") 

4448 

4449 item = (newloc, self.size) 

4450 

4451 try: 

4452 # check if item was in alloc-ed list 

4453 idx = [x for x, y in gef.session.heap_allocated_chunks].index(self.ptr) 

4454 # if so pop it out 

4455 item = gef.session.heap_allocated_chunks.pop(idx) 

4456 except ValueError: 

4457 if is_debug(): 

4458 warn(f"Chunk {self.ptr:#x} was not in tracking list") 

4459 finally: 

4460 # add new item to alloc-ed list 

4461 gef.session.heap_allocated_chunks.append(item) 

4462 

4463 return False 

4464 

4465 

4466class TraceFreeBreakpoint(gdb.Breakpoint): 

4467 """Track calls to free() and attempts to detect inconsistencies.""" 

4468 

4469 def __init__(self) -> None: 

4470 super().__init__("__libc_free", gdb.BP_BREAKPOINT, internal=True) 

4471 self.silent = True 

4472 return 

4473 

4474 def stop(self) -> bool: 

4475 reset_all_caches() 

4476 _, addr = gef.arch.get_ith_parameter(0) 

4477 msg = [] 

4478 check_free_null = gef.config["heap-analysis-helper.check_free_null"] 

4479 check_double_free = gef.config["heap-analysis-helper.check_double_free"] 

4480 check_weird_free = gef.config["heap-analysis-helper.check_weird_free"] 

4481 check_uaf = gef.config["heap-analysis-helper.check_uaf"] 

4482 

4483 ok(f"{Color.colorify('Heap-Analysis', 'yellow bold')} - free({addr:#x})") 

4484 if not addr: 4484 ↛ 4485line 4484 didn't jump to line 4485 because the condition on line 4484 was never true

4485 if check_free_null: 

4486 msg.append(Color.colorify("Heap-Analysis", "yellow bold")) 

4487 msg.append(f"Attempting to free(NULL) at {gef.arch.pc:#x}") 

4488 msg.append("Reason: if NULL page is allocatable, this can lead to code execution.") 

4489 push_context_message("warn", "\n".join(msg)) 

4490 return True 

4491 return False 

4492 

4493 if addr in [x for (x, _) in gef.session.heap_freed_chunks]: 4493 ↛ 4494line 4493 didn't jump to line 4494 because the condition on line 4493 was never true

4494 if check_double_free: 

4495 msg.append(Color.colorify("Heap-Analysis", "yellow bold")) 

4496 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") 

4497 msg.append("Execution will likely crash...") 

4498 push_context_message("warn", "\n".join(msg)) 

4499 return True 

4500 return False 

4501 

4502 # if here, no error 

4503 # 1. move alloc-ed item to free list 

4504 try: 

4505 # pop from alloc-ed list 

4506 idx = [x for x, y in gef.session.heap_allocated_chunks].index(addr) 

4507 item = gef.session.heap_allocated_chunks.pop(idx) 

4508 

4509 except ValueError: 

4510 if check_weird_free: 

4511 msg.append(Color.colorify("Heap-Analysis", "yellow bold")) 

4512 msg.append("Heap inconsistency detected:") 

4513 msg.append(f"Attempting to free an unknown value: {addr:#x}") 

4514 push_context_message("warn", "\n".join(msg)) 

4515 return True 

4516 return False 

4517 

4518 # 2. add it to free-ed list 

4519 gef.session.heap_freed_chunks.append(item) 

4520 

4521 self.retbp = None 

4522 if check_uaf: 4522 ↛ 4525line 4522 didn't jump to line 4525 because the condition on line 4522 was always true

4523 # 3. (opt.) add a watchpoint on pointer 

4524 self.retbp = TraceFreeRetBreakpoint(addr) 

4525 return False 

4526 

4527 

4528class TraceFreeRetBreakpoint(gdb.FinishBreakpoint): 

4529 """Internal temporary breakpoint to track free()d values.""" 

4530 

4531 def __init__(self, addr: int) -> None: 

4532 super().__init__(gdb.newest_frame(), internal=True) 

4533 self.silent = True 

4534 self.addr = addr 

4535 return 

4536 

4537 def stop(self) -> bool: 

4538 reset_all_caches() 

4539 wp = UafWatchpoint(self.addr) 

4540 gef.session.heap_uaf_watchpoints.append(wp) 

4541 return False 

4542 

4543 

4544class UafWatchpoint(gdb.Breakpoint): 

4545 """Custom watchpoints set TraceFreeBreakpoint() to monitor free()d pointers being used.""" 

4546 

4547 def __init__(self, addr: int) -> None: 

4548 super().__init__(f"*{addr:#x}", gdb.BP_WATCHPOINT, internal=True) 

4549 self.address = addr 

4550 self.silent = True 

4551 self.enabled = True 

4552 return 

4553 

4554 def stop(self) -> bool: 

4555 """If this method is triggered, we likely have a UaF. Break the execution and report it.""" 

4556 reset_all_caches() 

4557 frame = gdb.selected_frame() 

4558 if frame.name() in ("_int_malloc", "malloc_consolidate", "__libc_calloc", ): 

4559 return False 

4560 

4561 # software watchpoints stop after the next statement (see 

4562 # https://sourceware.org/gdb/onlinedocs/gdb/Set-Watchpoints.html) 

4563 pc = gdb_get_nth_previous_instruction_address(gef.arch.pc, 2) 

4564 assert pc 

4565 insn = gef_current_instruction(pc) 

4566 msg = [] 

4567 msg.append(Color.colorify("Heap-Analysis", "yellow bold")) 

4568 msg.append(f"Possible Use-after-Free in '{get_filepath()}': " 

4569 f"pointer {self.address:#x} was freed, but is attempted to be used at {pc:#x}") 

4570 msg.append(f"{insn.address:#x} {insn.mnemonic} {Color.yellowify(', '.join(insn.operands))}") 

4571 push_context_message("warn", "\n".join(msg)) 

4572 return True 

4573 

4574 

4575class EntryBreakBreakpoint(gdb.Breakpoint): 

4576 """Breakpoint used internally to stop execution at the most convenient entry point.""" 

4577 

4578 def __init__(self, location: str) -> None: 

4579 super().__init__(location, gdb.BP_BREAKPOINT, internal=True, temporary=True) 

4580 self.silent = True 

4581 return 

4582 

4583 def stop(self) -> bool: 

4584 reset_all_caches() 

4585 return True 

4586 

4587 

4588class NamedBreakpoint(gdb.Breakpoint): 

4589 """Breakpoint which shows a specified name, when hit.""" 

4590 

4591 def __init__(self, location: str, name: str) -> None: 

4592 super().__init__(spec=location, type=gdb.BP_BREAKPOINT, internal=False, temporary=False) 

4593 self.name = name 

4594 self.loc = location 

4595 return 

4596 

4597 def stop(self) -> bool: 

4598 reset_all_caches() 

4599 push_context_message("info", f"Hit breakpoint {self.loc} ({Color.colorify(self.name, 'red bold')})") 

4600 return True 

4601 

4602 

4603class JustSilentStopBreakpoint(gdb.Breakpoint): 

4604 """When hit, this temporary breakpoint stop the execution.""" 

4605 

4606 def __init__(self, loc: str) -> None: 

4607 super().__init__(loc, gdb.BP_BREAKPOINT, temporary=True) 

4608 self.silent = True 

4609 return 

4610 

4611 

4612# 

4613# Context Panes 

4614# 

4615 

4616def register_external_context_pane(pane_name: str, display_pane_function: Callable[[], None], pane_title_function: Callable[[], str | None], condition : Callable[[], bool] | None = None) -> None: 

4617 """ 

4618 Registering function for new GEF Context View. 

4619 pane_name: a string that has no spaces (used in settings) 

4620 display_pane_function: a function that uses gef_print() to print strings 

4621 pane_title_function: a function that returns a string or None, which will be displayed as the title. 

4622 If None, no title line is displayed. 

4623 condition: an optional callback: if not None, the callback will be executed first. If it returns true, 

4624 then only the pane title and content will displayed. Otherwise, it's simply skipped. 

4625 

4626 Example usage for a simple text to show when we hit a syscall: 

4627 def only_syscall(): return gef_current_instruction(gef.arch.pc).is_syscall() 

4628 def display_pane(): 

4629 gef_print("Wow, I am a context pane!") 

4630 def pane_title(): 

4631 return "example:pane" 

4632 register_external_context_pane("example_pane", display_pane, pane_title, only_syscall) 

4633 """ 

4634 gef.gdb.add_context_pane(pane_name, display_pane_function, pane_title_function, condition) 

4635 return 

4636 

4637def register_external_context_layout_mapping(current_pane_name: str, display_pane_function: Callable[[], None], pane_title_function: Callable[[], str | None], condition : Callable[[], bool] | None = None) -> None: 

4638 gef.gdb.add_context_layout_mapping(current_pane_name, display_pane_function, pane_title_function, condition) 

4639 return 

4640 

4641 

4642# 

4643# Commands 

4644# 

4645@deprecated("Use `register()`, and inherit from `GenericCommand` instead") 

4646def register_external_command(cls: Type["GenericCommand"]) -> Type["GenericCommand"]: 

4647 """Registering function for new GEF (sub-)command to GDB.""" 

4648 return cls 

4649 

4650@deprecated("Use `register()`, and inherit from `GenericCommand` instead") 

4651def register_command(cls: Type["GenericCommand"]) -> Type["GenericCommand"]: 

4652 """Decorator for registering new GEF (sub-)command to GDB.""" 

4653 return cls 

4654 

4655@deprecated("") 

4656def register_priority_command(cls: Type["GenericCommand"]) -> Type["GenericCommand"]: 

4657 """Decorator for registering new command with priority, meaning that it must 

4658 loaded before the other generic commands.""" 

4659 return cls 

4660 

4661 

4662ValidCommandType = TypeVar("ValidCommandType", bound="GenericCommand") 

4663ValidFunctionType = TypeVar("ValidFunctionType", bound="GenericFunction") 

4664 

4665def register(cls: Type["ValidCommandType"] | Type["ValidFunctionType"]) -> Type["ValidCommandType"] | Type["ValidFunctionType"]: 

4666 global __registered_commands__, __registered_functions__ 

4667 if issubclass(cls, GenericCommand): 

4668 assert hasattr(cls, "_cmdline_") 

4669 assert hasattr(cls, "do_invoke") 

4670 if any(map(lambda x: x._cmdline_ == cls._cmdline_, __registered_commands__)): 4670 ↛ 4671line 4670 didn't jump to line 4671 because the condition on line 4670 was never true

4671 raise AlreadyRegisteredException(cls._cmdline_) 

4672 __registered_commands__.add(cls) 

4673 return cls 

4674 

4675 if issubclass(cls, GenericFunction): 4675 ↛ 4683line 4675 didn't jump to line 4683 because the condition on line 4675 was always true

4676 assert hasattr(cls, "_function_") 

4677 assert hasattr(cls, "invoke") 

4678 if any(map(lambda x: x._function_ == cls._function_, __registered_functions__)): 4678 ↛ 4679line 4678 didn't jump to line 4679 because the condition on line 4678 was never true

4679 raise AlreadyRegisteredException(cls._function_) 

4680 __registered_functions__.add(cls) 

4681 return cls 

4682 

4683 raise TypeError(f"`{cls.__class__}` is an illegal class for `register`") 

4684 

4685 

4686class GenericCommand(gdb.Command): 

4687 """This is an abstract class for invoking commands, should not be instantiated.""" 

4688 

4689 _cmdline_: str 

4690 _syntax_: str 

4691 _example_: str | list[str] = "" 

4692 _aliases_: list[str] = [] 

4693 

4694 def __init_subclass__(cls, **kwargs): 

4695 super().__init_subclass__(**kwargs) 

4696 attributes = ("_cmdline_", "_syntax_", ) 

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

4698 raise NotImplementedError 

4699 

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

4701 self.pre_load() 

4702 syntax = Color.yellowify("\nSyntax: ") + self._syntax_ 

4703 example = Color.yellowify("\nExamples: \n\t") 

4704 if isinstance(self._example_, list): 

4705 example += "\n\t".join(self._example_) 

4706 elif isinstance(self._example_, str): 4706 ↛ 4708line 4706 didn't jump to line 4708 because the condition on line 4706 was always true

4707 example += self._example_ 

4708 self.__doc__ = (self.__doc__ or "").replace(" "*4, "") + syntax + example 

4709 self.repeat = False 

4710 self.repeat_count = 0 

4711 self.__last_command = None 

4712 command_type = kwargs.setdefault("command", gdb.COMMAND_USER) 

4713 complete_type = kwargs.setdefault("complete", -1) # -1=allow user-defined `complete()` 

4714 prefix = kwargs.setdefault("prefix", False) 

4715 super().__init__(name=self._cmdline_, command_class=command_type, 

4716 completer_class=complete_type, prefix=prefix) 

4717 self.post_load() 

4718 return 

4719 

4720 def invoke(self, args: str, from_tty: bool) -> None: 

4721 try: 

4722 argv = gdb.string_to_argv(args) 

4723 self.__set_repeat_count(argv, from_tty) 

4724 bufferize(self.do_invoke)(argv) 

4725 except Exception as e: 

4726 # Note: since we are intercepting cleaning exceptions here, commands preferably should avoid 

4727 # catching generic Exception, but rather specific ones. This is allows a much cleaner use. 

4728 if is_debug(): 4728 ↛ 4733line 4728 didn't jump to line 4733 because the condition on line 4728 was always true

4729 show_last_exception() 

4730 if gef.config["gef.propagate_debug_exception"] is True: 

4731 raise 

4732 else: 

4733 err(f"Command '{self._cmdline_}' failed to execute properly, reason: {e}") 

4734 return 

4735 

4736 def usage(self) -> None: 

4737 err(f"Syntax\n{self._syntax_}") 

4738 return 

4739 

4740 def do_invoke(self, argv: list[str]) -> None: 

4741 raise NotImplementedError 

4742 

4743 def pre_load(self) -> None: 

4744 return 

4745 

4746 def post_load(self) -> None: 

4747 return 

4748 

4749 def __get_setting_name(self, name: str) -> str: 

4750 clsname = self.__class__._cmdline_.replace(" ", "-") 

4751 return f"{clsname}.{name}" 

4752 

4753 def __iter__(self) -> Generator[str, None, None]: 

4754 for key in gef.config.keys(): 

4755 if key.startswith(self._cmdline_): 

4756 yield key.replace(f"{self._cmdline_}.", "", 1) 

4757 

4758 @property 

4759 def settings(self) -> list[str]: 

4760 """Return the list of settings for this command.""" 

4761 return list(iter(self)) 

4762 

4763 @deprecated("Use `self[setting_name]` instead") 

4764 def get_setting(self, name: str) -> Any: 

4765 return self.__getitem__(name) 

4766 

4767 def __getitem__(self, name: str) -> Any: 

4768 key = self.__get_setting_name(name) 

4769 return gef.config[key] 

4770 

4771 @deprecated("Use `setting_name in self` instead") 

4772 def has_setting(self, name: str) -> bool: 

4773 return self.__contains__(name) 

4774 

4775 def __contains__(self, name: str) -> bool: 

4776 return self.__get_setting_name(name) in gef.config 

4777 

4778 @deprecated("Use `self[setting_name] = value` instead") 

4779 def add_setting(self, name: str, value: tuple[Any, type, str], description: str = "") -> None: 

4780 return self.__setitem__(name, (value, description)) 

4781 

4782 def __setitem__(self, name: str, value: "GefSetting | tuple[Any, str]") -> None: 

4783 # make sure settings are always associated to the root command (which derives from GenericCommand) 

4784 if "GenericCommand" not in [x.__name__ for x in self.__class__.__bases__]: 

4785 return 

4786 key = self.__get_setting_name(name) 

4787 if key in gef.config: 

4788 # If the setting already exists, update the entry 

4789 setting = gef.config.raw_entry(key) 

4790 setting.value = value 

4791 return 

4792 

4793 # otherwise create it 

4794 if isinstance(value, GefSetting): 4794 ↛ 4795line 4794 didn't jump to line 4795 because the condition on line 4794 was never true

4795 gef.config[key] = value 

4796 else: 

4797 if len(value) == 1: 4797 ↛ 4798line 4797 didn't jump to line 4798 because the condition on line 4797 was never true

4798 gef.config[key] = GefSetting(value[0]) 

4799 elif len(value) == 2: 4799 ↛ 4801line 4799 didn't jump to line 4801 because the condition on line 4799 was always true

4800 gef.config[key] = GefSetting(value[0], description=value[1]) 

4801 return 

4802 

4803 @deprecated("Use `del self[setting_name]` instead") 

4804 def del_setting(self, name: str) -> None: 

4805 return self.__delitem__(name) 

4806 

4807 def __delitem__(self, name: str) -> None: 

4808 del gef.config[self.__get_setting_name(name)] 

4809 return 

4810 

4811 def __set_repeat_count(self, argv: list[str], from_tty: bool) -> None: 

4812 if not from_tty: 4812 ↛ 4817line 4812 didn't jump to line 4817 because the condition on line 4812 was always true

4813 self.repeat = False 

4814 self.repeat_count = 0 

4815 return 

4816 

4817 command = (gdb.execute("show commands", to_string=True) or "").strip().split("\n")[-1] 

4818 self.repeat = self.__last_command == command 

4819 self.repeat_count = self.repeat_count + 1 if self.repeat else 0 

4820 self.__last_command = command 

4821 return 

4822 

4823 

4824@register 

4825class ArchCommand(GenericCommand): 

4826 """Manage the current loaded architecture.""" 

4827 

4828 _cmdline_ = "arch" 

4829 _syntax_ = f"{_cmdline_} (list|get|set) ..." 

4830 _example_ = f"{_cmdline_} set X86" 

4831 

4832 def __init__(self) -> None: 

4833 super().__init__(prefix=True) 

4834 return 

4835 

4836 def do_invoke(self, argv: list[str]) -> None: 

4837 if not argv: 

4838 self.usage() 

4839 return 

4840 

4841@register 

4842class ArchGetCommand(GenericCommand): 

4843 """Get the current loaded architecture.""" 

4844 

4845 _cmdline_ = "arch get" 

4846 _syntax_ = f"{_cmdline_}" 

4847 _example_ = f"{_cmdline_}" 

4848 

4849 def do_invoke(self, args: list[str]) -> None: 

4850 gef_print(f"{Color.greenify('Arch')}: {gef.arch}") 

4851 gef_print(f"{Color.greenify('Reason')}: {gef.arch_reason}") 

4852 

4853 

4854@register 

4855class ArchSetCommand(GenericCommand): 

4856 """Set the current loaded architecture.""" 

4857 

4858 _cmdline_ = "arch set" 

4859 _syntax_ = f"{_cmdline_} <arch>" 

4860 _example_ = f"{_cmdline_} X86" 

4861 

4862 def do_invoke(self, args: list[str]) -> None: 

4863 reset_architecture(args[0] if args else None) 

4864 

4865 def complete(self, text: str, word: str) -> list[str]: 

4866 return sorted(x for x in __registered_architectures__.keys() if 

4867 isinstance(x, str) and x.lower().startswith(text.lower().strip())) 

4868 

4869@register 

4870class ArchListCommand(GenericCommand): 

4871 """List the available architectures.""" 

4872 

4873 _cmdline_ = "arch list" 

4874 _syntax_ = f"{_cmdline_}" 

4875 _example_ = f"{_cmdline_}" 

4876 

4877 def do_invoke(self, args: list[str]) -> None: 

4878 gef_print(Color.greenify("Available architectures:")) 

4879 for arch in sorted(set(__registered_architectures__.values()), key=lambda x: x.arch): 

4880 if arch is GenericArchitecture: 

4881 continue 

4882 

4883 gef_print(' ' + Color.yellowify(str(arch()))) 

4884 for alias in arch.aliases: 

4885 if isinstance(alias, str): 

4886 gef_print(f" {alias}") 

4887 

4888 

4889@register 

4890class VersionCommand(GenericCommand): 

4891 """Display GEF version info.""" 

4892 

4893 _cmdline_ = "version" 

4894 _syntax_ = f"{_cmdline_}" 

4895 _example_ = f"{_cmdline_}" 

4896 

4897 def do_invoke(self, argv: list[str]) -> None: 

4898 gef_fpath = pathlib.Path(inspect.stack()[0][1]).expanduser().absolute() 

4899 gef_dir = gef_fpath.parent 

4900 gef_hash = hashlib.sha256(gef_fpath.read_bytes()).hexdigest() 

4901 

4902 try: 

4903 git = which("git") 

4904 except FileNotFoundError: 

4905 git = None 

4906 

4907 if git: 4907 ↛ 4916line 4907 didn't jump to line 4916 because the condition on line 4907 was always true

4908 if (gef_dir / ".git").is_dir(): 4908 ↛ 4913line 4908 didn't jump to line 4913 because the condition on line 4908 was always true

4909 ver = subprocess.check_output("git log --format='%H' -n 1 HEAD", cwd=gef_dir, shell=True).decode("utf8").strip() 

4910 extra = "dirty" if len(subprocess.check_output("git ls-files -m", cwd=gef_dir, shell=True).decode("utf8").strip()) else "clean" 

4911 gef_print(f"GEF: rev:{ver} (Git - {extra})") 

4912 else: 

4913 gef_blob_hash = subprocess.check_output(f"git hash-object {gef_fpath}", shell=True).decode().strip() 

4914 gef_print("GEF: (Standalone)") 

4915 gef_print(f"Blob Hash({gef_fpath}): {gef_blob_hash}") 

4916 gef_print(f"SHA256({gef_fpath}): {gef_hash}") 

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

4918 py_ver = f"{sys.version_info.major:d}.{sys.version_info.minor:d}" 

4919 gef_print(f"GDB-Python: {py_ver}") 

4920 

4921 if "full" in argv: 

4922 gef_print(f"Loaded commands: {', '.join(gef.gdb.loaded_command_names)}") 

4923 return 

4924 

4925 

4926@register 

4927class PrintFormatCommand(GenericCommand): 

4928 """Print bytes format in commonly used formats, such as literals in high level languages.""" 

4929 

4930 valid_formats = ("py", "c", "js", "asm", "hex", "bytearray") 

4931 valid_bitness = (8, 16, 32, 64) 

4932 

4933 _cmdline_ = "print-format" 

4934 _aliases_ = ["pf",] 

4935 _syntax_ = (f"{_cmdline_} [--lang LANG] [--bitlen SIZE] [(--length,-l) LENGTH] [--clip] LOCATION" 

4936 f"\t--lang LANG specifies the output format for programming language (available: {valid_formats!s}, default 'py')." 

4937 f"\t--bitlen SIZE specifies size of bit (possible values: {valid_bitness!s}, default is 8)." 

4938 "\t--length LENGTH specifies length of array (default is 256)." 

4939 "\t--clip The output data will be copied to clipboard" 

4940 "\tLOCATION specifies where the address of bytes is stored.") 

4941 _example_ = f"{_cmdline_} --lang py -l 16 $rsp" 

4942 

4943 def __init__(self) -> None: 

4944 super().__init__(complete=gdb.COMPLETE_LOCATION) 

4945 self["max_size_preview"] = (10, "max size preview of bytes") 

4946 return 

4947 

4948 @property 

4949 def format_matrix(self) -> dict[int, tuple[str, str, str]]: 

4950 # `gef.arch.endianness` is a runtime property, should not be defined as a class property 

4951 return { 

4952 8: (f"{gef.arch.endianness}B", "char", "db"), 

4953 16: (f"{gef.arch.endianness}H", "short", "dw"), 

4954 32: (f"{gef.arch.endianness}I", "int", "dd"), 

4955 64: (f"{gef.arch.endianness}Q", "long long", "dq"), 

4956 } 

4957 

4958 @only_if_gdb_running 

4959 @parse_arguments({"location": "$pc", }, {("--length", "-l"): 256, "--bitlen": 0, "--lang": "py", "--clip": False,}) 

4960 def do_invoke(self, _: list[str], **kwargs: Any) -> None: 

4961 """Default value for print-format command.""" 

4962 args: argparse.Namespace = kwargs["arguments"] 

4963 args.bitlen = args.bitlen or gef.arch.ptrsize * 2 

4964 

4965 valid_bitlens = self.format_matrix.keys() 

4966 if args.bitlen not in valid_bitlens: 4966 ↛ 4967line 4966 didn't jump to line 4967 because the condition on line 4966 was never true

4967 err(f"Size of bit must be in: {valid_bitlens!s}") 

4968 return 

4969 

4970 if args.lang not in self.valid_formats: 

4971 err(f"Language must be in: {self.valid_formats!s}") 

4972 return 

4973 

4974 start_addr = parse_address(args.location) 

4975 size = int(args.bitlen / 8) 

4976 end_addr = start_addr + args.length * size 

4977 fmt = self.format_matrix[args.bitlen][0] 

4978 data = [] 

4979 

4980 if args.lang != "bytearray": 

4981 for addr in range(start_addr, end_addr, size): 

4982 value = struct.unpack(fmt, gef.memory.read(addr, size))[0] 

4983 data += [value] 

4984 sdata = ", ".join(map(hex, data)) 

4985 else: 

4986 sdata = "" 

4987 

4988 if args.lang == "bytearray": 

4989 data = gef.memory.read(start_addr, args.length) 

4990 preview = str(data[0:self["max_size_preview"]]) 

4991 out = f"Saved data {preview}... in '{gef_convenience(data)}'" 

4992 elif args.lang == "py": 

4993 out = f"buf = [{sdata}]" 

4994 elif args.lang == "c": 4994 ↛ 4995line 4994 didn't jump to line 4995 because the condition on line 4994 was never true

4995 c_type = self.format_matrix[args.bitlen][1] 

4996 out = f"unsigned {c_type} buf[{args.length}] = { {sdata}} ;" 

4997 elif args.lang == "js": 

4998 out = f"var buf = [{sdata}]" 

4999 elif args.lang == "asm": 4999 ↛ 5000line 4999 didn't jump to line 5000 because the condition on line 4999 was never true

5000 asm_type = self.format_matrix[args.bitlen][2] 

5001 out = f"buf {asm_type} {sdata}" 

5002 elif args.lang == "hex": 5002 ↛ 5005line 5002 didn't jump to line 5005 because the condition on line 5002 was always true

5003 out = gef.memory.read(start_addr, end_addr-start_addr).hex() 

5004 else: 

5005 raise ValueError(f"Invalid format: {args.lang}") 

5006 

5007 if args.clip: 5007 ↛ 5008line 5007 didn't jump to line 5008 because the condition on line 5007 was never true

5008 if copy_to_clipboard(gef_pybytes(out)): 

5009 info("Copied to clipboard") 

5010 else: 

5011 warn("There's a problem while copying") 

5012 

5013 gef_print(out) 

5014 return 

5015 

5016 

5017@register 

5018class PieCommand(GenericCommand): 

5019 """PIE breakpoint support.""" 

5020 

5021 _cmdline_ = "pie" 

5022 _syntax_ = f"{_cmdline_} (breakpoint|info|delete|run|attach|remote)" 

5023 

5024 def __init__(self) -> None: 

5025 super().__init__(prefix=True) 

5026 return 

5027 

5028 def do_invoke(self, argv: list[str]) -> None: 

5029 if not argv: 5029 ↛ 5031line 5029 didn't jump to line 5031 because the condition on line 5029 was always true

5030 self.usage() 

5031 return 

5032 

5033 

5034@register 

5035class PieBreakpointCommand(GenericCommand): 

5036 """Set a PIE breakpoint at an offset from the target binaries base address.""" 

5037 

5038 _cmdline_ = "pie breakpoint" 

5039 _syntax_ = f"{_cmdline_} OFFSET" 

5040 

5041 @parse_arguments({"offset": ""}, {}) 

5042 def do_invoke(self, _: list[str], **kwargs: Any) -> None: 

5043 args : argparse.Namespace = kwargs["arguments"] 

5044 if not args.offset: 5044 ↛ 5045line 5044 didn't jump to line 5045 because the condition on line 5044 was never true

5045 self.usage() 

5046 return 

5047 

5048 addr = parse_address(args.offset) 

5049 self.set_pie_breakpoint(lambda base: f"b *{base + addr}", addr) 

5050 

5051 # When the process is already on, set real breakpoints immediately 

5052 if is_alive(): 5052 ↛ 5053line 5052 didn't jump to line 5053 because the condition on line 5052 was never true

5053 vmmap = gef.memory.maps 

5054 base_address = [x.page_start for x in vmmap if x.path == get_filepath()][0] 

5055 for bp_ins in gef.session.pie_breakpoints.values(): 

5056 bp_ins.instantiate(base_address) 

5057 return 

5058 

5059 @staticmethod 

5060 def set_pie_breakpoint(set_func: Callable[[int], str], addr: int) -> None: 

5061 gef.session.pie_breakpoints[gef.session.pie_counter] = PieVirtualBreakpoint(set_func, gef.session.pie_counter, addr) 

5062 gef.session.pie_counter += 1 

5063 return 

5064 

5065 

5066@register 

5067class PieInfoCommand(GenericCommand): 

5068 """Display breakpoint info.""" 

5069 

5070 _cmdline_ = "pie info" 

5071 _syntax_ = f"{_cmdline_} BREAKPOINT" 

5072 

5073 @parse_arguments({"breakpoints": [-1,]}, {}) 

5074 def do_invoke(self, _: list[str], **kwargs: Any) -> None: 

5075 args : argparse.Namespace = kwargs["arguments"] 

5076 if args.breakpoints[0] == -1: 

5077 # No breakpoint info needed 

5078 bps = gef.session.pie_breakpoints.values() 

5079 else: 

5080 bps = [gef.session.pie_breakpoints[x] 

5081 for x in args.breakpoints 

5082 if x in gef.session.pie_breakpoints] 

5083 

5084 lines = [f"{'VNum':6s} {'Num':6s} {'Addr':18s}"] 

5085 lines += [ 

5086 f"{x.vbp_num:6d} {str(x.bp_num) if x.bp_num else 'N/A':6s} {x.addr:18s}" for x in bps 

5087 ] 

5088 gef_print("\n".join(lines)) 

5089 return 

5090 

5091 

5092@register 

5093class PieDeleteCommand(GenericCommand): 

5094 """Delete a PIE breakpoint.""" 

5095 

5096 _cmdline_ = "pie delete" 

5097 _syntax_ = f"{_cmdline_} [BREAKPOINT]" 

5098 

5099 @parse_arguments({"breakpoints": [-1,]}, {}) 

5100 def do_invoke(self, _: list[str], **kwargs: Any) -> None: 

5101 global gef 

5102 args : argparse.Namespace = kwargs["arguments"] 

5103 if args.breakpoints[0] == -1: 5103 ↛ 5105line 5103 didn't jump to line 5105 because the condition on line 5103 was never true

5104 # no arg, delete all 

5105 to_delete = list(gef.session.pie_breakpoints.values()) 

5106 self.delete_bp(to_delete) 

5107 else: 

5108 self.delete_bp([gef.session.pie_breakpoints[x] 

5109 for x in args.breakpoints 

5110 if x in gef.session.pie_breakpoints]) 

5111 return 

5112 

5113 

5114 @staticmethod 

5115 def delete_bp(breakpoints: list[PieVirtualBreakpoint]) -> None: 

5116 global gef 

5117 for bp in breakpoints: 

5118 # delete current real breakpoints if exists 

5119 if bp.bp_num: 5119 ↛ 5120line 5119 didn't jump to line 5120 because the condition on line 5119 was never true

5120 gdb.execute(f"delete {bp.bp_num}") 

5121 # delete virtual breakpoints 

5122 del gef.session.pie_breakpoints[bp.vbp_num] 

5123 return 

5124 

5125 

5126@register 

5127class PieRunCommand(GenericCommand): 

5128 """Run process with PIE breakpoint support.""" 

5129 

5130 _cmdline_ = "pie run" 

5131 _syntax_ = _cmdline_ 

5132 

5133 def do_invoke(self, argv: list[str]) -> None: 

5134 global gef 

5135 fpath = get_filepath() 

5136 if not fpath: 5136 ↛ 5137line 5136 didn't jump to line 5137 because the condition on line 5136 was never true

5137 warn("No executable to debug, use `file` to load a binary") 

5138 return 

5139 

5140 if not os.access(fpath, os.X_OK): 5140 ↛ 5141line 5140 didn't jump to line 5141 because the condition on line 5140 was never true

5141 warn(f"The file '{fpath}' is not executable.") 

5142 return 

5143 

5144 if is_alive(): 5144 ↛ 5145line 5144 didn't jump to line 5145 because the condition on line 5144 was never true

5145 warn("gdb is already running. Restart process.") 

5146 

5147 # get base address 

5148 gdb.execute("set stop-on-solib-events 1") 

5149 hide_context() 

5150 gdb.execute(f"run {' '.join(argv)}") 

5151 unhide_context() 

5152 gdb.execute("set stop-on-solib-events 0") 

5153 vmmap = gef.memory.maps 

5154 base_address = [x.page_start for x in vmmap if x.path == get_filepath()][0] 

5155 info(f"base address {hex(base_address)}") 

5156 

5157 # modify all breakpoints 

5158 for bp_ins in gef.session.pie_breakpoints.values(): 

5159 bp_ins.instantiate(base_address) 

5160 

5161 try: 

5162 gdb.execute("continue") 

5163 except gdb.error as e: 

5164 err(str(e)) 

5165 gdb.execute("kill") 

5166 return 

5167 

5168 

5169@register 

5170class PieAttachCommand(GenericCommand): 

5171 """Do attach with PIE breakpoint support.""" 

5172 

5173 _cmdline_ = "pie attach" 

5174 _syntax_ = f"{_cmdline_} PID" 

5175 

5176 def do_invoke(self, argv: list[str]) -> None: 

5177 try: 

5178 gdb.execute(f"attach {' '.join(argv)}", to_string=True) 

5179 except gdb.error as e: 

5180 err(str(e)) 

5181 return 

5182 # after attach, we are stopped so that we can 

5183 # get base address to modify our breakpoint 

5184 vmmap = gef.memory.maps 

5185 base_address = [x.page_start for x in vmmap if x.path == get_filepath()][0] 

5186 

5187 for bp_ins in gef.session.pie_breakpoints.values(): 

5188 bp_ins.instantiate(base_address) 

5189 gdb.execute("context") 

5190 return 

5191 

5192 

5193@register 

5194class PieRemoteCommand(GenericCommand): 

5195 """Attach to a remote connection with PIE breakpoint support.""" 

5196 

5197 _cmdline_ = "pie remote" 

5198 _syntax_ = f"{_cmdline_} REMOTE" 

5199 

5200 def do_invoke(self, argv: list[str]) -> None: 

5201 try: 

5202 gdb.execute(f"gef-remote {' '.join(argv)}") 

5203 except gdb.error as e: 

5204 err(str(e)) 

5205 return 

5206 # after remote attach, we are stopped so that we can 

5207 # get base address to modify our breakpoint 

5208 vmmap = gef.memory.maps 

5209 base_address = [x.page_start for x in vmmap if x.realpath == get_filepath()][0] 

5210 

5211 for bp_ins in gef.session.pie_breakpoints.values(): 

5212 bp_ins.instantiate(base_address) 

5213 gdb.execute("context") 

5214 return 

5215 

5216 

5217@register 

5218class SmartEvalCommand(GenericCommand): 

5219 """SmartEval: Smart eval (vague approach to mimic WinDBG `?`).""" 

5220 

5221 _cmdline_ = "$" 

5222 _syntax_ = f"{_cmdline_} EXPR\n{_cmdline_} ADDRESS1 ADDRESS2" 

5223 _example_ = (f"\n{_cmdline_} $pc+1" 

5224 f"\n{_cmdline_} 0x00007ffff7a10000 0x00007ffff7bce000") 

5225 

5226 def do_invoke(self, argv: list[str]) -> None: 

5227 argc = len(argv) 

5228 if argc == 1: 

5229 self.evaluate(argv) 

5230 return 

5231 

5232 if argc == 2: 5232 ↛ 5234line 5232 didn't jump to line 5234 because the condition on line 5232 was always true

5233 self.distance(argv) 

5234 return 

5235 

5236 def evaluate(self, expr: list[str]) -> None: 

5237 def show_as_int(i: int) -> None: 

5238 off = gef.arch.ptrsize*8 

5239 def comp2_x(x: Any) -> str: return f"{(x + (1 << off)) % (1 << off):x}" 

5240 def comp2_b(x: Any) -> str: return f"{(x + (1 << off)) % (1 << off):b}" 

5241 

5242 try: 

5243 s_i = comp2_x(res) 

5244 s_i = s_i.rjust(len(s_i)+1, "0") if len(s_i)%2 else s_i 

5245 gef_print(f"{i:d}") 

5246 gef_print("0x" + comp2_x(res)) 

5247 gef_print("0b" + comp2_b(res)) 

5248 gef_print(f"{binascii.unhexlify(s_i)}") 

5249 gef_print(f"{binascii.unhexlify(s_i)[::-1]}") 

5250 except Exception: 

5251 pass 

5252 return 

5253 

5254 parsed_expr = [] 

5255 for xp in expr: 

5256 try: 

5257 xp = gdb.parse_and_eval(xp) 

5258 xp = int(xp) 

5259 parsed_expr.append(f"{xp:d}") 

5260 except gdb.error: 

5261 parsed_expr.append(str(xp)) 

5262 

5263 try: 

5264 res = eval(" ".join(parsed_expr)) 

5265 if isinstance(res, int): 5265 ↛ 5268line 5265 didn't jump to line 5268 because the condition on line 5265 was always true

5266 show_as_int(res) 

5267 else: 

5268 gef_print(f"{res}") 

5269 except SyntaxError: 

5270 gef_print(" ".join(parsed_expr)) 

5271 return 

5272 

5273 def distance(self, args: list[str]) -> None: 

5274 try: 

5275 x = int(args[0], 16) if is_hex(args[0]) else int(args[0]) 

5276 y = int(args[1], 16) if is_hex(args[1]) else int(args[1]) 

5277 gef_print(f"{abs(x - y)}") 

5278 except ValueError: 

5279 warn(f"Distance requires 2 numbers: {self._cmdline_} 0 0xffff") 

5280 return 

5281 

5282 

5283@register 

5284class CanaryCommand(GenericCommand): 

5285 """Shows the canary value of the current process.""" 

5286 

5287 _cmdline_ = "canary" 

5288 _syntax_ = _cmdline_ 

5289 

5290 @only_if_gdb_running 

5291 def do_invoke(self, argv: list[str]) -> None: 

5292 self.dont_repeat() 

5293 

5294 fname = get_filepath() 

5295 assert fname 

5296 has_canary = Elf(fname).checksec["Canary"] 

5297 if not has_canary: 5297 ↛ 5298line 5297 didn't jump to line 5298 because the condition on line 5297 was never true

5298 warn("This binary was not compiled with SSP.") 

5299 return 

5300 

5301 res = gef.session.canary 

5302 if not res: 5302 ↛ 5303line 5302 didn't jump to line 5303 because the condition on line 5302 was never true

5303 err("Failed to get the canary") 

5304 return 

5305 

5306 canary, location = res 

5307 info(f"The canary of process {gef.session.pid} is at {location:#x}, value is {canary:#x}") 

5308 return 

5309 

5310 

5311@register 

5312class ProcessStatusCommand(GenericCommand): 

5313 """Extends the info given by GDB `info proc`, by giving an exhaustive description of the 

5314 process status (file descriptors, ancestor, descendants, etc.).""" 

5315 

5316 _cmdline_ = "process-status" 

5317 _syntax_ = _cmdline_ 

5318 _aliases_ = ["status", ] 

5319 

5320 def __init__(self) -> None: 

5321 super().__init__(complete=gdb.COMPLETE_NONE) 

5322 return 

5323 

5324 @only_if_gdb_running 

5325 @only_if_gdb_target_local 

5326 def do_invoke(self, argv: list[str]) -> None: 

5327 self.show_info_proc() 

5328 self.show_ancestor() 

5329 self.show_descendants() 

5330 self.show_fds() 

5331 self.show_connections() 

5332 return 

5333 

5334 def get_state_of(self, pid: int) -> dict[str, str]: 

5335 res = {} 

5336 with open(f"/proc/{pid}/status", "r") as f: 

5337 file = f.readlines() 

5338 for line in file: 

5339 key, value = line.split(":", 1) 

5340 res[key.strip()] = value.strip() 

5341 return res 

5342 

5343 def get_cmdline_of(self, pid: int) -> str: 

5344 with open(f"/proc/{pid}/cmdline", "r") as f: 

5345 return f.read().replace("\x00", "\x20").strip() 

5346 

5347 def get_process_path_of(self, pid: int) -> str: 

5348 return os.readlink(f"/proc/{pid}/exe") 

5349 

5350 def get_children_pids(self, pid: int) -> list[int]: 

5351 cmd = [gef.session.constants["ps"], "-o", "pid", "--ppid", f"{pid}", "--noheaders"] 

5352 try: 

5353 return [int(x) for x in gef_execute_external(cmd, as_list=True)] 

5354 except Exception: 

5355 return [] 

5356 

5357 def show_info_proc(self) -> None: 

5358 info("Process Information") 

5359 pid = gef.session.pid 

5360 cmdline = self.get_cmdline_of(pid) 

5361 gef_print(f"\tPID {RIGHT_ARROW} {pid}", 

5362 f"\tExecutable {RIGHT_ARROW} {self.get_process_path_of(pid)}", 

5363 f"\tCommand line {RIGHT_ARROW} '{cmdline}'", sep="\n") 

5364 return 

5365 

5366 def show_ancestor(self) -> None: 

5367 info("Parent Process Information") 

5368 ppid = int(self.get_state_of(gef.session.pid)["PPid"]) 

5369 state = self.get_state_of(ppid) 

5370 cmdline = self.get_cmdline_of(ppid) 

5371 gef_print(f"\tParent PID {RIGHT_ARROW} {state['Pid']}", 

5372 f"\tCommand line {RIGHT_ARROW} '{cmdline}'", sep="\n") 

5373 return 

5374 

5375 def show_descendants(self) -> None: 

5376 info("Children Process Information") 

5377 children = self.get_children_pids(gef.session.pid) 

5378 if not children: 5378 ↛ 5382line 5378 didn't jump to line 5382 because the condition on line 5378 was always true

5379 gef_print("\tNo child process") 

5380 return 

5381 

5382 for child_pid in children: 

5383 state = self.get_state_of(child_pid) 

5384 pid = int(state["Pid"]) 

5385 gef_print(f"\tPID {RIGHT_ARROW} {pid} (Name: '{self.get_process_path_of(pid)}'," 

5386 f" CmdLine: '{self.get_cmdline_of(pid)}')") 

5387 return 

5388 

5389 def show_fds(self) -> None: 

5390 pid = gef.session.pid 

5391 path = f"/proc/{pid:d}/fd" 

5392 

5393 info("File Descriptors:") 

5394 items = os.listdir(path) 

5395 if not items: 5395 ↛ 5396line 5395 didn't jump to line 5396 because the condition on line 5395 was never true

5396 gef_print("\tNo FD opened") 

5397 return 

5398 

5399 for fname in items: 

5400 fullpath = os.path.join(path, fname) 

5401 if os.path.islink(fullpath): 5401 ↛ 5399line 5401 didn't jump to line 5399 because the condition on line 5401 was always true

5402 gef_print(f"\t{fullpath} {RIGHT_ARROW} {os.readlink(fullpath)}") 

5403 return 

5404 

5405 def list_sockets(self, pid: int) -> list[int]: 

5406 sockets = [] 

5407 path = f"/proc/{pid:d}/fd" 

5408 items = os.listdir(path) 

5409 for fname in items: 

5410 fullpath = os.path.join(path, fname) 

5411 if os.path.islink(fullpath) and os.readlink(fullpath).startswith("socket:"): 5411 ↛ 5412line 5411 didn't jump to line 5412 because the condition on line 5411 was never true

5412 p = os.readlink(fullpath).replace("socket:", "")[1:-1] 

5413 sockets.append(int(p)) 

5414 return sockets 

5415 

5416 def parse_ip_port(self, addr: str) -> tuple[str, int]: 

5417 ip, port = addr.split(":") 

5418 return socket.inet_ntoa(struct.pack("<I", int(ip, 16))), int(port, 16) 

5419 

5420 def show_connections(self) -> None: 

5421 # https://github.com/torvalds/linux/blob/v4.7/include/net/tcp_states.h#L16 

5422 tcp_states_str = { 

5423 0x01: "TCP_ESTABLISHED", 

5424 0x02: "TCP_SYN_SENT", 

5425 0x03: "TCP_SYN_RECV", 

5426 0x04: "TCP_FIN_WAIT1", 

5427 0x05: "TCP_FIN_WAIT2", 

5428 0x06: "TCP_TIME_WAIT", 

5429 0x07: "TCP_CLOSE", 

5430 0x08: "TCP_CLOSE_WAIT", 

5431 0x09: "TCP_LAST_ACK", 

5432 0x0A: "TCP_LISTEN", 

5433 0x0B: "TCP_CLOSING", 

5434 0x0C: "TCP_NEW_SYN_RECV", 

5435 } 

5436 

5437 udp_states_str = { 

5438 0x07: "UDP_LISTEN", 

5439 } 

5440 

5441 info("Network Connections") 

5442 pid = gef.session.pid 

5443 sockets = self.list_sockets(pid) 

5444 if not sockets: 5444 ↛ 5448line 5444 didn't jump to line 5448 because the condition on line 5444 was always true

5445 gef_print("\tNo open connections") 

5446 return 

5447 

5448 entries = dict() 

5449 with open(f"/proc/{pid:d}/net/tcp", "r") as tcp: 

5450 entries["TCP"] = [x.split() for x in tcp.readlines()[1:]] 

5451 with open(f"/proc/{pid:d}/net/udp", "r") as udp: 

5452 entries["UDP"] = [x.split() for x in udp.readlines()[1:]] 

5453 

5454 for proto in entries: 

5455 for entry in entries[proto]: 

5456 local, remote, state = entry[1:4] 

5457 inode = int(entry[9]) 

5458 if inode in sockets: 

5459 local = self.parse_ip_port(local) 

5460 remote = self.parse_ip_port(remote) 

5461 state = int(state, 16) 

5462 state_str = tcp_states_str[state] if proto == "TCP" else udp_states_str[state] 

5463 

5464 gef_print(f"\t{local[0]}:{local[1]} {RIGHT_ARROW} {remote[0]}:{remote[1]} ({state_str})") 

5465 return 

5466 

5467 

5468@register 

5469class GefThemeCommand(GenericCommand): 

5470 """Customize GEF appearance.""" 

5471 

5472 _cmdline_ = "theme" 

5473 _syntax_ = f"{_cmdline_} [KEY [VALUE]]" 

5474 _example_ = (f"{_cmdline_} address_stack green") 

5475 

5476 def __init__(self) -> None: 

5477 super().__init__(self._cmdline_) 

5478 self["context_title_line"] = ("gray", "Color of the borders in context window") 

5479 self["context_title_message"] = ("cyan", "Color of the title in context window") 

5480 self["default_title_line"] = ("gray", "Default color of borders") 

5481 self["default_title_message"] = ("cyan", "Default color of title") 

5482 self["table_heading"] = ("blue", "Color of the column headings to tables (e.g. vmmap)") 

5483 self["old_context"] = ("gray", "Color to use to show things such as code that is not immediately relevant") 

5484 self["disassemble_current_instruction"] = ("green", "Color to use to highlight the current $pc when disassembling") 

5485 self["dereference_string"] = ("yellow", "Color of dereferenced string") 

5486 self["dereference_code"] = ("gray", "Color of dereferenced code") 

5487 self["dereference_base_address"] = ("cyan", "Color of dereferenced address") 

5488 self["dereference_register_value"] = ("bold blue", "Color of dereferenced register") 

5489 self["registers_register_name"] = ("blue", "Color of the register name in the register window") 

5490 self["registers_value_changed"] = ("bold red", "Color of the changed register in the register window") 

5491 self["address_stack"] = ("pink", "Color to use when a stack address is found") 

5492 self["address_heap"] = ("green", "Color to use when a heap address is found") 

5493 self["address_code"] = ("red", "Color to use when a code address is found") 

5494 self["source_current_line"] = ("green", "Color to use for the current code line in the source window") 

5495 return 

5496 

5497 def do_invoke(self, args: list[str]) -> None: 

5498 self.dont_repeat() 

5499 argc = len(args) 

5500 

5501 if argc == 0: 

5502 for key in self.settings: 

5503 setting = self[key] 

5504 value = Color.colorify(setting, setting) 

5505 gef_print(f"{key:40s}: {value}") 

5506 return 

5507 

5508 setting_name = args[0] 

5509 if setting_name not in self: 

5510 err("Invalid key") 

5511 return 

5512 

5513 if argc == 1: 

5514 value = self[setting_name] 

5515 gef_print(f"{setting_name:40s}: {Color.colorify(value, value)}") 

5516 return 

5517 

5518 colors = (color for color in args[1:] if color in Color.colors) 

5519 self[setting_name] = " ".join(colors) # type: ignore // this is valid since we overwrote __setitem__() 

5520 

5521 

5522class ExternalStructureManager: 

5523 class Structure: 

5524 def __init__(self, manager: "ExternalStructureManager", mod_path: pathlib.Path, struct_name: str) -> None: 

5525 self.manager = manager 

5526 self.module_path = mod_path 

5527 self.name = struct_name 

5528 self.class_type = self.__get_structure_class() 

5529 # if the symbol points to a class factory method and not a class 

5530 if not hasattr(self.class_type, "_fields_") and callable(self.class_type): 5530 ↛ 5531line 5530 didn't jump to line 5531 because the condition on line 5530 was never true

5531 self.class_type = self.class_type(gef) 

5532 return 

5533 

5534 def __str__(self) -> str: 

5535 return self.name 

5536 

5537 def pprint(self) -> None: 

5538 res: list[str] = [] 

5539 for _name, _type in self.class_type._fields_: # type: ignore 

5540 size = ctypes.sizeof(_type) 

5541 name = Color.colorify(_name, gef.config["pcustom.structure_name"]) 

5542 type = Color.colorify(_type.__name__, gef.config["pcustom.structure_type"]) 

5543 size = Color.colorify(hex(size), gef.config["pcustom.structure_size"]) 

5544 offset = Color.boldify(f"{getattr(self.class_type, _name).offset:04x}") 

5545 res.append(f"{offset} {name:32s} {type:16s} /* size={size} */") 

5546 gef_print("\n".join(res)) 

5547 return 

5548 

5549 def __get_structure_class(self) -> Type[ctypes.Structure]: 

5550 """Returns a tuple of (class, instance) if modname!classname exists""" 

5551 fpath = self.module_path 

5552 spec = importlib.util.spec_from_file_location(fpath.stem, fpath) 

5553 assert spec and spec.loader, "Failed to determine module specification" 

5554 module = importlib.util.module_from_spec(spec) 

5555 sys.modules[fpath.stem] = module 

5556 spec.loader.exec_module(module) 

5557 _class = getattr(module, self.name) 

5558 return _class 

5559 

5560 def apply_at(self, address: int, max_depth: int, depth: int = 0) -> None: 

5561 """Apply (recursively if possible) the structure format to the given address.""" 

5562 if depth >= max_depth: 5562 ↛ 5563line 5562 didn't jump to line 5563 because the condition on line 5562 was never true

5563 warn("maximum recursion level reached") 

5564 return 

5565 

5566 # read the data at the specified address 

5567 assert isinstance(self.class_type, type) 

5568 _structure = self.class_type() 

5569 _sizeof_structure = ctypes.sizeof(_structure) 

5570 

5571 try: 

5572 data = gef.memory.read(address, _sizeof_structure) 

5573 except gdb.MemoryError: 

5574 err(f"{' ' * depth}Cannot read memory {address:#x}") 

5575 return 

5576 

5577 # deserialize the data 

5578 length = min(len(data), _sizeof_structure) 

5579 ctypes.memmove(ctypes.addressof(_structure), data, length) 

5580 

5581 # pretty print all the fields (and call recursively if possible) 

5582 ptrsize = gef.arch.ptrsize 

5583 unpack = u32 if ptrsize == 4 else u64 

5584 for field in _structure._fields_: 

5585 assert len(field) == 2 

5586 _name, _type = field 

5587 _value = getattr(_structure, _name) 

5588 _offset = getattr(self.class_type, _name).offset 

5589 

5590 if ((ptrsize == 4 and _type is ctypes.c_uint32) 5590 ↛ 5594line 5590 didn't jump to line 5594 because the condition on line 5590 was never true

5591 or (ptrsize == 8 and _type is ctypes.c_uint64) 

5592 or (ptrsize == ctypes.sizeof(ctypes.c_void_p) and _type is ctypes.c_void_p)): 

5593 # try to dereference pointers 

5594 _value = RIGHT_ARROW.join(dereference_from(_value)) 

5595 

5596 line = f"{' ' * depth}" 

5597 line += f"{address:#x}+{_offset:#04x} {_name} : ".ljust(40) 

5598 line += f"{_value} ({_type.__name__})" 

5599 parsed_value = self.__get_ctypes_value(_structure, _name, _value) 

5600 if parsed_value: 5600 ↛ 5601line 5600 didn't jump to line 5601 because the condition on line 5600 was never true

5601 line += f"{RIGHT_ARROW} {parsed_value}" 

5602 gef_print(line) 

5603 

5604 if issubclass(_type, ctypes.Structure): 5604 ↛ 5605line 5604 didn't jump to line 5605 because the condition on line 5604 was never true

5605 self.apply_at(address + _offset, max_depth, depth + 1) 

5606 elif _type.__name__.startswith("LP_"): 

5607 # Pointer to a structure of a different type 

5608 __sub_type_name = _type.__name__.lstrip("LP_") 

5609 result = self.manager.find(__sub_type_name) 

5610 if result: 5610 ↛ 5584line 5610 didn't jump to line 5584 because the condition on line 5610 was always true

5611 _, __structure = result 

5612 __address = unpack(gef.memory.read(address + _offset, ptrsize)) 

5613 __structure.apply_at(__address, max_depth, depth + 1) 

5614 return 

5615 

5616 def __get_ctypes_value(self, struct, item, value) -> str: 

5617 if not hasattr(struct, "_values_"): return "" 5617 ↛ 5618line 5617 didn't jump to line 5618 because the condition on line 5617 was always true

5618 default = "" 

5619 for name, values in struct._values_: 

5620 if name != item: continue 

5621 if callable(values): 

5622 return str(values(value)) 

5623 try: 

5624 for val, desc in values: 

5625 if value == val: return desc 

5626 if val is None: default = desc 

5627 except Exception as e: 

5628 err(f"Error parsing '{name}': {e}") 

5629 return default 

5630 

5631 class Module(dict): 

5632 def __init__(self, manager: "ExternalStructureManager", path: pathlib.Path) -> None: 

5633 self.manager = manager 

5634 self.path = path 

5635 self.name = path.stem 

5636 self.raw = self.__load() 

5637 

5638 for entry in self: 

5639 structure = ExternalStructureManager.Structure(manager, self.path, entry) 

5640 self[structure.name] = structure 

5641 return 

5642 

5643 def __load(self) -> ModuleType: 

5644 """Load a custom module, and return it.""" 

5645 fpath = self.path 

5646 spec = importlib.util.spec_from_file_location(fpath.stem, fpath) 

5647 assert spec and spec.loader 

5648 module = importlib.util.module_from_spec(spec) 

5649 sys.modules[fpath.stem] = module 

5650 spec.loader.exec_module(module) 

5651 return module 

5652 

5653 def __str__(self) -> str: 

5654 return self.name 

5655 

5656 def __iter__(self) -> Generator[str, None, None]: 

5657 _invalid = {"BigEndianStructure", "LittleEndianStructure", "Structure"} 

5658 for x in dir(self.raw): 

5659 if x in _invalid: continue 

5660 _attr = getattr(self.raw, x) 

5661 

5662 # if it's a ctypes.Structure class, add it 

5663 if inspect.isclass(_attr) and issubclass(_attr, ctypes.Structure): 

5664 yield x 

5665 continue 

5666 

5667 # also accept class factory functions 

5668 if callable(_attr) and _attr.__module__ == self.name and x.endswith("_t"): 5668 ↛ 5669line 5668 didn't jump to line 5669 because the condition on line 5668 was never true

5669 yield x 

5670 continue 

5671 return 

5672 

5673 class Modules(dict): 

5674 def __init__(self, manager: "ExternalStructureManager") -> None: 

5675 self.manager: "ExternalStructureManager" = manager 

5676 self.root: pathlib.Path = manager.path 

5677 

5678 for entry in self.root.iterdir(): 

5679 if not entry.is_file(): continue 

5680 if entry.suffix != ".py": continue 5680 ↛ 5678line 5680 didn't jump to line 5678 because the continue on line 5680 wasn't executed

5681 if entry.name == "__init__.py": continue 5681 ↛ 5678line 5681 didn't jump to line 5678 because the continue on line 5681 wasn't executed

5682 module = ExternalStructureManager.Module(manager, entry) 

5683 self[module.name] = module 

5684 return 

5685 

5686 def __contains__(self, structure_name: str) -> bool: 

5687 """Return True if the structure name is found in any of the modules""" 

5688 for module in self.values(): 

5689 if structure_name in module: 

5690 return True 

5691 return False 

5692 

5693 def __init__(self) -> None: 

5694 self.clear_caches() 

5695 return 

5696 

5697 def clear_caches(self) -> None: 

5698 self._path = None 

5699 self._modules = None 

5700 return 

5701 

5702 @property 

5703 def modules(self) -> "ExternalStructureManager.Modules": 

5704 if not self._modules: 

5705 self._modules = ExternalStructureManager.Modules(self) 

5706 return self._modules 

5707 

5708 @property 

5709 def path(self) -> pathlib.Path: 

5710 if not self._path: 

5711 self._path = gef.config["pcustom.struct_path"].expanduser().absolute() 

5712 return self._path 

5713 

5714 @property 

5715 def structures(self) -> Generator[tuple["ExternalStructureManager.Module", "ExternalStructureManager.Structure"], None, None]: 

5716 for module in self.modules.values(): 

5717 for structure in module.values(): 

5718 yield module, structure 

5719 return 

5720 

5721 @lru_cache() 

5722 def find(self, structure_name: str) -> tuple["ExternalStructureManager.Module", "ExternalStructureManager.Structure"] | None: 

5723 """Return the module and structure for the given structure name; `None` if the structure name was not found.""" 

5724 for module in self.modules.values(): 

5725 if structure_name in module: 

5726 return module, module[structure_name] 

5727 return None 

5728 

5729 

5730@register 

5731class PCustomCommand(GenericCommand): 

5732 """Dump user defined structure. 

5733 This command attempts to reproduce WinDBG awesome `dt` command for GDB and allows 

5734 to apply structures (from symbols or custom) directly to an address. 

5735 Custom structures can be defined in pure Python using ctypes, and should be stored 

5736 in a specific directory, whose path must be stored in the `pcustom.struct_path` 

5737 configuration setting.""" 

5738 

5739 _cmdline_ = "pcustom" 

5740 _syntax_ = f"{_cmdline_} [list|edit <StructureName>|show <StructureName>]|<StructureName> 0xADDRESS]" 

5741 

5742 def __init__(self) -> None: 

5743 global gef 

5744 super().__init__(prefix=True) 

5745 self["max_depth"] = (4, "Maximum level of recursion supported") 

5746 self["structure_name"] = ("bold blue", "Color of the structure name") 

5747 self["structure_type"] = ("bold red", "Color of the attribute type") 

5748 self["structure_size"] = ("green", "Color of the attribute size") 

5749 gef.config[f"{self._cmdline_}.struct_path"] = GefSetting( gef.config["gef.tempdir"] / "structs", pathlib.Path, 

5750 "Path to store/load the structure ctypes files", 

5751 hooks={"on_write": [GefSetting.create_folder_tree,]}) 

5752 return 

5753 

5754 @parse_arguments({"type": "", "address": ""}, {}) 

5755 def do_invoke(self, *_: Any, **kwargs: dict[str, Any]) -> None: 

5756 args = cast(argparse.Namespace, kwargs["arguments"]) 

5757 if not args.type: 5757 ↛ 5758line 5757 didn't jump to line 5758 because the condition on line 5757 was never true

5758 gdb.execute("pcustom list") 

5759 return 

5760 

5761 structname = self.explode_type(args.type)[1] 

5762 

5763 if not args.address: 

5764 gdb.execute(f"pcustom show {structname}") 

5765 return 

5766 

5767 if not is_alive(): 5767 ↛ 5768line 5767 didn't jump to line 5768 because the condition on line 5767 was never true

5768 err("Session is not active") 

5769 return 

5770 

5771 manager = ExternalStructureManager() 

5772 address = parse_address(args.address) 

5773 result = manager.find(structname) 

5774 if not result: 

5775 err(f"No structure named '{structname}' found") 

5776 return 

5777 

5778 structure = result[1] 

5779 structure.apply_at(address, self["max_depth"]) 

5780 return 

5781 

5782 def explode_type(self, arg: str) -> tuple[str, str]: 

5783 modname, structname = arg.split(":", 1) if ":" in arg else (arg, arg) 

5784 structname = structname.split(".", 1)[0] if "." in structname else structname 

5785 return modname, structname 

5786 

5787 

5788@register 

5789class PCustomListCommand(PCustomCommand): 

5790 """PCustom: list available structures""" 

5791 

5792 _cmdline_ = "pcustom list" 

5793 _syntax_ = f"{_cmdline_}" 

5794 

5795 def __init__(self) -> None: 

5796 super().__init__() 

5797 return 

5798 

5799 def do_invoke(self, _: list[str]) -> None: 

5800 """Dump the list of all the structures and their respective.""" 

5801 manager = ExternalStructureManager() 

5802 info(f"Listing custom structures from '{manager.path}'") 

5803 struct_color = gef.config["pcustom.structure_type"] 

5804 filename_color = gef.config["pcustom.structure_name"] 

5805 for module in manager.modules.values(): 

5806 __modules = ", ".join([Color.colorify(str(structure), struct_color) for structure in module.values()]) 

5807 __filename = Color.colorify(str(module.path), filename_color) 

5808 gef_print(f"{RIGHT_ARROW} {__filename} ({__modules})") 

5809 return 

5810 

5811 

5812@register 

5813class PCustomShowCommand(PCustomCommand): 

5814 """PCustom: show the content of a given structure""" 

5815 

5816 _cmdline_ = "pcustom show" 

5817 _syntax_ = f"{_cmdline_} StructureName" 

5818 _aliases_ = ["pcustom create", "pcustom update"] 

5819 

5820 def __init__(self) -> None: 

5821 super().__init__() 

5822 return 

5823 

5824 def do_invoke(self, argv: list[str]) -> None: 

5825 if len(argv) == 0: 5825 ↛ 5826line 5825 didn't jump to line 5826 because the condition on line 5825 was never true

5826 self.usage() 

5827 return 

5828 

5829 _, structname = self.explode_type(argv[0]) 

5830 manager = ExternalStructureManager() 

5831 result = manager.find(structname) 

5832 if result: 

5833 _, structure = result 

5834 structure.pprint() 

5835 else: 

5836 err(f"No structure named '{structname}' found") 

5837 return 

5838 

5839 

5840@register 

5841class PCustomEditCommand(PCustomCommand): 

5842 """PCustom: edit the content of a given structure""" 

5843 

5844 _cmdline_ = "pcustom edit" 

5845 _syntax_ = f"{_cmdline_} StructureName" 

5846 __aliases__ = ["pcustom create", "pcustom new", "pcustom update"] 

5847 

5848 def __init__(self) -> None: 

5849 super().__init__() 

5850 return 

5851 

5852 def do_invoke(self, argv: list[str]) -> None: 

5853 if len(argv) == 0: 

5854 self.usage() 

5855 return 

5856 

5857 modname, structname = self.explode_type(argv[0]) 

5858 self.__create_or_edit_structure(modname, structname) 

5859 return 

5860 

5861 def __create_or_edit_structure(self, mod_name: str, struct_name: str) -> int: 

5862 path = gef.config["pcustom.struct_path"].expanduser() / f"{mod_name}.py" 

5863 if path.is_file(): 

5864 info(f"Editing '{path}'") 

5865 else: 

5866 ok(f"Creating '{path}' from template") 

5867 self.__create_template(struct_name, path) 

5868 

5869 cmd = (os.getenv("EDITOR") or "nano").split() 

5870 cmd.append(str(path.absolute())) 

5871 return subprocess.call(cmd) 

5872 

5873 def __create_template(self, structname: str, fpath: pathlib.Path) -> None: 

5874 template = f"""from ctypes import * 

5875 

5876class {structname}(Structure): 

5877 _fields_ = [] 

5878 

5879 _values_ = [] 

5880""" 

5881 with fpath.open("w") as f: 

5882 f.write(template) 

5883 return 

5884 

5885 

5886@register 

5887class ChangeFdCommand(GenericCommand): 

5888 """ChangeFdCommand: redirect file descriptor during runtime.""" 

5889 

5890 _cmdline_ = "hijack-fd" 

5891 _syntax_ = f"{_cmdline_} FD_NUM NEW_OUTPUT" 

5892 _example_ = f"{_cmdline_} 2 /tmp/stderr_output.txt" 

5893 

5894 @only_if_gdb_running 

5895 @only_if_gdb_target_local 

5896 def do_invoke(self, argv: list[str]) -> None: 

5897 if len(argv) != 2: 

5898 self.usage() 

5899 return 

5900 

5901 if not os.access(f"/proc/{gef.session.pid:d}/fd/{argv[0]}", os.R_OK): 

5902 self.usage() 

5903 return 

5904 

5905 old_fd = int(argv[0]) 

5906 new_output = argv[1] 

5907 

5908 if ":" in new_output: 

5909 address = socket.gethostbyname(new_output.split(":")[0]) 

5910 port = int(new_output.split(":")[1]) 

5911 

5912 AF_INET = 2 

5913 SOCK_STREAM = 1 

5914 res = gdb.execute(f"call (int)socket({AF_INET}, {SOCK_STREAM}, 0)", to_string=True) or "" 

5915 new_fd = self.get_fd_from_result(res) 

5916 

5917 # fill in memory with sockaddr_in struct contents 

5918 # we will do this in the stack, since connect() wants a pointer to a struct 

5919 vmmap = gef.memory.maps 

5920 stack_addr = [entry.page_start for entry in vmmap if entry.path == "[stack]"][0] 

5921 original_contents = gef.memory.read(stack_addr, 8) 

5922 

5923 gef.memory.write(stack_addr, b"\x02\x00", 2) 

5924 gef.memory.write(stack_addr + 0x2, struct.pack("<H", socket.htons(port)), 2) 

5925 gef.memory.write(stack_addr + 0x4, socket.inet_aton(address), 4) 

5926 

5927 info(f"Trying to connect to {new_output}") 

5928 res = gdb.execute(f"""call (int)connect({new_fd}, {stack_addr}, {16})""", to_string=True) 

5929 if res is None: 

5930 err("Call to `connect` failed") 

5931 return 

5932 

5933 # recover stack state 

5934 gef.memory.write(stack_addr, original_contents, 8) 

5935 

5936 res = self.get_fd_from_result(res) 

5937 if res == -1: 

5938 err(f"Failed to connect to {address}:{port}") 

5939 return 

5940 

5941 info(f"Connected to {new_output}") 

5942 else: 

5943 res = gdb.execute(f"""call (int)open("{new_output}", 66, 0666)""", to_string=True) 

5944 if res is None: 

5945 err("Call to `open` failed") 

5946 return 

5947 new_fd = self.get_fd_from_result(res) 

5948 

5949 info(f"Opened '{new_output}' as fd #{new_fd:d}") 

5950 gdb.execute(f"""call (int)dup2({new_fd:d}, {old_fd:d})""", to_string=True) 

5951 info(f"Duplicated fd #{new_fd:d}{RIGHT_ARROW}#{old_fd:d}") 

5952 gdb.execute(f"""call (int)close({new_fd:d})""", to_string=True) 

5953 info(f"Closed extra fd #{new_fd:d}") 

5954 ok("Success") 

5955 return 

5956 

5957 def get_fd_from_result(self, res: str) -> int: 

5958 # Output example: $1 = 3 

5959 res = gdb.execute(f"p/d {int(res.split()[2], 0)}", to_string=True) or "" 

5960 return int(res.split()[2], 0) 

5961 

5962 

5963@register 

5964class ScanSectionCommand(GenericCommand): 

5965 """Search for addresses that are located in a memory mapping (haystack) that belonging 

5966 to another (needle).""" 

5967 

5968 _cmdline_ = "scan" 

5969 _syntax_ = f"{_cmdline_} HAYSTACK NEEDLE" 

5970 _aliases_ = ["lookup",] 

5971 _example_ = f"\n{_cmdline_} stack libc" 

5972 

5973 @only_if_gdb_running 

5974 def do_invoke(self, argv: list[str]) -> None: 

5975 if len(argv) != 2: 5975 ↛ 5976line 5975 didn't jump to line 5976 because the condition on line 5975 was never true

5976 self.usage() 

5977 return 

5978 

5979 haystack = argv[0] 

5980 needle = argv[1] 

5981 

5982 info(f"Searching for addresses in '{Color.yellowify(haystack)}' " 

5983 f"that point to '{Color.yellowify(needle)}'") 

5984 

5985 fpath = get_filepath() or "" 

5986 

5987 if haystack == "binary": 

5988 haystack = fpath 

5989 

5990 if needle == "binary": 5990 ↛ 5991line 5990 didn't jump to line 5991 because the condition on line 5990 was never true

5991 needle = fpath 

5992 

5993 needle_sections = [] 

5994 haystack_sections = [] 

5995 

5996 if "0x" in haystack: 5996 ↛ 5997line 5996 didn't jump to line 5997 because the condition on line 5996 was never true

5997 start, end = parse_string_range(haystack) 

5998 haystack_sections.append((start, end, "")) 

5999 

6000 if "0x" in needle: 6000 ↛ 6001line 6000 didn't jump to line 6001 because the condition on line 6000 was never true

6001 start, end = parse_string_range(needle) 

6002 needle_sections.append((start, end)) 

6003 

6004 for sect in gef.memory.maps: 

6005 if sect.path is None: 6005 ↛ 6006line 6005 didn't jump to line 6006 because the condition on line 6005 was never true

6006 continue 

6007 if haystack in sect.path: 

6008 haystack_sections.append((sect.page_start, sect.page_end, os.path.basename(sect.path))) 

6009 if needle in sect.path: 

6010 needle_sections.append((sect.page_start, sect.page_end)) 

6011 

6012 step = gef.arch.ptrsize 

6013 unpack = u32 if step == 4 else u64 

6014 

6015 dereference_cmd = gef.gdb.commands["dereference"] 

6016 assert isinstance(dereference_cmd, DereferenceCommand) 

6017 

6018 for hstart, hend, hname in haystack_sections: 

6019 try: 

6020 mem = gef.memory.read(hstart, hend - hstart) 

6021 except gdb.MemoryError: 

6022 continue 

6023 

6024 for i in range(0, len(mem), step): 

6025 target = unpack(mem[i:i+step]) 

6026 for nstart, nend in needle_sections: 

6027 if target >= nstart and target < nend: 

6028 deref = dereference_cmd.pprint_dereferenced(hstart, int(i / step)) 

6029 if hname != "": 6029 ↛ 6033line 6029 didn't jump to line 6033 because the condition on line 6029 was always true

6030 name = Color.colorify(hname, "yellow") 

6031 gef_print(f"{name}: {deref}") 

6032 else: 

6033 gef_print(f" {deref}") 

6034 

6035 return 

6036 

6037 

6038@register 

6039class SearchPatternCommand(GenericCommand): 

6040 """SearchPatternCommand: search a pattern in memory. If given an hex value (starting with 0x) 

6041 the command will also try to look for upwards cross-references to this address.""" 

6042 

6043 _cmdline_ = "search-pattern" 

6044 _syntax_ = f"{_cmdline_} PATTERN [little|big] [section]" 

6045 _aliases_ = ["grep", "xref"] 

6046 _example_ = [f"{_cmdline_} AAAAAAAA", 

6047 f"{_cmdline_} 0x555555554000 little stack", 

6048 f"{_cmdline_} AAAA 0x600000-0x601000", 

6049 f"{_cmdline_} --regex 0x401000 0x401500 ([\\\\x20-\\\\x7E]{ 2,} )(?=\\\\x00) <-- It matches null-end-printable(from x20-x7e) C strings (min size 2 bytes)"] 

6050 

6051 def __init__(self) -> None: 

6052 super().__init__() 

6053 self["max_size_preview"] = (10, "max size preview of bytes") 

6054 self["nr_pages_chunk"] = (0x400, "number of pages readed for each memory read chunk") 

6055 return 

6056 

6057 def print_section(self, section: Section) -> None: 

6058 title = "In " 

6059 if section.path: 6059 ↛ 6062line 6059 didn't jump to line 6062 because the condition on line 6059 was always true

6060 title += f"'{Color.blueify(section.path)}'" 

6061 

6062 title += f"({section.page_start:#x}-{section.page_end:#x})" 

6063 title += f", permission={section.permission}" 

6064 ok(title) 

6065 return 

6066 

6067 def print_loc(self, loc: tuple[int, int, str]) -> None: 

6068 gef_print(f""" {loc[0]:#x} - {loc[1]:#x} {RIGHT_ARROW} "{Color.pinkify(loc[2])}" """) 

6069 return 

6070 

6071 def search_pattern_by_address(self, pattern: str, start_address: int, end_address: int) -> list[tuple[int, int, str]]: 

6072 """Search a pattern within a range defined by arguments.""" 

6073 _pattern = gef_pybytes(pattern) 

6074 step = self["nr_pages_chunk"] * gef.session.pagesize 

6075 locations = [] 

6076 

6077 for chunk_addr in range(start_address, end_address, step): 

6078 if chunk_addr + step > end_address: 6078 ↛ 6081line 6078 didn't jump to line 6081 because the condition on line 6078 was always true

6079 chunk_size = end_address - chunk_addr 

6080 else: 

6081 chunk_size = step 

6082 

6083 try: 

6084 mem = gef.memory.read(chunk_addr, chunk_size) 

6085 except gdb.MemoryError: 

6086 return [] 

6087 

6088 for match in re.finditer(_pattern, mem): 

6089 start = chunk_addr + match.start() 

6090 ustr = "" 

6091 if is_ascii_string(start): 6091 ↛ 6095line 6091 didn't jump to line 6095 because the condition on line 6091 was always true

6092 ustr = gef.memory.read_ascii_string(start) or "" 

6093 end = start + len(ustr) 

6094 else: 

6095 ustr = gef_pystring(_pattern) + "[...]" 

6096 end = start + len(_pattern) 

6097 locations.append((start, end, ustr)) 

6098 

6099 del mem 

6100 

6101 return locations 

6102 

6103 def search_binpattern_by_address(self, binpattern: bytes, start_address: int, end_address: int) -> list[tuple[int, int, str]]: 

6104 """Search a binary pattern within a range defined by arguments.""" 

6105 

6106 step = self["nr_pages_chunk"] * gef.session.pagesize 

6107 locations = [] 

6108 

6109 for chunk_addr in range(start_address, end_address, step): 

6110 if chunk_addr + step > end_address: 6110 ↛ 6113line 6110 didn't jump to line 6113 because the condition on line 6110 was always true

6111 chunk_size = end_address - chunk_addr 

6112 else: 

6113 chunk_size = step 

6114 

6115 try: 

6116 mem = gef.memory.read(chunk_addr, chunk_size) 

6117 except gdb.MemoryError: 

6118 return [] 

6119 preview_size = self["max_size_preview"] 

6120 preview = "" 

6121 for match in re.finditer(binpattern, mem): 

6122 start = chunk_addr + match.start() 

6123 preview = str(mem[slice(*match.span())][0:preview_size]) + "..." 

6124 size_match = match.span()[1] - match.span()[0] 

6125 if size_match > 0: 6125 ↛ 6127line 6125 didn't jump to line 6127 because the condition on line 6125 was always true

6126 size_match -= 1 

6127 end = start + size_match 

6128 locations.append((start, end, preview)) 

6129 

6130 del mem 

6131 

6132 return locations 

6133 

6134 def search_pattern(self, pattern: str, section_name: str) -> None: 

6135 """Search a pattern within the whole userland memory.""" 

6136 for section in gef.memory.maps: 

6137 if not section.permission & Permission.READ: continue 

6138 if section.path == "[vvar]": continue 

6139 if section_name not in section.path: continue 6139 ↛ 6136line 6139 didn't jump to line 6136 because the continue on line 6139 wasn't executed

6140 

6141 start = section.page_start 

6142 end = section.page_end - 1 

6143 old_section = None 

6144 

6145 for loc in self.search_pattern_by_address(pattern, start, end): 

6146 addr_loc_start = lookup_address(loc[0]) 

6147 if addr_loc_start and addr_loc_start.section: 6147 ↛ 6152line 6147 didn't jump to line 6152 because the condition on line 6147 was always true

6148 if old_section != addr_loc_start.section: 6148 ↛ 6152line 6148 didn't jump to line 6152 because the condition on line 6148 was always true

6149 self.print_section(addr_loc_start.section) 

6150 old_section = addr_loc_start.section 

6151 

6152 self.print_loc(loc) 

6153 return 

6154 

6155 @only_if_gdb_running 

6156 def do_invoke(self, argv: list[str]) -> None: 

6157 argc = len(argv) 

6158 if argc < 1: 6158 ↛ 6159line 6158 didn't jump to line 6159 because the condition on line 6158 was never true

6159 self.usage() 

6160 return 

6161 

6162 if argc > 3 and argv[0].startswith("--regex"): 

6163 pattern = ' '.join(argv[3:]) 

6164 pattern = ast.literal_eval("b'" + pattern + "'") 

6165 

6166 addr_start = parse_address(argv[1]) 

6167 addr_end = parse_address(argv[2]) 

6168 

6169 for loc in self.search_binpattern_by_address(pattern, addr_start, addr_end): 

6170 self.print_loc(loc) 

6171 

6172 return 

6173 

6174 pattern = argv[0] 

6175 endian = gef.arch.endianness 

6176 

6177 if argc >= 2: 6177 ↛ 6178line 6177 didn't jump to line 6178 because the condition on line 6177 was never true

6178 if argv[1].lower() == "big": endian = Endianness.BIG_ENDIAN 

6179 elif argv[1].lower() == "little": endian = Endianness.LITTLE_ENDIAN 

6180 

6181 if is_hex(pattern): 6181 ↛ 6182line 6181 didn't jump to line 6182 because the condition on line 6181 was never true

6182 if endian == Endianness.BIG_ENDIAN: 

6183 pattern = "".join(["\\x" + pattern[i:i + 2] for i in range(2, len(pattern), 2)]) 

6184 else: 

6185 pattern = "".join(["\\x" + pattern[i:i + 2] for i in range(len(pattern) - 2, 0, -2)]) 

6186 

6187 if argc == 3: 6187 ↛ 6188line 6187 didn't jump to line 6188 because the condition on line 6187 was never true

6188 info(f"Searching '{Color.yellowify(pattern)}' in {argv[2]}") 

6189 

6190 if "0x" in argv[2]: 

6191 start, end = parse_string_range(argv[2]) 

6192 

6193 loc = lookup_address(start) 

6194 if loc.valid: 

6195 self.print_section(loc.section) 

6196 

6197 for loc in self.search_pattern_by_address(pattern, start, end): 

6198 self.print_loc(loc) 

6199 else: 

6200 section_name = argv[2] 

6201 if section_name == "binary": 

6202 section_name = get_filepath() or "" 

6203 

6204 self.search_pattern(pattern, section_name) 

6205 else: 

6206 info(f"Searching '{Color.yellowify(pattern)}' in memory") 

6207 self.search_pattern(pattern, "") 

6208 return 

6209 

6210 

6211@register 

6212class FlagsCommand(GenericCommand): 

6213 """Edit flags in a human friendly way.""" 

6214 

6215 _cmdline_ = "edit-flags" 

6216 _syntax_ = f"{_cmdline_} [(+|-|~)FLAGNAME ...]" 

6217 _aliases_ = ["flags",] 

6218 _example_ = (f"\n{_cmdline_}" 

6219 f"\n{_cmdline_} +zero # sets ZERO flag") 

6220 

6221 def do_invoke(self, argv: list[str]) -> None: 

6222 if not gef.arch.flag_register: 6222 ↛ 6223line 6222 didn't jump to line 6223 because the condition on line 6222 was never true

6223 warn(f"The architecture {gef.arch.arch}:{gef.arch.mode} doesn't have flag register.") 

6224 return 

6225 

6226 for flag in argv: 

6227 if len(flag) < 2: 6227 ↛ 6228line 6227 didn't jump to line 6228 because the condition on line 6227 was never true

6228 continue 

6229 

6230 action = flag[0] 

6231 name = flag[1:].lower() 

6232 

6233 if action not in ("+", "-", "~"): 6233 ↛ 6234line 6233 didn't jump to line 6234 because the condition on line 6233 was never true

6234 err(f"Invalid action for flag '{flag}'") 

6235 continue 

6236 

6237 if name not in gef.arch.flags_table.values(): 6237 ↛ 6238line 6237 didn't jump to line 6238 because the condition on line 6237 was never true

6238 err(f"Invalid flag name '{flag[1:]}'") 

6239 continue 

6240 

6241 for off in gef.arch.flags_table: 

6242 if gef.arch.flags_table[off] != name: 

6243 continue 

6244 old_flag = gef.arch.register(gef.arch.flag_register) 

6245 if action == "+": 

6246 new_flags = old_flag | (1 << off) 

6247 elif action == "-": 

6248 new_flags = old_flag & ~(1 << off) 

6249 else: 

6250 new_flags = old_flag ^ (1 << off) 

6251 

6252 gdb.execute(f"set ({gef.arch.flag_register}) = {new_flags:#x}") 

6253 

6254 gef_print(gef.arch.flag_register_to_human()) 

6255 return 

6256 

6257 

6258@register 

6259class RemoteCommand(GenericCommand): 

6260 """GDB `target remote` command on steroids. This command will use the remote procfs to create 

6261 a local copy of the execution environment, including the target binary and its libraries 

6262 in the local temporary directory (the value by default is in `gef.config.tempdir`). Additionally, it 

6263 will fetch all the /proc/PID/maps and loads all its information. If procfs is not available remotely, the command 

6264 will likely fail. You can however still use the limited command provided by GDB `target remote`.""" 

6265 

6266 _cmdline_ = "gef-remote" 

6267 _syntax_ = f"{_cmdline_} [OPTIONS] TARGET" 

6268 _example_ = [f"{_cmdline_} localhost 1234", 

6269 f"{_cmdline_} --pid 6789 localhost 1234", 

6270 f"{_cmdline_} --qemu-user --qemu-binary /bin/debugme localhost 4444 "] 

6271 

6272 def __init__(self) -> None: 

6273 super().__init__(prefix=False) 

6274 return 

6275 

6276 @parse_arguments({"host": "", "port": 0}, {"--pid": -1, "--qemu-user": False, "--qemu-binary": ""}) 

6277 def do_invoke(self, _: list[str], **kwargs: Any) -> None: 

6278 if gef.session.remote is not None: 6278 ↛ 6279line 6278 didn't jump to line 6279 because the condition on line 6278 was never true

6279 err("You already are in remote session. Close it first before opening a new one...") 

6280 return 

6281 

6282 # argument check 

6283 args : argparse.Namespace = kwargs["arguments"] 

6284 if not args.host or not args.port: 6284 ↛ 6285line 6284 didn't jump to line 6285 because the condition on line 6284 was never true

6285 err("Missing parameters") 

6286 return 

6287 

6288 # qemu-user support 

6289 qemu_binary: pathlib.Path | None = None 

6290 if args.qemu_user: 

6291 try: 

6292 qemu_binary = pathlib.Path(args.qemu_binary).expanduser().absolute() if args.qemu_binary else gef.session.file 

6293 if not qemu_binary or not qemu_binary.exists(): 6293 ↛ 6294line 6293 didn't jump to line 6294 because the condition on line 6293 was never true

6294 raise FileNotFoundError(f"{qemu_binary} does not exist") 

6295 except Exception as e: 

6296 err(f"Failed to initialize qemu-user mode, reason: {str(e)}") 

6297 return 

6298 

6299 # Try to establish the remote session, throw on error 

6300 # Set `.remote_initializing` to True here - `GefRemoteSessionManager` invokes code which 

6301 # calls `is_remote_debug` which checks if `remote_initializing` is True or `.remote` is None 

6302 # This prevents some spurious errors being thrown during startup 

6303 gef.session.remote_initializing = True 

6304 session = GefRemoteSessionManager(args.host, args.port, args.pid, qemu_binary) 

6305 

6306 dbg(f"[remote] initializing remote session with {session.target} under {session.root}") 

6307 if not session.connect(args.pid) or not session.setup(): 

6308 gef.session.remote = None 

6309 gef.session.remote_initializing = False 

6310 raise EnvironmentError("Failed to setup remote target") 

6311 

6312 gef.session.remote_initializing = False 

6313 gef.session.remote = session 

6314 reset_all_caches() 

6315 gdb.execute("context") 

6316 return 

6317 

6318 

6319@register 

6320class SkipiCommand(GenericCommand): 

6321 """Skip N instruction(s) execution""" 

6322 

6323 _cmdline_ = "skipi" 

6324 _syntax_ = (f"{_cmdline_} [LOCATION] [--n NUM_INSTRUCTIONS]" 

6325 "\n\tLOCATION\taddress/symbol from where to skip" 

6326 "\t--n NUM_INSTRUCTIONS\tSkip the specified number of instructions instead of the default 1.") 

6327 

6328 _example_ = [f"{_cmdline_}", 

6329 f"{_cmdline_} --n 3", 

6330 f"{_cmdline_} 0x69696969", 

6331 f"{_cmdline_} 0x69696969 --n 6",] 

6332 

6333 def __init__(self) -> None: 

6334 super().__init__(complete=gdb.COMPLETE_LOCATION) 

6335 return 

6336 

6337 @only_if_gdb_running 

6338 @parse_arguments({"address": "$pc"}, {"--n": 1}) 

6339 def do_invoke(self, _: list[str], **kwargs: Any) -> None: 

6340 args : argparse.Namespace = kwargs["arguments"] 

6341 address = parse_address(args.address) 

6342 num_instructions = args.n 

6343 

6344 last_insn = gef_instruction_n(address, num_instructions-1) 

6345 total_bytes = (last_insn.address - address) + last_insn.size() 

6346 target_addr = address + total_bytes 

6347 

6348 info(f"skipping {num_instructions} instructions ({total_bytes} bytes) from {address:#x} to {target_addr:#x}") 

6349 gdb.execute(f"set $pc = {target_addr:#x}") 

6350 return 

6351 

6352 

6353@register 

6354class StepoverCommand(GenericCommand): 

6355 """Breaks on the instruction immediately following this one. Ex: Step over call instruction""" 

6356 

6357 _cmdline_ = "stepover" 

6358 _syntax_ = (f"{_cmdline_}" 

6359 "\n\tBreaks on the instruction immediately following this one. Ex: Step over call instruction.") 

6360 _aliases_ = ["so",] 

6361 _example_ = [f"{_cmdline_}",] 

6362 

6363 def __init__(self) -> None: 

6364 super().__init__(complete=gdb.COMPLETE_LOCATION) 

6365 return 

6366 

6367 @only_if_gdb_running 

6368 def do_invoke(self, _: list[str]) -> None: 

6369 target_addr = gef_next_instruction(parse_address("$pc")).address 

6370 JustSilentStopBreakpoint("".join(["*", str(target_addr)])) 

6371 gdb.execute("continue") 

6372 return 

6373 

6374 

6375@register 

6376class NopCommand(GenericCommand): 

6377 """Patch the instruction(s) pointed by parameters with NOP. Note: this command is architecture 

6378 aware.""" 

6379 

6380 _cmdline_ = "nop" 

6381 _syntax_ = (f"{_cmdline_} [LOCATION] [--i ITEMS] [--f] [--n] [--b]" 

6382 "\n\tLOCATION\taddress/symbol to patch (by default this command replaces whole instructions)" 

6383 "\t--i ITEMS\tnumber of items to insert (default 1)" 

6384 "\t--f\tForce patch even when the selected settings could overwrite partial instructions" 

6385 "\t--n\tInstead of replacing whole instructions, insert ITEMS nop instructions, no matter how many instructions it overwrites" 

6386 "\t--b\tInstead of replacing whole instructions, fill ITEMS bytes with nops") 

6387 _example_ = [f"{_cmdline_}", 

6388 f"{_cmdline_} $pc+3", 

6389 f"{_cmdline_} --i 2 $pc+3", 

6390 f"{_cmdline_} --b", 

6391 f"{_cmdline_} --b $pc+3", 

6392 f"{_cmdline_} --f --b --i 2 $pc+3" 

6393 f"{_cmdline_} --n --i 2 $pc+3",] 

6394 

6395 def __init__(self) -> None: 

6396 super().__init__(complete=gdb.COMPLETE_LOCATION) 

6397 return 

6398 

6399 @only_if_gdb_running 

6400 @parse_arguments({"address": "$pc"}, {"--i": 1, "--b": False, "--f": False, "--n": False}) 

6401 def do_invoke(self, _: list[str], **kwargs: Any) -> None: 

6402 args : argparse.Namespace = kwargs["arguments"] 

6403 address = parse_address(args.address) 

6404 nop = gef.arch.nop_insn 

6405 num_items = int(args.i) or 1 

6406 fill_bytes = bool(args.b) 

6407 fill_nops = bool(args.n) 

6408 force_flag = bool(args.f) or False 

6409 

6410 if fill_nops and fill_bytes: 

6411 err("--b and --n cannot be specified at the same time.") 

6412 return 

6413 

6414 total_bytes = 0 

6415 if fill_bytes: 

6416 total_bytes = num_items 

6417 elif fill_nops: 

6418 total_bytes = num_items * len(nop) 

6419 else: 

6420 try: 

6421 last_insn = gef_instruction_n(address, num_items-1) 

6422 last_addr = last_insn.address 

6423 except Exception as e: 

6424 err(f"Cannot patch instruction at {address:#x} reaching unmapped area, reason: {e}") 

6425 return 

6426 total_bytes = (last_addr - address) + gef_get_instruction_at(last_addr).size() 

6427 

6428 if len(nop) > total_bytes or total_bytes % len(nop): 

6429 warn(f"Patching {total_bytes} bytes at {address:#x} will result in LAST-NOP " 

6430 f"(byte nr {total_bytes % len(nop):#x}) broken and may cause a crash or " 

6431 "break disassembly.") 

6432 if not force_flag: 

6433 warn("Use --f (force) to ignore this warning.") 

6434 return 

6435 

6436 target_end_address = address + total_bytes 

6437 curr_ins = gef_current_instruction(address) 

6438 while curr_ins.address + curr_ins.size() < target_end_address: 

6439 if not Address(value=curr_ins.address + 1).valid: 

6440 err(f"Cannot patch instruction at {address:#x}: reaching unmapped area") 

6441 return 

6442 curr_ins = gef_next_instruction(curr_ins.address) 

6443 

6444 final_ins_end_addr = curr_ins.address + curr_ins.size() 

6445 

6446 if final_ins_end_addr != target_end_address: 

6447 warn(f"Patching {total_bytes} bytes at {address:#x} will result in LAST-INSTRUCTION " 

6448 f"({curr_ins.address:#x}) being partial overwritten and may cause a crash or " 

6449 "break disassembly.") 

6450 if not force_flag: 

6451 warn("Use --f (force) to ignore this warning.") 

6452 return 

6453 

6454 nops = bytearray(nop * total_bytes) 

6455 end_address = Address(value=address + total_bytes - 1) 

6456 if not end_address.valid: 6456 ↛ 6457line 6456 didn't jump to line 6457 because the condition on line 6456 was never true

6457 err(f"Cannot patch instruction at {address:#x}: reaching unmapped " 

6458 f"area: {end_address:#x}") 

6459 return 

6460 

6461 ok(f"Patching {total_bytes} bytes from {address:#x}") 

6462 gef.memory.write(address, nops, total_bytes) 

6463 

6464 return 

6465 

6466 

6467@register 

6468class StubCommand(GenericCommand): 

6469 """Stub out the specified function. This function is useful when needing to skip one 

6470 function to be called and disrupt your runtime flow (ex. fork).""" 

6471 

6472 _cmdline_ = "stub" 

6473 _syntax_ = (f"{_cmdline_} [--retval RETVAL] [address]" 

6474 "\taddress\taddress/symbol to stub out" 

6475 "\t--retval RETVAL\tSet the return value") 

6476 _example_ = f"{_cmdline_} --retval 0 fork" 

6477 

6478 def __init__(self) -> None: 

6479 super().__init__(complete=gdb.COMPLETE_LOCATION) 

6480 return 

6481 

6482 @only_if_gdb_running 

6483 @parse_arguments({"address": ""}, {("-r", "--retval"): 0}) 

6484 def do_invoke(self, _: list[str], **kwargs: Any) -> None: 

6485 args : argparse.Namespace = kwargs["arguments"] 

6486 loc = args.address if args.address else f"*{gef.arch.pc:#x}" 

6487 StubBreakpoint(loc, args.retval) 

6488 return 

6489 

6490 

6491@register 

6492class GlibcHeapCommand(GenericCommand): 

6493 """Base command to get information about the Glibc heap structure.""" 

6494 

6495 _cmdline_ = "heap" 

6496 _syntax_ = f"{_cmdline_} (chunk|chunks|bins|arenas|set-arena)" 

6497 

6498 def __init__(self) -> None: 

6499 super().__init__(prefix=True) 

6500 return 

6501 

6502 @only_if_gdb_running 

6503 def do_invoke(self, _: list[str]) -> None: 

6504 self.usage() 

6505 return 

6506 

6507 

6508@register 

6509class GlibcHeapSetArenaCommand(GenericCommand): 

6510 """Set the address of the main_arena or the currently selected arena.""" 

6511 

6512 _cmdline_ = "heap set-arena" 

6513 _syntax_ = f"{_cmdline_} [address|&symbol]" 

6514 _example_ = f"{_cmdline_} 0x001337001337" 

6515 

6516 def __init__(self) -> None: 

6517 super().__init__(complete=gdb.COMPLETE_LOCATION) 

6518 return 

6519 

6520 @only_if_gdb_running 

6521 @parse_arguments({"addr": ""}, {"--reset": False}) 

6522 def do_invoke(self, _: list[str], **kwargs: Any) -> None: 

6523 global gef 

6524 

6525 args: argparse.Namespace = kwargs["arguments"] 

6526 

6527 if args.reset: 6527 ↛ 6528line 6527 didn't jump to line 6528 because the condition on line 6527 was never true

6528 gef.heap.reset_caches() 

6529 return 

6530 

6531 if not args.addr: 6531 ↛ 6532line 6531 didn't jump to line 6532 because the condition on line 6531 was never true

6532 ok(f"Current arena set to: '{gef.heap.selected_arena}'") 

6533 return 

6534 

6535 try: 

6536 new_arena_address = parse_address(args.addr) 

6537 except gdb.error: 

6538 err("Invalid symbol for arena") 

6539 return 

6540 

6541 new_arena = GlibcArena( f"*{new_arena_address:#x}") 

6542 if new_arena in gef.heap.arenas: 6542 ↛ 6547line 6542 didn't jump to line 6547 because the condition on line 6542 was always true

6543 # if entered arena is in arena list then just select it 

6544 gef.heap.selected_arena = new_arena 

6545 else: 

6546 # otherwise set the main arena to the entered arena 

6547 gef.heap.main_arena = new_arena 

6548 return 

6549 

6550 

6551@register 

6552class GlibcHeapArenaCommand(GenericCommand): 

6553 """Display information on a heap chunk.""" 

6554 

6555 _cmdline_ = "heap arenas" 

6556 _syntax_ = _cmdline_ 

6557 

6558 @only_if_gdb_running 

6559 def do_invoke(self, _: list[str]) -> None: 

6560 for arena in gef.heap.arenas: 

6561 gef_print(str(arena)) 

6562 return 

6563 

6564 

6565@register 

6566class GlibcHeapChunkCommand(GenericCommand): 

6567 """Display information on a heap chunk. 

6568 See https://github.com/sploitfun/lsploits/blob/master/glibc/malloc/malloc.c#L1123.""" 

6569 

6570 _cmdline_ = "heap chunk" 

6571 _syntax_ = f"{_cmdline_} [-h] [--allow-unaligned] [--number] address" 

6572 

6573 def __init__(self) -> None: 

6574 super().__init__(complete=gdb.COMPLETE_LOCATION) 

6575 return 

6576 

6577 @parse_arguments({"address": ""}, {"--allow-unaligned": False, "--number": 1}) 

6578 @only_if_gdb_running 

6579 def do_invoke(self, _: list[str], **kwargs: Any) -> None: 

6580 args : argparse.Namespace = kwargs["arguments"] 

6581 if not args.address: 6581 ↛ 6582line 6581 didn't jump to line 6582 because the condition on line 6581 was never true

6582 err("Missing chunk address") 

6583 self.usage() 

6584 return 

6585 

6586 addr = parse_address(args.address) 

6587 current_chunk = GlibcChunk(addr, allow_unaligned=args.allow_unaligned) 

6588 

6589 if args.number > 1: 

6590 for _i in range(args.number): 6590 ↛ 6606line 6590 didn't jump to line 6606 because the loop on line 6590 didn't complete

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

6592 break 

6593 

6594 gef_print(str(current_chunk)) 

6595 next_chunk_addr = current_chunk.get_next_chunk_addr() 

6596 if not Address(value=next_chunk_addr).valid: 

6597 break 

6598 

6599 next_chunk = current_chunk.get_next_chunk() 

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

6601 break 

6602 

6603 current_chunk = next_chunk 

6604 else: 

6605 gef_print(current_chunk.psprint()) 

6606 return 

6607 

6608 

6609class GlibcHeapChunkSummary: 

6610 def __init__(self, desc = ""): 

6611 self.desc = desc 

6612 self.count = 0 

6613 self.total_bytes = 0 

6614 

6615 def process_chunk(self, chunk: GlibcChunk) -> None: 

6616 self.count += 1 

6617 self.total_bytes += chunk.size 

6618 

6619 

6620class GlibcHeapArenaSummary: 

6621 def __init__(self, resolve_type = False) -> None: 

6622 self.resolve_symbol = resolve_type 

6623 self.size_distribution = {} 

6624 self.flag_distribution = { 

6625 "PREV_INUSE": GlibcHeapChunkSummary(), 

6626 "IS_MMAPPED": GlibcHeapChunkSummary(), 

6627 "NON_MAIN_ARENA": GlibcHeapChunkSummary() 

6628 } 

6629 

6630 def process_chunk(self, chunk: GlibcChunk) -> None: 

6631 chunk_type = "" if not self.resolve_symbol else chunk.resolve_type() 

6632 

6633 per_size_summary = self.size_distribution.get((chunk.size, chunk_type), None) 

6634 if per_size_summary is None: 6634 ↛ 6637line 6634 didn't jump to line 6637 because the condition on line 6634 was always true

6635 per_size_summary = GlibcHeapChunkSummary(desc=chunk_type) 

6636 self.size_distribution[(chunk.size, chunk_type)] = per_size_summary 

6637 per_size_summary.process_chunk(chunk) 

6638 

6639 if chunk.has_p_bit(): 6639 ↛ 6641line 6639 didn't jump to line 6641 because the condition on line 6639 was always true

6640 self.flag_distribution["PREV_INUSE"].process_chunk(chunk) 

6641 if chunk.has_m_bit(): 6641 ↛ 6642line 6641 didn't jump to line 6642 because the condition on line 6641 was never true

6642 self.flag_distribution["IS_MAPPED"].process_chunk(chunk) 

6643 if chunk.has_n_bit(): 6643 ↛ 6644line 6643 didn't jump to line 6644 because the condition on line 6643 was never true

6644 self.flag_distribution["NON_MAIN_ARENA"].process_chunk(chunk) 

6645 

6646 def print(self) -> None: 

6647 gef_print("== Chunk distribution by size ==") 

6648 gef_print(f"{'ChunkBytes':<10s}\t{'Count':<10s}\t{'TotalBytes':15s}\t{'Description':s}") 

6649 for chunk_info, chunk_summary in sorted(self.size_distribution.items(), key=lambda x: x[1].total_bytes, reverse=True): 

6650 gef_print(f"{chunk_info[0]:<10d}\t{chunk_summary.count:<10d}\t{chunk_summary.total_bytes:<15d}\t{chunk_summary.desc:s}") 

6651 

6652 gef_print("\n== Chunk distribution by flag ==") 

6653 gef_print(f"{'Flag':<15s}\t{'TotalCount':<10s}\t{'TotalBytes':s}") 

6654 for chunk_flag, chunk_summary in self.flag_distribution.items(): 

6655 gef_print(f"{chunk_flag:<15s}\t{chunk_summary.count:<10d}\t{chunk_summary.total_bytes:<d}") 

6656 

6657class GlibcHeapWalkContext: 

6658 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: 

6659 self.print_arena = print_arena 

6660 self.allow_unaligned = allow_unaligned 

6661 self.min_size = min_size 

6662 self.max_size = max_size 

6663 self.remaining_chunk_count = count 

6664 self.summary = summary 

6665 self.resolve_type = resolve_type 

6666 

6667@register 

6668class GlibcHeapChunksCommand(GenericCommand): 

6669 """Display all heap chunks for the current arena. As an optional argument 

6670 the base address of a different arena can be passed""" 

6671 

6672 _cmdline_ = "heap chunks" 

6673 _syntax_ = f"{_cmdline_} [-h] [--all] [--allow-unaligned] [--summary] [--min-size MIN_SIZE] [--max-size MAX_SIZE] [--count COUNT] [--resolve] [arena_address]" 

6674 _example_ = (f"\n{_cmdline_}" 

6675 f"\n{_cmdline_} 0x555555775000") 

6676 

6677 def __init__(self) -> None: 

6678 super().__init__(complete=gdb.COMPLETE_LOCATION) 

6679 self["peek_nb_byte"] = (16, "Hexdump N first byte(s) inside the chunk data (0 to disable)") 

6680 return 

6681 

6682 @parse_arguments({"arena_address": ""}, {("--all", "-a"): False, "--allow-unaligned": False, "--min-size": 0, "--max-size": 0, ("--count", "-n"): -1, ("--summary", "-s"): False, "--resolve": False}) 

6683 @only_if_gdb_running 

6684 def do_invoke(self, _: list[str], **kwargs: Any) -> None: 

6685 args = kwargs["arguments"] 

6686 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) 

6687 if args.all or not args.arena_address: 

6688 for arena in gef.heap.arenas: 6688 ↛ 6692line 6688 didn't jump to line 6692 because the loop on line 6688 didn't complete

6689 self.dump_chunks_arena(arena, ctx) 

6690 if not args.all: 6690 ↛ 6688line 6690 didn't jump to line 6688 because the condition on line 6690 was always true

6691 return 

6692 try: 

6693 if not args.arena_address: 6693 ↛ 6694line 6693 didn't jump to line 6694 because the condition on line 6693 was never true

6694 return 

6695 arena_addr = parse_address(args.arena_address) 

6696 arena = GlibcArena(f"*{arena_addr:#x}") 

6697 self.dump_chunks_arena(arena, ctx) 

6698 except gdb.error as e: 

6699 err(f"Invalid arena: {e}\nArena Address: {args.arena_address}") 

6700 return 

6701 

6702 def dump_chunks_arena(self, arena: GlibcArena, ctx: GlibcHeapWalkContext) -> None: 

6703 heap_addr = arena.heap_addr(allow_unaligned=ctx.allow_unaligned) 

6704 if heap_addr is None: 6704 ↛ 6705line 6704 didn't jump to line 6705 because the condition on line 6704 was never true

6705 err("Could not find heap for arena") 

6706 return 

6707 if ctx.print_arena: 6707 ↛ 6708line 6707 didn't jump to line 6708 because the condition on line 6707 was never true

6708 gef_print(str(arena)) 

6709 if arena.is_main_arena(): 

6710 heap_end = arena.top + GlibcChunk(arena.top, from_base=True).size 

6711 self.dump_chunks_heap(heap_addr, heap_end, arena, ctx) 

6712 else: 

6713 heap_info_structs = arena.get_heap_info_list() or [] 

6714 for heap_info in heap_info_structs: 

6715 if not self.dump_chunks_heap(heap_info.heap_start, heap_info.heap_end, arena, ctx): 6715 ↛ 6716line 6715 didn't jump to line 6716 because the condition on line 6715 was never true

6716 break 

6717 return 

6718 

6719 def dump_chunks_heap(self, start: int, end: int, arena: GlibcArena, ctx: GlibcHeapWalkContext) -> bool: 

6720 nb = self["peek_nb_byte"] 

6721 chunk_iterator = GlibcChunk(start, from_base=True, allow_unaligned=ctx.allow_unaligned) 

6722 heap_summary = GlibcHeapArenaSummary(resolve_type=ctx.resolve_type) 

6723 top_printed = False 

6724 for chunk in chunk_iterator: 

6725 heap_corrupted = chunk.base_address > end 

6726 should_process = self.should_process_chunk(chunk, ctx) 

6727 

6728 if not ctx.summary and chunk.base_address == arena.top: 

6729 if should_process: 

6730 gef_print( 

6731 f"{chunk!s} {LEFT_ARROW} {Color.greenify('top chunk')}") 

6732 top_printed = True 

6733 break 

6734 

6735 if heap_corrupted: 6735 ↛ 6736line 6735 didn't jump to line 6736 because the condition on line 6735 was never true

6736 err("Corrupted heap, cannot continue.") 

6737 return False 

6738 

6739 if not should_process: 

6740 continue 

6741 

6742 if ctx.remaining_chunk_count == 0: 

6743 break 

6744 

6745 if ctx.summary: 

6746 heap_summary.process_chunk(chunk) 

6747 else: 

6748 line = str(chunk) 

6749 if nb: 6749 ↛ 6751line 6749 didn't jump to line 6751 because the condition on line 6749 was always true

6750 line += f"\n [{hexdump(gef.memory.read(chunk.data_address, nb), nb, base=chunk.data_address)}]" 

6751 gef_print(line) 

6752 

6753 ctx.remaining_chunk_count -= 1 

6754 

6755 if not top_printed and ctx.print_arena: 6755 ↛ 6756line 6755 didn't jump to line 6756 because the condition on line 6755 was never true

6756 top_chunk = GlibcChunk(arena.top, from_base=True, allow_unaligned=ctx.allow_unaligned) 

6757 gef_print(f"{top_chunk!s} {LEFT_ARROW} {Color.greenify('top chunk')}") 

6758 

6759 if ctx.summary: 

6760 heap_summary.print() 

6761 

6762 return True 

6763 

6764 def should_process_chunk(self, chunk: GlibcChunk, ctx: GlibcHeapWalkContext) -> bool: 

6765 if chunk.size < ctx.min_size: 

6766 return False 

6767 

6768 if 0 < ctx.max_size < chunk.size: 

6769 return False 

6770 

6771 return True 

6772 

6773 

6774@register 

6775class GlibcHeapBinsCommand(GenericCommand): 

6776 """Display information on the bins on an arena (default: main_arena). 

6777 See https://github.com/sploitfun/lsploits/blob/master/glibc/malloc/malloc.c#L1123.""" 

6778 

6779 _bin_types_ = ("tcache", "fast", "unsorted", "small", "large") 

6780 _cmdline_ = "heap bins" 

6781 _syntax_ = f"{_cmdline_} [{'|'.join(_bin_types_)}]" 

6782 

6783 def __init__(self) -> None: 

6784 super().__init__(prefix=True, complete=gdb.COMPLETE_LOCATION) 

6785 return 

6786 

6787 @only_if_gdb_running 

6788 def do_invoke(self, argv: list[str]) -> None: 

6789 if not argv: 

6790 for bin_t in self._bin_types_: 

6791 gdb.execute(f"heap bins {bin_t}") 

6792 return 

6793 

6794 bin_t = argv[0] 

6795 if bin_t not in self._bin_types_: 

6796 self.usage() 

6797 return 

6798 

6799 gdb.execute(f"heap bins {bin_t}") 

6800 return 

6801 

6802 def pprint_bin(self, arena_addr: str, index: int, _type: str = "") -> int: 

6803 arena = GlibcArena(arena_addr) 

6804 

6805 fd, bk = arena.bin(index) 

6806 if (fd, bk) == (0x00, 0x00): 6806 ↛ 6807line 6806 didn't jump to line 6807 because the condition on line 6806 was never true

6807 warn("Invalid backward and forward bin pointers(fw==bk==NULL)") 

6808 return -1 

6809 

6810 if _type == "tcache": 6810 ↛ 6811line 6810 didn't jump to line 6811 because the condition on line 6810 was never true

6811 chunkClass = GlibcTcacheChunk 

6812 elif _type == "fast": 6812 ↛ 6813line 6812 didn't jump to line 6813 because the condition on line 6812 was never true

6813 chunkClass = GlibcFastChunk 

6814 else: 

6815 chunkClass = GlibcChunk 

6816 

6817 nb_chunk = 0 

6818 head = chunkClass(bk, from_base=True).fd 

6819 if fd == head: 

6820 return nb_chunk 

6821 

6822 ok(f"{_type}bins[{index:d}]: fw={fd:#x}, bk={bk:#x}") 

6823 

6824 m = [] 

6825 while fd != head: 

6826 chunk = chunkClass(fd, from_base=True) 

6827 m.append(f"{RIGHT_ARROW} {chunk!s}") 

6828 fd = chunk.fd 

6829 nb_chunk += 1 

6830 

6831 if m: 6831 ↛ 6833line 6831 didn't jump to line 6833 because the condition on line 6831 was always true

6832 gef_print(" ".join(m)) 

6833 return nb_chunk 

6834 

6835 

6836@register 

6837class GlibcHeapTcachebinsCommand(GenericCommand): 

6838 """Display information on the Tcachebins on an arena (default: main_arena). 

6839 See https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=d5c3fafc4307c9b7a4c7d5cb381fcdbfad340bcc.""" 

6840 

6841 _cmdline_ = "heap bins tcache" 

6842 _syntax_ = f"{_cmdline_} [all] [thread_ids...]" 

6843 

6844 TCACHE_MAX_BINS = 0x40 

6845 

6846 def __init__(self) -> None: 

6847 super().__init__(complete=gdb.COMPLETE_LOCATION) 

6848 return 

6849 

6850 @only_if_gdb_running 

6851 def do_invoke(self, argv: list[str]) -> None: 

6852 # Determine if we are using libc with tcache built in (2.26+) 

6853 if gef.libc.version and gef.libc.version < (2, 26): 6853 ↛ 6854line 6853 didn't jump to line 6854 because the condition on line 6853 was never true

6854 info("No Tcache in this version of libc") 

6855 return 

6856 

6857 current_thread = gdb.selected_thread() 

6858 if current_thread is None: 6858 ↛ 6859line 6858 didn't jump to line 6859 because the condition on line 6858 was never true

6859 err("Couldn't find current thread") 

6860 return 

6861 

6862 # As a nicety, we want to display threads in ascending order by gdb number 

6863 threads = sorted(gdb.selected_inferior().threads(), key=lambda t: t.num) 

6864 if argv: 

6865 if "all" in argv: 6865 ↛ 6868line 6865 didn't jump to line 6868 because the condition on line 6865 was always true

6866 tids = [t.num for t in threads] 

6867 else: 

6868 tids = self.check_thread_ids([int(a) for a in argv]) 

6869 else: 

6870 tids = [current_thread.num] 

6871 

6872 for thread in threads: 

6873 if thread.num not in tids: 

6874 continue 

6875 

6876 thread.switch() 

6877 

6878 tcache_addr = self.find_tcache() 

6879 if tcache_addr == 0: 6879 ↛ 6880line 6879 didn't jump to line 6880 because the condition on line 6879 was never true

6880 info(f"Uninitialized tcache for thread {thread.num:d}") 

6881 continue 

6882 

6883 gef_print(titlify(f"Tcachebins for thread {thread.num:d}")) 

6884 tcache_empty = True 

6885 for i in range(self.TCACHE_MAX_BINS): 

6886 chunk, count = self.tcachebin(tcache_addr, i) 

6887 chunks = set() 

6888 msg = [] 

6889 chunk_size = 0 

6890 

6891 # Only print the entry if there are valid chunks. Don't trust count 

6892 while True: 

6893 if chunk is None: 

6894 break 

6895 

6896 try: 

6897 msg.append(f"{LEFT_ARROW} {chunk!s} ") 

6898 if not chunk_size: 

6899 chunk_size = chunk.usable_size 

6900 

6901 if chunk.data_address in chunks: 6901 ↛ 6902line 6901 didn't jump to line 6902 because the condition on line 6901 was never true

6902 msg.append(f"{RIGHT_ARROW} [loop detected]") 

6903 break 

6904 

6905 chunks.add(chunk.data_address) 

6906 

6907 next_chunk = chunk.fd 

6908 if next_chunk == 0: 

6909 break 

6910 

6911 chunk = GlibcTcacheChunk(next_chunk) 

6912 except gdb.MemoryError: 

6913 msg.append(f"{LEFT_ARROW} [Corrupted chunk at {chunk.data_address:#x}]") 

6914 break 

6915 

6916 if msg: 

6917 tcache_empty = False 

6918 tidx = gef.heap.csize2tidx(chunk_size) 

6919 size = gef.heap.tidx2size(tidx) 

6920 count = len(chunks) 

6921 gef_print(f"Tcachebins[idx={tidx:d}, size={size:#x}, count={count}]", end="") 

6922 gef_print("".join(msg)) 

6923 

6924 if tcache_empty: 

6925 gef_print("All tcachebins are empty") 

6926 

6927 current_thread.switch() 

6928 return 

6929 

6930 def find_tcache(self) -> int: 

6931 """Return the location of the current thread's tcache.""" 

6932 try: 

6933 # For multithreaded binaries, the tcache symbol (in thread local 

6934 # storage) will give us the correct address. 

6935 tcache_addr = parse_address("(void *) tcache") 

6936 except gdb.error: 

6937 # In binaries not linked with pthread (and therefore there is only 

6938 # one thread), we can't use the tcache symbol, but we can guess the 

6939 # correct address because the tcache is consistently the first 

6940 # allocation in the main arena. 

6941 heap_base = gef.heap.base_address 

6942 if heap_base is None: 

6943 err("No heap section") 

6944 return 0x0 

6945 tcache_addr = heap_base + 0x10 

6946 return tcache_addr 

6947 

6948 def check_thread_ids(self, tids: list[int]) -> list[int]: 

6949 """Return the subset of tids that are currently valid.""" 

6950 existing_tids = set(t.num for t in gdb.selected_inferior().threads()) 

6951 return list(set(tids) & existing_tids) 

6952 

6953 def tcachebin(self, tcache_base: int, i: int) -> tuple[GlibcTcacheChunk | None, int]: 

6954 """Return the head chunk in tcache[i] and the number of chunks in the bin.""" 

6955 if i >= self.TCACHE_MAX_BINS: 6955 ↛ 6956line 6955 didn't jump to line 6956 because the condition on line 6955 was never true

6956 err("Incorrect index value, index value must be between 0 and " 

6957 f"{self.TCACHE_MAX_BINS}-1, given {i}" 

6958 ) 

6959 return None, 0 

6960 

6961 tcache_chunk = GlibcTcacheChunk(tcache_base) 

6962 

6963 # Glibc changed the size of the tcache in version 2.30; this fix has 

6964 # been backported inconsistently between distributions. We detect the 

6965 # difference by checking the size of the allocated chunk for the 

6966 # tcache. 

6967 # Minimum usable size of allocated tcache chunk = ? 

6968 # For new tcache: 

6969 # TCACHE_MAX_BINS * _2_ + TCACHE_MAX_BINS * ptrsize 

6970 # For old tcache: 

6971 # TCACHE_MAX_BINS * _1_ + TCACHE_MAX_BINS * ptrsize 

6972 new_tcache_min_size = ( 

6973 self.TCACHE_MAX_BINS * 2 + 

6974 self.TCACHE_MAX_BINS * gef.arch.ptrsize) 

6975 

6976 if tcache_chunk.usable_size < new_tcache_min_size: 6976 ↛ 6977line 6976 didn't jump to line 6977 because the condition on line 6976 was never true

6977 tcache_count_size = 1 

6978 count = ord(gef.memory.read(tcache_base + tcache_count_size*i, 1)) 

6979 else: 

6980 tcache_count_size = 2 

6981 count = u16(gef.memory.read(tcache_base + tcache_count_size*i, 2)) 

6982 

6983 chunk = dereference(tcache_base + tcache_count_size*self.TCACHE_MAX_BINS + i*gef.arch.ptrsize) 

6984 chunk = GlibcTcacheChunk(int(chunk)) if chunk else None 

6985 return chunk, count 

6986 

6987 

6988@register 

6989class GlibcHeapFastbinsYCommand(GenericCommand): 

6990 """Display information on the fastbinsY on an arena (default: main_arena). 

6991 See https://github.com/sploitfun/lsploits/blob/master/glibc/malloc/malloc.c#L1123.""" 

6992 

6993 _cmdline_ = "heap bins fast" 

6994 _syntax_ = f"{_cmdline_} [ARENA_ADDRESS]" 

6995 

6996 def __init__(self) -> None: 

6997 super().__init__(complete=gdb.COMPLETE_LOCATION) 

6998 return 

6999 

7000 @parse_arguments({"arena_address": ""}, {}) 

7001 @only_if_gdb_running 

7002 def do_invoke(self, *_: Any, **kwargs: Any) -> None: 

7003 def fastbin_index(sz: int) -> int: 

7004 return (sz >> 4) - 2 if SIZE_SZ == 8 else (sz >> 3) - 2 

7005 

7006 args : argparse.Namespace = kwargs["arguments"] 

7007 if not gef.heap.main_arena: 7007 ↛ 7008line 7007 didn't jump to line 7008 because the condition on line 7007 was never true

7008 err("Heap not initialized") 

7009 return 

7010 

7011 SIZE_SZ = gef.arch.ptrsize 

7012 MAX_FAST_SIZE = 80 * SIZE_SZ // 4 

7013 NFASTBINS = fastbin_index(MAX_FAST_SIZE) - 1 

7014 

7015 arena = GlibcArena(f"*{args.arena_address}") if args.arena_address else gef.heap.selected_arena 

7016 if arena is None: 7016 ↛ 7017line 7016 didn't jump to line 7017 because the condition on line 7016 was never true

7017 err("Invalid Glibc arena") 

7018 return 

7019 

7020 gef_print(titlify(f"Fastbins for arena at {arena.addr:#x}")) 

7021 for i in range(NFASTBINS): 

7022 gef_print(f"Fastbins[idx={i:d}, size={(i+2)*SIZE_SZ*2:#x}] ", end="") 

7023 chunk = arena.fastbin(i) 

7024 chunks = set() 

7025 

7026 while True: 

7027 if chunk is None: 

7028 gef_print("0x00", end="") 

7029 break 

7030 

7031 try: 

7032 gef_print(f"{LEFT_ARROW} {chunk!s} ", end="") 

7033 if chunk.data_address in chunks: 7033 ↛ 7034line 7033 didn't jump to line 7034 because the condition on line 7033 was never true

7034 gef_print(f"{RIGHT_ARROW} [loop detected]", end="") 

7035 break 

7036 

7037 if fastbin_index(chunk.size) != i: 7037 ↛ 7038line 7037 didn't jump to line 7038 because the condition on line 7037 was never true

7038 gef_print("[incorrect fastbin_index] ", end="") 

7039 

7040 chunks.add(chunk.data_address) 

7041 

7042 next_chunk = chunk.fd 

7043 if next_chunk == 0: 7043 ↛ 7046line 7043 didn't jump to line 7046 because the condition on line 7043 was always true

7044 break 

7045 

7046 chunk = GlibcFastChunk(next_chunk, from_base=True) 

7047 except gdb.MemoryError: 

7048 gef_print(f"{LEFT_ARROW} [Corrupted chunk at {chunk.data_address:#x}]", end="") 

7049 break 

7050 gef_print() 

7051 return 

7052 

7053 

7054@register 

7055class GlibcHeapUnsortedBinsCommand(GenericCommand): 

7056 """Display information on the Unsorted Bins of an arena (default: main_arena). 

7057 See: https://github.com/sploitfun/lsploits/blob/master/glibc/malloc/malloc.c#L1689.""" 

7058 

7059 _cmdline_ = "heap bins unsorted" 

7060 _syntax_ = f"{_cmdline_} [ARENA_ADDRESS]" 

7061 

7062 def __init__(self) -> None: 

7063 super().__init__(complete=gdb.COMPLETE_LOCATION) 

7064 return 

7065 

7066 @parse_arguments({"arena_address": ""}, {}) 

7067 @only_if_gdb_running 

7068 def do_invoke(self, *_: Any, **kwargs: Any) -> None: 

7069 args : argparse.Namespace = kwargs["arguments"] 

7070 if not gef.heap.main_arena or not gef.heap.selected_arena: 7070 ↛ 7071line 7070 didn't jump to line 7071 because the condition on line 7070 was never true

7071 err("Heap not initialized") 

7072 return 

7073 

7074 arena_addr = args.arena_address if args.arena_address else f"{gef.heap.selected_arena.addr:#x}" 

7075 gef_print(titlify(f"Unsorted Bin for arena at {arena_addr}")) 

7076 heap_bins_cmd = gef.gdb.commands["heap bins"] 

7077 assert isinstance(heap_bins_cmd, GlibcHeapBinsCommand) 

7078 nb_chunk = heap_bins_cmd.pprint_bin(f"*{arena_addr}", 0, "unsorted_") 

7079 if nb_chunk >= 0: 7079 ↛ 7081line 7079 didn't jump to line 7081 because the condition on line 7079 was always true

7080 info(f"Found {nb_chunk:d} chunks in unsorted bin.") 

7081 return 

7082 

7083 

7084@register 

7085class GlibcHeapSmallBinsCommand(GenericCommand): 

7086 """Convenience command for viewing small bins.""" 

7087 

7088 _cmdline_ = "heap bins small" 

7089 _syntax_ = f"{_cmdline_} [ARENA_ADDRESS]" 

7090 

7091 def __init__(self) -> None: 

7092 super().__init__(complete=gdb.COMPLETE_LOCATION) 

7093 return 

7094 

7095 @parse_arguments({"arena_address": ""}, {}) 

7096 @only_if_gdb_running 

7097 def do_invoke(self, *_: Any, **kwargs: Any) -> None: 

7098 args : argparse.Namespace = kwargs["arguments"] 

7099 if not gef.heap.main_arena or not gef.heap.selected_arena: 7099 ↛ 7100line 7099 didn't jump to line 7100 because the condition on line 7099 was never true

7100 err("Heap not initialized") 

7101 return 

7102 

7103 arena_address = args.arena_address or f"{gef.heap.selected_arena.address:#x}" 

7104 gef_print(titlify(f"Small Bins for arena at {arena_address}")) 

7105 bins: dict[int, int] = {} 

7106 heap_bins_cmd = gef.gdb.commands["heap bins"] 

7107 assert isinstance (heap_bins_cmd, GlibcHeapBinsCommand) 

7108 for i in range(1, 63): 

7109 nb_chunk = heap_bins_cmd.pprint_bin(f"*{arena_address}", i, "small_") 

7110 if nb_chunk < 0: 7110 ↛ 7111line 7110 didn't jump to line 7111 because the condition on line 7110 was never true

7111 break 

7112 if nb_chunk > 0: 

7113 bins[i] = nb_chunk 

7114 info(f"Found {sum(list(bins.values())):d} chunks in {len(bins):d} small non-empty bins.") 

7115 return 

7116 

7117 

7118@register 

7119class GlibcHeapLargeBinsCommand(GenericCommand): 

7120 """Convenience command for viewing large bins.""" 

7121 

7122 _cmdline_ = "heap bins large" 

7123 _syntax_ = f"{_cmdline_} [ARENA_ADDRESS]" 

7124 

7125 def __init__(self) -> None: 

7126 super().__init__(complete=gdb.COMPLETE_LOCATION) 

7127 return 

7128 

7129 @parse_arguments({"arena_address": ""}, {}) 

7130 @only_if_gdb_running 

7131 def do_invoke(self, *_: Any, **kwargs: Any) -> None: 

7132 args : argparse.Namespace = kwargs["arguments"] 

7133 if not gef.heap.main_arena or not gef.heap.selected_arena: 7133 ↛ 7134line 7133 didn't jump to line 7134 because the condition on line 7133 was never true

7134 err("Heap not initialized") 

7135 return 

7136 

7137 arena_addr = args.arena_address if args.arena_address else f"{gef.heap.selected_arena.addr:#x}" 

7138 gef_print(titlify(f"Large Bins for arena at {arena_addr}")) 

7139 bins = {} 

7140 heap_bins_cmd = gef.gdb.commands["heap bins"] 

7141 assert isinstance(heap_bins_cmd, GlibcHeapBinsCommand) 

7142 for i in range(63, 126): 

7143 nb_chunk = heap_bins_cmd.pprint_bin(f"*{arena_addr}", i, "large_") 

7144 if nb_chunk < 0: 7144 ↛ 7145line 7144 didn't jump to line 7145 because the condition on line 7144 was never true

7145 break 

7146 if nb_chunk > 0: 

7147 bins[i] = nb_chunk 

7148 info(f"Found {sum(bins.values()):d} chunks in {len(bins):d} large non-empty bins.") 

7149 return 

7150 

7151 

7152@register 

7153class DetailRegistersCommand(GenericCommand): 

7154 """Display full details on one, many or all registers value from current architecture.""" 

7155 

7156 _cmdline_ = "registers" 

7157 _syntax_ = f"{_cmdline_} [[Register1][Register2] ... [RegisterN]]" 

7158 _example_ = (f"\n{_cmdline_}" 

7159 f"\n{_cmdline_} $eax $eip $esp") 

7160 

7161 @only_if_gdb_running 

7162 @parse_arguments({"registers": [""]}, {}) 

7163 def do_invoke(self, _: list[str], **kwargs: Any) -> None: 

7164 unchanged_color = gef.config["theme.registers_register_name"] 

7165 changed_color = gef.config["theme.registers_value_changed"] 

7166 string_color = gef.config["theme.dereference_string"] 

7167 regs = gef.arch.all_registers 

7168 

7169 args : argparse.Namespace = kwargs["arguments"] 

7170 if args.registers and args.registers[0]: 

7171 all_regs = set(gef.arch.all_registers) 

7172 regs = [reg for reg in args.registers if reg in all_regs] 

7173 invalid_regs = [reg for reg in args.registers if reg not in all_regs] 

7174 if invalid_regs: 7174 ↛ 7175line 7174 didn't jump to line 7175 because the condition on line 7174 was never true

7175 err(f"invalid registers for architecture: {', '.join(invalid_regs)}") 

7176 

7177 memsize = gef.arch.ptrsize 

7178 endian = str(gef.arch.endianness) 

7179 charset = string.printable 

7180 widest = max(map(len, gef.arch.all_registers)) 

7181 special_line = "" 

7182 

7183 for regname in regs: 

7184 reg = gdb.parse_and_eval(regname) 

7185 if reg.type.code == gdb.TYPE_CODE_VOID: 7185 ↛ 7186line 7185 didn't jump to line 7186 because the condition on line 7185 was never true

7186 continue 

7187 

7188 padreg = regname.ljust(widest, " ") 

7189 

7190 if str(reg) == "<unavailable>": 7190 ↛ 7191line 7190 didn't jump to line 7191 because the condition on line 7190 was never true

7191 gef_print(f"{Color.colorify(padreg, unchanged_color)}: " 

7192 f"{Color.colorify('no value', 'yellow underline')}") 

7193 continue 

7194 

7195 value = align_address(int(reg)) 

7196 ctx_cmd = gef.gdb.commands["context"] 

7197 assert isinstance(ctx_cmd, ContextCommand) 

7198 old_value = ctx_cmd.old_registers.get(regname, 0) 

7199 if value == old_value: 

7200 color = unchanged_color 

7201 else: 

7202 color = changed_color 

7203 

7204 # Special (e.g. segment) registers go on their own line 

7205 if regname in gef.arch.special_registers: 

7206 special_line += f"{Color.colorify(regname, color)}: " 

7207 special_line += f"{gef.arch.register(regname):#04x} " 

7208 continue 

7209 

7210 line = f"{Color.colorify(padreg, color)}: " 

7211 

7212 if regname == gef.arch.flag_register: 

7213 line += gef.arch.flag_register_to_human() 

7214 gef_print(line) 

7215 continue 

7216 

7217 addr = lookup_address(align_address(int(value))) 

7218 if addr.valid: 

7219 line += str(addr) 

7220 else: 

7221 line += format_address_spaces(value) 

7222 addrs = dereference_from(value) 

7223 

7224 if len(addrs) > 1: 

7225 sep = f" {RIGHT_ARROW} " 

7226 line += sep 

7227 line += sep.join(addrs[1:]) 

7228 

7229 # check to see if reg value is ascii 

7230 try: 

7231 fmt = f"{endian}{'I' if memsize == 4 else 'Q'}" 

7232 last_addr = int(addrs[-1], 16) 

7233 val = gef_pystring(struct.pack(fmt, last_addr)) 

7234 if all([_ in charset for _ in val]): 

7235 line += f" (\"{Color.colorify(val, string_color)}\"?)" 

7236 except ValueError: 

7237 pass 

7238 

7239 gef_print(line) 

7240 

7241 if special_line: 7241 ↛ 7243line 7241 didn't jump to line 7243 because the condition on line 7241 was always true

7242 gef_print(special_line) 

7243 return 

7244 

7245 

7246@register 

7247class ShellcodeCommand(GenericCommand): 

7248 """ShellcodeCommand uses @JonathanSalwan simple-yet-awesome shellcode API to 

7249 download shellcodes.""" 

7250 

7251 _cmdline_ = "shellcode" 

7252 _syntax_ = f"{_cmdline_} (search|get)" 

7253 

7254 def __init__(self) -> None: 

7255 super().__init__(prefix=True) 

7256 return 

7257 

7258 def do_invoke(self, _: list[str]) -> None: 

7259 err("Missing sub-command (search|get)") 

7260 self.usage() 

7261 return 

7262 

7263 

7264@register 

7265class ShellcodeSearchCommand(GenericCommand): 

7266 """Search pattern in shell-storm's shellcode database.""" 

7267 

7268 _cmdline_ = "shellcode search" 

7269 _syntax_ = f"{_cmdline_} PATTERN1 PATTERN2" 

7270 _aliases_ = ["sc-search",] 

7271 

7272 api_base = "http://shell-storm.org" 

7273 search_url = f"{api_base}/api/?s=" 

7274 

7275 def do_invoke(self, argv: list[str]) -> None: 

7276 if not argv: 7276 ↛ 7277line 7276 didn't jump to line 7277 because the condition on line 7276 was never true

7277 err("Missing pattern to search") 

7278 self.usage() 

7279 return 

7280 

7281 # API : http://shell-storm.org/shellcode/ 

7282 args = "*".join(argv) 

7283 

7284 res = http_get(self.search_url + args) 

7285 if res is None: 7285 ↛ 7286line 7285 didn't jump to line 7286 because the condition on line 7285 was never true

7286 err("Could not query search page") 

7287 return 

7288 

7289 ret = gef_pystring(res) 

7290 

7291 # format: [author, OS/arch, cmd, id, link] 

7292 lines = ret.split("\\n") 

7293 refs = [line.split("::::") for line in lines] 

7294 

7295 if refs: 7295 ↛ 7306line 7295 didn't jump to line 7306 because the condition on line 7295 was always true

7296 info("Showing matching shellcodes") 

7297 info("\t".join(["Id", "Platform", "Description"])) 

7298 for ref in refs: 

7299 try: 

7300 _, arch, cmd, sid, _ = ref 

7301 gef_print("\t".join([sid, arch, cmd])) 

7302 except ValueError: 

7303 continue 

7304 

7305 info("Use `shellcode get <id>` to fetch shellcode") 

7306 return 

7307 

7308 

7309@register 

7310class ShellcodeGetCommand(GenericCommand): 

7311 """Download shellcode from shell-storm's shellcode database.""" 

7312 

7313 _cmdline_ = "shellcode get" 

7314 _syntax_ = f"{_cmdline_} SHELLCODE_ID" 

7315 _aliases_ = ["sc-get",] 

7316 

7317 api_base = "http://shell-storm.org" 

7318 get_url = f"{api_base}/shellcode/files/shellcode-{ :d} .html" 

7319 

7320 def do_invoke(self, argv: list[str]) -> None: 

7321 if len(argv) != 1: 7321 ↛ 7322line 7321 didn't jump to line 7322 because the condition on line 7321 was never true

7322 err("Missing ID to download") 

7323 self.usage() 

7324 return 

7325 

7326 if not argv[0].isdigit(): 7326 ↛ 7327line 7326 didn't jump to line 7327 because the condition on line 7326 was never true

7327 err("ID is not a number") 

7328 self.usage() 

7329 return 

7330 

7331 self.get_shellcode(int(argv[0])) 

7332 return 

7333 

7334 def get_shellcode(self, sid: int) -> None: 

7335 info(f"Downloading shellcode id={sid}") 

7336 res = http_get(self.get_url.format(sid)) 

7337 if res is None: 

7338 err(f"Failed to fetch shellcode #{sid}") 

7339 return 

7340 

7341 ok("Downloaded, written to disk...") 

7342 with tempfile.NamedTemporaryFile(prefix="sc-", suffix=".txt", mode='w+b', delete=False, dir=gef.config["gef.tempdir"]) as fd: 

7343 shellcode = res.split(b"<pre>")[1].split(b"</pre>")[0] 

7344 shellcode = shellcode.replace(b"&quot;", b'"') 

7345 fd.write(shellcode) 

7346 ok(f"Shellcode written to '{fd.name}'") 

7347 return 

7348 

7349 

7350@register 

7351class ProcessListingCommand(GenericCommand): 

7352 """List and filter process. If a PATTERN is given as argument, results shown will be grepped 

7353 by this pattern.""" 

7354 

7355 _cmdline_ = "process-search" 

7356 _syntax_ = f"{_cmdline_} [-h] [--attach] [--smart-scan] [REGEX_PATTERN]" 

7357 _aliases_ = ["ps"] 

7358 _example_ = f"{_cmdline_} gdb.*" 

7359 

7360 def __init__(self) -> None: 

7361 super().__init__(complete=gdb.COMPLETE_LOCATION) 

7362 self["ps_command"] = (f"{gef.session.constants['ps']} auxww", "`ps` command to get process information") 

7363 return 

7364 

7365 @parse_arguments({"pattern": ""}, {"--attach": False, "--smart-scan": False}) 

7366 def do_invoke(self, _: list, **kwargs: Any) -> None: 

7367 args : argparse.Namespace = kwargs["arguments"] 

7368 do_attach = args.attach 

7369 smart_scan = args.smart_scan 

7370 pattern = args.pattern 

7371 pattern = re.compile("^.*$") if not args else re.compile(pattern) 

7372 

7373 for process in self.get_processes(): 

7374 pid = int(process["pid"]) 

7375 command = process["command"] 

7376 

7377 if not re.search(pattern, command): 

7378 continue 

7379 

7380 if smart_scan: 7380 ↛ 7381line 7380 didn't jump to line 7381 because the condition on line 7380 was never true

7381 if command.startswith("[") and command.endswith("]"): continue 

7382 if command.startswith("socat "): continue 

7383 if command.startswith("grep "): continue 

7384 if command.startswith("gdb "): continue 

7385 

7386 if args and do_attach: 7386 ↛ 7387line 7386 didn't jump to line 7387 because the condition on line 7386 was never true

7387 ok(f"Attaching to process='{process['command']}' pid={pid:d}") 

7388 gdb.execute(f"attach {pid:d}") 

7389 return None 

7390 

7391 line = [process[i] for i in ("pid", "user", "cpu", "mem", "tty", "command")] 

7392 gef_print("\t\t".join(line)) 

7393 

7394 return None 

7395 

7396 def get_processes(self) -> Generator[dict[str, str], None, None]: 

7397 output = gef_execute_external(self["ps_command"].split(), True) 

7398 names = [x.lower().replace("%", "") for x in output[0].split()] 

7399 

7400 for line in output[1:]: 

7401 fields = line.split() 

7402 t = {} 

7403 

7404 for i, name in enumerate(names): 

7405 if i == len(names) - 1: 

7406 t[name] = " ".join(fields[i:]) 

7407 else: 

7408 t[name] = fields[i] 

7409 

7410 yield t 

7411 

7412 return 

7413 

7414 

7415@register 

7416class ElfInfoCommand(GenericCommand): 

7417 """Display a limited subset of ELF header information. If no argument is provided, the command will 

7418 show information about the current ELF being debugged.""" 

7419 

7420 _cmdline_ = "elf-info" 

7421 _syntax_ = f"{_cmdline_} [FILE]" 

7422 _example_ = f"{_cmdline_} /bin/ls" 

7423 

7424 def __init__(self) -> None: 

7425 super().__init__(complete=gdb.COMPLETE_LOCATION) 

7426 return 

7427 

7428 @parse_arguments({}, {"--filename": ""}) 

7429 def do_invoke(self, _: list[str], **kwargs: Any) -> None: 

7430 args : argparse.Namespace = kwargs["arguments"] 

7431 

7432 if is_qemu_system(): 7432 ↛ 7433line 7432 didn't jump to line 7433 because the condition on line 7432 was never true

7433 err("Unsupported") 

7434 return 

7435 

7436 filename = args.filename or get_filepath() 

7437 if filename is None: 7437 ↛ 7438line 7437 didn't jump to line 7438 because the condition on line 7437 was never true

7438 return 

7439 

7440 try: 

7441 elf = Elf(filename) 

7442 except ValueError: 

7443 err(f"`{filename}` is an invalid value for ELF file") 

7444 return 

7445 

7446 data = [ 

7447 ("Magic", f"{hexdump(struct.pack('>I', elf.e_magic), show_raw=True)}"), 

7448 ("Class", f"{elf.e_class.value:#x} - {elf.e_class.name}"), 

7449 ("Endianness", f"{elf.e_endianness.value:#x} - {Endianness(elf.e_endianness).name}"), 

7450 ("Version", f"{elf.e_eiversion:#x}"), 

7451 ("OS ABI", f"{elf.e_osabi.value:#x} - {elf.e_osabi.name if elf.e_osabi else ''}"), 

7452 ("ABI Version", f"{elf.e_abiversion:#x}"), 

7453 ("Type", f"{elf.e_type.value:#x} - {elf.e_type.name}"), 

7454 ("Machine", f"{elf.e_machine.value:#x} - {elf.e_machine.name}"), 

7455 ("Program Header Table", f"{format_address(elf.e_phoff)}"), 

7456 ("Section Header Table", f"{format_address(elf.e_shoff)}"), 

7457 ("Header Table", f"{format_address(elf.e_phoff)}"), 

7458 ("ELF Version", f"{elf.e_version:#x}"), 

7459 ("Header size", f"{elf.e_ehsize} ({elf.e_ehsize:#x})"), 

7460 ("Entry point", f"{format_address(elf.e_entry)}"), 

7461 ] 

7462 

7463 for title, content in data: 

7464 gef_print(f"{Color.boldify(f'{title:<22}')}: {content}") 

7465 

7466 gef_print("") 

7467 gef_print(titlify("Program Header")) 

7468 

7469 gef_print(f" [{'#':>2s}] {'Type':12s} {'Offset':>8s} {'Virtaddr':>10s} {'Physaddr':>10s}" 

7470 f" {'FileSiz':>8s} {'MemSiz':>8s} {'Flags':5s} {'Align':>8s}") 

7471 

7472 for i, p in enumerate(elf.phdrs): 

7473 p_type = p.p_type.name if p.p_type else "" 

7474 p_flags = str(p.p_flags.name).lstrip("Flag.") if p.p_flags else "???" 

7475 

7476 gef_print(f" [{i:2d}] {p_type:12s} {p.p_offset:#8x} {p.p_vaddr:#10x} {p.p_paddr:#10x}" 

7477 f" {p.p_filesz:#8x} {p.p_memsz:#8x} {p_flags:5s} {p.p_align:#8x}") 

7478 

7479 gef_print("") 

7480 gef_print(titlify("Section Header")) 

7481 gef_print(f" [{'#':>2s}] {'Name':20s} {'Type':>15s} {'Address':>10s} {'Offset':>8s}" 

7482 f" {'Size':>8s} {'EntSiz':>8s} {'Flags':5s} {'Link':4s} {'Info':4s} {'Align':>8s}") 

7483 

7484 for i, s in enumerate(elf.shdrs): 

7485 sh_type = s.sh_type.name if s.sh_type else "UNKN" 

7486 sh_flags = str(s.sh_flags).lstrip("Flags.") if s.sh_flags else "UNKN" 

7487 

7488 gef_print(f" [{i:2d}] {s.name:20s} {sh_type:>15s} {s.sh_addr:#10x} {s.sh_offset:#8x} " 

7489 f"{s.sh_size:#8x} {s.sh_entsize:#8x} {sh_flags:5s} {s.sh_link:#4x} {s.sh_info:#4x} {s.sh_addralign:#8x}") 

7490 return 

7491 

7492 

7493@register 

7494class EntryPointBreakCommand(GenericCommand): 

7495 """Tries to find best entry point and sets a temporary breakpoint on it. The command will test for 

7496 well-known symbols for entry points, such as `main`, `_main`, `__libc_start_main`, etc. defined by 

7497 the setting `entrypoint_symbols`.""" 

7498 

7499 _cmdline_ = "entry-break" 

7500 _syntax_ = _cmdline_ 

7501 _aliases_ = ["start",] 

7502 

7503 def __init__(self) -> None: 

7504 super().__init__() 

7505 self["entrypoint_symbols"] = ("main _main __libc_start_main __uClibc_main start _start", "Possible symbols for entry points") 

7506 return 

7507 

7508 def do_invoke(self, argv: list[str]) -> None: 

7509 fpath = get_filepath() 

7510 if fpath is None: 7510 ↛ 7511line 7510 didn't jump to line 7511 because the condition on line 7510 was never true

7511 warn("No executable to debug, use `file` to load a binary") 

7512 return 

7513 

7514 if not os.access(fpath, os.X_OK): 7514 ↛ 7515line 7514 didn't jump to line 7515 because the condition on line 7514 was never true

7515 warn(f"The file '{fpath}' is not executable.") 

7516 return 

7517 

7518 if is_alive() and not gef.session.qemu_mode: 

7519 warn("gdb is already running") 

7520 return 

7521 

7522 bp = None 

7523 entrypoints = self["entrypoint_symbols"].split() 

7524 

7525 for sym in entrypoints: 

7526 try: 

7527 value = parse_address(sym) 

7528 info(f"Breaking at '{value:#x}'") 

7529 bp = EntryBreakBreakpoint(sym) 

7530 gdb.execute(f"run {' '.join(argv)}") 

7531 return 

7532 

7533 except gdb.error as gdb_error: 

7534 if 'The "remote" target does not support "run".' in str(gdb_error): 7534 ↛ 7536line 7534 didn't jump to line 7536 because the condition on line 7534 was never true

7535 # this case can happen when doing remote debugging 

7536 gdb.execute("continue") 

7537 return 

7538 continue 

7539 

7540 # if here, clear the breakpoint if any set 

7541 if bp: 7541 ↛ 7542line 7541 didn't jump to line 7542 because the condition on line 7541 was never true

7542 bp.delete() 

7543 

7544 assert gef.binary 

7545 # break at entry point 

7546 entry = gef.binary.entry_point 

7547 

7548 if is_pie(fpath): 7548 ↛ 7553line 7548 didn't jump to line 7553 because the condition on line 7548 was always true

7549 self.set_init_tbreak_pie(entry, argv) 

7550 gdb.execute("continue") 

7551 return 

7552 

7553 self.set_init_tbreak(entry) 

7554 gdb.execute(f"run {' '.join(argv)}") 

7555 return 

7556 

7557 def set_init_tbreak(self, addr: int) -> EntryBreakBreakpoint: 

7558 info(f"Breaking at entry-point: {addr:#x}") 

7559 bp = EntryBreakBreakpoint(f"*{addr:#x}") 

7560 return bp 

7561 

7562 def set_init_tbreak_pie(self, addr: int, argv: list[str]) -> EntryBreakBreakpoint: 

7563 warn("PIC binary detected, retrieving text base address") 

7564 gdb.execute("set stop-on-solib-events 1") 

7565 hide_context() 

7566 gdb.execute(f"run {' '.join(argv)}") 

7567 unhide_context() 

7568 gdb.execute("set stop-on-solib-events 0") 

7569 vmmap = gef.memory.maps 

7570 base_address = [x.page_start for x in vmmap if x.path == get_filepath()][0] 

7571 return self.set_init_tbreak(base_address + addr) 

7572 

7573 

7574@register 

7575class NamedBreakpointCommand(GenericCommand): 

7576 """Sets a breakpoint and assigns a name to it, which will be shown, when it's hit.""" 

7577 

7578 _cmdline_ = "name-break" 

7579 _syntax_ = f"{_cmdline_} name [address]" 

7580 _aliases_ = ["nb",] 

7581 _example = f"{_cmdline_} main *0x4008a9" 

7582 

7583 def __init__(self) -> None: 

7584 super().__init__() 

7585 return 

7586 

7587 @parse_arguments({"name": "", "address": "*$pc"}, {}) 

7588 def do_invoke(self, _: list[str], **kwargs: Any) -> None: 

7589 args : argparse.Namespace = kwargs["arguments"] 

7590 if not args.name: 7590 ↛ 7591line 7590 didn't jump to line 7591 because the condition on line 7590 was never true

7591 err("Missing name for breakpoint") 

7592 self.usage() 

7593 return 

7594 

7595 NamedBreakpoint(args.address, args.name) 

7596 return 

7597 

7598 

7599@register 

7600class ContextCommand(GenericCommand): 

7601 """Displays a comprehensive and modular summary of runtime context. Unless setting `enable` is 

7602 set to False, this command will be spawned automatically every time GDB hits a breakpoint, a 

7603 watchpoint, or any kind of interrupt. By default, it will show panes that contain the register 

7604 states, the stack, and the disassembly code around $pc.""" 

7605 

7606 _cmdline_ = "context" 

7607 _syntax_ = f"{_cmdline_} [legend|regs|stack|code|args|memory|source|trace|threads|extra]" 

7608 _aliases_ = ["ctx",] 

7609 

7610 old_registers: dict[str, int | None] = {} 

7611 

7612 def __init__(self) -> None: 

7613 super().__init__() 

7614 self["enable"] = (True, "Enable/disable printing the context when breaking") 

7615 self["show_source_code_variable_values"] = (True, "Show extra PC context info in the source code") 

7616 self["show_full_source_file_name_max_len"] = (30, "Show full source path name, if less than this value") 

7617 self["show_basename_source_file_name_max_len"] = (20, "Show the source basename in full, if less than this value") 

7618 self["show_prefix_source_path_name_len"] = (10, "When truncating source path, show this many path prefix characters") 

7619 self["show_stack_raw"] = (False, "Show the stack pane as raw hexdump (no dereference)") 

7620 self["show_registers_raw"] = (False, "Show the registers pane with raw values (no dereference)") 

7621 self["show_opcodes_size"] = (0, "Number of bytes of opcodes to display next to the disassembly") 

7622 self["peek_calls"] = (True, "Peek into calls") 

7623 self["peek_ret"] = (True, "Peek at return address") 

7624 self["nb_lines_stack"] = (8, "Number of line in the stack pane") 

7625 self["grow_stack_down"] = (False, "Order of stack downward starts at largest down to stack pointer") 

7626 self["nb_lines_backtrace"] = (10, "Number of line in the backtrace pane") 

7627 self["nb_lines_backtrace_before"] = (2, "Number of line in the backtrace pane before selected frame") 

7628 self["nb_lines_threads"] = (-1, "Number of line in the threads pane") 

7629 self["nb_lines_code"] = (6, "Number of instruction after $pc") 

7630 self["nb_lines_code_prev"] = (3, "Number of instruction before $pc") 

7631 self["ignore_registers"] = ("", "Space-separated list of registers not to display (e.g. '$cs $ds $gs')") 

7632 self["clear_screen"] = (True, "Clear the screen before printing the context") 

7633 self["layout"] = ("legend regs stack code args source memory threads trace extra", "Change the order/presence of the context sections") 

7634 self["redirect"] = ("", "Redirect the context information to another TTY") 

7635 self["libc_args"] = (False, "[DEPRECATED - Unused] Show libc function call args description") 

7636 self["libc_args_path"] = ("", "[DEPRECATED - Unused] Path to libc function call args json files, provided via gef-extras") 

7637 

7638 self.layout_mapping: dict[str, tuple[Callable, Callable | None, Callable | None]] = { 

7639 "legend": (self.show_legend, None, None), 

7640 "regs": (self.context_regs, None, None), 

7641 "stack": (self.context_stack, None, None), 

7642 "code": (self.context_code, None, None), 

7643 "args": (self.context_args, None, None), 

7644 "memory": (self.context_memory, None, None), 

7645 "source": (self.context_source, None, None), 

7646 "trace": (self.context_trace, None, None), 

7647 "threads": (self.context_threads, None, None), 

7648 "extra": (self.context_additional_information, None, None), 

7649 } 

7650 

7651 self.instruction_iterator = gef_disassemble 

7652 return 

7653 

7654 def post_load(self) -> None: 

7655 gef_on_continue_hook(self.update_registers) 

7656 gef_on_continue_hook(self.empty_extra_messages) 

7657 return 

7658 

7659 def show_legend(self) -> None: 

7660 if gef.config["gef.disable_color"] is True: 7660 ↛ 7662line 7660 didn't jump to line 7662 because the condition on line 7660 was always true

7661 return 

7662 changed_register_title = Color.colorify("Modified register", gef.config["theme.registers_value_changed"]) 

7663 code_title = Color.colorify("Code", gef.config["theme.address_code"]) 

7664 heap_title = Color.colorify("Heap", gef.config["theme.address_heap"]) 

7665 stack_title = Color.colorify("Stack", gef.config["theme.address_stack"]) 

7666 str_title = Color.colorify("String", gef.config["theme.dereference_string"]) 

7667 gef_print(f"[ Legend: {changed_register_title} | {code_title} | {heap_title} | {stack_title} | {str_title} ]") 

7668 return 

7669 

7670 @only_if_gdb_running 

7671 def do_invoke(self, argv: list[str]) -> None: 

7672 if not self["enable"] or gef.ui.context_hidden: 

7673 return 

7674 

7675 if not all(_ in self.layout_mapping for _ in argv): 7675 ↛ 7676line 7675 didn't jump to line 7676 because the condition on line 7675 was never true

7676 self.usage() 

7677 return 

7678 

7679 if len(argv) > 0: 7679 ↛ 7680line 7679 didn't jump to line 7680 because the condition on line 7679 was never true

7680 current_layout = argv 

7681 else: 

7682 current_layout = self["layout"].strip().split() 

7683 

7684 if not current_layout: 7684 ↛ 7685line 7684 didn't jump to line 7685 because the condition on line 7684 was never true

7685 return 

7686 

7687 self.tty_rows, self.tty_columns = get_terminal_size() 

7688 

7689 redirect = self["redirect"] 

7690 if redirect and os.access(redirect, os.W_OK): 7690 ↛ 7691line 7690 didn't jump to line 7691 because the condition on line 7690 was never true

7691 enable_redirect_output(to_file=redirect) 

7692 

7693 if self["clear_screen"] and len(argv) == 0: 7693 ↛ 7696line 7693 didn't jump to line 7696 because the condition on line 7693 was always true

7694 clear_screen(redirect) 

7695 

7696 for section in current_layout: 

7697 if section[0] == "-": 7697 ↛ 7698line 7697 didn't jump to line 7698 because the condition on line 7697 was never true

7698 continue 

7699 

7700 try: 

7701 display_pane_function, pane_title_function, condition = self.layout_mapping[section] 

7702 if condition: 7702 ↛ 7703line 7702 didn't jump to line 7703 because the condition on line 7702 was never true

7703 if not condition(): 

7704 continue 

7705 if pane_title_function: 7705 ↛ 7706line 7705 didn't jump to line 7706 because the condition on line 7705 was never true

7706 self.context_title(pane_title_function()) 

7707 display_pane_function() 

7708 except gdb.MemoryError as e: 

7709 # a MemoryError will happen when $pc is corrupted (invalid address) 

7710 err(str(e)) 

7711 except IndexError: 

7712 # the `section` is not present, just skip 

7713 pass 

7714 

7715 self.context_title("") 

7716 

7717 if redirect and os.access(redirect, os.W_OK): 7717 ↛ 7718line 7717 didn't jump to line 7718 because the condition on line 7717 was never true

7718 disable_redirect_output() 

7719 return 

7720 

7721 def context_title(self, m: str | None) -> None: 

7722 # allow for not displaying a title line 

7723 if m is None: 7723 ↛ 7724line 7723 didn't jump to line 7724 because the condition on line 7723 was never true

7724 return 

7725 

7726 line_color = gef.config["theme.context_title_line"] 

7727 msg_color = gef.config["theme.context_title_message"] 

7728 

7729 # print an empty line in case of "" 

7730 if not m: 

7731 gef_print(Color.colorify(HORIZONTAL_LINE * self.tty_columns, line_color)) 

7732 return 

7733 

7734 trail_len = len(m) + 6 

7735 title = "" 

7736 width = max(self.tty_columns - trail_len, 0) 

7737 padd = HORIZONTAL_LINE 

7738 title += Color.colorify(f"{'':{padd}<{width}} ", line_color) 

7739 title += Color.colorify(m, msg_color) 

7740 title += Color.colorify(f" {'':{padd}<4}", line_color) 

7741 gef_print(title) 

7742 return 

7743 

7744 def context_regs(self) -> None: 

7745 self.context_title("registers") 

7746 ignored_registers = set(self["ignore_registers"].split()) 

7747 

7748 # Defer to DetailRegisters by default 

7749 if self["show_registers_raw"] is False: 7749 ↛ 7755line 7749 didn't jump to line 7755 because the condition on line 7749 was always true

7750 regs = [reg for reg in gef.arch.all_registers if reg not in ignored_registers] 

7751 printable_registers = " ".join(regs) 

7752 gdb.execute(f"registers {printable_registers}") 

7753 return 

7754 

7755 widest = curlen = max(map(len, gef.arch.all_registers)) 

7756 curlen += 5 

7757 curlen += gef.arch.ptrsize * 2 

7758 nb = get_terminal_size()[1] // curlen 

7759 i = 1 

7760 line = "" 

7761 changed_color = gef.config["theme.registers_value_changed"] 

7762 regname_color = gef.config["theme.registers_register_name"] 

7763 

7764 for reg in gef.arch.all_registers: 

7765 if reg in ignored_registers: 

7766 continue 

7767 

7768 try: 

7769 r = gdb.parse_and_eval(reg) 

7770 if r.type.code == gdb.TYPE_CODE_VOID: 

7771 continue 

7772 

7773 new_value_type_flag = r.type.code == gdb.TYPE_CODE_FLAGS 

7774 new_value = int(r) 

7775 

7776 except (gdb.MemoryError, gdb.error): 

7777 # If this exception is triggered, it means that the current register 

7778 # is corrupted. Just use the register "raw" value (not eval-ed) 

7779 new_value = gef.arch.register(reg) 

7780 new_value_type_flag = False 

7781 

7782 except Exception: 

7783 new_value = 0 

7784 new_value_type_flag = False 

7785 

7786 old_value = self.old_registers.get(reg, 0) 

7787 

7788 padreg = reg.ljust(widest, " ") 

7789 value = align_address(new_value) 

7790 old_value = align_address(old_value or 0) 

7791 if value == old_value: 

7792 line += f"{Color.colorify(padreg, regname_color)}: " 

7793 else: 

7794 line += f"{Color.colorify(padreg, changed_color)}: " 

7795 if new_value_type_flag: 

7796 line += f"{format_address_spaces(value)} " 

7797 else: 

7798 addr = lookup_address(align_address(int(value))) 

7799 if addr.valid: 

7800 line += f"{addr!s} " 

7801 else: 

7802 line += f"{format_address_spaces(value)} " 

7803 

7804 if i % nb == 0: 

7805 gef_print(line) 

7806 line = "" 

7807 i += 1 

7808 

7809 if line: 

7810 gef_print(line) 

7811 

7812 gef_print(f"Flags: {gef.arch.flag_register_to_human()}") 

7813 return 

7814 

7815 def context_stack(self) -> None: 

7816 self.context_title("stack") 

7817 

7818 show_raw = self["show_stack_raw"] 

7819 nb_lines = self["nb_lines_stack"] 

7820 

7821 try: 

7822 sp = gef.arch.sp 

7823 if show_raw is True: 7823 ↛ 7824line 7823 didn't jump to line 7824 because the condition on line 7823 was never true

7824 mem = gef.memory.read(sp, 0x10 * nb_lines) 

7825 gef_print(hexdump(mem, base=sp)) 

7826 else: 

7827 gdb.execute(f"dereference -l {nb_lines:d} {sp:#x}") 

7828 

7829 except gdb.MemoryError: 

7830 err("Cannot read memory from $SP (corrupted stack pointer?)") 

7831 

7832 return 

7833 

7834 def addr_has_breakpoint(self, address: int, bp_locations: list[str]) -> bool: 

7835 return any(hex(address) in b for b in bp_locations) 

7836 

7837 def context_code(self) -> None: 

7838 nb_insn = self["nb_lines_code"] 

7839 nb_insn_prev = self["nb_lines_code_prev"] 

7840 show_opcodes_size = "show_opcodes_size" in self and self["show_opcodes_size"] 

7841 past_insns_color = gef.config["theme.old_context"] 

7842 cur_insn_color = gef.config["theme.disassemble_current_instruction"] 

7843 pc = gef.arch.pc 

7844 breakpoints = gdb.breakpoints() or [] 

7845 # breakpoint.locations was introduced in gdb 13.1 

7846 if len(breakpoints) and hasattr(breakpoints[-1], "locations"): 

7847 bp_locations = [hex(location.address) for b in breakpoints for location in b.locations if location is not None] # type: ignore 

7848 else: 

7849 # location relies on the user setting the breakpoints with "b *{hex(address)}" 

7850 bp_locations = [b.location for b in breakpoints if b.location and b.location.startswith("*")] 

7851 

7852 frame = gdb.selected_frame() 

7853 arch_name = f"{gef.arch.arch.lower()}:{gef.arch.mode}" 

7854 

7855 self.context_title(f"code:{arch_name}") 

7856 

7857 try: 

7858 

7859 

7860 for insn in self.instruction_iterator(pc, nb_insn, nb_prev=nb_insn_prev): 

7861 line = [] 

7862 is_taken = False 

7863 target = None 

7864 bp_prefix = Color.redify(BP_GLYPH) if self.addr_has_breakpoint(insn.address, bp_locations) else " " 

7865 

7866 if show_opcodes_size == 0: 

7867 text = str(insn) 

7868 else: 

7869 insn_fmt = f"{ :{show_opcodes_size}o} " 

7870 text = insn_fmt.format(insn) 

7871 

7872 if insn.address < pc: 

7873 line += f"{bp_prefix} {Color.colorify(text, past_insns_color)}" 

7874 

7875 elif insn.address == pc: 

7876 line += f"{bp_prefix}{Color.colorify(f'{RIGHT_ARROW[1:]}{text}', cur_insn_color)}" 

7877 

7878 if gef.arch.is_conditional_branch(insn): 7878 ↛ 7879line 7878 didn't jump to line 7879 because the condition on line 7878 was never true

7879 is_taken, reason = gef.arch.is_branch_taken(insn) 

7880 if is_taken: 

7881 target = insn.operands[-1].split()[0] 

7882 reason = f"[Reason: {reason}]" if reason else "" 

7883 line += Color.colorify(f"\tTAKEN {reason}", "bold green") 

7884 else: 

7885 reason = f"[Reason: !({reason})]" if reason else "" 

7886 line += Color.colorify(f"\tNOT taken {reason}", "bold red") 

7887 elif gef.arch.is_call(insn) and self["peek_calls"] is True: 7887 ↛ 7888line 7887 didn't jump to line 7888 because the condition on line 7887 was never true

7888 target = insn.operands[-1].split()[0] 

7889 elif gef.arch.is_ret(insn) and self["peek_ret"] is True: 

7890 target = gef.arch.get_ra(insn, frame) 

7891 

7892 else: 

7893 line += f"{bp_prefix} {text}" 

7894 

7895 gef_print("".join(line)) 

7896 

7897 if target: 

7898 try: 

7899 address = int(target, 0) if isinstance(target, str) else target 

7900 except ValueError: 

7901 # If the operand isn't an address right now we can't parse it 

7902 continue 

7903 for i, tinsn in enumerate(self.instruction_iterator(address, nb_insn)): 7903 ↛ anywhereline 7903 didn't jump anywhere: it always raised an exception.

7904 text= f" {DOWN_ARROW if i == 0 else ' '} {tinsn!s}" 

7905 gef_print(text) 

7906 break 

7907 

7908 except gdb.MemoryError: 

7909 err("Cannot disassemble from $PC") 

7910 return 

7911 

7912 def context_args(self) -> None: 

7913 insn = gef_current_instruction(gef.arch.pc) 

7914 if not gef.arch.is_call(insn): 7914 ↛ 7917line 7914 didn't jump to line 7917 because the condition on line 7914 was always true

7915 return 

7916 

7917 self.size2type = { 

7918 1: "BYTE", 

7919 2: "WORD", 

7920 4: "DWORD", 

7921 8: "QWORD", 

7922 } 

7923 

7924 if insn.operands[-1].startswith(self.size2type[gef.arch.ptrsize]+" PTR"): 

7925 target = "*" + insn.operands[-1].split()[-1] 

7926 elif "$"+insn.operands[0] in gef.arch.all_registers: 

7927 target = f"*{gef.arch.register('$' + insn.operands[0]):#x}" 

7928 else: 

7929 # is there a symbol? 

7930 ops = " ".join(insn.operands) 

7931 if "<" in ops and ">" in ops: 

7932 # extract it 

7933 target = re.sub(r".*<([^\(> ]*).*", r"\1", ops) 

7934 else: 

7935 # it's an address, just use as is 

7936 target = re.sub(r".*(0x[a-fA-F0-9]*).*", r"\1", ops) 

7937 

7938 sym = gdb.lookup_global_symbol(target) 

7939 if sym is None: 

7940 self.print_guessed_arguments(target) 

7941 return 

7942 

7943 if sym.type and sym.type.code != gdb.TYPE_CODE_FUNC: 

7944 err(f"Symbol '{target}' is not a function: type={sym.type.code}") 

7945 return 

7946 

7947 self.print_arguments_from_symbol(target, sym) 

7948 return 

7949 

7950 def print_arguments_from_symbol(self, function_name: str, symbol: "gdb.Symbol") -> None: 

7951 """If symbols were found, parse them and print the argument adequately.""" 

7952 args = [] 

7953 fields = symbol.type.fields() if symbol.type else [] 

7954 for i, f in enumerate(fields): 

7955 if not f.type: 

7956 continue 

7957 _value = gef.arch.get_ith_parameter(i, in_func=False)[1] 

7958 _value = RIGHT_ARROW.join(dereference_from(_value)) 

7959 _name = f.name or f"var_{i}" 

7960 _type = f.type.name or self.size2type[f.type.sizeof] 

7961 args.append(f"{_type} {_name} = {_value}") 

7962 

7963 self.context_title("arguments") 

7964 

7965 if not args: 

7966 gef_print(f"{function_name} (<void>)") 

7967 return 

7968 

7969 gef_print(f"{function_name} (\n "+",\n ".join(args)+"\n)") 

7970 return 

7971 

7972 def print_guessed_arguments(self, function_name: str) -> None: 

7973 """When no symbol, read the current basic block and look for "interesting" instructions.""" 

7974 

7975 def __get_current_block_start_address() -> int | None: 

7976 pc = gef.arch.pc 

7977 max_distance = 10 * 16 

7978 try: 

7979 block = gdb.block_for_pc(pc) 

7980 block_start = block.start \ 

7981 if block is not None and (pc - block.start) <= max_distance \ 

7982 else gdb_get_nth_previous_instruction_address(pc, 5) 

7983 except RuntimeError: 

7984 block_start = gdb_get_nth_previous_instruction_address(pc, 5) 

7985 return block_start 

7986 

7987 block_start = __get_current_block_start_address() 

7988 if not block_start: 

7989 return 

7990 

7991 parameter_set: set[str] = set() 

7992 pc = gef.arch.pc 

7993 function_parameters = gef.arch.function_parameters 

7994 arg_key_color = gef.config["theme.registers_register_name"] 

7995 

7996 for insn in self.instruction_iterator(block_start, pc - block_start): 

7997 if not insn.operands: 

7998 continue 

7999 

8000 if is_x86_32(): 

8001 if insn.mnemonic == "push": 

8002 parameter_set.add(insn.operands[0]) 

8003 else: 

8004 op = "$" + insn.operands[0] 

8005 if op in function_parameters: 

8006 parameter_set.add(op) 

8007 

8008 if is_x86_64(): 

8009 # also consider extended registers 

8010 extended_registers = {"$rdi": ["$edi", "$di"], 

8011 "$rsi": ["$esi", "$si"], 

8012 "$rdx": ["$edx", "$dx"], 

8013 "$rcx": ["$ecx", "$cx"], 

8014 } 

8015 for exreg in extended_registers: 

8016 if op in extended_registers[exreg]: 

8017 parameter_set.add(exreg) 

8018 

8019 if is_x86_32(): 

8020 nb_argument = len(parameter_set) 

8021 else: 

8022 nb_argument = max([function_parameters.index(p)+1 for p in parameter_set], default=0) 

8023 

8024 args = [] 

8025 for i in range(nb_argument): 

8026 _key, _values = gef.arch.get_ith_parameter(i, in_func=False) 

8027 _values = RIGHT_ARROW.join(dereference_from(_values)) 

8028 args.append(f"{Color.colorify(_key, arg_key_color)} = {_values}") 

8029 

8030 self.context_title("arguments (guessed)") 

8031 gef_print(f"{function_name} (") 

8032 if args: 

8033 gef_print(" " + ",\n ".join(args)) 

8034 gef_print(")") 

8035 return 

8036 

8037 def line_has_breakpoint(self, file_name: str, line_number: int, bp_locations: list[str]) -> bool: 

8038 filename_line = f"{file_name}:{line_number}" 

8039 return any(filename_line in loc for loc in bp_locations) 

8040 

8041 def context_source(self) -> None: 

8042 try: 

8043 pc = gef.arch.pc 

8044 symtabline = gdb.find_pc_line(pc) 

8045 symtab = symtabline.symtab 

8046 # we subtract one because the line number returned by gdb start at 1 

8047 line_num = symtabline.line - 1 

8048 if not symtab.is_valid(): 8048 ↛ 8049line 8048 didn't jump to line 8049 because the condition on line 8048 was never true

8049 return 

8050 

8051 fpath = pathlib.Path(symtab.fullname()) 

8052 lines = [curline.rstrip() for curline in fpath.read_text().splitlines()] 

8053 

8054 except Exception: 

8055 return 

8056 

8057 file_base_name = os.path.basename(symtab.filename) 

8058 breakpoints = gdb.breakpoints() or [] 

8059 bp_locations = [b.location for b in breakpoints if b.location and file_base_name in b.location] 

8060 past_lines_color = gef.config["theme.old_context"] 

8061 

8062 show_full_path_max = self["show_full_source_file_name_max_len"] 

8063 show_basename_path_max = self["show_basename_source_file_name_max_len"] 

8064 

8065 nb_line = self["nb_lines_code"] 

8066 fn = symtab.filename 

8067 if len(fn) > show_full_path_max: 8067 ↛ 8068line 8067 didn't jump to line 8068 because the condition on line 8067 was never true

8068 base = os.path.basename(fn) 

8069 if len(base) > show_basename_path_max: 

8070 base = base[-show_basename_path_max:] 

8071 fn = fn[:15] + "[...]" + base 

8072 title = f"source:{fn}+{line_num + 1}" 

8073 cur_line_color = gef.config["theme.source_current_line"] 

8074 self.context_title(title) 

8075 show_extra_info = self["show_source_code_variable_values"] 

8076 

8077 for i in range(line_num - nb_line + 1, line_num + nb_line): 

8078 if i < 0: 

8079 continue 

8080 

8081 bp_prefix = Color.redify(BP_GLYPH) if self.line_has_breakpoint(file_base_name, i + 1, bp_locations) else " " 

8082 

8083 if i < line_num: 

8084 gef_print("{}{}".format(bp_prefix, Color.colorify(f" {i + 1:4d}\t {lines[i]}", past_lines_color))) 

8085 

8086 if i == line_num: 

8087 prefix = f"{bp_prefix}{RIGHT_ARROW[1:]}{i + 1:4d}\t " 

8088 leading = len(lines[i]) - len(lines[i].lstrip()) 

8089 if show_extra_info: 8089 ↛ 8093line 8089 didn't jump to line 8093 because the condition on line 8089 was always true

8090 extra_info = self.get_pc_context_info(pc, lines[i]) 

8091 if extra_info: 

8092 gef_print(f"{' ' * (len(prefix) + leading)}{extra_info}") 

8093 gef_print(Color.colorify(f"{prefix}{lines[i]}", cur_line_color)) 

8094 

8095 if i > line_num: 

8096 try: 

8097 gef_print(f"{bp_prefix} {i + 1:4d}\t {lines[i]}") 

8098 except IndexError: 

8099 break 

8100 return 

8101 

8102 def get_pc_context_info(self, pc: int, line: str) -> str: 

8103 try: 

8104 current_block = gdb.block_for_pc(pc) 

8105 if not current_block or not current_block.is_valid(): return "" 8105 ↛ exitline 8105 didn't return from function 'get_pc_context_info' because the return on line 8105 wasn't executed

8106 m = collections.OrderedDict() 

8107 while current_block and not current_block.is_static: 

8108 for sym in list(current_block): 

8109 symbol = sym.name 

8110 if not sym.is_function and re.search(fr"\W{symbol}\W", line): 

8111 val = gdb.parse_and_eval(symbol) 

8112 if val.type.code in (gdb.TYPE_CODE_PTR, gdb.TYPE_CODE_ARRAY): 8112 ↛ 8113line 8112 didn't jump to line 8113 because the condition on line 8112 was never true

8113 addr = int(val.address) 

8114 addrs = dereference_from(addr) 

8115 if len(addrs) > 2: 

8116 addrs = [addrs[0], "[...]", addrs[-1]] 

8117 

8118 f = f" {RIGHT_ARROW} " 

8119 val = f.join(addrs) 

8120 elif val.type.code == gdb.TYPE_CODE_INT: 8120 ↛ 8123line 8120 didn't jump to line 8123 because the condition on line 8120 was always true

8121 val = hex(int(val)) 

8122 else: 

8123 continue 

8124 

8125 if symbol not in m: 8125 ↛ 8108line 8125 didn't jump to line 8108 because the condition on line 8125 was always true

8126 m[symbol] = val 

8127 current_block = current_block.superblock 

8128 

8129 if m: 

8130 return "// " + ", ".join([f"{Color.yellowify(a)}={b}" for a, b in m.items()]) 

8131 except Exception: 

8132 pass 

8133 return "" 

8134 

8135 def context_trace(self) -> None: 

8136 self.context_title("trace") 

8137 

8138 nb_backtrace = self["nb_lines_backtrace"] 

8139 if nb_backtrace <= 0: 8139 ↛ 8140line 8139 didn't jump to line 8140 because the condition on line 8139 was never true

8140 return 

8141 

8142 # backward compat for gdb (gdb < 7.10) 

8143 if not hasattr(gdb, "FrameDecorator"): 8143 ↛ 8144line 8143 didn't jump to line 8144 because the condition on line 8143 was never true

8144 gdb.execute(f"backtrace {nb_backtrace:d}") 

8145 return 

8146 

8147 orig_frame: gdb.Frame = gdb.selected_frame() 

8148 current_frame: gdb.Frame = gdb.newest_frame() 

8149 frames = [current_frame,] 

8150 while current_frame != orig_frame and current_frame: 8150 ↛ 8151line 8150 didn't jump to line 8151 because the condition on line 8150 was never true

8151 current_frame = current_frame.older() 

8152 if not current_frame: break 

8153 frames.append(current_frame) 

8154 

8155 nb_backtrace_before = self["nb_lines_backtrace_before"] 

8156 level = max(len(frames) - nb_backtrace_before - 1, 0) 

8157 current_frame: gdb.Frame = frames[level] 

8158 

8159 while current_frame: 8159 ↛ 8199line 8159 didn't jump to line 8199 because the condition on line 8159 was always true

8160 current_frame.select() 

8161 if not current_frame.is_valid(): 8161 ↛ 8162line 8161 didn't jump to line 8162 because the condition on line 8161 was never true

8162 continue 

8163 

8164 pc = int(current_frame.pc()) 

8165 name = current_frame.name() 

8166 items = [] 

8167 items.append(f"{pc:#x}") 

8168 if name: 

8169 frame_args = gdb.FrameDecorator.FrameDecorator(current_frame).frame_args() or [] # type: ignore 

8170 symstr= ", ".join([f"{Color.yellowify(x.sym)}={x.sym.value(current_frame)!s}" for x in frame_args]) 

8171 m = f"{Color.greenify(name)}({symstr})" 

8172 items.append(m) 

8173 else: 

8174 try: 

8175 insn = next(gef_disassemble(int(pc), 1)) 

8176 except gdb.MemoryError: 

8177 break 

8178 

8179 # check if the gdb symbol table may know the address 

8180 sym_found = gdb_get_location_from_symbol(pc) 

8181 symbol = "" 

8182 if sym_found: 8182 ↛ 8183line 8182 didn't jump to line 8183 because the condition on line 8182 was never true

8183 sym_name, offset = sym_found 

8184 symbol = f" <{sym_name}+{offset:x}> " 

8185 

8186 items.append(Color.redify(f"{symbol}{insn.mnemonic} {', '.join(insn.operands)}")) 

8187 

8188 title = Color.colorify(f"#{level}", "bold green" if current_frame == orig_frame else "bold pink") 

8189 gef_print(f"[{title}] {RIGHT_ARROW.join(items)}") 

8190 older = current_frame.older() 

8191 level += 1 

8192 nb_backtrace -= 1 

8193 if nb_backtrace == 0: 

8194 break 

8195 if not older: 

8196 break 

8197 current_frame = older 

8198 

8199 orig_frame.select() 

8200 return 

8201 

8202 def context_threads(self) -> None: 

8203 def reason() -> str: 

8204 res = gdb.execute("info program", to_string=True) 

8205 if not res: 8205 ↛ 8206line 8205 didn't jump to line 8206 because the condition on line 8205 was never true

8206 return "NOT RUNNING" 

8207 

8208 for line in res.splitlines(): 8208 ↛ 8221line 8208 didn't jump to line 8221 because the loop on line 8208 didn't complete

8209 line = line.strip() 

8210 if line.startswith("It stopped with signal "): 

8211 return line.replace("It stopped with signal ", "").split(",", 1)[0] 

8212 if line == "The program being debugged is not being run.": 8212 ↛ 8213line 8212 didn't jump to line 8213 because the condition on line 8212 was never true

8213 return "NOT RUNNING" 

8214 if line == "It stopped at a breakpoint that has since been deleted.": 8214 ↛ 8215line 8214 didn't jump to line 8215 because the condition on line 8214 was never true

8215 return "TEMPORARY BREAKPOINT" 

8216 if line.startswith("It stopped at breakpoint "): 

8217 return "BREAKPOINT" 

8218 if line == "It stopped after being stepped.": 8218 ↛ 8219line 8218 didn't jump to line 8219 because the condition on line 8218 was never true

8219 return "SINGLE STEP" 

8220 

8221 return "STOPPED" 

8222 

8223 self.context_title("threads") 

8224 

8225 threads = gdb.selected_inferior().threads()[::-1] 

8226 idx = self["nb_lines_threads"] 

8227 if idx > 0: 8227 ↛ 8228line 8227 didn't jump to line 8228 because the condition on line 8227 was never true

8228 threads = threads[0:idx] 

8229 

8230 if idx == 0: 8230 ↛ 8231line 8230 didn't jump to line 8231 because the condition on line 8230 was never true

8231 return 

8232 

8233 if not threads: 8233 ↛ 8234line 8233 didn't jump to line 8234 because the condition on line 8233 was never true

8234 err("No thread selected") 

8235 return 

8236 

8237 selected_thread = gdb.selected_thread() 

8238 selected_frame = gdb.selected_frame() 

8239 

8240 for i, thread in enumerate(threads): 

8241 line = f"[{Color.colorify(f'#{i:d}', 'bold green' if thread == selected_thread else 'bold pink')}] Id {thread.num:d}, " 

8242 if thread.name: 

8243 line += f"""Name: "{thread.name}", """ 

8244 if thread.is_running(): 8244 ↛ 8245line 8244 didn't jump to line 8245 because the condition on line 8244 was never true

8245 line += Color.colorify("running", "bold green") 

8246 elif thread.is_stopped(): 8246 ↛ 8262line 8246 didn't jump to line 8262 because the condition on line 8246 was always true

8247 line += Color.colorify("stopped", "bold red") 

8248 thread.switch() 

8249 frame = gdb.selected_frame() 

8250 frame_name = frame.name() 

8251 

8252 # check if the gdb symbol table may know the address 

8253 if not frame_name: 

8254 sym_found = gdb_get_location_from_symbol(int(frame.pc())) 

8255 if sym_found: 8255 ↛ 8256line 8255 didn't jump to line 8256 because the condition on line 8255 was never true

8256 sym_name, offset = sym_found 

8257 frame_name = f"<{sym_name}+{offset:x}>" 

8258 

8259 line += (f" {Color.colorify(f'{frame.pc():#x}', 'blue')} in " 

8260 f"{Color.colorify(frame_name or '??', 'bold yellow')} (), " 

8261 f"reason: {Color.colorify(reason(), 'bold pink')}") 

8262 elif thread.is_exited(): 

8263 line += Color.colorify("exited", "bold yellow") 

8264 gef_print(line) 

8265 i += 1 

8266 

8267 selected_thread.switch() 

8268 selected_frame.select() 

8269 return 

8270 

8271 def context_additional_information(self) -> None: 

8272 if not gef.ui.context_messages: 

8273 return 

8274 

8275 self.context_title("extra") 

8276 for level, text in gef.ui.context_messages: 

8277 if level == "error": err(text) 8277 ↛ 8276line 8277 didn't jump to line 8276 because

8278 elif level == "warn": warn(text) 8278 ↛ 8279line 8278 didn't jump to line 8279 because the condition on line 8278 was always true

8279 elif level == "success": ok(text) 

8280 else: info(text) 

8281 return 

8282 

8283 def context_memory(self) -> None: 

8284 for address, opt in sorted(gef.ui.watches.items()): 

8285 sz, fmt = opt[0:2] 

8286 self.context_title(f"memory:{address:#x}") 

8287 if fmt == "pointers": 8287 ↛ 8288line 8287 didn't jump to line 8288 because the condition on line 8287 was never true

8288 gdb.execute(f"dereference -l {sz:d} {address:#x}") 

8289 else: 

8290 gdb.execute(f"hexdump {fmt} -s {sz:d} {address:#x}") 

8291 

8292 @classmethod 

8293 def update_registers(cls, _) -> None: 

8294 for reg in gef.arch.all_registers: 

8295 try: 

8296 cls.old_registers[reg] = gef.arch.register(reg) 

8297 except Exception: 

8298 cls.old_registers[reg] = 0 

8299 return 

8300 

8301 def empty_extra_messages(self, _) -> None: 

8302 gef.ui.context_messages.clear() 

8303 return 

8304 

8305 

8306@register 

8307class MemoryCommand(GenericCommand): 

8308 """Add or remove address ranges to the memory view.""" 

8309 _cmdline_ = "memory" 

8310 _syntax_ = f"{_cmdline_} (watch|unwatch|reset|list)" 

8311 

8312 def __init__(self) -> None: 

8313 super().__init__(prefix=True) 

8314 return 

8315 

8316 @only_if_gdb_running 

8317 def do_invoke(self, argv: list[str]) -> None: 

8318 self.usage() 

8319 return 

8320 

8321 

8322@register 

8323class MemoryWatchCommand(GenericCommand): 

8324 """Adds address ranges to the memory view.""" 

8325 _cmdline_ = "memory watch" 

8326 _syntax_ = f"{_cmdline_} ADDRESS [SIZE] [(qword|dword|word|byte|pointers)]" 

8327 _example_ = (f"\n{_cmdline_} 0x603000 0x100 byte" 

8328 f"\n{_cmdline_} $sp") 

8329 

8330 def __init__(self) -> None: 

8331 super().__init__(complete=gdb.COMPLETE_LOCATION) 

8332 return 

8333 

8334 @only_if_gdb_running 

8335 def do_invoke(self, argv: list[str]) -> None: 

8336 if len(argv) not in (1, 2, 3): 8336 ↛ 8337line 8336 didn't jump to line 8337 because the condition on line 8336 was never true

8337 self.usage() 

8338 return 

8339 

8340 address = parse_address(argv[0]) 

8341 size = parse_address(argv[1]) if len(argv) > 1 else 0x10 

8342 group = "byte" 

8343 

8344 if len(argv) == 3: 

8345 group = argv[2].lower() 

8346 if group not in ("qword", "dword", "word", "byte", "pointers"): 8346 ↛ 8347line 8346 didn't jump to line 8347 because the condition on line 8346 was never true

8347 warn(f"Unexpected grouping '{group}'") 

8348 self.usage() 

8349 return 

8350 else: 

8351 if gef.arch.ptrsize == 4: 8351 ↛ 8352line 8351 didn't jump to line 8352 because the condition on line 8351 was never true

8352 group = "dword" 

8353 elif gef.arch.ptrsize == 8: 8353 ↛ 8356line 8353 didn't jump to line 8356 because the condition on line 8353 was always true

8354 group = "qword" 

8355 

8356 gef.ui.watches[address] = (size, group) 

8357 ok(f"Adding memwatch to {address:#x}") 

8358 return 

8359 

8360 

8361@register 

8362class MemoryUnwatchCommand(GenericCommand): 

8363 """Removes address ranges to the memory view.""" 

8364 _cmdline_ = "memory unwatch" 

8365 _syntax_ = f"{_cmdline_} ADDRESS" 

8366 _example_ = (f"\n{_cmdline_} 0x603000" 

8367 f"\n{_cmdline_} $sp") 

8368 

8369 def __init__(self) -> None: 

8370 super().__init__(complete=gdb.COMPLETE_LOCATION) 

8371 return 

8372 

8373 @only_if_gdb_running 

8374 def do_invoke(self, argv: list[str]) -> None: 

8375 if not argv: 8375 ↛ 8376line 8375 didn't jump to line 8376 because the condition on line 8375 was never true

8376 self.usage() 

8377 return 

8378 

8379 address = parse_address(argv[0]) 

8380 res = gef.ui.watches.pop(address, None) 

8381 if not res: 8381 ↛ 8384line 8381 didn't jump to line 8384 because the condition on line 8381 was always true

8382 warn(f"You weren't watching {address:#x}") 

8383 else: 

8384 ok(f"Removed memwatch of {address:#x}") 

8385 return 

8386 

8387 

8388@register 

8389class MemoryWatchResetCommand(GenericCommand): 

8390 """Removes all watchpoints.""" 

8391 _cmdline_ = "memory reset" 

8392 _syntax_ = f"{_cmdline_}" 

8393 

8394 @only_if_gdb_running 

8395 def do_invoke(self, _: list[str]) -> None: 

8396 gef.ui.watches.clear() 

8397 ok("Memory watches cleared") 

8398 return 

8399 

8400 

8401@register 

8402class MemoryWatchListCommand(GenericCommand): 

8403 """Lists all watchpoints to display in context layout.""" 

8404 _cmdline_ = "memory list" 

8405 _syntax_ = f"{_cmdline_}" 

8406 

8407 @only_if_gdb_running 

8408 def do_invoke(self, _: list[str]) -> None: 

8409 if not gef.ui.watches: 8409 ↛ 8413line 8409 didn't jump to line 8413 because the condition on line 8409 was always true

8410 info("No memory watches") 

8411 return 

8412 

8413 info("Memory watches:") 

8414 for address, opt in sorted(gef.ui.watches.items()): 

8415 gef_print(f"- {address:#x} ({opt[0]}, {opt[1]})") 

8416 return 

8417 

8418 

8419@register 

8420class HexdumpCommand(GenericCommand): 

8421 """Display SIZE lines of hexdump from the memory location pointed by LOCATION.""" 

8422 

8423 _cmdline_ = "hexdump" 

8424 _syntax_ = f"{_cmdline_} (qword|dword|word|byte) [LOCATION] [--size SIZE] [--reverse]" 

8425 _example_ = f"{_cmdline_} byte $rsp --size 16 --reverse" 

8426 

8427 def __init__(self) -> None: 

8428 super().__init__(complete=gdb.COMPLETE_LOCATION, prefix=True) 

8429 self["always_show_ascii"] = (False, "If true, hexdump will always display the ASCII dump") 

8430 self.format: str | None = None 

8431 self.__last_target = "$sp" 

8432 return 

8433 

8434 @only_if_gdb_running 

8435 @parse_arguments({"address": "",}, {("--reverse", "-r"): False, ("--size", "-s"): 0}) 

8436 def do_invoke(self, _: list[str], **kwargs: Any) -> None: 

8437 valid_formats = ["byte", "word", "dword", "qword"] 

8438 if not self.format or self.format not in valid_formats: 8438 ↛ 8439line 8438 didn't jump to line 8439 because the condition on line 8438 was never true

8439 err("Invalid command") 

8440 return 

8441 

8442 args : argparse.Namespace = kwargs["arguments"] 

8443 target = args.address or self.__last_target 

8444 start_addr = parse_address(target) 

8445 read_from = align_address(start_addr) 

8446 

8447 if self.format == "byte": 

8448 read_len = args.size or 0x40 

8449 read_from += self.repeat_count * read_len 

8450 mem = gef.memory.read(read_from, read_len) 

8451 lines = hexdump(mem, base=read_from).splitlines() 

8452 else: 

8453 read_len = args.size or 0x10 

8454 lines = self._hexdump(read_from, read_len, self.format, self.repeat_count * read_len) 

8455 

8456 if args.reverse: 

8457 lines.reverse() 

8458 

8459 self.__last_target = target 

8460 gef_print("\n".join(lines)) 

8461 return 

8462 

8463 def _hexdump(self, start_addr: int, length: int, arrange_as: str, offset: int = 0) -> list[str]: 

8464 endianness = gef.arch.endianness 

8465 

8466 base_address_color = gef.config["theme.dereference_base_address"] 

8467 show_ascii = gef.config["hexdump.always_show_ascii"] 

8468 

8469 formats = { 

8470 "qword": ("Q", 8), 

8471 "dword": ("I", 4), 

8472 "word": ("H", 2), 

8473 } 

8474 

8475 formatter, width = formats[arrange_as] 

8476 fmt_str = f"{ base} {VERTICAL_LINE}+{ offset:#06x} { sym} { val:#0{width*2+2}x} { text} " 

8477 fmt_pack = f"{endianness!s}{formatter}" 

8478 lines = [] 

8479 

8480 i = 0 

8481 text = "" 

8482 while i < length: 

8483 cur_addr = start_addr + (i + offset) * width 

8484 sym = gdb_get_location_from_symbol(cur_addr) 

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

8486 mem = gef.memory.read(cur_addr, width) 

8487 val = struct.unpack(fmt_pack, mem)[0] 

8488 if show_ascii: 8488 ↛ 8489line 8488 didn't jump to line 8489 because the condition on line 8488 was never true

8489 text = "".join([chr(b) if 0x20 <= b < 0x7F else "." for b in mem]) 

8490 lines.append(fmt_str.format(base=Color.colorify(format_address(cur_addr), base_address_color), 

8491 offset=(i + offset) * width, sym=sym, val=val, text=text)) 

8492 i += 1 

8493 

8494 return lines 

8495 

8496 

8497@register 

8498class HexdumpQwordCommand(HexdumpCommand): 

8499 """Display SIZE lines of hexdump as QWORD from the memory location pointed by ADDRESS.""" 

8500 

8501 _cmdline_ = "hexdump qword" 

8502 _syntax_ = f"{_cmdline_} [ADDRESS] [--size SIZE] [--reverse]" 

8503 _example_ = f"{_cmdline_} qword $rsp -s 16 --reverse" 

8504 

8505 def __init__(self) -> None: 

8506 super().__init__() 

8507 self.format = "qword" 

8508 return 

8509 

8510 

8511@register 

8512class HexdumpDwordCommand(HexdumpCommand): 

8513 """Display SIZE lines of hexdump as DWORD from the memory location pointed by ADDRESS.""" 

8514 

8515 _cmdline_ = "hexdump dword" 

8516 _syntax_ = f"{_cmdline_} [ADDRESS] [--size SIZE] [--reverse]" 

8517 _example_ = f"{_cmdline_} $esp -s 16 --reverse" 

8518 

8519 def __init__(self) -> None: 

8520 super().__init__() 

8521 self.format = "dword" 

8522 return 

8523 

8524 

8525@register 

8526class HexdumpWordCommand(HexdumpCommand): 

8527 """Display SIZE lines of hexdump as WORD from the memory location pointed by ADDRESS.""" 

8528 

8529 _cmdline_ = "hexdump word" 

8530 _syntax_ = f"{_cmdline_} [ADDRESS] [--size SIZE] [--reverse]" 

8531 _example_ = f"{_cmdline_} $esp -s 16 --reverse" 

8532 

8533 def __init__(self) -> None: 

8534 super().__init__() 

8535 self.format = "word" 

8536 return 

8537 

8538 

8539@register 

8540class HexdumpByteCommand(HexdumpCommand): 

8541 """Display SIZE lines of hexdump as BYTE from the memory location pointed by ADDRESS.""" 

8542 

8543 _cmdline_ = "hexdump byte" 

8544 _syntax_ = f"{_cmdline_} [ADDRESS] [--size SIZE] [--reverse]" 

8545 _example_ = f"{_cmdline_} $rsp -s 16" 

8546 

8547 def __init__(self) -> None: 

8548 super().__init__() 

8549 self.format = "byte" 

8550 return 

8551 

8552 

8553@register 

8554class PatchCommand(GenericCommand): 

8555 """Write specified values to the specified address.""" 

8556 

8557 _cmdline_ = "patch" 

8558 _syntax_ = (f"{_cmdline_} (qword|dword|word|byte) LOCATION VALUES\n" 

8559 f"{_cmdline_} string LOCATION \"double-escaped string\"") 

8560 SUPPORTED_SIZES = { 

8561 "qword": (8, "Q"), 

8562 "dword": (4, "L"), 

8563 "word": (2, "H"), 

8564 "byte": (1, "B"), 

8565 } 

8566 

8567 def __init__(self) -> None: 

8568 super().__init__(prefix=True, complete=gdb.COMPLETE_LOCATION) 

8569 self.format: str | None = None 

8570 return 

8571 

8572 @only_if_gdb_running 

8573 @parse_arguments({"location": "", "values": ["", ]}, {}) 

8574 def do_invoke(self, _: list[str], **kwargs: Any) -> None: 

8575 args : argparse.Namespace = kwargs["arguments"] 

8576 if not self.format or self.format not in self.SUPPORTED_SIZES: 8576 ↛ 8577line 8576 didn't jump to line 8577 because the condition on line 8576 was never true

8577 self.usage() 

8578 return 

8579 

8580 if not args.location or not args.values: 8580 ↛ 8581line 8580 didn't jump to line 8581 because the condition on line 8580 was never true

8581 self.usage() 

8582 return 

8583 

8584 addr = align_address(parse_address(args.location)) 

8585 size, fcode = self.SUPPORTED_SIZES[self.format] 

8586 values = args.values 

8587 

8588 if size == 1: 

8589 if values[0].startswith("$_gef"): 

8590 var_name = values[0] 

8591 try: 

8592 values = str(gdb.parse_and_eval(var_name)).lstrip("{").rstrip("}").replace(",","").split(" ") 

8593 except Exception: 

8594 gef_print(f"Bad variable specified, check value with command: p {var_name}") 

8595 return 

8596 

8597 d = str(gef.arch.endianness) 

8598 for value in values: 

8599 value = parse_address(value) & ((1 << size * 8) - 1) 

8600 vstr = struct.pack(d + fcode, value) 

8601 gef.memory.write(addr, vstr, length=size) 

8602 addr += size 

8603 return 

8604 

8605 

8606@register 

8607class PatchQwordCommand(PatchCommand): 

8608 """Write specified QWORD to the specified address.""" 

8609 

8610 _cmdline_ = "patch qword" 

8611 _syntax_ = f"{_cmdline_} LOCATION QWORD1 [QWORD2 [QWORD3..]]" 

8612 _example_ = f"{_cmdline_} $rip 0x4141414141414141" 

8613 

8614 def __init__(self) -> None: 

8615 super().__init__() 

8616 self.format = "qword" 

8617 return 

8618 

8619 

8620@register 

8621class PatchDwordCommand(PatchCommand): 

8622 """Write specified DWORD to the specified address.""" 

8623 

8624 _cmdline_ = "patch dword" 

8625 _syntax_ = f"{_cmdline_} LOCATION DWORD1 [DWORD2 [DWORD3..]]" 

8626 _example_ = f"{_cmdline_} $rip 0x41414141" 

8627 

8628 def __init__(self) -> None: 

8629 super().__init__() 

8630 self.format = "dword" 

8631 return 

8632 

8633 

8634@register 

8635class PatchWordCommand(PatchCommand): 

8636 """Write specified WORD to the specified address.""" 

8637 

8638 _cmdline_ = "patch word" 

8639 _syntax_ = f"{_cmdline_} LOCATION WORD1 [WORD2 [WORD3..]]" 

8640 _example_ = f"{_cmdline_} $rip 0x4141" 

8641 

8642 def __init__(self) -> None: 

8643 super().__init__() 

8644 self.format = "word" 

8645 return 

8646 

8647 

8648@register 

8649class PatchByteCommand(PatchCommand): 

8650 """Write specified BYTE to the specified address.""" 

8651 

8652 _cmdline_ = "patch byte" 

8653 _syntax_ = f"{_cmdline_} LOCATION BYTE1 [BYTE2 [BYTE3..]]" 

8654 _example_ = f"{_cmdline_} $pc 0x41 0x41 0x41 0x41 0x41" 

8655 

8656 def __init__(self) -> None: 

8657 super().__init__() 

8658 self.format = "byte" 

8659 return 

8660 

8661 

8662@register 

8663class PatchStringCommand(GenericCommand): 

8664 """Write specified string to the specified memory location pointed by ADDRESS.""" 

8665 

8666 _cmdline_ = "patch string" 

8667 _syntax_ = f"{_cmdline_} ADDRESS \"double backslash-escaped string\"" 

8668 _example_ = [ 

8669 f"{_cmdline_} $sp \"GEFROCKS\"", 

8670 f"{_cmdline_} $sp \"\\\\x41\\\\x41\\\\x41\\\\x41\"" 

8671 ] 

8672 

8673 @only_if_gdb_running 

8674 def do_invoke(self, argv: list[str]) -> None: 

8675 argc = len(argv) 

8676 if argc != 2: 8676 ↛ 8677line 8676 didn't jump to line 8677 because the condition on line 8676 was never true

8677 self.usage() 

8678 return 

8679 

8680 location, msg = argv[0:2] 

8681 addr = align_address(parse_address(location)) 

8682 

8683 try: 

8684 msg_as_bytes = codecs.escape_decode(msg, "utf-8")[0] 

8685 gef.memory.write(addr, msg_as_bytes, len(msg_as_bytes)) # type: ignore 

8686 except (binascii.Error, gdb.error): 

8687 err(f"Could not decode '\\xXX' encoded string \"{msg}\"") 

8688 return 

8689 

8690 

8691@lru_cache() 

8692def dereference_from(address: int) -> list[str]: 

8693 if not is_alive(): 8693 ↛ 8694line 8693 didn't jump to line 8694 because the condition on line 8693 was never true

8694 return [format_address(address),] 

8695 

8696 code_color = gef.config["theme.dereference_code"] 

8697 string_color = gef.config["theme.dereference_string"] 

8698 max_recursion = gef.config["dereference.max_recursion"] or 10 

8699 addr = lookup_address(align_address(address)) 

8700 msg = [format_address(addr.value),] 

8701 seen_addrs = set() 

8702 

8703 while addr.section and max_recursion: 

8704 if addr.value in seen_addrs: 

8705 msg.append("[loop detected]") 

8706 break 

8707 seen_addrs.add(addr.value) 

8708 

8709 max_recursion -= 1 

8710 

8711 # Is this value a pointer or a value? 

8712 # -- If it's a pointer, dereference 

8713 deref = addr.dereference() 

8714 if deref is None: 

8715 # if here, dereferencing addr has triggered a MemoryError, no need to go further 

8716 msg.append(str(addr)) 

8717 break 

8718 

8719 new_addr = lookup_address(deref) 

8720 if new_addr.valid: 

8721 addr = new_addr 

8722 msg.append(str(addr)) 

8723 continue 

8724 

8725 # -- Otherwise try to parse the value 

8726 if addr.section: 8726 ↛ 8747line 8726 didn't jump to line 8747 because the condition on line 8726 was always true

8727 if addr.section.is_executable() and addr.is_in_text_segment() and not is_ascii_string(addr.value): 

8728 insn = gef_current_instruction(addr.value) 

8729 insn_str = f"{insn.location} {insn.mnemonic} {', '.join(insn.operands)}" 

8730 msg.append(Color.colorify(insn_str, code_color)) 

8731 break 

8732 

8733 elif addr.section.permission & Permission.READ: 8733 ↛ 8747line 8733 didn't jump to line 8747 because the condition on line 8733 was always true

8734 if is_ascii_string(addr.value): 

8735 s = gef.memory.read_cstring(addr.value) 

8736 if len(s) < gef.arch.ptrsize: 

8737 txt = f'{format_address(deref)} ("{Color.colorify(s, string_color)}"?)' 

8738 elif len(s) > GEF_MAX_STRING_LENGTH: 

8739 txt = Color.colorify(f'"{s[:GEF_MAX_STRING_LENGTH]}[...]"', string_color) 

8740 else: 

8741 txt = Color.colorify(f'"{s}"', string_color) 

8742 

8743 msg.append(txt) 

8744 break 

8745 

8746 # if not able to parse cleanly, simply display and break 

8747 msg.append(format_address(deref)) 

8748 break 

8749 

8750 return msg 

8751 

8752 

8753@register 

8754class DereferenceCommand(GenericCommand): 

8755 """Dereference recursively from an address and display information. This acts like WinDBG `dps` 

8756 command.""" 

8757 

8758 _cmdline_ = "dereference" 

8759 _syntax_ = f"{_cmdline_} [-h] [--length LENGTH] [--reference REFERENCE] [address]" 

8760 _aliases_ = ["telescope", ] 

8761 _example_ = f"{_cmdline_} --length 20 --reference $sp+0x10 $sp" 

8762 

8763 def __init__(self) -> None: 

8764 super().__init__(complete=gdb.COMPLETE_LOCATION) 

8765 self["max_recursion"] = (7, "Maximum level of pointer recursion") 

8766 return 

8767 

8768 @staticmethod 

8769 def pprint_dereferenced(addr: int, idx: int, base_offset: int = 0) -> str: 

8770 base_address_color = gef.config["theme.dereference_base_address"] 

8771 registers_color = gef.config["theme.dereference_register_value"] 

8772 

8773 sep = f" {RIGHT_ARROW} " 

8774 memalign = gef.arch.ptrsize 

8775 

8776 offset = idx * memalign 

8777 current_address = align_address(addr + offset) 

8778 addrs = dereference_from(current_address) 

8779 addr_l = format_address(int(addrs[0], 16)) 

8780 ma = (memalign*2 + 2) 

8781 line = ( 

8782 f"{Color.colorify(addr_l, base_address_color)}{VERTICAL_LINE}" 

8783 f"{base_offset+offset:+#07x}: {sep.join(addrs[1:]):{ma}s}" 

8784 ) 

8785 

8786 register_hints = [] 

8787 

8788 for regname in gef.arch.all_registers: 

8789 regvalue = gef.arch.register(regname) 

8790 if current_address == regvalue: 

8791 register_hints.append(regname) 

8792 

8793 if register_hints: 

8794 m = f"\t{LEFT_ARROW}{', '.join(list(register_hints))}" 

8795 line += Color.colorify(m, registers_color) 

8796 

8797 offset += memalign 

8798 return line 

8799 

8800 @only_if_gdb_running 

8801 @parse_arguments({"address": "$sp"}, {("-r", "--reference"): "", ("-l", "--length"): 10}) 

8802 def do_invoke(self, _: list[str], **kwargs: Any) -> None: 

8803 args : argparse.Namespace = kwargs["arguments"] 

8804 nb = args.length 

8805 

8806 target = args.address 

8807 target_addr = parse_address(target) 

8808 

8809 reference = args.reference or target 

8810 ref_addr = parse_address(reference) 

8811 

8812 if process_lookup_address(target_addr) is None: 

8813 err(f"Unmapped address: '{target}'") 

8814 return 

8815 

8816 if process_lookup_address(ref_addr) is None: 8816 ↛ 8817line 8816 didn't jump to line 8817 because the condition on line 8816 was never true

8817 err(f"Unmapped address: '{reference}'") 

8818 return 

8819 

8820 if gef.config["context.grow_stack_down"] is True: 8820 ↛ 8821line 8820 didn't jump to line 8821 because the condition on line 8820 was never true

8821 insnum_step = -1 

8822 if nb > 0: 

8823 from_insnum = nb * (self.repeat_count + 1) - 1 

8824 to_insnum = self.repeat_count * nb - 1 

8825 else: 

8826 from_insnum = self.repeat_count * nb 

8827 to_insnum = nb * (self.repeat_count + 1) 

8828 else: 

8829 insnum_step = 1 

8830 if nb > 0: 

8831 from_insnum = self.repeat_count * nb 

8832 to_insnum = nb * (self.repeat_count + 1) 

8833 else: 

8834 from_insnum = nb * (self.repeat_count + 1) + 1 

8835 to_insnum = (self.repeat_count * nb) + 1 

8836 

8837 start_address = align_address(target_addr) 

8838 base_offset = start_address - align_address(ref_addr) 

8839 

8840 dereference_cmd = gef.gdb.commands["dereference"] 

8841 assert isinstance(dereference_cmd, DereferenceCommand) 

8842 for i in range(from_insnum, to_insnum, insnum_step): 

8843 gef_print(dereference_cmd.pprint_dereferenced(start_address, i, base_offset)) 

8844 

8845 return 

8846 

8847 

8848@register 

8849class ASLRCommand(GenericCommand): 

8850 """View/modify the ASLR setting of GDB. By default, GDB will disable ASLR when it starts the process. (i.e. not 

8851 attached). This command allows to change that setting.""" 

8852 

8853 _cmdline_ = "aslr" 

8854 _syntax_ = f"{_cmdline_} [(on|off)]" 

8855 

8856 def do_invoke(self, argv: list[str]) -> None: 

8857 argc = len(argv) 

8858 

8859 if argc == 0: 

8860 ret = gdb.execute("show disable-randomization", to_string=True) or "" 

8861 i = ret.find("virtual address space is ") 

8862 if i < 0: 8862 ↛ 8863line 8862 didn't jump to line 8863 because the condition on line 8862 was never true

8863 return 

8864 

8865 msg = "ASLR is currently " 

8866 if ret[i + 25:].strip() == "on.": 

8867 msg += Color.redify("disabled") 

8868 else: 

8869 msg += Color.greenify("enabled") 

8870 

8871 gef_print(msg) 

8872 return 

8873 

8874 elif argc == 1: 8874 ↛ 8886line 8874 didn't jump to line 8886 because the condition on line 8874 was always true

8875 if argv[0] == "on": 8875 ↛ 8879line 8875 didn't jump to line 8879 because the condition on line 8875 was always true

8876 info("Enabling ASLR") 

8877 gdb.execute("set disable-randomization off") 

8878 return 

8879 elif argv[0] == "off": 

8880 info("Disabling ASLR") 

8881 gdb.execute("set disable-randomization on") 

8882 return 

8883 

8884 warn("Invalid command") 

8885 

8886 self.usage() 

8887 return 

8888 

8889 

8890@register 

8891class ResetCacheCommand(GenericCommand): 

8892 """Reset cache of all stored data. This command is here for debugging and test purposes, GEF 

8893 handles properly the cache reset under "normal" scenario.""" 

8894 

8895 _cmdline_ = "reset-cache" 

8896 _syntax_ = _cmdline_ 

8897 

8898 def do_invoke(self, _: list[str]) -> None: 

8899 reset_all_caches() 

8900 return 

8901 

8902 

8903@register 

8904class VMMapCommand(GenericCommand): 

8905 """Display a comprehensive layout of the virtual memory mapping. If a filter argument, GEF will 

8906 filter out the mapping whose pathname do not match that filter.""" 

8907 

8908 _cmdline_ = "vmmap" 

8909 _syntax_ = f"{_cmdline_} [FILTER]" 

8910 _example_ = f"{_cmdline_} libc" 

8911 

8912 @only_if_gdb_running 

8913 @parse_arguments({"unknown_types": [""]}, {("--addr", "-a"): [""], ("--name", "-n"): [""]}) 

8914 def do_invoke(self, _: list[str], **kwargs: Any) -> None: 

8915 args : argparse.Namespace = kwargs["arguments"] 

8916 vmmap = gef.memory.maps 

8917 if not vmmap: 8917 ↛ 8918line 8917 didn't jump to line 8918 because the condition on line 8917 was never true

8918 err("No address mapping information found") 

8919 return 

8920 

8921 addrs: dict[str, int] = {x: parse_address(x) for x in args.addr} 

8922 names: list[str] = [x for x in args.name] 

8923 

8924 for arg in args.unknown_types: 

8925 if not arg: 

8926 continue 

8927 

8928 if self.is_integer(arg): 8928 ↛ 8929line 8928 didn't jump to line 8929 because the condition on line 8928 was never true

8929 addr = int(arg, 0) 

8930 else: 

8931 addr = safe_parse_and_eval(arg) 

8932 

8933 if addr is None: 

8934 names.append(arg) 

8935 warn(f"`{arg}` has no type specified. We guessed it was a name filter.") 

8936 else: 

8937 addrs[arg] = int(addr) 

8938 warn(f"`{arg}` has no type specified. We guessed it was an address filter.") 

8939 warn("You can use --name or --addr before the filter value for specifying its type manually.") 

8940 gef_print() 

8941 

8942 if not gef.config["gef.disable_color"]: 8942 ↛ 8943line 8942 didn't jump to line 8943 because the condition on line 8942 was never true

8943 self.show_legend() 

8944 

8945 color = gef.config["theme.table_heading"] 

8946 

8947 headers = ["Start", "End", "Offset", "Perm", "Path"] 

8948 gef_print(Color.colorify("{:<{w}s}{:<{w}s}{:<{w}s}{:<4s} {:s}".format(*headers, w=gef.arch.ptrsize*2+3), color)) 

8949 

8950 last_printed_filter = None 

8951 

8952 for entry in vmmap: 

8953 names_filter = [f"name = '{x}'" for x in names if x in entry.path] 

8954 addrs_filter = [f"addr = {self.format_addr_filter(arg, addr)}" for arg, addr in addrs.items() 

8955 if entry.page_start <= addr < entry.page_end] 

8956 filter_content = f"[{' & '.join([*names_filter, *addrs_filter])}]" 

8957 

8958 if not names and not addrs: 

8959 self.print_entry(entry) 

8960 

8961 elif names_filter or addrs_filter: 

8962 if filter_content != last_printed_filter: 8962 ↛ 8966line 8962 didn't jump to line 8966 because the condition on line 8962 was always true

8963 gef_print() # skip a line between different filters 

8964 gef_print(Color.greenify(filter_content)) 

8965 last_printed_filter = filter_content 

8966 self.print_entry(entry) 

8967 

8968 gef_print() 

8969 return 

8970 

8971 def format_addr_filter(self, arg: str, addr: int): 

8972 return f"`{arg}`" if self.is_integer(arg) else f"`{arg}` ({addr:#x})" 

8973 

8974 def print_entry(self, entry: Section) -> None: 

8975 line_color = "" 

8976 if entry.path == "[stack]": 

8977 line_color = gef.config["theme.address_stack"] 

8978 elif entry.path == "[heap]": 8978 ↛ 8979line 8978 didn't jump to line 8979 because the condition on line 8978 was never true

8979 line_color = gef.config["theme.address_heap"] 

8980 elif entry.permission & Permission.READ and entry.permission & Permission.EXECUTE: 

8981 line_color = gef.config["theme.address_code"] 

8982 

8983 line_parts = [ 

8984 Color.colorify(format_address(entry.page_start), line_color), 

8985 Color.colorify(format_address(entry.page_end), line_color), 

8986 Color.colorify(format_address(entry.offset), line_color), 

8987 ] 

8988 if entry.permission == Permission.ALL: 8988 ↛ 8989line 8988 didn't jump to line 8989 because the condition on line 8988 was never true

8989 line_parts.append(Color.colorify(str(entry.permission), "underline " + line_color)) 

8990 else: 

8991 line_parts.append(Color.colorify(str(entry.permission), line_color)) 

8992 

8993 line_parts.append(Color.colorify(entry.path, line_color)) 

8994 line = " ".join(line_parts) 

8995 

8996 gef_print(line) 

8997 return 

8998 

8999 def show_legend(self) -> None: 

9000 code_title = Color.colorify("Code", gef.config["theme.address_code"]) 

9001 stack_title = Color.colorify("Stack", gef.config["theme.address_stack"]) 

9002 heap_title = Color.colorify("Heap", gef.config["theme.address_heap"]) 

9003 gef_print(f"[ Legend: {code_title} | {stack_title} | {heap_title} ]") 

9004 return 

9005 

9006 def is_integer(self, n: str) -> bool: 

9007 try: 

9008 int(n, 0) 

9009 except ValueError: 

9010 return False 

9011 return True 

9012 

9013 

9014@register 

9015class XFilesCommand(GenericCommand): 

9016 """Shows all libraries (and sections) loaded by binary. This command extends the GDB command 

9017 `info files`, by retrieving more information from extra sources, and providing a better 

9018 display. If an argument FILE is given, the output will grep information related to only that file. 

9019 If an argument name is also given, the output will grep to the name within FILE.""" 

9020 

9021 _cmdline_ = "xfiles" 

9022 _syntax_ = f"{_cmdline_} [FILE [NAME]]" 

9023 _example_ = f"\n{_cmdline_} libc\n{_cmdline_} libc IO_vtables" 

9024 

9025 @only_if_gdb_running 

9026 def do_invoke(self, argv: list[str]) -> None: 

9027 color = gef.config["theme.table_heading"] 

9028 headers = ["Start", "End", "Name", "File"] 

9029 gef_print(Color.colorify("{:<{w}s}{:<{w}s}{:<21s} {:s}".format(*headers, w=gef.arch.ptrsize*2+3), color)) 

9030 

9031 filter_by_file = argv[0] if argv and argv[0] else None 

9032 filter_by_name = argv[1] if len(argv) > 1 and argv[1] else None 

9033 

9034 for xfile in get_info_files(): 

9035 if filter_by_file: 9035 ↛ 9036line 9035 didn't jump to line 9036 because the condition on line 9035 was never true

9036 if filter_by_file not in xfile.filename: 

9037 continue 

9038 if filter_by_name and filter_by_name not in xfile.name: 

9039 continue 

9040 

9041 line_parts = [ 

9042 format_address(xfile.zone_start), 

9043 format_address(xfile.zone_end), 

9044 f"{xfile.name:<21s}", 

9045 xfile.filename, 

9046 ] 

9047 gef_print(" ".join(line_parts)) 

9048 return 

9049 

9050 

9051@register 

9052class XAddressInfoCommand(GenericCommand): 

9053 """Retrieve and display runtime information for the location(s) given as parameter.""" 

9054 

9055 _cmdline_ = "xinfo" 

9056 _syntax_ = f"{_cmdline_} LOCATION" 

9057 _example_ = f"{_cmdline_} $pc" 

9058 

9059 def __init__(self) -> None: 

9060 super().__init__(complete=gdb.COMPLETE_LOCATION) 

9061 return 

9062 

9063 @only_if_gdb_running 

9064 def do_invoke(self, argv: list[str]) -> None: 

9065 if not argv: 

9066 err("At least one valid address must be specified") 

9067 self.usage() 

9068 return 

9069 

9070 for sym in argv: 

9071 try: 

9072 addr = align_address(parse_address(sym)) 

9073 gef_print(titlify(f"xinfo: {addr:#x}")) 

9074 self.infos(addr) 

9075 

9076 except gdb.error as gdb_err: 

9077 err(f"{gdb_err}") 

9078 return 

9079 

9080 def infos(self, address: int) -> None: 

9081 addr = lookup_address(address) 

9082 if not addr.valid: 9082 ↛ 9083line 9082 didn't jump to line 9083 because the condition on line 9082 was never true

9083 warn(f"Cannot reach {address:#x} in memory space") 

9084 return 

9085 

9086 sect = addr.section 

9087 info = addr.info 

9088 

9089 if sect: 9089 ↛ 9097line 9089 didn't jump to line 9097 because the condition on line 9089 was always true

9090 gef_print(f"Page: {format_address(sect.page_start)} {RIGHT_ARROW} " 

9091 f"{format_address(sect.page_end)} (size={sect.page_end-sect.page_start:#x})" 

9092 f"\nPermissions: {sect.permission}" 

9093 f"\nPathname: {sect.path}" 

9094 f"\nOffset (from page): {addr.value-sect.page_start:#x}" 

9095 f"\nInode: {sect.inode}") 

9096 

9097 if info: 

9098 gef_print(f"Segment: {info.name} " 

9099 f"({format_address(info.zone_start)}-{format_address(info.zone_end)})" 

9100 f"\nOffset (from segment): {addr.value-info.zone_start:#x}") 

9101 

9102 sym = gdb_get_location_from_symbol(address) 

9103 if sym: 

9104 name, offset = sym 

9105 msg = f"Symbol: {name}" 

9106 if offset: 9106 ↛ 9108line 9106 didn't jump to line 9108 because the condition on line 9106 was always true

9107 msg += f"+{offset:d}" 

9108 gef_print(msg) 

9109 

9110 return 

9111 

9112 

9113@register 

9114class XorMemoryCommand(GenericCommand): 

9115 """XOR a block of memory. The command allows to simply display the result, or patch it 

9116 runtime at runtime.""" 

9117 

9118 _cmdline_ = "xor-memory" 

9119 _syntax_ = f"{_cmdline_} (display|patch) ADDRESS SIZE KEY" 

9120 

9121 def __init__(self) -> None: 

9122 super().__init__(prefix=True) 

9123 return 

9124 

9125 def do_invoke(self, _: list[str]) -> None: 

9126 self.usage() 

9127 return 

9128 

9129 

9130@register 

9131class XorMemoryDisplayCommand(GenericCommand): 

9132 """Display a block of memory pointed by ADDRESS by xor-ing each byte with KEY. The key must be 

9133 provided in hexadecimal format.""" 

9134 

9135 _cmdline_ = "xor-memory display" 

9136 _syntax_ = f"{_cmdline_} ADDRESS SIZE KEY" 

9137 _example_ = f"{_cmdline_} $sp 16 41414141" 

9138 

9139 @only_if_gdb_running 

9140 def do_invoke(self, argv: list[str]) -> None: 

9141 if len(argv) != 3: 9141 ↛ 9142line 9141 didn't jump to line 9142 because the condition on line 9141 was never true

9142 self.usage() 

9143 return 

9144 

9145 address = parse_address(argv[0]) 

9146 length = int(argv[1], 0) 

9147 key = argv[2] 

9148 block = gef.memory.read(address, length) 

9149 info(f"Displaying XOR-ing {address:#x}-{address + len(block):#x} with {key!r}") 

9150 

9151 gef_print(titlify("Original block")) 

9152 gef_print(hexdump(block, base=address)) 

9153 

9154 gef_print(titlify("XOR-ed block")) 

9155 gef_print(hexdump(xor(block, key), base=address)) 

9156 return 

9157 

9158 

9159@register 

9160class XorMemoryPatchCommand(GenericCommand): 

9161 """Patch a block of memory pointed by ADDRESS by xor-ing each byte with KEY. The key must be 

9162 provided in hexadecimal format.""" 

9163 

9164 _cmdline_ = "xor-memory patch" 

9165 _syntax_ = f"{_cmdline_} ADDRESS SIZE KEY" 

9166 _example_ = f"{_cmdline_} $sp 16 41414141" 

9167 

9168 @only_if_gdb_running 

9169 def do_invoke(self, argv: list[str]) -> None: 

9170 if len(argv) != 3: 9170 ↛ 9171line 9170 didn't jump to line 9171 because the condition on line 9170 was never true

9171 self.usage() 

9172 return 

9173 

9174 address = parse_address(argv[0]) 

9175 length = int(argv[1], 0) 

9176 key = argv[2] 

9177 block = gef.memory.read(address, length) 

9178 info(f"Patching XOR-ing {address:#x}-{address + len(block):#x} with {key!r}") 

9179 xored_block = xor(block, key) 

9180 gef.memory.write(address, xored_block, length) 

9181 return 

9182 

9183 

9184@register 

9185class TraceRunCommand(GenericCommand): 

9186 """Create a runtime trace of all instructions executed from $pc to LOCATION specified. The 

9187 trace is stored in a text file that can be next imported in IDA Pro to visualize the runtime 

9188 path.""" 

9189 

9190 _cmdline_ = "trace-run" 

9191 _syntax_ = f"{_cmdline_} LOCATION [MAX_CALL_DEPTH]" 

9192 _example_ = f"{_cmdline_} 0x555555554610" 

9193 

9194 def __init__(self) -> None: 

9195 super().__init__(self._cmdline_, complete=gdb.COMPLETE_LOCATION) 

9196 self["max_tracing_recursion"] = (1, "Maximum depth of tracing") 

9197 self["tracefile_prefix"] = ("./gef-trace-", "Specify the tracing output file prefix") 

9198 return 

9199 

9200 @only_if_gdb_running 

9201 def do_invoke(self, argv: list[str]) -> None: 

9202 if len(argv) not in (1, 2): 9202 ↛ 9203line 9202 didn't jump to line 9203 because the condition on line 9202 was never true

9203 self.usage() 

9204 return 

9205 

9206 if len(argv) == 2 and argv[1].isdigit(): 9206 ↛ 9207line 9206 didn't jump to line 9207 because the condition on line 9206 was never true

9207 depth = int(argv[1]) 

9208 else: 

9209 depth = 1 

9210 

9211 try: 

9212 loc_start = gef.arch.pc 

9213 loc_end = parse_address(argv[0]) 

9214 except gdb.error as e: 

9215 err(f"Invalid location: {e}") 

9216 return 

9217 

9218 self.trace(loc_start, loc_end, depth) 

9219 return 

9220 

9221 def get_frames_size(self) -> int: 

9222 n = 0 

9223 f = gdb.newest_frame() 

9224 while f: 

9225 n += 1 

9226 f = f.older() 

9227 return n 

9228 

9229 def trace(self, loc_start: int, loc_end: int, depth: int) -> None: 

9230 info(f"Tracing from {loc_start:#x} to {loc_end:#x} (max depth={depth:d})") 

9231 logfile = f"{self['tracefile_prefix']}{loc_start:#x}-{loc_end:#x}.txt" 

9232 with RedirectOutputContext(to_file=logfile): 

9233 hide_context() 

9234 self.start_tracing(loc_start, loc_end, depth) 

9235 unhide_context() 

9236 ok(f"Done, logfile stored as '{logfile}'") 

9237 info("Hint: import logfile with `ida_color_gdb_trace.py` script in IDA to visualize path") 

9238 return 

9239 

9240 def start_tracing(self, loc_start: int, loc_end: int, depth: int) -> None: 

9241 loc_cur = loc_start 

9242 frame_count_init = self.get_frames_size() 

9243 

9244 gef_print("#", 

9245 f"# Execution tracing of {get_filepath()}", 

9246 f"# Start address: {format_address(loc_start)}", 

9247 f"# End address: {format_address(loc_end)}", 

9248 f"# Recursion level: {depth:d}", 

9249 "# automatically generated by gef.py", 

9250 "#\n", sep="\n") 

9251 

9252 while loc_cur != loc_end: 9252 ↛ 9271line 9252 didn't jump to line 9271 because the condition on line 9252 was always true

9253 try: 

9254 delta = self.get_frames_size() - frame_count_init 

9255 

9256 if delta <= depth: 

9257 gdb.execute("stepi") 

9258 else: 

9259 gdb.execute("finish") 

9260 

9261 loc_cur = gef.arch.pc 

9262 gdb.flush() 

9263 

9264 except gdb.error as e: 

9265 gef_print("#", 

9266 f"# Execution interrupted at address {format_address(loc_cur)}", 

9267 f"# Exception: {e}", 

9268 "#\n", sep="\n") 

9269 break 

9270 

9271 return 

9272 

9273 

9274@register 

9275class PatternCommand(GenericCommand): 

9276 """Generate or Search a De Bruijn Sequence of unique substrings of length N 

9277 and a total length of LENGTH. The default value of N is set to match the 

9278 currently loaded architecture.""" 

9279 

9280 _cmdline_ = "pattern" 

9281 _syntax_ = f"{_cmdline_} (create|search) ARGS" 

9282 

9283 def __init__(self) -> None: 

9284 super().__init__(prefix=True) 

9285 self["length"] = (1024, "Default length of a cyclic buffer to generate") 

9286 return 

9287 

9288 def do_invoke(self, _: list[str]) -> None: 

9289 self.usage() 

9290 return 

9291 

9292 

9293@register 

9294class PatternCreateCommand(GenericCommand): 

9295 """Generate a De Bruijn Sequence of unique substrings of length N and a 

9296 total length of LENGTH. The default value of N is set to match the currently 

9297 loaded architecture.""" 

9298 

9299 _cmdline_ = "pattern create" 

9300 _syntax_ = f"{_cmdline_} [-h] [-n N] [length]" 

9301 _example_ = [ 

9302 f"{_cmdline_} 4096", 

9303 f"{_cmdline_} -n 4 128" 

9304 ] 

9305 

9306 @parse_arguments({"length": 0}, {"-n": 0,}) 

9307 def do_invoke(self, _: list[str], **kwargs: Any) -> None: 

9308 args : argparse.Namespace = kwargs["arguments"] 

9309 length = args.length or gef.config["pattern.length"] 

9310 n = args.n or gef.arch.ptrsize 

9311 info(f"Generating a pattern of {length:d} bytes (n={n:d})") 

9312 pattern_str = gef_pystring(generate_cyclic_pattern(length, n)) 

9313 gef_print(pattern_str) 

9314 ok(f"Saved as '{gef_convenience(pattern_str)}'") 

9315 return 

9316 

9317 

9318@register 

9319class PatternSearchCommand(GenericCommand): 

9320 """Search a De Bruijn Sequence of unique substrings of length N and a 

9321 maximum total length of MAX_LENGTH. The default value of N is set to match 

9322 the currently loaded architecture. The PATTERN argument can be a GDB symbol 

9323 (such as a register name), a string or a hexadecimal value""" 

9324 

9325 _cmdline_ = "pattern search" 

9326 _syntax_ = f"{_cmdline_} [-h] [-n N] [--max-length MAX_LENGTH] [pattern]" 

9327 _example_ = [f"{_cmdline_} $pc", 

9328 f"{_cmdline_} 0x61616164", 

9329 f"{_cmdline_} aaab"] 

9330 _aliases_ = ["pattern offset"] 

9331 

9332 @only_if_gdb_running 

9333 @parse_arguments({"pattern": ""}, {("--period", "-n"): 0, ("--max-length", "-l"): 0}) 

9334 def do_invoke(self, _: list[str], **kwargs: Any) -> None: 

9335 args = kwargs["arguments"] 

9336 if not args.pattern: 9336 ↛ 9337line 9336 didn't jump to line 9337 because the condition on line 9336 was never true

9337 warn("No pattern provided") 

9338 return 

9339 

9340 max_length = args.max_length or gef.config["pattern.length"] 

9341 n = args.period or gef.arch.ptrsize 

9342 if n not in (2, 4, 8) or n > gef.arch.ptrsize: 9342 ↛ 9343line 9342 didn't jump to line 9343 because the condition on line 9342 was never true

9343 err("Incorrect value for period") 

9344 return 

9345 self.search(args.pattern, max_length, n) 

9346 return 

9347 

9348 def search(self, pattern: str, size: int, period: int) -> None: 

9349 pattern_be, pattern_le = None, None 

9350 

9351 # 1. check if it's a symbol (like "$sp" or "0x1337") 

9352 symbol = safe_parse_and_eval(pattern) 

9353 if symbol: 

9354 addr = int(abs(to_unsigned_long(symbol))) 

9355 dereferenced_value = dereference(addr) 

9356 if dereferenced_value: 9356 ↛ 9357line 9356 didn't jump to line 9357 because the condition on line 9356 was never true

9357 addr = int(abs(to_unsigned_long(dereferenced_value))) 

9358 mask = (1<<(8 * period))-1 

9359 addr &= mask 

9360 pattern_le = addr.to_bytes(period, 'little') 

9361 pattern_be = addr.to_bytes(period, 'big') 

9362 else: 

9363 # 2. assume it's a plain string 

9364 pattern_be = gef_pybytes(pattern) 

9365 pattern_le = gef_pybytes(pattern[::-1]) 

9366 

9367 info(f"Searching for '{pattern_le.hex()}'/'{pattern_be.hex()}' with period={period}") 

9368 cyclic_pattern = generate_cyclic_pattern(size, period) 

9369 off = cyclic_pattern.find(pattern_le) 

9370 if off >= 0: 

9371 ok(f"Found at offset {off:d} (little-endian search) " 

9372 f"{Color.colorify('likely', 'bold red') if gef.arch.endianness == Endianness.LITTLE_ENDIAN else ''}") 

9373 return 

9374 

9375 off = cyclic_pattern.find(pattern_be) 

9376 if off >= 0: 9376 ↛ 9377line 9376 didn't jump to line 9377 because the condition on line 9376 was never true

9377 ok(f"Found at offset {off:d} (big-endian search) " 

9378 f"{Color.colorify('likely', 'bold green') if gef.arch.endianness == Endianness.BIG_ENDIAN else ''}") 

9379 return 

9380 

9381 err(f"Pattern '{pattern}' not found") 

9382 return 

9383 

9384 

9385@register 

9386class ChecksecCommand(GenericCommand): 

9387 """Checksec the security properties of the current executable or passed as argument. The 

9388 command checks for the following protections: 

9389 - PIE 

9390 - NX 

9391 - RelRO 

9392 - Glibc Stack Canaries 

9393 - Fortify Source""" 

9394 

9395 _cmdline_ = "checksec" 

9396 _syntax_ = f"{_cmdline_} [FILENAME]" 

9397 _example_ = f"{_cmdline_} /bin/ls" 

9398 

9399 def __init__(self) -> None: 

9400 super().__init__(complete=gdb.COMPLETE_FILENAME) 

9401 return 

9402 

9403 def do_invoke(self, argv: list[str]) -> None: 

9404 argc = len(argv) 

9405 

9406 if argc == 0: 9406 ↛ 9411line 9406 didn't jump to line 9411 because the condition on line 9406 was always true

9407 filename = get_filepath() 

9408 if filename is None: 9408 ↛ 9409line 9408 didn't jump to line 9409 because the condition on line 9408 was never true

9409 warn("No executable/library specified") 

9410 return 

9411 elif argc == 1: 

9412 filename = os.path.realpath(os.path.expanduser(argv[0])) 

9413 if not os.access(filename, os.R_OK): 

9414 err("Invalid filename") 

9415 return 

9416 else: 

9417 self.usage() 

9418 return 

9419 

9420 info(f"{self._cmdline_} for '{filename}'") 

9421 self.print_security_properties(filename) 

9422 return 

9423 

9424 def print_security_properties(self, filename: str) -> None: 

9425 sec = Elf(filename).checksec 

9426 for prop in sec: 

9427 if prop in ("Partial RelRO", "Full RelRO"): continue 

9428 val = sec[prop] 

9429 msg = Color.greenify(Color.boldify(TICK)) if val is True else Color.redify(Color.boldify(CROSS)) 

9430 if val and prop == "Canary" and is_alive(): 9430 ↛ 9431line 9430 didn't jump to line 9431 because the condition on line 9430 was never true

9431 canary = gef.session.canary[0] if gef.session.canary else 0 

9432 msg += f"(value: {canary:#x})" 

9433 

9434 gef_print(f"{prop:<30s}: {msg}") 

9435 

9436 if sec["Full RelRO"]: 

9437 gef_print(f"{'RelRO':<30s}: {Color.greenify('Full')}") 

9438 elif sec["Partial RelRO"]: 9438 ↛ 9441line 9438 didn't jump to line 9441 because the condition on line 9438 was always true

9439 gef_print(f"{'RelRO':<30s}: {Color.yellowify('Partial')}") 

9440 else: 

9441 gef_print(f"{'RelRO':<30s}: {Color.redify(Color.boldify(CROSS))}") 

9442 return 

9443 

9444 

9445@register 

9446class GotCommand(GenericCommand): 

9447 """Display current status of the got inside the process.""" 

9448 

9449 _cmdline_ = "got" 

9450 _syntax_ = f"{_cmdline_} [FUNCTION_NAME ...] " 

9451 _example_ = "got read printf exit" 

9452 

9453 def __init__(self): 

9454 super().__init__() 

9455 self["function_resolved"] = ("green", 

9456 "Line color of the got command output for resolved function") 

9457 self["function_not_resolved"] = ("yellow", 

9458 "Line color of the got command output for unresolved function") 

9459 return 

9460 

9461 def build_line(self, name: str, _path: str, color: str, address_val: int, got_address: int) -> str: 

9462 line = f"[{hex(address_val)}] " 

9463 line += Color.colorify(f"{name} {RIGHT_ARROW} {hex(got_address)}", color) 

9464 return line 

9465 

9466 @only_if_gdb_running 

9467 @parse_arguments({"symbols": [""]}, {"--all": False}) 

9468 def do_invoke(self, _: list[str], **kwargs: Any) -> None: 

9469 args : argparse.Namespace = kwargs["arguments"] 

9470 vmmap = gef.memory.maps 

9471 mapfiles = [mapfile for mapfile in vmmap if 

9472 (args.all or mapfile.path == str(gef.session.file)) and 

9473 pathlib.Path(mapfile.realpath).is_file() and 

9474 mapfile.permission & Permission.EXECUTE] 

9475 for mapfile in mapfiles: 

9476 self.print_got_for(mapfile.path, mapfile.realpath, args.symbols) 

9477 

9478 def print_got_for(self, file: str, realpath: str, argv: list[str]) -> None: 

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

9480 

9481 elf_file = realpath 

9482 elf_virtual_path = file 

9483 

9484 func_names_filter = argv if argv else [] 

9485 vmmap = gef.memory.maps 

9486 base_address = min(x.page_start for x in vmmap if x.path == elf_virtual_path) 

9487 end_address = max(x.page_end for x in vmmap if x.path == elf_virtual_path) 

9488 

9489 # get the checksec output. 

9490 checksec_status = Elf(elf_file).checksec 

9491 relro_status = "Full RelRO" 

9492 full_relro = checksec_status["Full RelRO"] 

9493 pie = checksec_status["PIE"] # if pie we will have offset instead of abs address. 

9494 

9495 if not full_relro: 9495 ↛ 9496line 9495 didn't jump to line 9496 because the condition on line 9495 was never true

9496 relro_status = "Partial RelRO" 

9497 partial_relro = checksec_status["Partial RelRO"] 

9498 

9499 if not partial_relro: 

9500 relro_status = "No RelRO" 

9501 

9502 # retrieve jump slots using readelf 

9503 lines = gef_execute_external([readelf, "--wide", "--relocs", elf_file], as_list=True) 

9504 jmpslots = [line for line in lines if "JUMP" in line] 

9505 

9506 gef_print(f"{titlify(file)}\n\nGOT protection: {relro_status} | GOT functions: {len(jmpslots)}\n ") 

9507 

9508 for line in jmpslots: 

9509 address, _, _, _, name = line.split()[:5] 

9510 

9511 # if we have a filter let's skip the entries that are not requested. 

9512 if func_names_filter: 9512 ↛ 9516line 9512 didn't jump to line 9516 because the condition on line 9512 was always true

9513 if not any(map(lambda x: x in name, func_names_filter)): 

9514 continue 

9515 

9516 address_val = int(address, 16) 

9517 

9518 # address_val is an offset from the base_address if we have PIE. 

9519 if pie or is_remote_debug(): 9519 ↛ 9523line 9519 didn't jump to line 9523 because the condition on line 9519 was always true

9520 address_val = base_address + address_val 

9521 

9522 # read the address of the function. 

9523 got_address = gef.memory.read_integer(address_val) 

9524 

9525 # for the swag: different colors if the function has been resolved or not. 

9526 if base_address < got_address < end_address: 9526 ↛ 9527line 9526 didn't jump to line 9527 because the condition on line 9526 was never true

9527 color = self["function_not_resolved"] 

9528 else: 

9529 color = self["function_resolved"] 

9530 

9531 line = self.build_line(name, elf_virtual_path, color, address_val, got_address) 

9532 gef_print(line) 

9533 return 

9534 

9535 

9536@register 

9537class HighlightCommand(GenericCommand): 

9538 """Highlight user-defined text matches in GEF output universally.""" 

9539 _cmdline_ = "highlight" 

9540 _syntax_ = f"{_cmdline_} (add|remove|list|clear)" 

9541 _aliases_ = ["hl"] 

9542 

9543 def __init__(self) -> None: 

9544 super().__init__(prefix=True) 

9545 self["regex"] = (False, "Enable regex highlighting") 

9546 

9547 def do_invoke(self, _: list[str]) -> None: 

9548 return self.usage() 

9549 

9550 

9551@register 

9552class HighlightListCommand(GenericCommand): 

9553 """Show the current highlight table with matches to colors.""" 

9554 _cmdline_ = "highlight list" 

9555 _aliases_ = ["highlight ls", "hll"] 

9556 _syntax_ = _cmdline_ 

9557 

9558 def print_highlight_table(self) -> None: 

9559 if not gef.ui.highlight_table: 

9560 err("no matches found") 

9561 return 

9562 

9563 left_pad = max(map(len, gef.ui.highlight_table.keys())) 

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

9565 print(f"{Color.colorify(match.ljust(left_pad), color)} {VERTICAL_LINE} " 

9566 f"{Color.colorify(color, color)}") 

9567 return 

9568 

9569 def do_invoke(self, _: list[str]) -> None: 

9570 return self.print_highlight_table() 

9571 

9572 

9573@register 

9574class HighlightClearCommand(GenericCommand): 

9575 """Clear the highlight table, remove all matches.""" 

9576 _cmdline_ = "highlight clear" 

9577 _aliases_ = ["hlc"] 

9578 _syntax_ = _cmdline_ 

9579 

9580 def do_invoke(self, _: list[str]) -> None: 

9581 return gef.ui.highlight_table.clear() 

9582 

9583 

9584@register 

9585class HighlightAddCommand(GenericCommand): 

9586 """Add a match to the highlight table.""" 

9587 _cmdline_ = "highlight add" 

9588 _syntax_ = f"{_cmdline_} MATCH COLOR" 

9589 _aliases_ = ["highlight set", "hla"] 

9590 _example_ = f"{_cmdline_} 41414141 yellow" 

9591 

9592 def do_invoke(self, argv: list[str]) -> None: 

9593 if len(argv) < 2: 9593 ↛ 9594line 9593 didn't jump to line 9594 because the condition on line 9593 was never true

9594 return self.usage() 

9595 

9596 match, color = argv 

9597 gef.ui.highlight_table[match] = color 

9598 return 

9599 

9600 

9601@register 

9602class HighlightRemoveCommand(GenericCommand): 

9603 """Remove a match in the highlight table.""" 

9604 _cmdline_ = "highlight remove" 

9605 _syntax_ = f"{_cmdline_} MATCH" 

9606 _aliases_ = [ 

9607 "highlight delete", 

9608 "highlight del", 

9609 "highlight unset", 

9610 "highlight rm", 

9611 "hlr", 

9612 ] 

9613 _example_ = f"{_cmdline_} remove 41414141" 

9614 

9615 def do_invoke(self, argv: list[str]) -> None: 

9616 if not argv: 

9617 return self.usage() 

9618 

9619 gef.ui.highlight_table.pop(argv[0], None) 

9620 return 

9621 

9622 

9623@register 

9624class FormatStringSearchCommand(GenericCommand): 

9625 """Exploitable format-string helper: this command will set up specific breakpoints 

9626 at well-known dangerous functions (printf, snprintf, etc.), and check if the pointer 

9627 holding the format string is writable, and therefore susceptible to format string 

9628 attacks if an attacker can control its content.""" 

9629 _cmdline_ = "format-string-helper" 

9630 _syntax_ = _cmdline_ 

9631 _aliases_ = ["fmtstr-helper",] 

9632 

9633 def do_invoke(self, _: list[str]) -> None: 

9634 dangerous_functions = { 

9635 "printf": 0, 

9636 "sprintf": 1, 

9637 "fprintf": 1, 

9638 "snprintf": 2, 

9639 "vsnprintf": 2, 

9640 } 

9641 

9642 nb_installed_breaks = 0 

9643 

9644 with RedirectOutputContext(to_file="/dev/null"): 

9645 for function_name in dangerous_functions: 

9646 argument_number = dangerous_functions[function_name] 

9647 FormatStringBreakpoint(function_name, argument_number) 

9648 nb_installed_breaks += 1 

9649 

9650 ok(f"Enabled {nb_installed_breaks} FormatString " 

9651 f"breakpoint{'s' if nb_installed_breaks > 1 else ''}") 

9652 return 

9653 

9654 

9655@register 

9656class HeapAnalysisCommand(GenericCommand): 

9657 """Heap vulnerability analysis helper: this command aims to track dynamic heap allocation 

9658 done through malloc()/free() to provide some insights on possible heap vulnerabilities. The 

9659 following vulnerabilities are checked: 

9660 - NULL free 

9661 - Use-after-Free 

9662 - Double Free 

9663 - Heap overlap""" 

9664 _cmdline_ = "heap-analysis-helper" 

9665 _syntax_ = _cmdline_ 

9666 

9667 def __init__(self) -> None: 

9668 super().__init__(complete=gdb.COMPLETE_NONE) 

9669 self["check_free_null"] = (False, "Break execution when a free(NULL) is encountered") 

9670 self["check_double_free"] = (True, "Break execution when a double free is encountered") 

9671 self["check_weird_free"] = (True, "Break execution when free() is called against a non-tracked pointer") 

9672 self["check_uaf"] = (True, "Break execution when a possible Use-after-Free condition is found") 

9673 self["check_heap_overlap"] = (True, "Break execution when a possible overlap in allocation is found") 

9674 

9675 self.bp_malloc = None 

9676 self.bp_calloc = None 

9677 self.bp_free = None 

9678 self.bp_realloc = None 

9679 return 

9680 

9681 @only_if_gdb_running 

9682 @experimental_feature 

9683 def do_invoke(self, argv: list[str]) -> None: 

9684 if not argv: 9684 ↛ 9688line 9684 didn't jump to line 9688 because the condition on line 9684 was always true

9685 self.setup() 

9686 return 

9687 

9688 if argv[0] == "show": 

9689 self.dump_tracked_allocations() 

9690 return 

9691 

9692 def setup(self) -> None: 

9693 ok("Tracking malloc() & calloc()") 

9694 self.bp_malloc = TraceMallocBreakpoint("__libc_malloc") 

9695 self.bp_calloc = TraceMallocBreakpoint("__libc_calloc") 

9696 ok("Tracking free()") 

9697 self.bp_free = TraceFreeBreakpoint() 

9698 ok("Tracking realloc()") 

9699 self.bp_realloc = TraceReallocBreakpoint() 

9700 

9701 ok("Disabling hardware watchpoints (this may increase the latency)") 

9702 gdb.execute("set can-use-hw-watchpoints 0") 

9703 

9704 info("Dynamic breakpoints correctly setup, " 

9705 "GEF will break execution if a possible vulnerability is found.") 

9706 warn(f"{Color.colorify('Note', 'bold underline yellow')}: " 

9707 "The heap analysis slows down the execution noticeably.") 

9708 

9709 # when inferior quits, we need to clean everything for a next execution 

9710 gef_on_exit_hook(self.clean) 

9711 return 

9712 

9713 def dump_tracked_allocations(self) -> None: 

9714 global gef 

9715 

9716 if gef.session.heap_allocated_chunks: 

9717 ok("Tracked as in-use chunks:") 

9718 for addr, sz in gef.session.heap_allocated_chunks: 

9719 gef_print(f"{CROSS} malloc({sz:d}) = {addr:#x}") 

9720 else: 

9721 ok("No malloc() chunk tracked") 

9722 

9723 if gef.session.heap_freed_chunks: 

9724 ok("Tracked as free-ed chunks:") 

9725 for addr, sz in gef.session.heap_freed_chunks: 

9726 gef_print(f"{TICK} free({sz:d}) = {addr:#x}") 

9727 else: 

9728 ok("No free() chunk tracked") 

9729 return 

9730 

9731 def clean(self, _: "gdb.ExitedEvent") -> None: 

9732 global gef 

9733 

9734 ok(f"{Color.colorify('Heap-Analysis', 'yellow bold')} - Cleaning up") 

9735 for bp in [self.bp_malloc, self.bp_calloc, self.bp_free, self.bp_realloc]: 

9736 if hasattr(bp, "retbp") and bp.retbp: 9736 ↛ 9744line 9736 didn't jump to line 9744 because the condition on line 9736 was always true

9737 try: 

9738 bp.retbp.delete() 

9739 except RuntimeError: 

9740 # in some cases, gdb was found failing to correctly remove the retbp 

9741 # but they can be safely ignored since the debugging session is over 

9742 pass 

9743 

9744 bp.delete() 

9745 

9746 for wp in gef.session.heap_uaf_watchpoints: 

9747 wp.delete() 

9748 

9749 gef.session.heap_allocated_chunks = [] 

9750 gef.session.heap_freed_chunks = [] 

9751 gef.session.heap_uaf_watchpoints = [] 

9752 

9753 ok(f"{Color.colorify('Heap-Analysis', 'yellow bold')} - Re-enabling hardware watchpoints") 

9754 gdb.execute("set can-use-hw-watchpoints 1") 

9755 

9756 gef_on_exit_unhook(self.clean) 

9757 return 

9758 

9759 

9760# 

9761# GDB Function declaration 

9762# 

9763@deprecated("") 

9764def register_function(cls: Type["GenericFunction"]) -> Type["GenericFunction"]: 

9765 """Decorator for registering a new convenience function to GDB.""" 

9766 return cls 

9767 

9768 

9769class GenericFunction(gdb.Function): 

9770 """This is an abstract class for invoking convenience functions, should not be instantiated.""" 

9771 

9772 _function_ : str 

9773 _syntax_: str = "" 

9774 _example_ : str = "" 

9775 

9776 def __init__(self) -> None: 

9777 super().__init__(self._function_) 

9778 

9779 def invoke(self, *args: Any) -> int: 

9780 if not is_alive(): 

9781 raise gdb.GdbError("No debugging session active") 

9782 return self.do_invoke(args) 

9783 

9784 def arg_to_long(self, args: Any, index: int, default: int = 0) -> int: 

9785 try: 

9786 addr = args[index] 

9787 return int(addr) if addr.address is None else int(addr.address) 

9788 except IndexError: 

9789 return default 

9790 

9791 def do_invoke(self, args: Any) -> int: 

9792 raise NotImplementedError 

9793 

9794 

9795@register 

9796class StackOffsetFunction(GenericFunction): 

9797 """Return the current stack base address plus an optional offset.""" 

9798 _function_ = "_stack" 

9799 _syntax_ = f"${_function_}()" 

9800 

9801 def do_invoke(self, args: list) -> int: 

9802 base = get_section_base_address("[stack]") 

9803 if not base: 9803 ↛ 9804line 9803 didn't jump to line 9804 because the condition on line 9803 was never true

9804 raise gdb.GdbError("Stack not found") 

9805 

9806 return self.arg_to_long(args, 0) + base 

9807 

9808 

9809@register 

9810class HeapBaseFunction(GenericFunction): 

9811 """Return the current heap base address plus an optional offset.""" 

9812 _function_ = "_heap" 

9813 _syntax_ = f"${_function_}()" 

9814 

9815 def do_invoke(self, args: list[str]) -> int: 

9816 base = gef.heap.base_address 

9817 if not base: 9817 ↛ 9818line 9817 didn't jump to line 9818 because the condition on line 9817 was never true

9818 base = get_section_base_address("[heap]") 

9819 if not base: 

9820 raise gdb.GdbError("Heap not found") 

9821 return self.arg_to_long(args, 0) + base 

9822 

9823 

9824@register 

9825class SectionBaseFunction(GenericFunction): 

9826 """Return the matching file's base address plus an optional offset. 

9827 Defaults to current file. Note that quotes need to be escaped""" 

9828 _function_ = "_base" 

9829 _syntax_ = "$_base([filepath])" 

9830 _example_ = "p $_base(\\\"/usr/lib/ld-2.33.so\\\")" 

9831 

9832 def do_invoke(self, args: list) -> int: 

9833 addr = 0 

9834 try: 

9835 name = args[0].string() 

9836 except IndexError: 

9837 assert gef.session.file 

9838 name = gef.session.file.name 

9839 except gdb.error: 

9840 err(f"Invalid arg: {args[0]}") 

9841 return 0 

9842 

9843 try: 

9844 base = get_section_base_address(name) 

9845 if base: 9845 ↛ 9850line 9845 didn't jump to line 9850 because the condition on line 9845 was always true

9846 addr = int(base) 

9847 except TypeError: 

9848 err(f"Cannot find section {name}") 

9849 return 0 

9850 return addr 

9851 

9852 

9853@register 

9854class BssBaseFunction(GenericFunction): 

9855 """Return the current bss base address plus the given offset.""" 

9856 _function_ = "_bss" 

9857 _syntax_ = f"${_function_}([OFFSET])" 

9858 _example_ = "deref $_bss(0x20)" 

9859 

9860 def do_invoke(self, args: list) -> int: 

9861 base = get_zone_base_address(".bss") 

9862 if not base: 9862 ↛ 9863line 9862 didn't jump to line 9863 because the condition on line 9862 was never true

9863 raise gdb.GdbError("BSS not found") 

9864 return self.arg_to_long(args, 0) + base 

9865 

9866 

9867@register 

9868class GotBaseFunction(GenericFunction): 

9869 """Return the current GOT base address plus the given offset.""" 

9870 _function_ = "_got" 

9871 _syntax_ = f"${_function_}([OFFSET])" 

9872 _example_ = "deref $_got(0x20)" 

9873 

9874 def do_invoke(self, args: list) -> int: 

9875 base = get_zone_base_address(".got") 

9876 if not base: 9876 ↛ 9877line 9876 didn't jump to line 9877 because the condition on line 9876 was never true

9877 raise gdb.GdbError("GOT not found") 

9878 return base + self.arg_to_long(args, 0) 

9879 

9880 

9881@register 

9882class GefFunctionsCommand(GenericCommand): 

9883 """List the convenience functions provided by GEF.""" 

9884 _cmdline_ = "functions" 

9885 _syntax_ = _cmdline_ 

9886 

9887 def __init__(self) -> None: 

9888 super().__init__() 

9889 self.docs = [] 

9890 self.should_refresh = True 

9891 return 

9892 

9893 def __add__(self, function: GenericFunction): 

9894 """Add function to documentation.""" 

9895 doc = getattr(function, "__doc__", "").lstrip() 

9896 if not hasattr(function, "_syntax_"): 9896 ↛ 9897line 9896 didn't jump to line 9897 because the condition on line 9896 was never true

9897 raise ValueError("Function is invalid") 

9898 syntax = getattr(function, "_syntax_").lstrip() 

9899 msg = f"{Color.colorify(syntax, 'bold cyan')}\n {doc}" 

9900 example = getattr(function, "_example_", "").strip() 

9901 if example: 

9902 msg += f"\n {Color.yellowify('Example:')} {example}" 

9903 self.docs.append(msg) 

9904 return self 

9905 

9906 def __radd__(self, function: GenericFunction): 

9907 return self.__add__(function) 

9908 

9909 def __str__(self) -> str: 

9910 if self.should_refresh: 9910 ↛ 9912line 9910 didn't jump to line 9912 because the condition on line 9910 was always true

9911 self.__rebuild() 

9912 return self.__doc__ or "" 

9913 

9914 def __rebuild(self) -> None: 

9915 """Rebuild the documentation for functions.""" 

9916 for function in gef.gdb.functions.values(): 

9917 self += function 

9918 

9919 self.command_size = len(gef.gdb.commands) 

9920 _, cols = get_terminal_size() 

9921 separator = HORIZONTAL_LINE*cols 

9922 self.__doc__ = f"\n{separator}\n".join(sorted(self.docs)) 

9923 self.should_refresh = False 

9924 return 

9925 

9926 def do_invoke(self, argv) -> None: 

9927 self.dont_repeat() 

9928 gef_print(titlify("GEF - Convenience Functions")) 

9929 gef_print("These functions can be used as arguments to other " 

9930 "commands to dynamically calculate values\n") 

9931 gef_print(str(self)) 

9932 return 

9933 

9934 

9935# 

9936# GEF internal command classes 

9937# 

9938class GefCommand(gdb.Command): 

9939 """GEF main command: view all new commands by typing `gef`.""" 

9940 

9941 _cmdline_ = "gef" 

9942 _syntax_ = f"{_cmdline_} (missing|config|save|restore|set|run)" 

9943 

9944 def __init__(self) -> None: 

9945 super().__init__(self._cmdline_, gdb.COMMAND_SUPPORT, gdb.COMPLETE_NONE, True) 

9946 gef.config["gef.follow_child"] = GefSetting(True, bool, "Automatically set GDB to follow child when forking") 

9947 gef.config["gef.readline_compat"] = GefSetting(False, bool, "Workaround for readline SOH/ETX issue (SEGV)") 

9948 gef.config["gef.debug"] = GefSetting(False, bool, "Enable debug mode for gef") 

9949 gef.config["gef.autosave_breakpoints_file"] = GefSetting("", str, "Automatically save and restore breakpoints") 

9950 gef.config["gef.disable_target_remote_overwrite"] = GefSetting(False, bool, "Disable the overwrite of `target remote`") 

9951 plugins_dir = GefSetting("", str, "Autoload additional GEF commands from external directory", hooks={"on_write": [GefSetting.no_spaces, ]}) 

9952 plugins_dir.add_hook("on_changed", [lambda _, new_val: GefSetting.must_exist(new_val), lambda _, new_val: self.load_extra_plugins(new_val), ]) 

9953 gef.config["gef.extra_plugins_dir"] = plugins_dir 

9954 gef.config["gef.disable_color"] = GefSetting(False, bool, "Disable all colors in GEF") 

9955 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]}) 

9956 gef.config["gef.show_deprecation_warnings"] = GefSetting(True, bool, "Toggle the display of the `deprecated` warnings") 

9957 gef.config["gef.buffer"] = GefSetting(True, bool, "Internally buffer command output until completion") 

9958 gef.config["gef.bruteforce_main_arena"] = GefSetting(False, bool, "Allow bruteforcing main_arena symbol if everything else fails") 

9959 gef.config["gef.libc_version"] = GefSetting("", str, "Specify libc version when auto-detection fails") 

9960 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.") 

9961 gef.config["gef.propagate_debug_exception"] = GefSetting(False, bool, "If true, when debug mode is enabled, Python exceptions will be propagated all the way.") 

9962 

9963 self.commands : dict[str, GenericCommand] = {} 

9964 self.functions : dict[str, GenericFunction] = {} 

9965 self.missing: dict[str, Exception] = {} 

9966 return 

9967 

9968 @property 

9969 @deprecated() 

9970 def loaded_commands(self) -> list[tuple[str, Type[GenericCommand], Any]]: 

9971 raise ObsoleteException("Obsolete loaded_commands") 

9972 

9973 @property 

9974 @deprecated() 

9975 def loaded_functions(self) -> list[Type[GenericFunction]]: 

9976 raise ObsoleteException("Obsolete loaded_functions") 

9977 

9978 @property 

9979 @deprecated() 

9980 def missing_commands(self) -> dict[str, Exception]: 

9981 raise ObsoleteException("Obsolete missing_commands") 

9982 

9983 def setup(self) -> None: 

9984 self.load() 

9985 

9986 GefHelpCommand() 

9987 GefConfigCommand() 

9988 GefSaveCommand() 

9989 GefMissingCommand() 

9990 GefSetCommand() 

9991 GefRunCommand() 

9992 GefInstallExtraScriptCommand() 

9993 

9994 # At this point, commands (incl. extras) are loaded with default settings. 

9995 # Load custom settings from config file if any 

9996 GefRestoreCommand() 

9997 return 

9998 

9999 def load_extra_plugins(self, extra_plugins_dir: pathlib.Path | None = None) -> int: 

10000 """Load the plugins from the gef-extras setting. Returns the number of new plugins added.""" 

10001 def load_plugin(fpath: pathlib.Path) -> bool: 

10002 try: 

10003 dbg(f"Loading '{fpath}'") 

10004 gdb.execute(f"source {fpath}") 

10005 except AlreadyRegisteredException: 

10006 pass 

10007 except Exception as e: 

10008 warn(f"Exception while loading {fpath}: {str(e)}") 

10009 return False 

10010 return True 

10011 

10012 def load_plugins_from_directory(plugin_directory: pathlib.Path): 

10013 nb_added = -1 

10014 nb_initial = len(__registered_commands__) 

10015 start_time = time.perf_counter() 

10016 for entry in plugin_directory.glob("**/*.py"): 

10017 load_plugin(entry) 

10018 

10019 try: 

10020 nb_added = len(__registered_commands__) - nb_initial 

10021 if nb_added > 0: 

10022 self.load() 

10023 nb_failed = len(__registered_commands__) - len(self.commands) 

10024 load_time = time.perf_counter() - start_time 

10025 ok(f"{Color.colorify(str(nb_added), 'bold green')} extra commands added " \ 

10026 f"in {load_time:.2f} seconds") 

10027 if nb_failed != 0: 

10028 warn(f"{Color.colorify(str(nb_failed), 'bold light_gray')} extra commands/functions failed to be added. " 

10029 "Check `gef missing` to know why") 

10030 except gdb.error as e: 

10031 err(f"failed: {e}") 

10032 return nb_added 

10033 

10034 directory = extra_plugins_dir or gef.config["gef.extra_plugins_dir"] 

10035 if not directory: 10035 ↛ 10037line 10035 didn't jump to line 10037 because the condition on line 10035 was always true

10036 return 0 

10037 directory = pathlib.Path(directory).expanduser().absolute() 

10038 if not directory.exists(): 

10039 return 0 

10040 dbg(f"Loading extra plugins from directory={directory}") 

10041 return load_plugins_from_directory(directory) 

10042 

10043 @property 

10044 def loaded_command_names(self) -> Iterable[str]: 

10045 print("obsolete loaded_command_names") 

10046 return self.commands.keys() 

10047 

10048 def invoke(self, args: Any, from_tty: bool) -> None: 

10049 self.dont_repeat() 

10050 gdb.execute("gef help") 

10051 return 

10052 

10053 def add_context_layout_mapping(self, current_pane_name: str, display_pane_function: Callable, pane_title_function: Callable, condition: Callable | None) -> None: 

10054 """Add a new context layout mapping.""" 

10055 context = self.commands["context"] 

10056 assert isinstance(context, ContextCommand) 

10057 

10058 # overload the printing of pane title 

10059 context.layout_mapping[current_pane_name] = (display_pane_function, pane_title_function, condition) 

10060 

10061 def add_context_pane(self, pane_name: str, display_pane_function: Callable, pane_title_function: Callable, condition: Callable | None) -> None: 

10062 """Add a new context pane to ContextCommand.""" 

10063 # assure users can toggle the new context 

10064 corrected_settings_name: str = pane_name.replace(" ", "_") 

10065 if corrected_settings_name in gef.config["context.layout"]: 

10066 warn(f"Duplicate name for `{pane_name}` (`{corrected_settings_name}`), skipping") 

10067 return 

10068 

10069 gef.config["context.layout"] += f" {corrected_settings_name}" 

10070 self.add_context_layout_mapping(corrected_settings_name, display_pane_function, pane_title_function, condition) 

10071 

10072 def load(self) -> None: 

10073 """Load all the commands and functions defined by GEF into GDB.""" 

10074 current_commands = set(self.commands.keys()) 

10075 new_commands = set(x._cmdline_ for x in __registered_commands__) - current_commands 

10076 current_functions = set(self.functions.keys()) 

10077 new_functions = set(x._function_ for x in __registered_functions__) - current_functions 

10078 self.missing.clear() 

10079 self.__load_time_ms = time.time()* 1000 

10080 

10081 # load all new functions 

10082 for name in sorted(new_functions): 

10083 for function_cls in __registered_functions__: 10083 ↛ 10082line 10083 didn't jump to line 10082 because the loop on line 10083 didn't complete

10084 if function_cls._function_ == name: 

10085 assert issubclass(function_cls, GenericFunction) 

10086 self.functions[name] = function_cls() 

10087 break 

10088 

10089 # load all new commands 

10090 for name in sorted(new_commands): 

10091 try: 

10092 for command_cls in __registered_commands__: 10092 ↛ 10090line 10092 didn't jump to line 10090 because the loop on line 10092 didn't complete

10093 if command_cls._cmdline_ == name: 

10094 assert issubclass(command_cls, GenericCommand) 

10095 command_instance = command_cls() 

10096 

10097 # create the aliases if any 

10098 if hasattr(command_instance, "_aliases_"): 10098 ↛ 10103line 10098 didn't jump to line 10103 because the condition on line 10098 was always true

10099 aliases = getattr(command_instance, "_aliases_") 

10100 for alias in aliases: 

10101 GefAlias(alias, name) 

10102 

10103 self.commands[name] = command_instance 

10104 break 

10105 

10106 except Exception as reason: 

10107 self.missing[name] = reason 

10108 

10109 self.__load_time_ms = (time.time()* 1000) - self.__load_time_ms 

10110 return 

10111 

10112 

10113 def show_banner(self) -> None: 

10114 gef_print(f"{Color.greenify('GEF')} for {gef.session.os} ready, " 

10115 f"type `{Color.colorify('gef', 'underline yellow')}' to start, " 

10116 f"`{Color.colorify('gef config', 'underline pink')}' to configure") 

10117 

10118 ver = f"{sys.version_info.major:d}.{sys.version_info.minor:d}" 

10119 gef_print(f"{Color.colorify(str(len(self.commands)), 'bold green')} commands loaded " 

10120 f"and {Color.colorify(str(len(self.functions)), 'bold blue')} functions added for " 

10121 f"GDB {Color.colorify(gdb.VERSION, 'bold yellow')} in {self.__load_time_ms:.2f}ms " 

10122 f"using Python engine {Color.colorify(ver, 'bold red')}") 

10123 

10124 nb_missing = len(self.missing) 

10125 if nb_missing: 10125 ↛ 10126line 10125 didn't jump to line 10126 because the condition on line 10125 was never true

10126 warn(f"{Color.colorify(str(nb_missing), 'bold red')} " 

10127 f"command{'s' if nb_missing > 1 else ''} could not be loaded, " 

10128 f"run `{Color.colorify('gef missing', 'underline pink')}` to know why.") 

10129 return 

10130 

10131 

10132class GefHelpCommand(gdb.Command): 

10133 """GEF help sub-command.""" 

10134 _cmdline_ = "gef help" 

10135 _syntax_ = _cmdline_ 

10136 

10137 def __init__(self) -> None: 

10138 super().__init__(self._cmdline_, gdb.COMMAND_SUPPORT, gdb.COMPLETE_NONE, False) 

10139 self.docs = [] 

10140 self.should_refresh = True 

10141 self.command_size = 0 

10142 return 

10143 

10144 def invoke(self, args: Any, from_tty: bool) -> None: 

10145 self.dont_repeat() 

10146 gef_print(titlify("GEF - GDB Enhanced Features")) 

10147 gef_print(str(self)) 

10148 return 

10149 

10150 def __rebuild(self) -> None: 

10151 """Rebuild the documentation.""" 

10152 for name, cmd in gef.gdb.commands.items(): 

10153 self += (name, cmd) 

10154 

10155 self.command_size = len(gef.gdb.commands) 

10156 _, cols = get_terminal_size() 

10157 separator = HORIZONTAL_LINE*cols 

10158 self.__doc__ = f"\n{separator}\n".join(sorted(self.docs)) 

10159 self.should_refresh = False 

10160 return 

10161 

10162 def __add__(self, command: tuple[str, GenericCommand]): 

10163 """Add command to GEF documentation.""" 

10164 cmd, class_obj = command 

10165 if " " in cmd: 

10166 # do not print subcommands in gef help 

10167 return self 

10168 doc = getattr(class_obj, "__doc__", "").lstrip() 

10169 aliases = f"Aliases: {', '.join(class_obj._aliases_)}" if hasattr(class_obj, "_aliases_") else "" 

10170 msg = f"{Color.colorify(cmd, 'bold red')}\n{doc}\n{aliases}" 

10171 self.docs.append(msg) 

10172 return self 

10173 

10174 def __radd__(self, command: tuple[str, GenericCommand]): 

10175 return self.__add__(command) 

10176 

10177 def __str__(self) -> str: 

10178 """Lazily regenerate the `gef help` object if it was modified""" 

10179 # quick check in case the docs have changed 

10180 if self.should_refresh or self.command_size != len(gef.gdb.commands): 10180 ↛ 10182line 10180 didn't jump to line 10182 because the condition on line 10180 was always true

10181 self.__rebuild() 

10182 return self.__doc__ or "" 

10183 

10184 

10185class GefConfigCommand(gdb.Command): 

10186 """GEF configuration sub-command 

10187 This command will help set/view GEF settings for the current debugging session. 

10188 It is possible to make those changes permanent by running `gef save` (refer 

10189 to this command help), and/or restore previously saved settings by running 

10190 `gef restore` (refer help). 

10191 """ 

10192 _cmdline_ = "gef config" 

10193 _syntax_ = f"{_cmdline_} [setting_name] [setting_value]" 

10194 

10195 def __init__(self) -> None: 

10196 super().__init__(self._cmdline_, gdb.COMMAND_NONE, prefix=False) 

10197 return 

10198 

10199 def invoke(self, args: str, from_tty: bool) -> None: 

10200 self.dont_repeat() 

10201 argv = gdb.string_to_argv(args) 

10202 argc = len(argv) 

10203 

10204 if not (0 <= argc <= 2): 10204 ↛ 10205line 10204 didn't jump to line 10205 because the condition on line 10204 was never true

10205 err("Invalid number of arguments") 

10206 return 

10207 

10208 if argc == 0: 

10209 gef_print(titlify("GEF configuration settings")) 

10210 self.print_settings() 

10211 return 

10212 

10213 if argc == 1: 

10214 prefix = argv[0] 

10215 names = [x for x in gef.config.keys() if x.startswith(prefix)] 

10216 if names: 10216 ↛ 10223line 10216 didn't jump to line 10223 because the condition on line 10216 was always true

10217 if len(names) == 1: 10217 ↛ 10221line 10217 didn't jump to line 10221 because the condition on line 10217 was always true

10218 gef_print(titlify(f"GEF configuration setting: {names[0]}")) 

10219 self.print_setting(names[0], verbose=True) 

10220 else: 

10221 gef_print(titlify(f"GEF configuration settings matching '{argv[0]}'")) 

10222 for name in names: self.print_setting(name) 

10223 return 

10224 

10225 if not is_debug(): 

10226 try: 

10227 self.set_setting(argv) 

10228 except (ValueError, KeyError) as e: 

10229 err(str(e)) 

10230 else: 

10231 # Let exceptions (if any) propagate 

10232 self.set_setting(argv) 

10233 return 

10234 

10235 def print_setting(self, plugin_name: str, verbose: bool = False) -> None: 

10236 res = gef.config.raw_entry(plugin_name) 

10237 string_color = gef.config["theme.dereference_string"] 

10238 misc_color = gef.config["theme.dereference_base_address"] 

10239 

10240 if not res: 10240 ↛ 10241line 10240 didn't jump to line 10241 because the condition on line 10240 was never true

10241 return 

10242 

10243 _setting = Color.colorify(plugin_name, "green") 

10244 _type = res.type.__name__ 

10245 if _type == "str": 

10246 _value = f'"{Color.colorify(res.value, string_color)}"' 

10247 else: 

10248 _value = Color.colorify(res.value, misc_color) 

10249 

10250 gef_print(f"{_setting} ({_type}) = {_value}") 

10251 

10252 if verbose: 

10253 gef_print(Color.colorify("\nDescription:", "bold underline")) 

10254 gef_print(f"\t{res.description}") 

10255 return 

10256 

10257 def print_settings(self) -> None: 

10258 for x in sorted(gef.config): 

10259 self.print_setting(x) 

10260 return 

10261 

10262 def set_setting(self, argv: list[str]) -> bool: 

10263 global gef 

10264 key, new_value = argv 

10265 

10266 if "." not in key: 10266 ↛ 10267line 10266 didn't jump to line 10267 because the condition on line 10266 was never true

10267 err("Invalid command format") 

10268 return False 

10269 

10270 loaded_commands = list( gef.gdb.commands.keys()) + ["gef"] 

10271 plugin_name = key.split(".", 1)[0] 

10272 if plugin_name not in loaded_commands: 10272 ↛ 10273line 10272 didn't jump to line 10273 because the condition on line 10272 was never true

10273 err(f"Unknown plugin '{plugin_name}'") 

10274 return False 

10275 

10276 if key not in gef.config: 10276 ↛ 10277line 10276 didn't jump to line 10277 because the condition on line 10276 was never true

10277 dbg(f"'{key}' is not a valid configuration setting") 

10278 return False 

10279 

10280 _type = gef.config.raw_entry(key).type 

10281 

10282 # Attempt to parse specific values for known types 

10283 if _type is bool: 

10284 if new_value.upper() in ("TRUE", "T", "1"): 

10285 _newval = True 

10286 elif new_value.upper() in ("FALSE", "F", "0"): 

10287 _newval = False 

10288 else: 

10289 raise ValueError(f"Cannot parse '{new_value}' as bool") 

10290 else: 

10291 _newval = _type(new_value) 

10292 

10293 gef.config[key] = _newval 

10294 

10295 reset_all_caches() 

10296 return True 

10297 

10298 def complete(self, text: str, word: str) -> list[str]: 

10299 settings = sorted(gef.config) 

10300 

10301 if text == "": 

10302 # no prefix: example: `gef config TAB` 

10303 return [s for s in settings if word in s] 

10304 

10305 if "." not in text: 

10306 # if looking for possible prefix 

10307 return [s for s in settings if s.startswith(text.strip())] 

10308 

10309 # finally, look for possible values for given prefix 

10310 return [s.split(".", 1)[1] for s in settings if s.startswith(text.strip())] 

10311 

10312 

10313class GefSaveCommand(gdb.Command): 

10314 """GEF save sub-command. 

10315 Saves the current configuration of GEF to disk (by default in file '~/.gef.rc').""" 

10316 _cmdline_ = "gef save" 

10317 _syntax_ = _cmdline_ 

10318 

10319 def __init__(self) -> None: 

10320 super().__init__(self._cmdline_, gdb.COMMAND_SUPPORT, gdb.COMPLETE_NONE, False) 

10321 return 

10322 

10323 def invoke(self, args: Any, from_tty: bool) -> None: 

10324 self.dont_repeat() 

10325 cfg = configparser.RawConfigParser() 

10326 old_sect = None 

10327 

10328 # save the configuration 

10329 for key in sorted(gef.config): 

10330 sect, optname = key.split(".", 1) 

10331 value = gef.config[key] 

10332 

10333 if old_sect != sect: 

10334 cfg.add_section(sect) 

10335 old_sect = sect 

10336 

10337 cfg.set(sect, optname, value) 

10338 

10339 # save the aliases 

10340 cfg.add_section("aliases") 

10341 for alias in gef.session.aliases: 

10342 cfg.set("aliases", alias.alias, alias.command) 

10343 

10344 with GEF_RC.open("w") as fd: 

10345 cfg.write(fd) 

10346 

10347 ok(f"Configuration saved to '{GEF_RC}'") 

10348 return 

10349 

10350 

10351class GefRestoreCommand(gdb.Command): 

10352 """GEF restore sub-command. 

10353 Loads settings from file '~/.gef.rc' and apply them to the configuration of GEF.""" 

10354 _cmdline_ = "gef restore" 

10355 _syntax_ = _cmdline_ 

10356 

10357 def __init__(self) -> None: 

10358 super().__init__(self._cmdline_, gdb.COMMAND_SUPPORT, gdb.COMPLETE_NONE, False) 

10359 self.reload(True) 

10360 return 

10361 

10362 def invoke(self, args: str, from_tty: bool) -> None: 

10363 self.dont_repeat() 

10364 if GEF_RC.is_file(): 

10365 quiet = (args.lower() == "quiet") 

10366 self.reload(quiet) 

10367 return 

10368 

10369 def reload(self, quiet: bool): 

10370 cfg = configparser.ConfigParser() 

10371 cfg.read(GEF_RC) 

10372 

10373 for section in cfg.sections(): 

10374 if section == "aliases": 

10375 # load the aliases 

10376 for key in cfg.options(section): 

10377 try: 

10378 GefAlias(key, cfg.get(section, key)) 

10379 except Exception as e: 

10380 dbg(f"GefAlias() raised exception {e}") 

10381 continue 

10382 

10383 # load the other options 

10384 for optname in cfg.options(section): 

10385 key = f"{section}.{optname}" 

10386 try: 

10387 setting = gef.config.raw_entry(key) 

10388 except Exception: 

10389 continue 

10390 new_value = cfg.get(section, optname) 

10391 if setting.type is bool: 

10392 new_value = True if new_value.upper() in ("TRUE", "T", "1") else False 

10393 setting.value = setting.type(new_value) 

10394 

10395 if not quiet: 10395 ↛ 10396line 10395 didn't jump to line 10396 because the condition on line 10395 was never true

10396 ok(f"Configuration from '{Color.colorify(str(GEF_RC), 'bold blue')}' restored") 

10397 return 

10398 

10399 

10400class GefMissingCommand(gdb.Command): 

10401 """GEF missing sub-command 

10402 Display the GEF commands that could not be loaded, along with the reason of why 

10403 they could not be loaded. 

10404 """ 

10405 _cmdline_ = "gef missing" 

10406 _syntax_ = _cmdline_ 

10407 

10408 def __init__(self) -> None: 

10409 super().__init__(self._cmdline_, gdb.COMMAND_SUPPORT, gdb.COMPLETE_NONE, False) 

10410 return 

10411 

10412 def invoke(self, args: Any, from_tty: bool) -> None: 

10413 self.dont_repeat() 

10414 missing_commands: dict[str, Exception] = gef.gdb.missing 

10415 if not missing_commands: 

10416 ok("No missing command") 

10417 return 

10418 

10419 for cmd, exc in missing_commands.items(): 

10420 warn(f"Missing `{cmd}`: reason: {str(exc)})") 

10421 return 

10422 

10423 

10424class GefSetCommand(gdb.Command): 

10425 """Override GDB set commands with the context from GEF.""" 

10426 _cmdline_ = "gef set" 

10427 _syntax_ = f"{_cmdline_} [GDB_SET_ARGUMENTS]" 

10428 

10429 def __init__(self) -> None: 

10430 super().__init__(self._cmdline_, gdb.COMMAND_SUPPORT, gdb.COMPLETE_SYMBOL, False) 

10431 return 

10432 

10433 def invoke(self, args: Any, from_tty: bool) -> None: 

10434 self.dont_repeat() 

10435 args = args.split() 

10436 cmd = ["set", args[0],] 

10437 for p in args[1:]: 

10438 if p.startswith("$_gef"): 10438 ↛ 10442line 10438 didn't jump to line 10442 because the condition on line 10438 was always true

10439 c = gdb.parse_and_eval(p) 

10440 cmd.append(c.string()) 

10441 else: 

10442 cmd.append(p) 

10443 

10444 gdb.execute(" ".join(cmd)) 

10445 return 

10446 

10447 

10448class GefRunCommand(gdb.Command): 

10449 """Override GDB run commands with the context from GEF. 

10450 Simple wrapper for GDB run command to use arguments set from `gef set args`.""" 

10451 _cmdline_ = "gef run" 

10452 _syntax_ = f"{_cmdline_} [GDB_RUN_ARGUMENTS]" 

10453 

10454 def __init__(self) -> None: 

10455 super().__init__(self._cmdline_, gdb.COMMAND_SUPPORT, gdb.COMPLETE_FILENAME, False) 

10456 return 

10457 

10458 def invoke(self, args: Any, from_tty: bool) -> None: 

10459 self.dont_repeat() 

10460 if is_alive(): 

10461 gdb.execute("continue") 

10462 return 

10463 

10464 argv = args.split() 

10465 gdb.execute(f"gef set args {' '.join(argv)}") 

10466 gdb.execute("run") 

10467 return 

10468 

10469 

10470class GefAlias(gdb.Command): 

10471 """Simple aliasing wrapper because GDB doesn't do what it should.""" 

10472 

10473 def __init__(self, alias: str, command: str, completer_class: int = gdb.COMPLETE_NONE, command_class: int = gdb.COMMAND_NONE) -> None: 

10474 p = command.split() 

10475 if not p: 10475 ↛ 10476line 10475 didn't jump to line 10476 because the condition on line 10475 was never true

10476 return 

10477 

10478 if any(x for x in gef.session.aliases if x.alias == alias): 

10479 return 

10480 

10481 self.command = command 

10482 self.alias = alias 

10483 c = command.split()[0] 

10484 r = self.lookup_command(c) 

10485 self.__doc__ = f"Alias for '{Color.greenify(command)}'" 

10486 if r is not None: 

10487 _instance = r[1] 

10488 self.__doc__ += f": {_instance.__doc__}" 

10489 

10490 if hasattr(_instance, "complete"): 10490 ↛ 10491line 10490 didn't jump to line 10491 because the condition on line 10490 was never true

10491 self.complete = _instance.complete 

10492 

10493 super().__init__(alias, command_class, completer_class=completer_class) 

10494 gef.session.aliases.append(self) 

10495 return 

10496 

10497 def __repr__(self) -> str: 

10498 return f"GefAlias(from={self.alias}, to={self.command})" 

10499 

10500 def __str__(self) -> str: 

10501 return f"GefAlias(from={self.alias}, to={self.command})" 

10502 

10503 def invoke(self, args: Any, from_tty: bool) -> None: 

10504 gdb.execute(f"{self.command} {args}", from_tty=from_tty) 

10505 return 

10506 

10507 def lookup_command(self, cmd: str) -> tuple[str, GenericCommand] | None: 

10508 global gef 

10509 for _name, _instance in gef.gdb.commands.items(): 

10510 if cmd == _name: 

10511 return _name, _instance 

10512 

10513 return None 

10514 

10515 

10516@register 

10517class AliasesCommand(GenericCommand): 

10518 """Base command to add, remove, or list aliases.""" 

10519 

10520 _cmdline_ = "aliases" 

10521 _syntax_ = f"{_cmdline_} (add|rm|ls)" 

10522 

10523 def __init__(self) -> None: 

10524 super().__init__(prefix=True) 

10525 return 

10526 

10527 def do_invoke(self, _: list[str]) -> None: 

10528 self.usage() 

10529 return 

10530 

10531 

10532@register 

10533class AliasesAddCommand(AliasesCommand): 

10534 """Command to add aliases.""" 

10535 

10536 _cmdline_ = "aliases add" 

10537 _syntax_ = f"{_cmdline_} [ALIAS] [COMMAND]" 

10538 _example_ = f"{_cmdline_} scope telescope" 

10539 

10540 def __init__(self) -> None: 

10541 super().__init__() 

10542 return 

10543 

10544 def do_invoke(self, argv: list[str]) -> None: 

10545 if len(argv) < 2: 10545 ↛ 10546line 10545 didn't jump to line 10546 because the condition on line 10545 was never true

10546 self.usage() 

10547 return 

10548 GefAlias(argv[0], " ".join(argv[1:])) 

10549 return 

10550 

10551 

10552@register 

10553class AliasesRmCommand(AliasesCommand): 

10554 """Command to remove aliases.""" 

10555 

10556 _cmdline_ = "aliases rm" 

10557 _syntax_ = f"{_cmdline_} [ALIAS]" 

10558 

10559 def __init__(self) -> None: 

10560 super().__init__() 

10561 return 

10562 

10563 def do_invoke(self, argv: list[str]) -> None: 

10564 global gef 

10565 if len(argv) != 1: 10565 ↛ 10566line 10565 didn't jump to line 10566 because the condition on line 10565 was never true

10566 self.usage() 

10567 return 

10568 try: 

10569 alias_to_remove = next(filter(lambda x: x.alias == argv[0], gef.session.aliases)) 

10570 gef.session.aliases.remove(alias_to_remove) 

10571 except (ValueError, StopIteration): 

10572 err(f"{argv[0]} not found in aliases.") 

10573 return 

10574 gef_print("You must reload GEF for alias removals to apply.") 

10575 return 

10576 

10577 

10578@register 

10579class AliasesListCommand(AliasesCommand): 

10580 """Command to list aliases.""" 

10581 

10582 _cmdline_ = "aliases ls" 

10583 _syntax_ = _cmdline_ 

10584 

10585 def __init__(self) -> None: 

10586 super().__init__() 

10587 return 

10588 

10589 def do_invoke(self, _: list[str]) -> None: 

10590 ok("Aliases defined:") 

10591 for a in gef.session.aliases: 

10592 gef_print(f"{a.alias:30s} {RIGHT_ARROW} {a.command}") 

10593 return 

10594 

10595 

10596class GefTmuxSetup(gdb.Command): 

10597 """Setup a comfortable tmux debugging environment.""" 

10598 

10599 def __init__(self) -> None: 

10600 super().__init__("tmux-setup", gdb.COMMAND_NONE, gdb.COMPLETE_NONE) 

10601 GefAlias("screen-setup", "tmux-setup") 

10602 return 

10603 

10604 def invoke(self, args: Any, from_tty: bool) -> None: 

10605 self.dont_repeat() 

10606 

10607 tmux = os.getenv("TMUX") 

10608 if tmux: 

10609 self.tmux_setup() 

10610 return 

10611 

10612 screen = os.getenv("TERM") 

10613 if screen is not None and screen == "screen": 

10614 self.screen_setup() 

10615 return 

10616 

10617 warn("Not in a tmux/screen session") 

10618 return 

10619 

10620 def tmux_setup(self) -> None: 

10621 """Prepare the tmux environment by vertically splitting the current pane, and 

10622 forcing the context to be redirected there.""" 

10623 tmux = which("tmux") 

10624 ok("tmux session found, splitting window...") 

10625 

10626 pane, pty = subprocess.check_output([tmux, "splitw", "-h", '-F#{session_name}:#{window_index}.#{pane_index}-#{pane_tty}', "-P"]).decode().strip().split("-") 

10627 atexit.register(lambda : subprocess.run([tmux, "kill-pane", "-t", pane])) 

10628 # clear the screen and let it wait for input forever 

10629 gdb.execute(f"!'{tmux}' send-keys -t {pane} 'clear ; cat' C-m") 

10630 gdb.execute(f"!'{tmux}' select-pane -L") 

10631 

10632 ok(f"Setting `context.redirect` to '{pty}'...") 

10633 gdb.execute(f"gef config context.redirect {pty}") 

10634 ok("Done!") 

10635 return 

10636 

10637 def screen_setup(self) -> None: 

10638 """Hackish equivalent of the tmux_setup() function for screen.""" 

10639 screen = which("screen") 

10640 sty = os.getenv("STY") 

10641 ok("screen session found, splitting window...") 

10642 fd_script, script_path = tempfile.mkstemp() 

10643 fd_tty, tty_path = tempfile.mkstemp() 

10644 os.close(fd_tty) 

10645 

10646 with os.fdopen(fd_script, "w") as f: 

10647 f.write("startup_message off\n") 

10648 f.write("split -v\n") 

10649 f.write("focus right\n") 

10650 f.write(f"screen bash -c 'tty > {tty_path}; clear; cat'\n") 

10651 f.write("focus left\n") 

10652 

10653 gdb.execute(f"!'{screen}' -r '{sty}' -m -d -X source {script_path}") 

10654 # artificial delay to make sure `tty_path` is populated 

10655 time.sleep(0.25) 

10656 with open(tty_path, "r") as f: 

10657 pty = f.read().strip() 

10658 ok(f"Setting `context.redirect` to '{pty}'...") 

10659 gdb.execute(f"gef config context.redirect {pty}") 

10660 ok("Done!") 

10661 os.unlink(script_path) 

10662 os.unlink(tty_path) 

10663 return 

10664 

10665 

10666class GefInstallExtraScriptCommand(gdb.Command): 

10667 """`gef install` command: installs one or more scripts from the `gef-extras` script repo. Note that the command 

10668 doesn't check for external dependencies the script(s) might require.""" 

10669 _cmdline_ = "gef install" 

10670 _syntax_ = f"{_cmdline_} SCRIPTNAME [SCRIPTNAME [SCRIPTNAME...]]" 

10671 

10672 def __init__(self) -> None: 

10673 super().__init__(self._cmdline_, gdb.COMMAND_SUPPORT, gdb.COMPLETE_NONE, False) 

10674 self.branch = gef.config.get("gef.extras_default_branch", GEF_EXTRAS_DEFAULT_BRANCH) 

10675 return 

10676 

10677 def invoke(self, argv: str, from_tty: bool) -> None: 

10678 self.dont_repeat() 

10679 if not argv: 10679 ↛ 10680line 10679 didn't jump to line 10680 because the condition on line 10679 was never true

10680 err("No script name provided") 

10681 return 

10682 

10683 args = argv.split() 

10684 

10685 if "--list" in args or "-l" in args: 10685 ↛ 10686line 10685 didn't jump to line 10686 because the condition on line 10685 was never true

10686 subprocess.run(["xdg-open", f"https://github.com/hugsy/gef-extras/{self.branch}/"]) 

10687 return 

10688 

10689 self.dirpath = gef.config["gef.tempdir"].expanduser().absolute() 

10690 if not self.dirpath.is_dir(): 10690 ↛ 10691line 10690 didn't jump to line 10691 because the condition on line 10690 was never true

10691 err("'gef.tempdir' is not a valid directory") 

10692 return 

10693 

10694 for script in args: 

10695 script = script.lower() 

10696 if not self.__install_extras_script(script): 10696 ↛ 10697line 10696 didn't jump to line 10697 because the condition on line 10696 was never true

10697 warn(f"Failed to install '{script}', skipping...") 

10698 return 

10699 

10700 

10701 def __install_extras_script(self, script: str) -> bool: 

10702 fpath = self.dirpath / f"{script}.py" 

10703 if not fpath.exists(): 10703 ↛ 10715line 10703 didn't jump to line 10715 because the condition on line 10703 was always true

10704 url = f"https://raw.githubusercontent.com/hugsy/gef-extras/{self.branch}/scripts/{script}.py" 

10705 info(f"Searching for '{script}.py' in `gef-extras@{self.branch}`...") 

10706 data = http_get(url) 

10707 if not data: 10707 ↛ 10708line 10707 didn't jump to line 10708 because the condition on line 10707 was never true

10708 warn("Not found") 

10709 return False 

10710 

10711 with fpath.open("wb") as fd: 

10712 fd.write(data) 

10713 fd.flush() 

10714 

10715 old_command_set = set(gef.gdb.commands) 

10716 gdb.execute(f"source {fpath}") 

10717 new_command_set = set(gef.gdb.commands) 

10718 new_commands = [f"`{c[0]}`" for c in (new_command_set - old_command_set)] 

10719 ok(f"Installed file '{fpath}', new command(s) available: {', '.join(new_commands)}") 

10720 return True 

10721 

10722 

10723# 

10724# GEF internal classes 

10725# 

10726 

10727def __gef_prompt__(current_prompt: Callable[[Callable], str]) -> str: 

10728 """GEF custom prompt function.""" 

10729 if gef.config["gef.readline_compat"] is True: return GEF_PROMPT 

10730 if gef.config["gef.disable_color"] is True: return GEF_PROMPT 

10731 prompt = gef.session.remote.mode.prompt_string() if gef.session.remote else "" 

10732 prompt += "(core) " if is_target_coredump() else "" 

10733 prompt += GEF_PROMPT_ON if is_alive() else GEF_PROMPT_OFF 

10734 return prompt 

10735 

10736 

10737class GefManager(metaclass=abc.ABCMeta): 

10738 def reset_caches(self) -> None: 

10739 """Reset the LRU-cached attributes""" 

10740 for attr in dir(self): 

10741 try: 

10742 obj = getattr(self, attr) 

10743 if not hasattr(obj, "cache_clear"): 10743 ↛ 10745line 10743 didn't jump to line 10745 because the condition on line 10743 was always true

10744 continue 

10745 obj.cache_clear() 

10746 except Exception: 

10747 # we're resetting the cache here, we don't care if (or which) exception triggers 

10748 continue 

10749 return 

10750 

10751 

10752class GefMemoryManager(GefManager): 

10753 """Class that manages memory access for gef.""" 

10754 def __init__(self) -> None: 

10755 self.reset_caches() 

10756 return 

10757 

10758 def reset_caches(self) -> None: 

10759 super().reset_caches() 

10760 self.__maps: list[Section] | None = None 

10761 return 

10762 

10763 def write(self, address: int, buffer: ByteString, length: int | None = None) -> None: 

10764 """Write `buffer` at address `address`.""" 

10765 length = length or len(buffer) 

10766 gdb.selected_inferior().write_memory(address, buffer, length) 

10767 

10768 def read(self, addr: int, length: int = 0x10) -> bytes: 

10769 """Return a `length` long byte array with the copy of the process memory at `addr`.""" 

10770 return gdb.selected_inferior().read_memory(addr, length).tobytes() 

10771 

10772 def read_integer(self, addr: int) -> int: 

10773 """Return an integer read from memory.""" 

10774 sz = gef.arch.ptrsize 

10775 mem = self.read(addr, sz) 

10776 unpack = u32 if sz == 4 else u64 

10777 return unpack(mem) 

10778 

10779 def read_cstring(self, 

10780 address: int, 

10781 max_length: int = GEF_MAX_STRING_LENGTH, 

10782 encoding: str | None = None) -> str: 

10783 """Return a C-string read from memory.""" 

10784 encoding = encoding or "unicode-escape" 

10785 length = min(address | (DEFAULT_PAGE_SIZE-1), max_length+1) 

10786 

10787 try: 

10788 res_bytes = self.read(address, length) 

10789 except gdb.error: 

10790 current_address = address 

10791 res_bytes = b"" 

10792 while len(res_bytes) < length: 

10793 try: 

10794 # Calculate how many bytes there are until next page 

10795 next_page = current_address + DEFAULT_PAGE_SIZE 

10796 page_mask = ~(DEFAULT_PAGE_SIZE - 1) 

10797 size = (next_page & page_mask) - current_address 

10798 

10799 # Read until the end of the current page 

10800 res_bytes += self.read(current_address, size) 

10801 

10802 current_address += size 

10803 except gdb.error: 

10804 if not res_bytes: 

10805 err(f"Can't read memory at '{address:#x}'") 

10806 return "" 

10807 break 

10808 try: 

10809 with warnings.catch_warnings(): 

10810 # ignore DeprecationWarnings (see #735) 

10811 warnings.simplefilter("ignore") 

10812 res = res_bytes.decode(encoding, "strict") 

10813 except UnicodeDecodeError: 

10814 # latin-1 as fallback due to its single-byte to glyph mapping 

10815 res = res_bytes.decode("latin-1", "replace") 

10816 

10817 res = res.split("\x00", 1)[0] 

10818 ustr = res.replace("\n", "\\n").replace("\r", "\\r").replace("\t", "\\t") 

10819 if max_length and len(res) > max_length: 

10820 return f"{ustr[:max_length]}[...]" 

10821 return ustr 

10822 

10823 def read_ascii_string(self, address: int) -> str | None: 

10824 """Read an ASCII string from memory""" 

10825 cstr = self.read_cstring(address) 

10826 if isinstance(cstr, str) and cstr and all(x in string.printable for x in cstr): 

10827 return cstr 

10828 return None 

10829 

10830 @property 

10831 def maps(self) -> list[Section]: 

10832 if not self.__maps: 

10833 maps = self.__parse_maps() 

10834 if not maps: 10834 ↛ 10835line 10834 didn't jump to line 10835 because the condition on line 10834 was never true

10835 raise RuntimeError("Failed to determine memory layout") 

10836 self.__maps = maps 

10837 return self.__maps 

10838 

10839 def __parse_maps(self) -> list[Section] | None: 

10840 """Return the mapped memory sections. If the current arch has its maps 

10841 method defined, then defer to that to generated maps, otherwise, try to 

10842 figure it out from procfs, then info sections, then monitor info 

10843 mem.""" 

10844 if gef.arch.maps is not None: 10844 ↛ 10845line 10844 didn't jump to line 10845 because the condition on line 10844 was never true

10845 return list(gef.arch.maps()) 

10846 

10847 # Coredumps are the only case where `maintenance info sections` collected more 

10848 # info than `info proc sections`.so use this unconditionally. See #1154 

10849 

10850 if is_target_coredump(): 10850 ↛ 10851line 10850 didn't jump to line 10851 because the condition on line 10850 was never true

10851 return list(self.parse_gdb_maintenance_info_sections()) 

10852 

10853 try: 

10854 return list(self.parse_gdb_info_proc_maps()) 

10855 except Exception: 

10856 pass 

10857 

10858 try: 

10859 return list(self.parse_procfs_maps()) 

10860 except Exception: 

10861 pass 

10862 

10863 try: 

10864 return list(self.parse_monitor_info_mem()) 

10865 except Exception: 

10866 pass 

10867 

10868 raise RuntimeError("Failed to get memory layout") 

10869 

10870 @staticmethod 

10871 def parse_procfs_maps() -> Generator[Section, None, None]: 

10872 """Get the memory mapping from procfs.""" 

10873 procfs_mapfile = gef.session.maps 

10874 if not procfs_mapfile: 

10875 is_remote = gef.session.remote is not None 

10876 raise FileNotFoundError(f"Missing {'remote ' if is_remote else ''}procfs map file") 

10877 

10878 with procfs_mapfile.open("r") as fd: 

10879 for line in fd: 

10880 line = line.strip() 

10881 addr, perm, off, _, rest = line.split(" ", 4) 

10882 rest = rest.split(" ", 1) 

10883 if len(rest) == 1: 

10884 inode = rest[0] 

10885 pathname = "" 

10886 else: 

10887 inode = rest[0] 

10888 pathname = rest[1].lstrip() 

10889 

10890 addr_start, addr_end = parse_string_range(addr) 

10891 off = int(off, 16) 

10892 perm = Permission.from_process_maps(perm) 

10893 inode = int(inode) 

10894 yield Section(page_start=addr_start, 

10895 page_end=addr_end, 

10896 offset=off, 

10897 permission=perm, 

10898 inode=inode, 

10899 path=pathname) 

10900 return 

10901 

10902 @staticmethod 

10903 def parse_gdb_info_proc_maps() -> Generator[Section, None, None]: 

10904 """Get the memory mapping from GDB's command `info proc mappings`.""" 

10905 if GDB_VERSION < (11, 0): 10905 ↛ 10906line 10905 didn't jump to line 10906 because the condition on line 10905 was never true

10906 raise AttributeError("Disregarding old format") 

10907 

10908 output = (gdb.execute("info proc mappings", to_string=True) or "") 

10909 if not output: 10909 ↛ 10910line 10909 didn't jump to line 10910 because the condition on line 10909 was never true

10910 raise AttributeError 

10911 

10912 start_idx = output.find("Start Addr") 

10913 if start_idx == -1: 

10914 raise AttributeError 

10915 

10916 output = output[start_idx:] 

10917 lines = output.splitlines() 

10918 if len(lines) < 2: 10918 ↛ 10919line 10918 didn't jump to line 10919 because the condition on line 10918 was never true

10919 raise AttributeError 

10920 

10921 # The function assumes the following output format (as of GDB 11+) for `info proc mappings`: 

10922 # - live process (incl. remote) 

10923 # ``` 

10924 # Start Addr End Addr Size Offset Perms objfile 

10925 # 0x555555554000 0x555555558000 0x4000 0x0 r--p /usr/bin/ls 

10926 # 0x555555558000 0x55555556c000 0x14000 0x4000 r-xp /usr/bin/ls 

10927 # [...] 

10928 # ``` 

10929 # or 

10930 # - coredump & rr 

10931 # ``` 

10932 # Start Addr End Addr Size Offset objfile 

10933 # 0x555555554000 0x555555558000 0x4000 0x0 /usr/bin/ls 

10934 # 0x555555558000 0x55555556c000 0x14000 0x4000 /usr/bin/ls 

10935 # ``` 

10936 # In the latter case the 'Perms' header is missing, so mock the Permission to `rwx` so 

10937 # `dereference` will still work. 

10938 

10939 mock_permission = all(map(lambda x: x.strip() != "Perms", lines[0].split())) 

10940 for line in lines[1:]: 

10941 if not line: 10941 ↛ 10942line 10941 didn't jump to line 10942 because the condition on line 10941 was never true

10942 break 

10943 

10944 parts = [x.strip() for x in line.split()] 

10945 addr_start, addr_end, _, offset = [int(x, 16) for x in parts[0:4]] 

10946 if mock_permission: 10946 ↛ 10947line 10946 didn't jump to line 10947 because the condition on line 10946 was never true

10947 perm = Permission(7) 

10948 path = " ".join(parts[4:]) if len(parts) >= 4 else "" 

10949 else: 

10950 perm = Permission.from_process_maps(parts[4]) 

10951 path = " ".join(parts[5:]) if len(parts) >= 5 else "" 

10952 yield Section( 

10953 page_start=addr_start, 

10954 page_end=addr_end, 

10955 offset=offset, 

10956 permission=perm, 

10957 path=path, 

10958 ) 

10959 return 

10960 

10961 @staticmethod 

10962 def parse_monitor_info_mem() -> Generator[Section, None, None]: 

10963 """Get the memory mapping from GDB's command `monitor info mem` 

10964 This can raise an exception, which the memory manager takes to mean 

10965 that this method does not work to get a map. 

10966 """ 

10967 stream = StringIO(gdb.execute("monitor info mem", to_string=True)) 

10968 

10969 for line in stream: 

10970 try: 

10971 ranges, off, perms = line.split() 

10972 off = int(off, 16) 

10973 start, end = [int(s, 16) for s in ranges.split("-")] 

10974 except ValueError: 

10975 continue 

10976 

10977 perm = Permission.from_monitor_info_mem(perms) 

10978 yield Section(page_start=start, 

10979 page_end=end, 

10980 offset=off, 

10981 permission=perm) 

10982 

10983 @staticmethod 

10984 def parse_gdb_maintenance_info_sections() -> Generator[Section, None, None]: 

10985 """Get the memory mapping from GDB's command `maintenance info sections` (limited info). In some cases (i.e. coredumps), 

10986 the memory info collected by `info proc sections` is insufficient.""" 

10987 stream = StringIO(gdb.execute("maintenance info sections", to_string=True)) 

10988 

10989 for line in stream: 

10990 if not line: 

10991 break 

10992 

10993 try: 

10994 parts = line.split() 

10995 addr_start, addr_end = [int(x, 16) for x in parts[1].split("->")] 

10996 off = int(parts[3][:-1], 16) 

10997 path = parts[4] 

10998 perm = Permission.NONE 

10999 if "DATA" in parts[5:]: 

11000 perm |= Permission.READ | Permission.WRITE 

11001 if "CODE" in parts[5:]: 

11002 perm |= Permission.READ | Permission.EXECUTE 

11003 yield Section( 

11004 page_start=addr_start, 

11005 page_end=addr_end, 

11006 offset=off, 

11007 permission=perm, 

11008 path=path, 

11009 ) 

11010 

11011 except IndexError: 

11012 continue 

11013 except ValueError: 

11014 continue 

11015 

11016 @staticmethod 

11017 def parse_info_mem(): 

11018 """Get the memory mapping from GDB's command `info mem`. This can be 

11019 provided by certain gdbserver implementations.""" 

11020 for line in StringIO(gdb.execute("info mem", to_string=True)): 

11021 # Using memory regions provided by the target. 

11022 # Num Enb Low Addr High Addr Attrs 

11023 # 0 y 0x10000000 0x10200000 flash blocksize 0x1000 nocache 

11024 # 1 y 0x20000000 0x20042000 rw nocache 

11025 _, en, start, end, *attrs = line.split() 

11026 if en != "y": 

11027 continue 

11028 

11029 if "flash" in attrs: 

11030 perm = Permission.from_info_mem("r") 

11031 else: 

11032 perm = Permission.from_info_mem("rw") 

11033 yield Section(page_start=int(start, 0), 

11034 page_end=int(end, 0), 

11035 permission=perm) 

11036 

11037 def append(self, section: Section): 

11038 if not self.maps: 

11039 raise AttributeError("No mapping defined") 

11040 if not isinstance(section, Section): 

11041 raise TypeError("section has an invalid type") 

11042 

11043 assert self.__maps 

11044 for s in self.__maps: 

11045 if section.overlaps(s): 

11046 raise RuntimeError(f"{section} overlaps {s}") 

11047 self.__maps.append(section) 

11048 return self 

11049 

11050 def __iadd__(self, section: Section): 

11051 return self.append(section) 

11052 

11053 

11054class GefHeapManager(GefManager): 

11055 """Class managing session heap.""" 

11056 def __init__(self) -> None: 

11057 self.reset_caches() 

11058 return 

11059 

11060 def reset_caches(self) -> None: 

11061 self.__libc_main_arena: GlibcArena | None = None 

11062 self.__libc_selected_arena: GlibcArena | None = None 

11063 self.__heap_base = None 

11064 return 

11065 

11066 @property 

11067 def main_arena(self) -> GlibcArena | None: 

11068 if not self.__libc_main_arena: 

11069 try: 

11070 __main_arena_addr = GefHeapManager.find_main_arena_addr() 

11071 self.__libc_main_arena = GlibcArena(f"*{__main_arena_addr:#x}") 

11072 # the initialization of `main_arena` also defined `selected_arena`, so 

11073 # by default, `main_arena` == `selected_arena` 

11074 self.selected_arena = self.__libc_main_arena 

11075 except Exception: 

11076 # the search for arena can fail when the session is not started 

11077 pass 

11078 return self.__libc_main_arena 

11079 

11080 @main_arena.setter 

11081 def main_arena(self, value: GlibcArena) -> None: 

11082 self.__libc_main_arena = value 

11083 return 

11084 

11085 @staticmethod 

11086 @lru_cache() 

11087 def find_main_arena_addr() -> int: 

11088 assert gef.libc.version 

11089 """A helper function to find the glibc `main_arena` address, either from 

11090 symbol, from its offset from `__malloc_hook` or by brute force.""" 

11091 # Before anything else, use libc offset from config if available 

11092 if gef.config["gef.main_arena_offset"]: 11092 ↛ 11093line 11092 didn't jump to line 11093 because the condition on line 11092 was never true

11093 try: 

11094 libc_base = get_section_base_address("libc") 

11095 offset: int = gef.config["gef.main_arena_offset"] 

11096 if libc_base: 

11097 dbg(f"Using main_arena_offset={offset:#x} from config") 

11098 addr = libc_base + offset 

11099 

11100 # Verify the found address before returning 

11101 if GlibcArena.verify(addr): 

11102 return addr 

11103 except gdb.error: 

11104 pass 

11105 

11106 # First, try to find `main_arena` symbol directly 

11107 try: 

11108 return parse_address(f"&{LIBC_HEAP_MAIN_ARENA_DEFAULT_NAME}") 

11109 except gdb.error: 

11110 pass 

11111 

11112 # Second, try to find it by offset from `__malloc_hook` 

11113 if gef.libc.version < (2, 34): 

11114 try: 

11115 malloc_hook_addr = parse_address("(void *)&__malloc_hook") 

11116 

11117 struct_size = ctypes.sizeof(GlibcArena.malloc_state_t()) 

11118 

11119 if is_x86(): 

11120 addr = align_address_to_size(malloc_hook_addr + gef.arch.ptrsize, 0x20) 

11121 elif is_arch(Elf.Abi.AARCH64): 

11122 addr = malloc_hook_addr - gef.arch.ptrsize*2 - struct_size 

11123 elif is_arch(Elf.Abi.ARM): 

11124 addr = malloc_hook_addr - gef.arch.ptrsize - struct_size 

11125 else: 

11126 addr = None 

11127 

11128 # Verify the found address before returning 

11129 if addr and GlibcArena.verify(addr): 

11130 return addr 

11131 

11132 except gdb.error: 

11133 pass 

11134 

11135 # Last resort, try to find it via brute force if enabled in settings 

11136 if gef.config["gef.bruteforce_main_arena"]: 

11137 alignment = 0x8 

11138 try: 

11139 dbg("Trying to bruteforce main_arena address") 

11140 # setup search_range for `main_arena` to `.data` of glibc 

11141 def search_filter(zone: Zone) -> bool: 

11142 return "libc" in pathlib.Path(zone.filename).name and zone.name == ".data" 

11143 

11144 for dotdata in list(filter(search_filter, get_info_files())): 

11145 search_range = range(dotdata.zone_start, dotdata.zone_end, alignment) 

11146 # find first possible candidate 

11147 for addr in search_range: 

11148 if GlibcArena.verify(addr): 

11149 dbg(f"Found candidate at {addr:#x}") 

11150 return addr 

11151 dbg("Bruteforce not successful") 

11152 except Exception: 

11153 pass 

11154 

11155 # Nothing helped 

11156 err_msg = f"Cannot find main_arena for {gef.arch.arch}. You might want to set a manually found libc offset " 

11157 if not gef.config["gef.bruteforce_main_arena"]: 

11158 err_msg += "or allow bruteforcing " 

11159 err_msg += "through the GEF config." 

11160 raise OSError(err_msg) 

11161 

11162 @property 

11163 def selected_arena(self) -> GlibcArena | None: 

11164 if not self.__libc_selected_arena: 11164 ↛ 11166line 11164 didn't jump to line 11166 because the condition on line 11164 was never true

11165 # `selected_arena` must default to `main_arena` 

11166 self.__libc_selected_arena = self.main_arena 

11167 return self.__libc_selected_arena 

11168 

11169 @selected_arena.setter 

11170 def selected_arena(self, value: GlibcArena) -> None: 

11171 self.__libc_selected_arena = value 

11172 return 

11173 

11174 @property 

11175 def arenas(self) -> list | Iterator[GlibcArena]: 

11176 if not self.main_arena: 11176 ↛ 11177line 11176 didn't jump to line 11177 because the condition on line 11176 was never true

11177 return [] 

11178 return iter(self.main_arena) 

11179 

11180 @property 

11181 def base_address(self) -> int | None: 

11182 if not self.__heap_base: 

11183 base = 0 

11184 try: 

11185 base = parse_address("mp_->sbrk_base") 

11186 base = self.malloc_align_address(base) 

11187 except gdb.error: 

11188 # missing symbol, try again 

11189 base = 0 

11190 if not base: 11190 ↛ 11191line 11190 didn't jump to line 11191 because the condition on line 11190 was never true

11191 base = get_section_base_address("[heap]") 

11192 self.__heap_base = base 

11193 return self.__heap_base 

11194 

11195 @property 

11196 def chunks(self) -> list | Iterator: 

11197 if not self.base_address: 

11198 return [] 

11199 return iter(GlibcChunk(self.base_address, from_base=True)) 

11200 

11201 @property 

11202 def min_chunk_size(self) -> int: 

11203 return 4 * gef.arch.ptrsize 

11204 

11205 @property 

11206 def malloc_alignment(self) -> int: 

11207 assert gef.libc.version 

11208 __default_malloc_alignment = 0x10 

11209 if gef.libc.version >= (2, 26) and is_x86_32(): 11209 ↛ 11212line 11209 didn't jump to line 11212 because the condition on line 11209 was never true

11210 # Special case introduced in Glibc 2.26: 

11211 # https://elixir.bootlin.com/glibc/glibc-2.26/source/sysdeps/i386/malloc-alignment.h#L22 

11212 return __default_malloc_alignment 

11213 # Generic case: 

11214 # https://elixir.bootlin.com/glibc/glibc-2.26/source/sysdeps/generic/malloc-alignment.h#L22 

11215 return 2 * gef.arch.ptrsize 

11216 

11217 def csize2tidx(self, size: int) -> int: 

11218 return abs((size - self.min_chunk_size + self.malloc_alignment - 1)) // self.malloc_alignment 

11219 

11220 def tidx2size(self, idx: int) -> int: 

11221 return idx * self.malloc_alignment + self.min_chunk_size 

11222 

11223 def malloc_align_address(self, address: int) -> int: 

11224 """Align addresses according to glibc's MALLOC_ALIGNMENT. See also Issue #689 on Github""" 

11225 def ceil(n: float) -> int: 

11226 return int(-1 * n // 1 * -1) 

11227 malloc_alignment = self.malloc_alignment 

11228 return malloc_alignment * ceil((address / malloc_alignment)) 

11229 

11230 

11231class GefSetting: 

11232 """Basic class for storing gef settings as objects""" 

11233 

11234 def __init__(self, value: Any, cls: type | None = None, description: str | None = None, hooks: dict[str, list[Callable]] | None = None) -> None: 

11235 self.value = value 

11236 self.type = cls or type(value) 

11237 self.description = description or "" 

11238 self.hooks: dict[str, list[Callable]] = collections.defaultdict(list) 

11239 if not hooks: 

11240 hooks = {"on_read": [], "on_write": [], "on_changed": []} 

11241 

11242 for access, funcs in hooks.items(): 

11243 self.add_hook(access, funcs) 

11244 return 

11245 

11246 def __str__(self) -> str: 

11247 return f"Setting(type={self.type.__name__}, value='{self.value}', desc='{self.description[:10]}...', " \ 

11248 f"read_hooks={len(self.hooks['on_read'])}, write_hooks={len(self.hooks['on_write'])}, "\ 

11249 f"changed_hooks={len(self.hooks['on_changed'])})" 

11250 

11251 def add_hook(self, access: str, funcs: list[Callable]): 

11252 if access not in ("on_read", "on_write", "on_changed"): 11252 ↛ 11253line 11252 didn't jump to line 11253 because the condition on line 11252 was never true

11253 raise ValueError("invalid access type") 

11254 for func in funcs: 

11255 if not callable(func): 11255 ↛ 11256line 11255 didn't jump to line 11256 because the condition on line 11255 was never true

11256 raise ValueError("hook is not callable") 

11257 self.hooks[access].append(func) 

11258 return self 

11259 

11260 @staticmethod 

11261 def no_spaces(value: pathlib.Path): 

11262 if " " in str(value): 

11263 raise ValidationError("setting cannot contain spaces") 

11264 

11265 @staticmethod 

11266 def must_exist(value: pathlib.Path): 

11267 if not value or not pathlib.Path(value).expanduser().absolute().exists(): 

11268 raise ValidationError("specified path must exist") 

11269 

11270 @staticmethod 

11271 def create_folder_tree(value: pathlib.Path): 

11272 value.mkdir(0o755, exist_ok=True, parents=True) 

11273 

11274 

11275class GefSettingsManager(dict): 

11276 """ 

11277 GefSettings acts as a dict where the global settings are stored and can be read, written or deleted as any other dict. 

11278 For instance, to read a specific command setting: `gef.config[mycommand.mysetting]` 

11279 """ 

11280 def __getitem__(self, name: str) -> Any: 

11281 setting : GefSetting = super().__getitem__(name) 

11282 self.__invoke_read_hooks(setting) 

11283 return setting.value 

11284 

11285 def __setitem__(self, name: str, value: Any) -> None: 

11286 # check if the key exists 

11287 if super().__contains__(name): 

11288 # if so, update its value directly 

11289 setting = super().__getitem__(name) 

11290 if not isinstance(setting, GefSetting): raise TypeError 11290 ↛ exitline 11290 didn't except from function '__setitem__' because the raise on line 11290 wasn't executed

11291 new_value = setting.type(value) 

11292 dbg(f"in __invoke_changed_hooks(\"{name}\"), setting.value={setting.value} -> new_value={new_value}, changing={bool(setting.value != new_value)}") 

11293 self.__invoke_changed_hooks(setting, new_value) 

11294 self.__invoke_write_hooks(setting, new_value) 

11295 setting.value = new_value 

11296 return 

11297 

11298 # if not, assert `value` is a GefSetting, then insert it 

11299 if not isinstance(value, GefSetting): raise TypeError("Invalid argument") 11299 ↛ exitline 11299 didn't except from function '__setitem__' because the raise on line 11299 wasn't executed

11300 if not value.type: raise TypeError("Invalid type") 11300 ↛ exitline 11300 didn't except from function '__setitem__' because the raise on line 11300 wasn't executed

11301 if not value.description: raise AttributeError("Invalid description") 11301 ↛ exitline 11301 didn't except from function '__setitem__' because the raise on line 11301 wasn't executed

11302 setting = value 

11303 value = setting.value 

11304 self.__invoke_write_hooks(setting, value) 

11305 super().__setitem__(name, setting) 

11306 return 

11307 

11308 def __delitem__(self, name: str) -> None: 

11309 return super().__delitem__(name) 

11310 

11311 def raw_entry(self, name: str) -> GefSetting: 

11312 return super().__getitem__(name) 

11313 

11314 def __invoke_read_hooks(self, setting: GefSetting) -> None: 

11315 for callback in setting.hooks["on_read"]: 11315 ↛ 11316line 11315 didn't jump to line 11316 because the loop on line 11315 never started

11316 callback() 

11317 return 

11318 

11319 def __invoke_changed_hooks(self, setting: GefSetting, new_value: Any) -> None: 

11320 old_value = setting.value 

11321 if old_value == new_value: 

11322 return 

11323 for callback in setting.hooks["on_changed"]: 11323 ↛ 11324line 11323 didn't jump to line 11324 because the loop on line 11323 never started

11324 callback(old_value, new_value) 

11325 

11326 def __invoke_write_hooks(self, setting: GefSetting, new_value: Any) -> None: 

11327 for callback in setting.hooks["on_write"]: 

11328 callback(new_value) 

11329 

11330 

11331class GefSessionManager(GefManager): 

11332 """Class managing the runtime properties of GEF. """ 

11333 def __init__(self) -> None: 

11334 self.reset_caches() 

11335 self.remote: "GefRemoteSessionManager | None" = None 

11336 self.remote_initializing: bool = False 

11337 self.qemu_mode: bool = False 

11338 self.coredump_mode: bool | None = None 

11339 self.convenience_vars_index: int = 0 

11340 self.heap_allocated_chunks: list[tuple[int, int]] = [] 

11341 self.heap_freed_chunks: list[tuple[int, int]] = [] 

11342 self.heap_uaf_watchpoints: list[UafWatchpoint] = [] 

11343 self.pie_breakpoints: dict[int, PieVirtualBreakpoint] = {} 

11344 self.pie_counter: int = 1 

11345 self.aliases: list[GefAlias] = [] 

11346 self.modules: list[FileFormat] = [] 

11347 self.constants = {} # a dict for runtime constants (like 3rd party file paths) 

11348 for constant in ("python3", "readelf", "nm", "file", "ps"): 

11349 self.constants[constant] = which(constant) 

11350 return 

11351 

11352 def reset_caches(self) -> None: 

11353 super().reset_caches() 

11354 self._auxiliary_vector = None 

11355 self._pagesize = None 

11356 self._os = None 

11357 self._pid = None 

11358 self._file = None 

11359 self._maps: pathlib.Path | None = None 

11360 self._root: pathlib.Path | None = None 

11361 return 

11362 

11363 def __str__(self) -> str: 

11364 _type = "Local" if self.remote is None else f"Remote/{self.remote.mode}" 

11365 return f"Session(type={_type}, pid={self.pid or 'Not running'}, os='{self.os}')" 

11366 

11367 def __repr__(self) -> str: 

11368 return str(self) 

11369 

11370 @property 

11371 def auxiliary_vector(self) -> dict[str, int] | None: 

11372 if not is_alive(): 

11373 return None 

11374 if is_qemu_system(): 11374 ↛ 11375line 11374 didn't jump to line 11375 because the condition on line 11374 was never true

11375 return None 

11376 if not self._auxiliary_vector: 

11377 auxiliary_vector = {} 

11378 auxv_info = gdb.execute("info auxv", to_string=True) or "" 

11379 if not auxv_info or "failed" in auxv_info: 11379 ↛ 11380line 11379 didn't jump to line 11380 because the condition on line 11379 was never true

11380 err("Failed to query auxiliary variables") 

11381 return None 

11382 for line in auxv_info.splitlines(): 

11383 line = line.split('"')[0].strip() # remove the ending string (if any) 

11384 line = line.split() # split the string by whitespace(s) 

11385 if len(line) < 4: 11385 ↛ 11386line 11385 didn't jump to line 11386 because the condition on line 11385 was never true

11386 continue 

11387 __av_type = line[1] 

11388 __av_value = line[-1] 

11389 auxiliary_vector[__av_type] = int(__av_value, base=0) 

11390 self._auxiliary_vector = auxiliary_vector 

11391 return self._auxiliary_vector 

11392 

11393 @property 

11394 def os(self) -> str: 

11395 """Return the current OS.""" 

11396 if not self._os: 

11397 self._os = platform.system().lower() 

11398 return self._os 

11399 

11400 @property 

11401 def pid(self) -> int: 

11402 """Return the PID of the target process.""" 

11403 if not self._pid: 

11404 pid = gdb.selected_inferior().pid if not self.qemu_mode else gdb.selected_thread().ptid[1] 

11405 if not pid: 

11406 raise RuntimeError("cannot retrieve PID for target process") 

11407 self._pid = pid 

11408 return self._pid 

11409 

11410 @property 

11411 def file(self) -> pathlib.Path | None: 

11412 """Return a Path object of the target process.""" 

11413 if self.remote is not None: 

11414 return self.remote.file 

11415 progspace = gdb.current_progspace() 

11416 assert progspace 

11417 fpath: str = progspace.filename or "" 

11418 if fpath and not self._file: 

11419 self._file = pathlib.Path(fpath).expanduser() 

11420 return self._file 

11421 

11422 @property 

11423 def cwd(self) -> pathlib.Path | None: 

11424 if self.remote is not None: 

11425 return self.remote.root 

11426 return self.file.parent if self.file else None 

11427 

11428 @property 

11429 def pagesize(self) -> int: 

11430 """Get the system page size""" 

11431 auxval = self.auxiliary_vector 

11432 if not auxval: 

11433 return DEFAULT_PAGE_SIZE 

11434 self._pagesize = auxval["AT_PAGESZ"] 

11435 return self._pagesize 

11436 

11437 @property 

11438 def canary(self) -> tuple[int, int] | None: 

11439 """Return a tuple of the canary address and value, read from the canonical 

11440 location if supported by the architecture. Otherwise, read from the auxiliary 

11441 vector.""" 

11442 try: 

11443 canary_location = gef.arch.canary_address() 

11444 canary = gef.memory.read_integer(canary_location) 

11445 except (NotImplementedError, gdb.error): 

11446 # Fall back to `AT_RANDOM`, which is the original source 

11447 # of the canary value but not the canonical location 

11448 return self.original_canary 

11449 return canary, canary_location 

11450 

11451 @property 

11452 def original_canary(self) -> tuple[int, int] | None: 

11453 """Return a tuple of the initial canary address and value, read from the 

11454 auxiliary vector.""" 

11455 auxval = self.auxiliary_vector 

11456 if not auxval: 

11457 return None 

11458 canary_location = auxval["AT_RANDOM"] 

11459 canary = gef.memory.read_integer(canary_location) 

11460 canary &= ~0xFF 

11461 return canary, canary_location 

11462 

11463 @property 

11464 def maps(self) -> pathlib.Path | None: 

11465 """Returns the Path to the procfs entry for the memory mapping.""" 

11466 if not is_alive(): 

11467 return None 

11468 if not self._maps: 

11469 if self.remote is not None: 

11470 self._maps = self.remote.maps 

11471 else: 

11472 self._maps = pathlib.Path(f"/proc/{self.pid}/maps") 

11473 return self._maps 

11474 

11475 @property 

11476 def root(self) -> pathlib.Path | None: 

11477 """Returns the path to the process's root directory.""" 

11478 if not is_alive(): 

11479 return None 

11480 if not self._root: 

11481 self._root = pathlib.Path(f"/proc/{self.pid}/root") 

11482 return self._root 

11483 

11484 

11485class GefRemoteSessionManager(GefSessionManager): 

11486 """Class for managing remote sessions with GEF. It will create a temporary environment 

11487 designed to clone the remote one.""" 

11488 

11489 class RemoteMode(enum.IntEnum): 

11490 GDBSERVER = 0 

11491 QEMU = 1 

11492 RR = 2 

11493 

11494 def __str__(self): 

11495 return self.name 

11496 

11497 def __repr__(self): 

11498 return f"RemoteMode = {str(self)} ({int(self)})" 

11499 

11500 def prompt_string(self) -> str: 

11501 match self: 

11502 case GefRemoteSessionManager.RemoteMode.QEMU: 

11503 return Color.boldify("(qemu) ") 

11504 case GefRemoteSessionManager.RemoteMode.RR: 

11505 return Color.boldify("(rr) ") 

11506 case GefRemoteSessionManager.RemoteMode.GDBSERVER: 

11507 return Color.boldify("(remote) ") 

11508 raise AttributeError("Unknown value") 

11509 

11510 def __init__(self, host: str, port: int, pid: int =-1, qemu: pathlib.Path | None = None) -> None: 

11511 super().__init__() 

11512 self.__host = host 

11513 self.__port = port 

11514 self.__local_root_fd = tempfile.TemporaryDirectory() 

11515 self.__local_root_path = pathlib.Path(self.__local_root_fd.name) 

11516 self.__qemu = qemu 

11517 if pid > 0: 11517 ↛ 11518line 11517 didn't jump to line 11518 because the condition on line 11517 was never true

11518 self._pid = pid 

11519 

11520 if self.__qemu is not None: 

11521 self._mode = GefRemoteSessionManager.RemoteMode.QEMU 

11522 elif os.environ.get("GDB_UNDER_RR", None) == "1": 11522 ↛ 11523line 11522 didn't jump to line 11523 because the condition on line 11522 was never true

11523 self._mode = GefRemoteSessionManager.RemoteMode.RR 

11524 else: 

11525 self._mode = GefRemoteSessionManager.RemoteMode.GDBSERVER 

11526 

11527 def close(self) -> None: 

11528 self.__local_root_fd.cleanup() 

11529 try: 

11530 gef_on_new_unhook(self.remote_objfile_event_handler) 

11531 gef_on_new_hook(new_objfile_handler) 

11532 except Exception as e: 

11533 warn(f"Exception while restoring local context: {str(e)}") 

11534 raise 

11535 

11536 def __str__(self) -> str: 

11537 return f"RemoteSession(target='{self.target}', local='{self.root}', pid={self.pid}, mode={self.mode})" 

11538 

11539 def __repr__(self) -> str: 

11540 return str(self) 

11541 

11542 @property 

11543 def target(self) -> str: 

11544 return f"{self.__host}:{self.__port}" 

11545 

11546 @property 

11547 def root(self) -> pathlib.Path: 

11548 return self.__local_root_path.absolute() 

11549 

11550 @property 

11551 def file(self) -> pathlib.Path: 

11552 """Path to the file being debugged as seen by the remote endpoint.""" 

11553 if not self._file: 

11554 progspace = gdb.current_progspace() 

11555 if not progspace: 11555 ↛ 11556line 11555 didn't jump to line 11556 because the condition on line 11555 was never true

11556 raise RuntimeError("No session started") 

11557 filename = progspace.filename 

11558 if not filename: 11558 ↛ 11559line 11558 didn't jump to line 11559 because the condition on line 11558 was never true

11559 raise RuntimeError("No session started") 

11560 start_idx = len("target:") if filename.startswith("target:") else 0 

11561 self._file = pathlib.Path(filename[start_idx:]) 

11562 return self._file 

11563 

11564 @property 

11565 def lfile(self) -> pathlib.Path: 

11566 """Local path to the file being debugged.""" 

11567 return self.root / str(self.file).lstrip("/") 

11568 

11569 @property 

11570 def maps(self) -> pathlib.Path: 

11571 if not self._maps: 

11572 self._maps = self.root / f"proc/{self.pid}/maps" 

11573 return self._maps 

11574 

11575 @property 

11576 def mode(self) -> RemoteMode: 

11577 return self._mode 

11578 

11579 def sync(self, src: str, dst: str | None = None) -> bool: 

11580 """Copy the `src` into the temporary chroot. If `dst` is provided, that path will be 

11581 used instead of `src`.""" 

11582 if not dst: 

11583 dst = src 

11584 tgt = self.root / dst.lstrip("/") 

11585 if tgt.exists(): 11585 ↛ 11586line 11585 didn't jump to line 11586 because the condition on line 11585 was never true

11586 return True 

11587 tgt.parent.mkdir(parents=True, exist_ok=True) 

11588 dbg(f"[remote] downloading '{src}' -> '{tgt}'") 

11589 gdb.execute(f"remote get '{src}' '{tgt.absolute()}'") 

11590 return tgt.exists() 

11591 

11592 def connect(self, pid: int) -> bool: 

11593 """Connect to remote target. If in extended mode, also attach to the given PID.""" 

11594 # before anything, register our new hook to download files from the remote target 

11595 dbg("[remote] Installing new objfile handlers") 

11596 try: 

11597 gef_on_new_unhook(new_objfile_handler) 

11598 except SystemError: 

11599 # the default objfile handler might already have been removed, ignore failure 

11600 pass 

11601 

11602 gef_on_new_hook(self.remote_objfile_event_handler) 

11603 

11604 # then attempt to connect 

11605 is_extended_mode = (pid > -1) 

11606 dbg(f"[remote] Enabling extended remote: {bool(is_extended_mode)}") 

11607 try: 

11608 with DisableContextOutputContext(): 

11609 cmd = f"target {'extended-' if is_extended_mode else ''}remote {self.target}" 

11610 dbg(f"[remote] Executing '{cmd}'") 

11611 gdb.execute(cmd) 

11612 if is_extended_mode: 11612 ↛ 11613line 11612 didn't jump to line 11613 because the condition on line 11612 was never true

11613 gdb.execute(f"attach {pid:d}") 

11614 return True 

11615 except Exception as e: 

11616 err(f"Failed to connect to {self.target}: {e}") 

11617 

11618 # a failure will trigger the cleanup, deleting our hook anyway 

11619 return False 

11620 

11621 def setup(self) -> bool: 

11622 # setup remote adequately depending on remote or qemu mode 

11623 match self.mode: 

11624 case GefRemoteSessionManager.RemoteMode.QEMU: 

11625 dbg(f"Setting up as qemu session, target={self.__qemu}") 

11626 self.__setup_qemu() 

11627 case GefRemoteSessionManager.RemoteMode.RR: 11627 ↛ 11628line 11627 didn't jump to line 11628 because the pattern on line 11627 never matched

11628 dbg("Setting up as rr session") 

11629 self.__setup_rr() 

11630 case GefRemoteSessionManager.RemoteMode.GDBSERVER: 11630 ↛ 11633line 11630 didn't jump to line 11633 because the pattern on line 11630 always matched

11631 dbg("Setting up as remote session") 

11632 self.__setup_remote() 

11633 case _: 

11634 raise ValueError 

11635 

11636 # refresh gef to consider the binary 

11637 reset_all_caches() 

11638 gef.binary = Elf(self.lfile) 

11639 reset_architecture() 

11640 return True 

11641 

11642 def __setup_qemu(self) -> bool: 

11643 # setup emulated file in the chroot 

11644 assert self.__qemu 

11645 target = self.root / str(self.__qemu.parent).lstrip("/") 

11646 target.mkdir(parents=True, exist_ok=False) 

11647 shutil.copy2(self.__qemu, target) 

11648 self._file = self.__qemu 

11649 assert self.lfile.exists() 

11650 

11651 # create a procfs 

11652 procfs = self.root / f"proc/{self.pid}/" 

11653 procfs.mkdir(parents=True, exist_ok=True) 

11654 

11655 ## /proc/pid/cmdline 

11656 cmdline = procfs / "cmdline" 

11657 if not cmdline.exists(): 11657 ↛ 11662line 11657 didn't jump to line 11662 because the condition on line 11657 was always true

11658 with cmdline.open("w") as fd: 

11659 fd.write("") 

11660 

11661 ## /proc/pid/environ 

11662 environ = procfs / "environ" 

11663 if not environ.exists(): 11663 ↛ 11668line 11663 didn't jump to line 11668 because the condition on line 11663 was always true

11664 with environ.open("wb") as fd: 

11665 fd.write(b"PATH=/bin\x00HOME=/tmp\x00") 

11666 

11667 ## /proc/pid/maps 

11668 maps = procfs / "maps" 

11669 if not maps.exists(): 11669 ↛ 11674line 11669 didn't jump to line 11674 because the condition on line 11669 was always true

11670 with maps.open("w") as fd: 

11671 fname = self.file.absolute() 

11672 mem_range = "00000000-ffffffff" if is_32bit() else "0000000000000000-ffffffffffffffff" 

11673 fd.write(f"{mem_range} rwxp 00000000 00:00 0 {fname}\n") 

11674 return True 

11675 

11676 def __setup_remote(self) -> bool: 

11677 # get the file 

11678 fpath = f"/proc/{self.pid}/exe" 

11679 if not self.sync(fpath, str(self.file)): 11679 ↛ 11680line 11679 didn't jump to line 11680 because the condition on line 11679 was never true

11680 err(f"'{fpath}' could not be fetched on the remote system.") 

11681 return False 

11682 

11683 # pseudo procfs 

11684 for _file in ("maps", "environ", "cmdline"): 

11685 fpath = f"/proc/{self.pid}/{_file}" 

11686 if not self.sync(fpath): 11686 ↛ 11687line 11686 didn't jump to line 11687 because the condition on line 11686 was never true

11687 err(f"'{fpath}' could not be fetched on the remote system.") 

11688 return False 

11689 

11690 return True 

11691 

11692 def __setup_rr(self) -> bool: 

11693 # 

11694 # Simply override the local root path, the binary must exist 

11695 # on the host. 

11696 # 

11697 self.__local_root_path = pathlib.Path("/") 

11698 return True 

11699 

11700 def remote_objfile_event_handler(self, evt: "gdb.NewObjFileEvent") -> None: 

11701 dbg(f"[remote] in remote_objfile_handler({evt.new_objfile.filename if evt else 'None'}))") 

11702 if not evt or not evt.new_objfile.filename: 11702 ↛ 11703line 11702 didn't jump to line 11703 because the condition on line 11702 was never true

11703 return 

11704 if not evt.new_objfile.filename.startswith("target:") and not evt.new_objfile.filename.startswith("/"): 

11705 warn(f"[remote] skipping '{evt.new_objfile.filename}'") 

11706 return 

11707 if evt.new_objfile.filename.startswith("target:"): 

11708 src: str = evt.new_objfile.filename[len("target:"):] 

11709 if not self.sync(src): 11709 ↛ 11710line 11709 didn't jump to line 11710 because the condition on line 11709 was never true

11710 raise FileNotFoundError(f"Failed to sync '{src}'") 

11711 return 

11712 

11713 

11714class GefUiManager(GefManager): 

11715 """Class managing UI settings.""" 

11716 def __init__(self) -> None: 

11717 self.redirect_fd : TextIOWrapper | None = None 

11718 self.context_hidden = False 

11719 self.stream_buffer : StringIO | None = None 

11720 self.highlight_table: dict[str, str] = {} 

11721 self.watches: dict[int, tuple[int, str]] = {} 

11722 self.context_messages: list[tuple[str, str]] = [] 

11723 return 

11724 

11725 

11726class GefLibcManager(GefManager): 

11727 """Class managing everything libc-related (except heap).""" 

11728 PATTERN_LIBC_VERSION_MEMORY = re.compile(rb"glibc (\d+)\.(\d+)") 

11729 PATTERN_LIBC_VERSION_FILENAME = re.compile(r"libc6?[-_](\d+)\.(\d+)\.so") 

11730 

11731 def __init__(self) -> None: 

11732 self._version : tuple[int, int] | None = None 

11733 self._patch: int | None = None 

11734 self._release: str | None = None 

11735 return 

11736 

11737 def __str__(self) -> str: 

11738 return f"Libc(version='{self.version}')" 

11739 

11740 @property 

11741 def version(self) -> tuple[int, int] | None: 

11742 if not is_alive(): 11742 ↛ 11743line 11742 didn't jump to line 11743 because the condition on line 11742 was never true

11743 return None 

11744 

11745 if not self._version: 

11746 self._version = GefLibcManager.find_libc_version() 

11747 

11748 # Whenever auto-detection fails, try use the user-provided version. 

11749 if self._version == (0, 0): 11749 ↛ 11750line 11749 didn't jump to line 11750 because the condition on line 11749 was never true

11750 if gef.config["gef.libc_version"]: 

11751 ver = [int(v) for v in gef.config["gef.libc_version"].split(".", 1)] 

11752 assert len(ver) >= 2 

11753 self._version = ver[0], ver[1] 

11754 

11755 return self._version 

11756 

11757 @staticmethod 

11758 @lru_cache() 

11759 def find_libc_version() -> tuple[int, int]: 

11760 """Attempt to determine the libc version. This operation can be long.""" 

11761 libc_sections = (m for m in gef.memory.maps if "libc" in m.path and m.permission & Permission.READ) 

11762 for section in libc_sections: 11762 ↛ 11777line 11762 didn't jump to line 11777 because the loop on line 11762 didn't complete

11763 # Try to determine from the filepath 

11764 match = re.search(GefLibcManager.PATTERN_LIBC_VERSION_FILENAME, section.path) 

11765 if match: 11765 ↛ 11766line 11765 didn't jump to line 11766 because the condition on line 11765 was never true

11766 return int(match.group(1)), int(match.group(2)) 

11767 

11768 # Try to determine from memory 

11769 try: 

11770 mem = gef.memory.read(section.page_start, section.size) 

11771 match = re.search(GefLibcManager.PATTERN_LIBC_VERSION_MEMORY, mem) 

11772 if match: 

11773 return int(match.group(1)), int(match.group(2)) 

11774 except gdb.MemoryError: 

11775 continue 

11776 

11777 return 0, 0 

11778 

11779 

11780class Gef: 

11781 """The GEF root class, which serves as a entrypoint for all the debugging session attributes (architecture, 

11782 memory, settings, etc.).""" 

11783 binary: FileFormat | None 

11784 arch: Architecture 

11785 config : GefSettingsManager 

11786 ui: GefUiManager 

11787 libc: GefLibcManager 

11788 memory : GefMemoryManager 

11789 heap : GefHeapManager 

11790 session : GefSessionManager 

11791 gdb: GefCommand 

11792 

11793 def __init__(self) -> None: 

11794 self.binary: FileFormat | None = None 

11795 self.arch: Architecture = GenericArchitecture() # see PR #516, will be reset by `new_objfile_handler` 

11796 self.arch_reason: str = "This is the default architecture" 

11797 self.config = GefSettingsManager() 

11798 self.ui = GefUiManager() 

11799 self.libc = GefLibcManager() 

11800 return 

11801 

11802 def __str__(self) -> str: 

11803 return f"Gef(binary='{self.binary or 'None'}', arch={self.arch})" 

11804 

11805 def reinitialize_managers(self) -> None: 

11806 """Reinitialize the managers. Avoid calling this function directly, using `pi reset()` is preferred""" 

11807 self.memory = GefMemoryManager() 

11808 self.heap = GefHeapManager() 

11809 self.session = GefSessionManager() 

11810 return 

11811 

11812 def setup(self) -> None: 

11813 """Setup initialize the runtime setup, which may require for the `gef` to be not None.""" 

11814 self.reinitialize_managers() 

11815 self.gdb = GefCommand() 

11816 self.gdb.setup() 

11817 gdb.execute(f"save gdb-index '{self.config['gef.tempdir']}'") 

11818 return 

11819 

11820 def reset_caches(self) -> None: 

11821 """Recursively clean the cache of all the managers. Avoid calling this function directly, using `reset-cache` 

11822 is preferred""" 

11823 for mgr in (self.memory, self.heap, self.session, self.arch): 

11824 mgr.reset_caches() 

11825 return 

11826 

11827 

11828def target_remote_posthook(): 

11829 if gef.session.remote_initializing: 

11830 return 

11831 

11832 gef.session.remote = GefRemoteSessionManager("", 0) 

11833 if not gef.session.remote.setup(): 11833 ↛ 11834line 11833 didn't jump to line 11834 because the condition on line 11833 was never true

11834 raise EnvironmentError(f"Failed to create a proper environment for {gef.session.remote}") 

11835 

11836if __name__ == "__main__": 11836 ↛ exitline 11836 didn't exit the module because the condition on line 11836 was always true

11837 if sys.version_info[0] == 2: 11837 ↛ 11838line 11837 didn't jump to line 11838 because the condition on line 11837 was never true

11838 err("GEF has dropped Python2 support for GDB when it reached EOL on 2020/01/01.") 

11839 err("If you require GEF for GDB+Python2, use https://github.com/hugsy/gef-legacy.") 

11840 exit(1) 

11841 

11842 if GDB_VERSION < GDB_MIN_VERSION or PYTHON_VERSION < PYTHON_MIN_VERSION: 11842 ↛ 11843line 11842 didn't jump to line 11843 because the condition on line 11842 was never true

11843 err("You're using an old version of GDB. GEF will not work correctly. " 

11844 f"Consider updating to GDB {'.'.join(map(str, GDB_MIN_VERSION))} or higher " 

11845 f"(with Python {'.'.join(map(str, PYTHON_MIN_VERSION))} or higher).") 

11846 exit(1) 

11847 

11848 # setup config 

11849 gdb_initial_settings = ( 

11850 "set confirm off", 

11851 "set verbose off", 

11852 "set pagination off", 

11853 "set print elements 0", 

11854 "set history save on", 

11855 f"set history filename {os.getenv('GDBHISTFILE', '~/.gdb_history')}", 

11856 "set output-radix 0x10", 

11857 "set print pretty on", 

11858 "set disassembly-flavor intel", 

11859 "handle SIGALRM print nopass", 

11860 ) 

11861 for cmd in gdb_initial_settings: 

11862 try: 

11863 gdb.execute(cmd) 

11864 except gdb.error: 

11865 pass 

11866 

11867 # set fallback 'debug-file-directory' for gdbs that installed outside `/usr`. 

11868 try: 

11869 default_dbgsym_path = "/usr/lib/debug" 

11870 param_name = "debug-file-directory" 

11871 dbgsym_paths = gdb.parameter(param_name) 

11872 if not isinstance(dbgsym_paths, str): 11872 ↛ 11873line 11872 didn't jump to line 11873 because the condition on line 11872 was never true

11873 raise TypeError 

11874 if default_dbgsym_path not in dbgsym_paths: 11874 ↛ 11875line 11874 didn't jump to line 11875 because the condition on line 11874 was never true

11875 newpath = f"{dbgsym_paths}:" if dbgsym_paths else "" 

11876 newpath += default_dbgsym_path 

11877 gdb.execute(f"set {param_name} {newpath}") 

11878 except gdb.error as e: 

11879 warn(f"Failed to set {param_name}, reason: {str(e)}") 

11880 

11881 # load GEF, set up the managers and load the plugins, functions, 

11882 gef = Gef() 

11883 reset() 

11884 assert isinstance(gef, Gef) 

11885 gef.gdb.load() 

11886 gef.gdb.show_banner() 

11887 

11888 # load config 

11889 if gef.gdb.load_extra_plugins(): 11889 ↛ 11891line 11889 didn't jump to line 11891 because the condition on line 11889 was never true

11890 # reload settings 

11891 gdb.execute("gef restore") 

11892 

11893 # setup gdb prompt 

11894 gdb.prompt_hook = __gef_prompt__ 

11895 

11896 # gdb events configuration 

11897 gef_on_continue_hook(continue_handler) 

11898 gef_on_stop_hook(hook_stop_handler) 

11899 gef_on_new_hook(new_objfile_handler) 

11900 gef_on_exit_hook(exit_handler) 

11901 gef_on_memchanged_hook(memchanged_handler) 

11902 gef_on_regchanged_hook(regchanged_handler) 

11903 

11904 progspace = gdb.current_progspace() 

11905 if progspace and progspace.filename: 11905 ↛ 11909line 11905 didn't jump to line 11909 because the condition on line 11905 was always true

11906 # if here, we are sourcing gef from a gdb session already attached, force call to new_objfile (see issue #278) 

11907 new_objfile_handler(None) 

11908 

11909 GefTmuxSetup() 

11910 

11911 if GDB_VERSION > (9, 0): 11911 ↛ 11939line 11911 didn't jump to line 11939 because the condition on line 11911 was always true

11912 disable_tr_overwrite_setting = "gef.disable_target_remote_overwrite" 

11913 

11914 if not gef.config[disable_tr_overwrite_setting]: 11914 ↛ 11931line 11914 didn't jump to line 11931 because the condition on line 11914 was always true

11915 warnmsg = ("Using `target remote` with GEF should work in most cases, " 

11916 "but use `gef-remote` if you can. You can disable the " 

11917 "overwrite of the `target remote` command by toggling " 

11918 f"`{disable_tr_overwrite_setting}` in the config.") 

11919 hook = f""" 

11920 define target hookpost-{ } 

11921 pi target_remote_posthook() 

11922 context 

11923 pi if calling_function() != "connect": warn("{warnmsg}") 

11924 end 

11925 """ 

11926 

11927 # Register a post-hook for `target remote` that initialize the remote session 

11928 gdb.execute(hook.format("remote")) 

11929 gdb.execute(hook.format("extended-remote")) 

11930 else: 

11931 errmsg = ("Using `target remote` does not work, use `gef-remote` " 

11932 f"instead. You can toggle `{disable_tr_overwrite_setting}` " 

11933 "if this is not desired.") 

11934 hook = f"""pi if calling_function() != "connect": err("{errmsg}")""" 

11935 gdb.execute(f"define target hook-remote\n{hook}\nend") 

11936 gdb.execute(f"define target hook-extended-remote\n{hook}\nend") 

11937 

11938 # restore saved breakpoints (if any) 

11939 bkp_fpath = pathlib.Path(gef.config["gef.autosave_breakpoints_file"]).expanduser().absolute() 

11940 if bkp_fpath.is_file(): 11940 ↛ 11941line 11940 didn't jump to line 11941 because the condition on line 11940 was never true

11941 gdb.execute(f"source {bkp_fpath}") 

11942 

11943 # Add a `source` post hook to force gef to recheck the registered plugins and 

11944 # eventually load the missing one(s) 

11945 gdb.execute("define hookpost-source\npi gef.gdb.load()\nend")