Architectural stack operations
Qiling abstracts the architectural stack operations as follows:
Pop a value off the top of stack:
value = ql.arch.stack_pop()
Push a value to the top of stack:
ql.arch.stack_push(value)
Peek the value at a certain offset from the top without modifying the stack pointer: Note: the offset may be either positive, negative or zero (to peek the top of stack)
value = ql.arch.stack_read(offset)
Replace a value at a certain offset from the top without modifying the stack pointer: Note: the offset may be either positive, negative or zero (to replace the top of stack)
ql.arch.stack_write(offset, value)
Memory subsystem
Represents the emulated memory space.
Managing memory
Qiling offers several methods for managing the emulated memory space:
Method | Description |
---|---|
map |
Map a memory region at a certain location so it become available for access |
unmap |
Reclaim a mapped memory region |
unmap_all |
Reclaim all mapped memory regions |
map_anywhere |
Map a memory region in an unspecified location |
protect |
Modify access protection bits of a mapped region (rwx) |
find_free_space |
Find an available memory region |
is_available |
Query whether a memory region is available |
is_mapped |
Query whether a memory region is mapped |
Note: is_available
and is_mapped
are not necessarily ooposites; when a memory region is partially taken (mapped), both methods will return False
.
Mapping memory pages
Memory has to be mapped before it can be accessed. The map
method binds a contiguous memory region at a specified location, and sets its access protection bits. A string label may be provided for easy identification on the mapping info table (see: get_map_info
).
Synposys:
ql.mem.map(addr: int, size: int, perms: int = UC_PROT_ALL, info: Optional[str] = None) -> None
Arguments:
- addr
- requested mapping base address; should be on a page granularity (see: pagesize
)
- size
- mapping size in bytes; must be a multiplication of page size
- perms
- protection bitmap; defines whether this memory range is readable, writeable and / or executable (optional, see: UC_PROT_*
constants)
- info
- sets a string label to the mapped range for easy identification (optional)
Returns: None
Raises: QlMemoryMappedError
if the requested memory range is not entirely available
Unmapping memory pages
Mapped memory regions may be reclaimed by unmapping them. The unmap
method reclaims a memory region at a specified location. The unmapping functionality is not limited to compelte memory regions, and may be used for partial ranges as well.
Synposys:
ql.mem.unmap(addr: int, size: int) -> None:
Arguments:
- addr
- region base address to unmap
- size
- region size in bytes
Returns: None
Raises: QlMemoryMappedError
if the requested memory range is not entirely mapped
Search bytes pattern from memory
- Search for a pattern from entire memory
address = ql.mem.search(b"\xFF\xFE\xFD\xFC\xFB\xFA")
- Search for a pattern from entire memory range
address = ql.mem.search(b"\xFF\xFE\xFD\xFC\xFB\xFA", begin= 0x1000, end= 0x2000)
Read from a memory address
ql.mem.read(address, size)
Write to a memory address
ql.mem.write(address, data)
Map a memory area
map a memory before writing into it. Info can be empty.
ql.mem.map(addr,size,info = [my_first_map])
Address:
You need to align the memory offset and address for mapping.
addr//size*size
-> 0x7fefc9e0//4096*4096
Size:
The amounts of memory that should be mapped
This parameter is OS dependant; If you use a linux system, consider at least a multiple of 4096 for alignment
example (Linux):
[..]
def memory_fix(ql, access, addr, size, value):
ql.nprint("[_] Mapping "+str(size)+" bytes at "+hex(addr)+" | access: "+ str(access)+" | value: "+ str(value))
ql.mem.map(addr//4096*4096, 4096)
ql.mem.write(addr, struct.pack(">I",value)) # memory packing is OS dependant
return
[...]
ql.hook_mem_unmapped(memory_fix)
[...]
See qiling/loader/elf.py for a proper mapping example
read and write string
to read a string from memory
ql.mem.string(address)
to write a string to memory
ql.mem.string(address, "stringwith")
Show all the mapped area
ql.mem.get_formatted_mapinfo()
Example:
for info_line in self.ql.mem.get_formatted_mapinfo():
self.ql.log.error(info_line)
find a free space
Find a specific free space size.
ql.mem.find_free_space(size)
check for availablity
The main function of is_available is to determine whether the memory starting with addr and having a size of length can be used for allocation. If it can be allocated, returns True. If it cannot be allocated, it returns False.
ql.mem.is_available(addr, size)
check for is the memory area being mapped
The main function of is_mmaped is to determine whether the memory starting with addr and size has been mapped. Returns true if it has already been allocated. If unassigned, returns False.
ql.mem.is_mapped(addr, size)
Find a matching size of unmapped usable space
Finds a region of memory that is free, larger than 'size' arg, and aligned.
ql.mem.find_free_space(size, min_addr=0, max_addr = 0, alignment=0x10000)
Find a matching size of unmapped usable space and map it
Maps a region of memory with requested size, within the addresses specified. The size and start address will respect the alignment.
ql.mem.map_anywhere(size)