Memory Control

What is Memory Control?

Memory control refers to reading, writing, inspecting, allocating, freeing, and protecting memory.

Example: Memory Control

"""
Example: Memory Control (64 bit)
"""
import queue
import sys
from x64dbg_automate import X64DbgClient
from x64dbg_automate.events import DbgEvent, EventType
from x64dbg_automate.models import MemPage, PageRightsConfiguration

if len(sys.argv) != 2:
    print("Usage: python hello64.py <x64dbg_path>")
    quit(1)

print('[+] Creating a new x64dbg Automate session')
client = X64DbgClient(x64dbg_path=sys.argv[1])
client.start_session(r'c:\Windows\system32\winver.exe')
client.go() # Continue from system breakpoint
client.wait_until_stopped()
client.go() # Continue from entrypoint

print('[+] Registering a callback for debug string events')
received: queue.Queue[DbgEvent] = queue.Queue()
callback = lambda x: received.put(x)
client.watch_debug_event(EventType.EVENT_OUTPUT_DEBUG_STRING, callback)

print('[+] Allocating memory for shellcode and debug string')
shellcode = client.virt_alloc()
debug_string = client.virt_alloc()

print('[+] Retrieving memory protection on debug string memory')
mem: MemPage = client.virt_query(debug_string)
print(f'\tMemory Protection: 0x{mem.allocation_protect:X}')

print('[+] Setting memory protection on debug string memory to readonly')
client.virt_protect(debug_string, PageRightsConfiguration.ReadOnly)

print('[+] Writing debug string to debugee memory')
client.write_memory(debug_string, b'https://www.youtube.com/watch?v=FKROYzWRiQ0')

print('[+] Writing shellcode to debugee memory')
i = shellcode
i = i + client.assemble_at(i, 'push rcx')
i = i + client.assemble_at(i, 'push rcx')
i = i + client.assemble_at(i, 'push rcx')
i = i + client.assemble_at(i, 'mov rax, OutputDebugStringA')
i = i + client.assemble_at(i, 'call rax')
i = i + client.assemble_at(i, 'pop rcx')
i = i + client.assemble_at(i, 'pop rcx')
i = i + client.assemble_at(i, 'pop rcx')
i = i + client.assemble_at(i, 'ret')

print('[+] Executing shellcode')
client.thread_create(shellcode, debug_string)

ev = received.get(timeout=4)
print('[+] Received debug string event')
print('\tEvent Type:', ev.event_type)
print('\tlpDebugStringData:', ev.event_data.lpDebugStringData.decode('utf-8').strip('\0'))

print('[+] Freeing memory')
client.virt_free(shellcode)
client.virt_free(debug_string)

print('[+] Retrieving full memory map and finding all *.DLL references')
pages: list[MemPage] = client.memmap()
for page in pages:
    if '.dll' in page.info.lower():
        print(f'\t{page.allocation_base:X} - {page.region_size:X} {page.protect} {page.info}')

print('[+] Terminating the session')
client.terminate_session()
[+] Creating a new x64dbg Automate session
[+] Registering a callback for debug string events
[+] Allocating memory for shellcode and debug string
[+] Retrieving memory protection on debug string memory
        Memory Protection: 0x40
[+] Setting memory protection on debug string memory to readonly
[+] Writing debug string to debugee memory
[+] Writing shellcode to debugee memory
[+] Executing shellcode
[+] Received debug string event
        Event Type: EVENT_OUTPUT_DEBUG_STRING
        lpDebugStringData: https://www.youtube.com/watch?v=FKROYzWRiQ0
[+] Freeing memory
[+] Retrieving full memory map and finding all *.DLL references
        13B701B0000 - 63000 2 \Device\HarddiskVolume3\Windows\System32\en-US\shell32.dll.mui
        13B70250000 - 3000 2 \Device\HarddiskVolume3\Windows\WinSxS\amd64_microsoft.windows.c..-controls.resources_6595b64144ccf1df_6.0.26100.1591_en-us_541af4fe0fd3faf0\comctl32.dll.mui
        13B70270000 - 3000 2 \Device\HarddiskVolume3\Windows\System32\oleaccrc.dll
        7FFC726F0000 - 1000 2 oleacc.dll
        7FFC77140000 - 1000 2 comctl32.dll
        7FFC80AD0000 - 1000 2 textshaping.dll
        7FFC82A30000 - 1000 2 textinputframework.dll
        7FFC84210000 - 1000 2 winbrand.dll
        7FFC8DA80000 - 1000 2 coreuicomponents.dll
        7FFC903D0000 - 1000 2 coremessaging.dll
        7FFC910D0000 - 1000 2 wintypes.dll
        7FFC92380000 - 1000 2 uxtheme.dll
        7FFC93F10000 - 1000 2 kernel.appcore.dll
        7FFC94620000 - 1000 2 cryptbase.dll
        7FFC95290000 - 1000 2 ucrtbase.dll
        7FFC95470000 - 1000 2 gdi32full.dll
        7FFC955A0000 - 1000 2 win32u.dll
        7FFC955D0000 - 1000 2 kernelbase.dll
        7FFC95990000 - 1000 2 msvcp_win.dll
        7FFC95A40000 - 1000 2 bcryptprimitives.dll
        7FFC95D40000 - 1000 2 shcore.dll
        7FFC95E30000 - 1000 2 gdi32.dll
        7FFC95FD0000 - 1000 2 user32.dll
        7FFC96260000 - 1000 2 combase.dll
        7FFC96A80000 - 1000 2 oleaut32.dll
        7FFC96BB0000 - 1000 2 shell32.dll
        7FFC972C0000 - 1000 2 advapi32.dll
        7FFC97380000 - 1000 2 kernel32.dll
        7FFC97450000 - 1000 2 msctf.dll
        7FFC975B0000 - 1000 2 sechost.dll
        7FFC976D0000 - 1000 2 shlwapi.dll
        7FFC97730000 - 1000 2 clbcatq.dll
        7FFC97860000 - 1000 2 msvcrt.dll
        7FFC97930000 - 1000 2 imm32.dll
        7FFC97970000 - 1000 2 rpcrt4.dll
        7FFC97D40000 - 1000 2 ntdll.dll
