Skip to content

libdebug.memory.abstract_memory_view

AbstractMemoryView

Bases: MutableSequence, ABC

An abstract memory interface for the target process.

An implementation of class must be used to read and write memory of the target process.

Source code in libdebug/memory/abstract_memory_view.py
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
class AbstractMemoryView(MutableSequence, ABC):
    """An abstract memory interface for the target process.

    An implementation of class must be used to read and write memory of the target process.
    """

    def __init__(self: AbstractMemoryView) -> None:
        """Initializes the MemoryView."""
        self._internal_debugger = provide_internal_debugger(self)

    @abstractmethod
    def read(self: AbstractMemoryView, address: int, size: int) -> bytes:
        """Reads memory from the target process.

        Args:
            address (int): The address to read from.
            size (int): The number of bytes to read.

        Returns:
            bytes: The read bytes.
        """

    @abstractmethod
    def write(self: AbstractMemoryView, address: int, data: bytes) -> None:
        """Writes memory to the target process.

        Args:
            address (int): The address to write to.
            data (bytes): The data to write.
        """

    def find(
        self: AbstractMemoryView,
        value: bytes | str | int,
        file: str = "all",
        start: int | None = None,
        end: int | None = None,
    ) -> list[int]:
        """Searches for the given value in the specified memory maps of the process.

        The start and end addresses can be used to limit the search to a specific range.
        If not specified, the search will be performed on the whole memory map.

        Args:
            value (bytes | str | int): The value to search for.
            file (str): The backing file to search the value in. Defaults to "all", which means all memory.
            start (int | None): The start address of the search. Defaults to None.
            end (int | None): The end address of the search. Defaults to None.

        Returns:
            list[int]: A list of offset where the value was found.
        """
        if isinstance(value, str):
            value = value.encode()
        elif isinstance(value, int):
            value = value.to_bytes(1, sys.byteorder)

        occurrences = []
        if file == "all" and start is None and end is None:
            for vmap in self.maps:
                liblog.debugger(f"Searching in {vmap.backing_file}...")
                try:
                    memory_content = self.read(vmap.start, vmap.end - vmap.start)
                except (OSError, OverflowError, ValueError):
                    # There are some memory regions that cannot be read, such as [vvar], [vdso], etc.
                    continue
                occurrences += find_all_overlapping_occurrences(value, memory_content, vmap.start)
        elif file == "all" and start is not None and end is None:
            for vmap in self.maps:
                if vmap.end > start:
                    liblog.debugger(f"Searching in {vmap.backing_file}...")
                    read_start = max(vmap.start, start)
                    try:
                        memory_content = self.read(read_start, vmap.end - read_start)
                    except (OSError, OverflowError, ValueError):
                        # There are some memory regions that cannot be read, such as [vvar], [vdso], etc.
                        continue
                    occurrences += find_all_overlapping_occurrences(value, memory_content, read_start)
        elif file == "all" and start is None and end is not None:
            for vmap in self.maps:
                if vmap.start < end:
                    liblog.debugger(f"Searching in {vmap.backing_file}...")
                    read_end = min(vmap.end, end)
                    try:
                        memory_content = self.read(vmap.start, read_end - vmap.start)
                    except (OSError, OverflowError, ValueError):
                        # There are some memory regions that cannot be read, such as [vvar], [vdso], etc.
                        continue
                    occurrences += find_all_overlapping_occurrences(value, memory_content, vmap.start)
        elif file == "all" and start is not None and end is not None:
            # Search in the specified range, hybrid mode
            start = self.resolve_address(start, "hybrid", True)
            end = self.resolve_address(end, "hybrid", True)
            liblog.debugger(f"Searching in the range {start:#x}-{end:#x}...")
            memory_content = self.read(start, end - start)
            occurrences = find_all_overlapping_occurrences(value, memory_content, start)
        else:
            maps = self.maps.filter(file)
            start = self.resolve_address(start, file, True) if start is not None else maps[0].start
            end = self.resolve_address(end, file, True) if end is not None else maps[-1].end - 1

            liblog.debugger(f"Searching in the range {start:#x}-{end:#x}...")
            memory_content = self.read(start, end - start)

            occurrences = find_all_overlapping_occurrences(value, memory_content, start)

        return occurrences

    def find_pointers(
        self: AbstractMemoryView,
        where: int | str = "*",
        target: int | str = "*",
        step: int = 1,
    ) -> list[tuple[int, int]]:
        """
        Find all pointers in the specified memory map that point to the target memory map.

        If the where parameter or the target parameter is a string, it is treated as a backing file. If it is an integer, the memory map containing the address will be used.

        If "*", "ALL", "all" or -1 is passed, all memory maps will be considered.

        Args:
            where (int | str): Identifier of the memory map where we want to search for references. Defaults to "*", which means all memory maps.
            target (int | str): Identifier of the memory map whose pointers we want to find. Defaults to "*", which means all memory maps.
            step (int): The interval step size while iterating over the memory buffer. Defaults to 1.

        Returns:
            list[tuple[int, int]]: A list of tuples containing the address where the pointer was found and the pointer itself.
        """
        # Filter memory maps that match the target
        if target in {"*", "ALL", "all", -1}:
            target_maps = self._internal_debugger.maps
        else:
            target_maps = self._internal_debugger.maps.filter(target)

        if not target_maps:
            raise ValueError("No memory map found for the specified target.")

        target_backing_files = {vmap.backing_file for vmap in target_maps}

        # Filter memory maps that match the where parameter
        if where in {"*", "ALL", "all", -1}:
            where_maps = self._internal_debugger.maps
        else:
            where_maps = self._internal_debugger.maps.filter(where)

        if not where_maps:
            raise ValueError("No memory map found for the specified where parameter.")

        where_backing_files = {vmap.backing_file for vmap in where_maps}

        if len(where_backing_files) == 1 and len(target_backing_files) == 1:
            return self.__internal_find_pointers(where_maps, target_maps, step)
        elif len(where_backing_files) == 1:
            found_pointers = []
            for target_backing_file in target_backing_files:
                found_pointers += self.__internal_find_pointers(
                    where_maps,
                    self._internal_debugger.maps.filter(target_backing_file),
                    step,
                )
            return found_pointers
        elif len(target_backing_files) == 1:
            found_pointers = []
            for where_backing_file in where_backing_files:
                found_pointers += self.__internal_find_pointers(
                    self._internal_debugger.maps.filter(where_backing_file),
                    target_maps,
                    step,
                )
            return found_pointers
        else:
            found_pointers = []
            for where_backing_file in where_backing_files:
                for target_backing_file in target_backing_files:
                    found_pointers += self.__internal_find_pointers(
                        self._internal_debugger.maps.filter(where_backing_file),
                        self._internal_debugger.maps.filter(target_backing_file),
                        step,
                    )

        return found_pointers

    def __internal_find_pointers(
        self: AbstractMemoryView,
        where_maps: list[MemoryMap],
        target_maps: list[MemoryMap],
        stride: int,
    ) -> list[tuple[int, int]]:
        """Find all pointers to a specific memory map within another memory map. Internal implementation.

        Args:
            where_maps (list[MemoryMap]): The memory maps where to search for pointers.
            target_maps (list[MemoryMap]): The memory maps for which to search for pointers.
            stride (int): The interval step size while iterating over the memory buffer.

        Returns:
            list[tuple[int, int]]: A list of tuples containing the address where the pointer was found and the pointer itself.
        """
        found_pointers = []

        # Obtain the start/end of the target memory segment
        target_start_address = target_maps[0].start
        target_end_address = target_maps[-1].end

        # Obtain the start/end of the where memory segment
        where_start_address = where_maps[0].start
        where_end_address = where_maps[-1].end

        # Read the memory from the where memory segment
        if not self._internal_debugger.fast_memory:
            liblog.warning(
                "Fast memory reading is disabled. Using find_pointers with fast_memory=False may be very slow.",
            )
        try:
            where_memory_buffer = self.read(where_start_address, where_end_address - where_start_address)
        except (OSError, OverflowError):
            liblog.error(f"Cannot read the target memory segment with backing file: {where_maps[0].backing_file}.")
            return found_pointers

        # Get the size of a pointer in the target process
        pointer_size = get_platform_gp_register_size(self._internal_debugger.arch)

        # Get the byteorder of the target machine (endianness)
        byteorder = sys.byteorder

        # Search for references in the where memory segment
        append = found_pointers.append
        for i in range(0, len(where_memory_buffer), stride):
            reference = where_memory_buffer[i : i + pointer_size]
            reference = int.from_bytes(reference, byteorder=byteorder)
            if target_start_address <= reference < target_end_address:
                append((where_start_address + i, reference))

        return found_pointers

    def __getitem__(self: AbstractMemoryView, key: int | slice | str | tuple) -> bytes:
        """Read from memory, either a single byte or a byte string.

        Args:
            key (int | slice | str | tuple): The key to read from memory.
        """
        return self._manage_memory_read_type(key)

    def __setitem__(self: AbstractMemoryView, key: int | slice | str | tuple, value: bytes) -> None:
        """Write to memory, either a single byte or a byte string.

        Args:
            key (int | slice | str | tuple): The key to write to memory.
            value (bytes): The value to write.
        """
        if not isinstance(value, bytes):
            raise TypeError("Invalid type for the value to write to memory. Expected bytes.")
        self._manage_memory_write_type(key, value)

    def _manage_memory_read_type(
        self: AbstractMemoryView,
        key: int | slice | str | tuple,
        file: str = "hybrid",
    ) -> bytes:
        """Manage the read from memory, according to the typing.

        Args:
            key (int | slice | str | tuple): The key to read from memory.
            file (str, optional): The user-defined backing file to resolve the address in. Defaults to "hybrid" (libdebug will first try to solve the address as an absolute address, then as a relative address w.r.t. the "binary" map file).
        """
        if isinstance(key, int):
            address = self.resolve_address(key, file, skip_absolute_address_validation=True)
            try:
                return self.read(address, 1)
            except OSError as e:
                raise ValueError("Invalid address.") from e
        elif isinstance(key, slice):
            if isinstance(key.start, str):
                start = self.resolve_symbol(key.start, file)
            else:
                start = self.resolve_address(key.start, file, skip_absolute_address_validation=True)

            if isinstance(key.stop, str):
                stop = self.resolve_symbol(key.stop, file)
            else:
                stop = self.resolve_address(key.stop, file, skip_absolute_address_validation=True)

            if stop < start:
                raise ValueError("Invalid slice range.")

            try:
                return self.read(start, stop - start)
            except OSError as e:
                raise ValueError("Invalid address.") from e
        elif isinstance(key, str):
            address = self.resolve_symbol(key, file)

            return self.read(address, 1)
        elif isinstance(key, tuple):
            return self._manage_memory_read_tuple(key)
        else:
            raise TypeError("Invalid key type.")

    def _manage_memory_read_tuple(self: AbstractMemoryView, key: tuple) -> bytes:
        """Manage the read from memory, when the access is through a tuple.

        Args:
            key (tuple): The key to read from memory.
        """
        if len(key) == 3:
            # It can only be a tuple of the type (address, size, file)
            address, size, file = key
            if not isinstance(file, str):
                raise TypeError("Invalid type for the backing file. Expected string.")
        elif len(key) == 2:
            left, right = key
            if isinstance(right, str):
                # The right element can only be the backing file
                return self._manage_memory_read_type(left, right)
            elif isinstance(right, int):
                # The right element must be the size
                address = left
                size = right
                file = "hybrid"
        else:
            raise TypeError("Tuple must have 2 or 3 elements.")

        if not isinstance(size, int):
            raise TypeError("Invalid type for the size. Expected int.")

        if isinstance(address, str):
            address = self.resolve_symbol(address, file)
        elif isinstance(address, int):
            address = self.resolve_address(address, file, skip_absolute_address_validation=True)
        else:
            raise TypeError("Invalid type for the address. Expected int or string.")

        try:
            return self.read(address, size)
        except OSError as e:
            raise ValueError("Invalid address.") from e

    def _manage_memory_write_type(
        self: AbstractMemoryView,
        key: int | slice | str | tuple,
        value: bytes,
        file: str = "hybrid",
    ) -> None:
        """Manage the write to memory, according to the typing.

        Args:
            key (int | slice | str | tuple): The key to read from memory.
            value (bytes): The value to write.
            file (str, optional): The user-defined backing file to resolve the address in. Defaults to "hybrid" (libdebug will first try to solve the address as an absolute address, then as a relative address w.r.t. the "binary" map file).
        """
        if isinstance(key, int):
            address = self.resolve_address(key, file, skip_absolute_address_validation=True)
            try:
                self.write(address, value)
            except OSError as e:
                raise ValueError("Invalid address.") from e
        elif isinstance(key, slice):
            if isinstance(key.start, str):
                start = self.resolve_symbol(key.start, file)
            else:
                start = self.resolve_address(key.start, file, skip_absolute_address_validation=True)

            if key.stop is not None:
                if isinstance(key.stop, str):
                    stop = self.resolve_symbol(key.stop, file)
                else:
                    stop = self.resolve_address(
                        key.stop,
                        file,
                        skip_absolute_address_validation=True,
                    )

                if stop < start:
                    raise ValueError("Invalid slice range")

                if len(value) != stop - start:
                    liblog.warning(f"Mismatch between slice width and value size, writing {len(value)} bytes.")

            try:
                self.write(start, value)
            except OSError as e:
                raise ValueError("Invalid address.") from e

        elif isinstance(key, str):
            address = self.resolve_symbol(key, file)

            self.write(address, value)
        elif isinstance(key, tuple):
            self._manage_memory_write_tuple(key, value)
        else:
            raise TypeError("Invalid key type.")

    def _manage_memory_write_tuple(self: AbstractMemoryView, key: tuple, value: bytes) -> None:
        """Manage the write to memory, when the access is through a tuple.

        Args:
            key (tuple): The key to read from memory.
            value (bytes): The value to write.
        """
        if len(key) == 3:
            # It can only be a tuple of the type (address, size, file)
            address, size, file = key
            if not isinstance(file, str):
                raise TypeError("Invalid type for the backing file. Expected string.")
        elif len(key) == 2:
            left, right = key
            if isinstance(right, str):
                # The right element can only be the backing file
                self._manage_memory_write_type(left, value, right)
                return
            elif isinstance(right, int):
                # The right element must be the size
                address = left
                size = right
                file = "hybrid"
        else:
            raise TypeError("Tuple must have 2 or 3 elements.")

        if not isinstance(size, int):
            raise TypeError("Invalid type for the size. Expected int.")

        if isinstance(address, str):
            address = self.resolve_symbol(address, file)
        elif isinstance(address, int):
            address = self.resolve_address(address, file, skip_absolute_address_validation=True)
        else:
            raise TypeError("Invalid type for the address. Expected int or string.")

        if len(value) != size:
            liblog.warning(f"Mismatch between specified size and actual value size, writing {len(value)} bytes.")

        try:
            self.write(address, value)
        except OSError as e:
            raise ValueError("Invalid address.") from e

    def __delitem__(self: AbstractMemoryView, key: int | slice | str | tuple) -> None:
        """MemoryView doesn't support deletion."""
        raise NotImplementedError("MemoryView doesn't support deletion")

    def __len__(self: AbstractMemoryView) -> None:
        """MemoryView doesn't support length."""
        raise NotImplementedError("MemoryView doesn't support length")

    def insert(self: AbstractMemoryView, index: int, value: int) -> None:
        """MemoryView doesn't support insertion."""
        raise NotImplementedError("MemoryView doesn't support insertion")

    @property
    def maps(self: AbstractMemoryView) -> list:
        """Returns the list of memory maps of the target process."""
        raise NotImplementedError("The maps property must be implemented in the subclass.")

    def resolve_address(
        self: AbstractMemoryView,
        address: int,
        backing_file: str,
        skip_absolute_address_validation: bool = False,
    ) -> int:
        """Normalizes and validates the specified address.

        Args:
            address (int): The address to normalize and validate.
            backing_file (str): The backing file to resolve the address in.
            skip_absolute_address_validation (bool, optional): Whether to skip bounds checking for absolute addresses. Defaults to False.

        Returns:
            int: The normalized and validated address.

        Raises:
            ValueError: If the substring `backing_file` is present in multiple backing files.
        """
        return self._internal_debugger.resolve_address(
            address, backing_file, skip_absolute_address_validation,
        )

    def resolve_symbol(self: AbstractMemoryView, symbol: str, backing_file: str) -> int:
        """Resolves the address of the specified symbol.

        Args:
            symbol (str): The symbol to resolve.
            backing_file (str): The backing file to resolve the symbol in.

        Returns:
            int: The address of the symbol.
        """
        return self._internal_debugger.resolve_symbol(symbol, backing_file)