[+] Terminating the session

API Method Reference

write_memory(addr, data)

Writes data to the debugee's memory

Parameters:

Name Type Description Default
addr int

The address to write to

required
data bytes

The data to be written

required

Returns:

Type Description
bool

Success

Source code in x64dbg_automate/commands_xauto.py
def write_memory(self, addr: int, data: bytes) -> bool:
    """
    Writes data to the debugee's memory

    Args:
        addr: The address to write to
        data: The data to be written

    Returns:
        Success
    """
    return self._send_request(XAutoCommand.XAUTO_REQ_DBG_WRITE_MEMORY, addr, data)

read_memory(addr, size)

Reads data from the debugee's memory

Parameters:

Name Type Description Default
addr int

The address to read from

required
size int

The number of bytes to read

required

Returns:

Type Description
bytes

Bytes read from memory

Source code in x64dbg_automate/commands_xauto.py
def read_memory(self, addr: int, size: int) -> bytes:
    """
    Reads data from the debugee's memory

    Args:
        addr: The address to read from
        size: The number of bytes to read

    Returns:
        Bytes read from memory
    """
    return self._send_request(XAutoCommand.XAUTO_REQ_DBG_READ_MEMORY, addr, size)

read_word(addr)

Reads word size data from the debugee's memory

Parameters:

Name Type Description Default
addr int

The address to read from

required

Returns:

Type Description
int

The word read from memory

Source code in x64dbg_automate/commands_xauto.py
def read_word(self, addr: int) -> int:
    """
    Reads word size data from the debugee's memory

    Args:
        addr: The address to read from

    Returns:
        The word read from memory
    """
    mem = self._send_request(XAutoCommand.XAUTO_REQ_DBG_READ_MEMORY, addr, 2)
    return int.from_bytes(mem, 'little')

read_dword(addr)

Reads dword size data from the debugee's memory

Parameters:

Name Type Description Default
addr int

The address to read from

required

Returns:

Type Description
int

The dword read from memory

Source code in x64dbg_automate/commands_xauto.py
def read_dword(self, addr: int) -> int:
    """
    Reads dword size data from the debugee's memory

    Args:
        addr: The address to read from

    Returns:
        The dword read from memory
    """
    mem = self._send_request(XAutoCommand.XAUTO_REQ_DBG_READ_MEMORY, addr, 4)
    return int.from_bytes(mem, 'little')

read_qword(addr)

Reads qword size data from the debugee's memory

Parameters:

Name Type Description Default
addr int

The address to read from

required

Returns:

Type Description
int

The qword read from memory

Source code in x64dbg_automate/commands_xauto.py
def read_qword(self, addr: int) -> int:
    """
    Reads qword size data from the debugee's memory

    Args:
        addr: The address to read from

    Returns:
        The qword read from memory
    """
    mem = self._send_request(XAutoCommand.XAUTO_REQ_DBG_READ_MEMORY, addr, 8)
    return int.from_bytes(mem, 'little')

memmap()

Retrieves the memory map of the debugee

Returns:

Type Description
list[MemPage]

A list of MemPage objects

Source code in x64dbg_automate/commands_xauto.py
def memmap(self) -> list[MemPage]:
    """
    Retrieves the memory map of the debugee

    Returns:
        A list of MemPage objects
    """
    resp = self._send_request(XAutoCommand.XAUTO_REQ_DBG_MEMMAP)
    pages = []
    for page in resp:
        pages.append(MemPage(**{k: v for k, v in zip(MemPage.model_fields.keys(), page)}))
    return pages

virt_alloc(n=4096, addr=0)

Allocates memory in the debugee's address space

Parameters:

Name Type Description Default
n int

Size of memory to allocate

4096
addr int

Address to allocate memory at

0

Returns:

Type Description
int

Address of the allocated memory

Source code in x64dbg_automate/hla_xauto.py
def virt_alloc(self, n: int = 0x1000, addr: int = 0) -> int:
    """
    Allocates memory in the debugee's address space

    Args:
        n: Size of memory to allocate
        addr: Address to allocate memory at

    Returns:
        Address of the allocated memory
    """
    if not self.cmd_sync(f"alloc 0x{n:x}, 0x{addr:x}"):
        raise ValueError("Failed to allocate memory")
    addr, success = self.eval_sync("$result")
    if not success:
        raise ValueError("Failed to evaluate result")
    return addr

virt_protect(addr, page_rights, guard=False)

Changes a pages memory protection in the debugee's address space, optionally setting a page guard.

Parameters:

Name Type Description Default
addr int

Address to operate on

required
page_rights PageRightsConfiguration

New memory protection configuration

required
guard bool

page guard toggle

False

Returns:

Type Description
bool

Success

Source code in x64dbg_automate/hla_xauto.py
def virt_protect(self, addr: int, page_rights: PageRightsConfiguration, guard: bool = False) -> bool:
    """
    Changes a pages memory protection in the debugee's address space, optionally setting a page guard.

    Args:
        addr: Address to operate on
        page_rights: New memory protection configuration
        guard: page guard toggle

    Returns:
        Success
    """
    rights_str = str(page_rights)
    if guard:
        rights_str = f'G{rights_str}'
    if not self.cmd_sync(f"setpagerights 0x{addr:x}, {rights_str}"):
        raise ValueError("Failed to set memory protection")
    return True

virt_query(addr)

Retrieves information about a memory region.

Parameters:

Name Type Description Default
addr int

Address to query

required

Returns:

Type Description
MemPage | None

MemPage on success, None on failure

Source code in x64dbg_automate/hla_xauto.py
def virt_query(self, addr: int) -> MemPage | None:
    """
    Retrieves information about a memory region.

    Args:
        addr: Address to query

    Returns:
        MemPage on success, None on failure
    """
    map = self.memmap()
    for m in map:
        if m.base_address <= addr < m.base_address + m.region_size:
            return m
    return None

virt_free(addr)

Frees memory in the debugee's address space

Parameters:

Name Type Description Default
addr int

Address to free memory at

required

Returns:

Type Description
bool

Success

Source code in x64dbg_automate/hla_xauto.py
def virt_free(self, addr: int) -> bool:
    """
    Frees memory in the debugee's address space

    Args:
        addr: Address to free memory at

    Returns:
        Success
    """
    if not self.cmd_sync(f"free 0x{addr:x}"):
        raise ValueError("Failed to free memory")
    return True

memset(addr, byte_val, size)

Sets memory in the debugee's address space to the specified value

Parameters:

Name Type Description Default
addr int

Address to set memory at

required
byte_val int

Value to set memory to

required
size int

Number of bytes to set

required

Returns:

Type Description
bool

Success

Source code in x64dbg_automate/hla_xauto.py
def memset(self, addr: int, byte_val: int, size: int) -> bool:
    """
    Sets memory in the debugee's address space to the specified value

    Args:
        addr: Address to set memory at
        byte_val: Value to set memory to
        size: Number of bytes to set

    Returns:
        Success
    """
    if not self.cmd_sync(f"memset 0x{addr:x}, 0x{byte_val:x}, 0x{size:x}"):
        raise ValueError("Failed to set memory")
    return True

check_valid_read_ptr(addr)

Checks if the specified address is accessible read memory in the debugee

Parameters:

Name Type Description Default
addr int

The address to check

required

Returns:

Type Description
bool

True if the address is valid, False otherwise

Source code in x64dbg_automate/commands_xauto.py
def check_valid_read_ptr(self, addr: int) -> bool:
    """
    Checks if the specified address is accessible read memory in the debugee

    Args:
        addr: The address to check

    Returns:
        True if the address is valid, False otherwise
    """
    return self._send_request(XAutoCommand.XAUTO_REQ_DBG_IS_VALID_READ_PTR, addr)

API Model Reference

MemPage

Source code in x64dbg_automate/models.py
class MemPage(BaseModel):
    base_address: int
    allocation_base: int
    allocation_protect: int
    partition_id: int
    region_size: int
    state: int
    protect: int
    type: int
    info: str

PageRightsConfiguration

Source code in x64dbg_automate/models.py
class PageRightsConfiguration(StrEnum):
    Execute = "Execute"
    ExecuteRead = "ExecuteRead"
    ExecuteReadWrite = "ExecuteReadWrite"
    ExecuteWriteCopy = "ExecuteWriteCopy"
    NoAccess = "NoAccess"
    ReadOnly = "ReadOnly"
    ReadWrite = "ReadWrite"
    WriteCopy = "WriteCopy"