maps property

Returns the list of memory maps of the target process.

__delitem__(key)

MemoryView doesn't support deletion.

Source code in libdebug/memory/abstract_memory_view.py
def __delitem__(self: AbstractMemoryView, key: int | slice | str | tuple) -> None:
    """MemoryView doesn't support deletion."""
    raise NotImplementedError("MemoryView doesn't support deletion")

__getitem__(key)

Read from memory, either a single byte or a byte string.

Parameters:

Name Type Description Default
key int | slice | str | tuple

The key to read from memory.

required
Source code in libdebug/memory/abstract_memory_view.py
def __getitem__(self: AbstractMemoryView, key: int | slice | str | tuple) -> bytes:
    """Read from memory, either a single byte or a byte string.

    Args:
        key (int | slice | str | tuple): The key to read from memory.
    """
    return self._manage_memory_read_type(key)

__init__()

Initializes the MemoryView.

Source code in libdebug/memory/abstract_memory_view.py
def __init__(self: AbstractMemoryView) -> None:
    """Initializes the MemoryView."""
    self._internal_debugger = provide_internal_debugger(self)

__internal_find_pointers(where_maps, target_maps, stride)

Find all pointers to a specific memory map within another memory map. Internal implementation.

Parameters:

Name Type Description Default
where_maps list[MemoryMap]

The memory maps where to search for pointers.

required
target_maps list[MemoryMap]

The memory maps for which to search for pointers.

required
stride int

The interval step size while iterating over the memory buffer.

required

Returns:

Type Description
list[tuple[int, int]]

list[tuple[int, int]]: A list of tuples containing the address where the pointer was found and the pointer itself.

Source code in libdebug/memory/abstract_memory_view.py
def __internal_find_pointers(
    self: AbstractMemoryView,
    where_maps: list[MemoryMap],
    target_maps: list[MemoryMap],
    stride: int,
) -> list[tuple[int, int]]:
    """Find all pointers to a specific memory map within another memory map. Internal implementation.

    Args:
        where_maps (list[MemoryMap]): The memory maps where to search for pointers.
        target_maps (list[MemoryMap]): The memory maps for which to search for pointers.
        stride (int): The interval step size while iterating over the memory buffer.

    Returns:
        list[tuple[int, int]]: A list of tuples containing the address where the pointer was found and the pointer itself.
    """
    found_pointers = []

    # Obtain the start/end of the target memory segment
    target_start_address = target_maps[0].start
    target_end_address = target_maps[-1].end

    # Obtain the start/end of the where memory segment
    where_start_address = where_maps[0].start
    where_end_address = where_maps[-1].end

    # Read the memory from the where memory segment
    if not self._internal_debugger.fast_memory:
        liblog.warning(
            "Fast memory reading is disabled. Using find_pointers with fast_memory=False may be very slow.",
        )
    try:
        where_memory_buffer = self.read(where_start_address, where_end_address - where_start_address)
    except (OSError, OverflowError):
        liblog.error(f"Cannot read the target memory segment with backing file: {where_maps[0].backing_file}.")
        return found_pointers

    # Get the size of a pointer in the target process
    pointer_size = get_platform_gp_register_size(self._internal_debugger.arch)

    # Get the byteorder of the target machine (endianness)
    byteorder = sys.byteorder

    # Search for references in the where memory segment
    append = found_pointers.append
    for i in range(0, len(where_memory_buffer), stride):
        reference = where_memory_buffer[i : i + pointer_size]
        reference = int.from_bytes(reference, byteorder=byteorder)
        if target_start_address <= reference < target_end_address:
            append((where_start_address + i, reference))

    return found_pointers

__len__()

MemoryView doesn't support length.

Source code in libdebug/memory/abstract_memory_view.py
def __len__(self: AbstractMemoryView) -> None:
    """MemoryView doesn't support length."""
    raise NotImplementedError("MemoryView doesn't support length")

__setitem__(key, value)

Write to memory, either a single byte or a byte string.

Parameters:

Name Type Description Default
key int | slice | str | tuple

The key to write to memory.

required
value bytes

The value to write.

required
Source code in libdebug/memory/abstract_memory_view.py
def __setitem__(self: AbstractMemoryView, key: int | slice | str | tuple, value: bytes) -> None:
    """Write to memory, either a single byte or a byte string.

    Args:
        key (int | slice | str | tuple): The key to write to memory.
        value (bytes): The value to write.
    """
    if not isinstance(value, bytes):
        raise TypeError("Invalid type for the value to write to memory. Expected bytes.")
    self._manage_memory_write_type(key, value)

_manage_memory_read_tuple(key)

Manage the read from memory, when the access is through a tuple.

Parameters:

Name Type Description Default
key tuple

The key to read from memory.

required
Source code in libdebug/memory/abstract_memory_view.py
def _manage_memory_read_tuple(self: AbstractMemoryView, key: tuple) -> bytes:
    """Manage the read from memory, when the access is through a tuple.

    Args:
        key (tuple): The key to read from memory.
    """
    if len(key) == 3:
        # It can only be a tuple of the type (address, size, file)
        address, size, file = key
        if not isinstance(file, str):
            raise TypeError("Invalid type for the backing file. Expected string.")
    elif len(key) == 2:
        left, right = key
        if isinstance(right, str):
            # The right element can only be the backing file
            return self._manage_memory_read_type(left, right)
        elif isinstance(right, int):
            # The right element must be the size
            address = left
            size = right
            file = "hybrid"
    else:
        raise TypeError("Tuple must have 2 or 3 elements.")

    if not isinstance(size, int):
        raise TypeError("Invalid type for the size. Expected int.")

    if isinstance(address, str):
        address = self.resolve_symbol(address, file)
    elif isinstance(address, int):
        address = self.resolve_address(address, file, skip_absolute_address_validation=True)
    else:
        raise TypeError("Invalid type for the address. Expected int or string.")

    try:
        return self.read(address, size)
    except OSError as e:
        raise ValueError("Invalid address.") from e

_manage_memory_read_type(key, file='hybrid')

Manage the read from memory, according to the typing.

Parameters:

Name Type Description Default
key int | slice | str | tuple

The key to read from memory.

required
file str

The user-defined backing file to resolve the address in. Defaults to "hybrid" (libdebug will first try to solve the address as an absolute address, then as a relative address w.r.t. the "binary" map file).

'hybrid'
Source code in libdebug/memory/abstract_memory_view.py
def _manage_memory_read_type(
    self: AbstractMemoryView,
    key: int | slice | str | tuple,
    file: str = "hybrid",
) -> bytes:
    """Manage the read from memory, according to the typing.

    Args:
        key (int | slice | str | tuple): The key to read from memory.
        file (str, optional): The user-defined backing file to resolve the address in. Defaults to "hybrid" (libdebug will first try to solve the address as an absolute address, then as a relative address w.r.t. the "binary" map file).
    """
    if isinstance(key, int):
        address = self.resolve_address(key, file, skip_absolute_address_validation=True)
        try:
            return self.read(address, 1)
        except OSError as e:
            raise ValueError("Invalid address.") from e
    elif isinstance(key, slice):
        if isinstance(key.start, str):
            start = self.resolve_symbol(key.start, file)
        else:
            start = self.resolve_address(key.start, file, skip_absolute_address_validation=True)

        if isinstance(key.stop, str):
            stop = self.resolve_symbol(key.stop, file)
        else:
            stop = self.resolve_address(key.stop, file, skip_absolute_address_validation=True)

        if stop < start:
            raise ValueError("Invalid slice range.")

        try:
            return self.read(start, stop - start)
        except OSError as e:
            raise ValueError("Invalid address.") from e
    elif isinstance(key, str):
        address = self.resolve_symbol(key, file)

        return self.read(address, 1)
    elif isinstance(key, tuple):
        return self._manage_memory_read_tuple(key)
    else:
        raise TypeError("Invalid key type.")

_manage_memory_write_tuple(key, value)

Manage the write to memory, when the access is through a tuple.

Parameters:

Name Type Description Default
key tuple

The key to read from memory.

required
value bytes

The value to write.

required
Source code in libdebug/memory/abstract_memory_view.py
def _manage_memory_write_tuple(self: AbstractMemoryView, key: tuple, value: bytes) -> None:
    """Manage the write to memory, when the access is through a tuple.

    Args:
        key (tuple): The key to read from memory.
        value (bytes): The value to write.
    """
    if len(key) == 3:
        # It can only be a tuple of the type (address, size, file)
        address, size, file = key
        if not isinstance(file, str):
            raise TypeError("Invalid type for the backing file. Expected string.")
    elif len(key) == 2:
        left, right = key
        if isinstance(right, str):
            # The right element can only be the backing file
            self._manage_memory_write_type(left, value, right)
            return
        elif isinstance(right, int):
            # The right element must be the size
            address = left
            size = right
            file = "hybrid"
    else:
        raise TypeError("Tuple must have 2 or 3 elements.")

    if not isinstance(size, int):
        raise TypeError("Invalid type for the size. Expected int.")

    if isinstance(address, str):
        address = self.resolve_symbol(address, file)
    elif isinstance(address, int):
        address = self.resolve_address(address, file, skip_absolute_address_validation=True)
    else:
        raise TypeError("Invalid type for the address. Expected int or string.")

    if len(value) != size:
        liblog.warning(f"Mismatch between specified size and actual value size, writing {len(value)} bytes.")

    try:
        self.write(address, value)
    except OSError as e:
        raise ValueError("Invalid address.") from e

_manage_memory_write_type(key, value, file='hybrid')

Manage the write to memory, according to the typing.

Parameters:

Name Type Description Default
key int | slice | str | tuple

The key to read from memory.

required
value bytes

The value to write.

required
file str

The user-defined backing file to resolve the address in. Defaults to "hybrid" (libdebug will first try to solve the address as an absolute address, then as a relative address w.r.t. the "binary" map file).

'hybrid'
Source code in libdebug/memory/abstract_memory_view.py
def _manage_memory_write_type(
    self: AbstractMemoryView,
    key: int | slice | str | tuple,
    value: bytes,
    file: str = "hybrid",
) -> None:
    """Manage the write to memory, according to the typing.

    Args:
        key (int | slice | str | tuple): The key to read from memory.
        value (bytes): The value to write.
        file (str, optional): The user-defined backing file to resolve the address in. Defaults to "hybrid" (libdebug will first try to solve the address as an absolute address, then as a relative address w.r.t. the "binary" map file).
    """
    if isinstance(key, int):
        address = self.resolve_address(key, file, skip_absolute_address_validation=True)
        try:
            self.write(address, value)
        except OSError as e:
            raise ValueError("Invalid address.") from e
    elif isinstance(key, slice):
        if isinstance(key.start, str):
            start = self.resolve_symbol(key.start, file)
        else:
            start = self.resolve_address(key.start, file, skip_absolute_address_validation=True)

        if key.stop is not None:
            if isinstance(key.stop, str):
                stop = self.resolve_symbol(key.stop, file)
            else:
                stop = self.resolve_address(
                    key.stop,
                    file,
                    skip_absolute_address_validation=True,
                )

            if stop < start:
                raise ValueError("Invalid slice range")

            if len(value) != stop - start:
                liblog.warning(f"Mismatch between slice width and value size, writing {len(value)} bytes.")

        try:
            self.write(start, value)
        except OSError as e:
            raise ValueError("Invalid address.") from e

    elif isinstance(key, str):
        address = self.resolve_symbol(key, file)

        self.write(address, value)
    elif isinstance(key, tuple):
        self._manage_memory_write_tuple(key, value)
    else:
        raise TypeError("Invalid key type.")

find(value, file='all', start=None, end=None)

Searches for the given value in the specified memory maps of the process.

The start and end addresses can be used to limit the search to a specific range. If not specified, the search will be performed on the whole memory map.

Parameters:

Name Type Description Default
value bytes | str | int

The value to search for.

required
file str

The backing file to search the value in. Defaults to "all", which means all memory.

'all'
start int | None

The start address of the search. Defaults to None.

None
end int | None

The end address of the search. Defaults to None.

None

Returns:

Type Description
list[int]

list[int]: A list of offset where the value was found.

Source code in libdebug/memory/abstract_memory_view.py
def find(
    self: AbstractMemoryView,
    value: bytes | str | int,
    file: str = "all",
    start: int | None = None,
    end: int | None = None,
) -> list[int]:
    """Searches for the given value in the specified memory maps of the process.

    The start and end addresses can be used to limit the search to a specific range.
    If not specified, the search will be performed on the whole memory map.

    Args:
        value (bytes | str | int): The value to search for.
        file (str): The backing file to search the value in. Defaults to "all", which means all memory.
        start (int | None): The start address of the search. Defaults to None.
        end (int | None): The end address of the search. Defaults to None.

    Returns:
        list[int]: A list of offset where the value was found.
    """
    if isinstance(value, str):
        value = value.encode()
    elif isinstance(value, int):
        value = value.to_bytes(1, sys.byteorder)

    occurrences = []
    if file == "all" and start is None and end is None:
        for vmap in self.maps:
            liblog.debugger(f"Searching in {vmap.backing_file}...")
            try:
                memory_content = self.read(vmap.start, vmap.end - vmap.start)
            except (OSError, OverflowError, ValueError):
                # There are some memory regions that cannot be read, such as [vvar], [vdso], etc.
                continue
            occurrences += find_all_overlapping_occurrences(value, memory_content, vmap.start)
    elif file == "all" and start is not None and end is None:
        for vmap in self.maps:
            if vmap.end > start:
                liblog.debugger(f"Searching in {vmap.backing_file}...")
                read_start = max(vmap.start, start)
                try:
                    memory_content = self.read(read_start, vmap.end - read_start)
                except (OSError, OverflowError, ValueError):
                    # There are some memory regions that cannot be read, such as [vvar], [vdso], etc.
                    continue
                occurrences += find_all_overlapping_occurrences(value, memory_content, read_start)
    elif file == "all" and start is None and end is not None:
        for vmap in self.maps:
            if vmap.start < end:
                liblog.debugger(f"Searching in {vmap.backing_file}...")
                read_end = min(vmap.end, end)
                try:
                    memory_content = self.read(vmap.start, read_end - vmap.start)
                except (OSError, OverflowError, ValueError):
                    # There are some memory regions that cannot be read, such as [vvar], [vdso], etc.
                    continue
                occurrences += find_all_overlapping_occurrences(value, memory_content, vmap.start)
    elif file == "all" and start is not None and end is not None:
        # Search in the specified range, hybrid mode
        start = self.resolve_address(start, "hybrid", True)
        end = self.resolve_address(end, "hybrid", True)
        liblog.debugger(f"Searching in the range {start:#x}-{end:#x}...")
        memory_content = self.read(start, end - start)
        occurrences = find_all_overlapping_occurrences(value, memory_content, start)
    else:
        maps = self.maps.filter(file)
        start = self.resolve_address(start, file, True) if start is not None else maps[0].start
        end = self.resolve_address(end, file, True) if end is not None else maps[-1].end - 1

        liblog.debugger(f"Searching in the range {start:#x}-{end:#x}...")
        memory_content = self.read(start, end - start)

        occurrences = find_all_overlapping_occurrences(value, memory_content, start)

    return occurrences

find_pointers(where='*', target='*', step=1)

Find all pointers in the specified memory map that point to the target memory map.

If the where parameter or the target parameter is a string, it is treated as a backing file. If it is an integer, the memory map containing the address will be used.

If "*", "ALL", "all" or -1 is passed, all memory maps will be considered.

Parameters:

Name Type Description Default
where int | str

Identifier of the memory map where we want to search for references. Defaults to "*", which means all memory maps.

'*'
target int | str

Identifier of the memory map whose pointers we want to find. Defaults to "*", which means all memory maps.

'*'
step int

The interval step size while iterating over the memory buffer. Defaults to 1.

1

Returns:

Type Description
list[tuple[int, int]]

list[tuple[int, int]]: A list of tuples containing the address where the pointer was found and the pointer itself.

Source code in libdebug/memory/abstract_memory_view.py
def find_pointers(
    self: AbstractMemoryView,
    where: int | str = "*",
    target: int | str = "*",
    step: int = 1,
) -> list[tuple[int, int]]:
    """
    Find all pointers in the specified memory map that point to the target memory map.

    If the where parameter or the target parameter is a string, it is treated as a backing file. If it is an integer, the memory map containing the address will be used.

    If "*", "ALL", "all" or -1 is passed, all memory maps will be considered.

    Args:
        where (int | str): Identifier of the memory map where we want to search for references. Defaults to "*", which means all memory maps.
        target (int | str): Identifier of the memory map whose pointers we want to find. Defaults to "*", which means all memory maps.
        step (int): The interval step size while iterating over the memory buffer. Defaults to 1.

    Returns:
        list[tuple[int, int]]: A list of tuples containing the address where the pointer was found and the pointer itself.
    """
    # Filter memory maps that match the target
    if target in {"*", "ALL", "all", -1}:
        target_maps = self._internal_debugger.maps
    else:
        target_maps = self._internal_debugger.maps.filter(target)

    if not target_maps:
        raise ValueError("No memory map found for the specified target.")

    target_backing_files = {vmap.backing_file for vmap in target_maps}

    # Filter memory maps that match the where parameter
    if where in {"*", "ALL", "all", -1}:
        where_maps = self._internal_debugger.maps
    else:
        where_maps = self._internal_debugger.maps.filter(where)

    if not where_maps:
        raise ValueError("No memory map found for the specified where parameter.")

    where_backing_files = {vmap.backing_file for vmap in where_maps}

    if len(where_backing_files) == 1 and len(target_backing_files) == 1:
        return self.__internal_find_pointers(where_maps, target_maps, step)
    elif len(where_backing_files) == 1:
        found_pointers = []
        for target_backing_file in target_backing_files:
            found_pointers += self.__internal_find_pointers(
                where_maps,
                self._internal_debugger.maps.filter(target_backing_file),
                step,
            )
        return found_pointers
    elif len(target_backing_files) == 1:
        found_pointers = []
        for where_backing_file in where_backing_files:
            found_pointers += self.__internal_find_pointers(
                self._internal_debugger.maps.filter(where_backing_file),
                target_maps,
                step,
            )
        return found_pointers
    else:
        found_pointers = []
        for where_backing_file in where_backing_files:
            for target_backing_file in target_backing_files:
                found_pointers += self.__internal_find_pointers(
                    self._internal_debugger.maps.filter(where_backing_file),
                    self._internal_debugger.maps.filter(target_backing_file),
                    step,
                )

    return found_pointers

insert(index, value)

MemoryView doesn't support insertion.

Source code in libdebug/memory/abstract_memory_view.py
def insert(self: AbstractMemoryView, index: int, value: int) -> None:
    """MemoryView doesn't support insertion."""
    raise NotImplementedError("MemoryView doesn't support insertion")

read(address, size) abstractmethod

Reads memory from the target process.

Parameters:

Name Type Description Default
address int

The address to read from.

required
size int

The number of bytes to read.

required

Returns:

Name Type Description
bytes bytes

The read bytes.

Source code in libdebug/memory/abstract_memory_view.py
@abstractmethod
def read(self: AbstractMemoryView, address: int, size: int) -> bytes:
    """Reads memory from the target process.

    Args:
        address (int): The address to read from.
        size (int): The number of bytes to read.

    Returns:
        bytes: The read bytes.
    """

resolve_address(address, backing_file, skip_absolute_address_validation=False)

Normalizes and validates the specified address.

Parameters:

Name Type Description Default
address int

The address to normalize and validate.

required
backing_file str

The backing file to resolve the address in.

required
skip_absolute_address_validation bool

Whether to skip bounds checking for absolute addresses. Defaults to False.

False

Returns:

Name Type Description
int int

The normalized and validated address.

Raises:

Type Description
ValueError

If the substring backing_file is present in multiple backing files.

Source code in libdebug/memory/abstract_memory_view.py
def resolve_address(
    self: AbstractMemoryView,
    address: int,
    backing_file: str,
    skip_absolute_address_validation: bool = False,
) -> int:
    """Normalizes and validates the specified address.

    Args:
        address (int): The address to normalize and validate.
        backing_file (str): The backing file to resolve the address in.
        skip_absolute_address_validation (bool, optional): Whether to skip bounds checking for absolute addresses. Defaults to False.

    Returns:
        int: The normalized and validated address.

    Raises:
        ValueError: If the substring `backing_file` is present in multiple backing files.
    """
    return self._internal_debugger.resolve_address(
        address, backing_file, skip_absolute_address_validation,
    )

resolve_symbol(symbol, backing_file)

Resolves the address of the specified symbol.

Parameters:

Name Type Description Default
symbol str

The symbol to resolve.

required
backing_file str

The backing file to resolve the symbol in.

required

Returns:

Name Type Description
int int

The address of the symbol.

Source code in libdebug/memory/abstract_memory_view.py
def resolve_symbol(self: AbstractMemoryView, symbol: str, backing_file: str) -> int:
    """Resolves the address of the specified symbol.

    Args:
        symbol (str): The symbol to resolve.
        backing_file (str): The backing file to resolve the symbol in.

    Returns:
        int: The address of the symbol.
    """
    return self._internal_debugger.resolve_symbol(symbol, backing_file)

write(address, data) abstractmethod

Writes memory to the target process.

Parameters:

Name Type Description Default
address int

The address to write to.

required
data bytes

The data to write.

required
Source code in libdebug/memory/abstract_memory_view.py
@abstractmethod
def write(self: AbstractMemoryView, address: int, data: bytes) -> None:
    """Writes memory to the target process.

    Args:
        address (int): The address to write to.
        data (bytes): The data to write.
    """