from xnu import *
import xnudefines
from kdp import *
from utils import *
def ReadPhysInt(phys_addr, bitsize = 64, cpuval = None):
""" Read a physical memory data based on address.
params:
phys_addr : int - Physical address to read
bitsize : int - defines how many bytes to read. defaults to 64 bit
cpuval : None (optional)
returns:
int - int value read from memory. in case of failure 0xBAD10AD is returned.
"""
if "kdp" == GetConnectionProtocol():
return KDPReadPhysMEM(phys_addr, bitsize)
paddr_in_kva = kern.PhysToKernelVirt(long(phys_addr))
if paddr_in_kva :
if bitsize == 64 :
return kern.GetValueFromAddress(paddr_in_kva, 'uint64_t *').GetSBValue().Dereference().GetValueAsUnsigned()
if bitsize == 32 :
return kern.GetValueFromAddress(paddr_in_kva, 'uint32_t *').GetSBValue().Dereference().GetValueAsUnsigned()
if bitsize == 16 :
return kern.GetValueFromAddress(paddr_in_kva, 'uint16_t *').GetSBValue().Dereference().GetValueAsUnsigned()
if bitsize == 8 :
return kern.GetValueFromAddress(paddr_in_kva, 'uint8_t *').GetSBValue().Dereference().GetValueAsUnsigned()
return 0xBAD10AD
@lldb_command('readphys')
def ReadPhys(cmd_args = None):
""" Reads the specified untranslated address
The argument is interpreted as a physical address, and the 64-bit word
addressed is displayed.
usage: readphys <nbits> <address>
nbits: 8,16,32,64
address: 1234 or 0x1234
"""
if cmd_args == None or len(cmd_args) < 2:
print "Insufficient arguments.", ReadPhys.__doc__
return False
else:
nbits = ArgumentStringToInt(cmd_args[0])
phys_addr = ArgumentStringToInt(cmd_args[1])
print "{0: <#x}".format(ReadPhysInt(phys_addr, nbits))
return True
lldb_alias('readphys8', 'readphys 8 ')
lldb_alias('readphys16', 'readphys 16 ')
lldb_alias('readphys32', 'readphys 32 ')
lldb_alias('readphys64', 'readphys 64 ')
def KDPReadPhysMEM(address, bits):
""" Setup the state for READPHYSMEM64 commands for reading data via kdp
params:
address : int - address where to read the data from
bits : int - number of bits in the intval (8/16/32/64)
returns:
int: read value from memory.
0xBAD10AD: if failed to read data.
"""
retval = 0xBAD10AD
if "kdp" != GetConnectionProtocol():
print "Target is not connected over kdp. Nothing to do here."
return retval
input_address = unsigned(addressof(kern.globals.manual_pkt.input))
len_address = unsigned(addressof(kern.globals.manual_pkt.len))
data_address = unsigned(addressof(kern.globals.manual_pkt.data))
if not WriteInt32ToMemoryAddress(0, input_address):
return retval
kdp_pkt_size = GetType('kdp_readphysmem64_req_t').GetByteSize()
if not WriteInt32ToMemoryAddress(kdp_pkt_size, len_address):
return retval
data_addr = int(addressof(kern.globals.manual_pkt))
pkt = kern.GetValueFromAddress(data_addr, 'kdp_readphysmem64_req_t *')
header_value =GetKDPPacketHeaderInt(request=GetEnumValue('kdp_req_t::KDP_READPHYSMEM64'), length=kdp_pkt_size)
if ( WriteInt64ToMemoryAddress((header_value), int(addressof(pkt.hdr))) and
WriteInt64ToMemoryAddress(address, int(addressof(pkt.address))) and
WriteInt32ToMemoryAddress((bits/8), int(addressof(pkt.nbytes))) and
WriteInt16ToMemoryAddress(xnudefines.lcpu_self, int(addressof(pkt.lcpu)))
):
if WriteInt32ToMemoryAddress(1, input_address):
data_address = unsigned(addressof(kern.GetValueFromAddress(int(addressof(kern.globals.manual_pkt.data)), 'kdp_readphysmem64_reply_t *').data))
if bits == 64 :
retval = kern.GetValueFromAddress(data_address, 'uint64_t *').GetSBValue().Dereference().GetValueAsUnsigned()
if bits == 32 :
retval = kern.GetValueFromAddress(data_address, 'uint32_t *').GetSBValue().Dereference().GetValueAsUnsigned()
if bits == 16 :
retval = kern.GetValueFromAddress(data_address, 'uint16_t *').GetSBValue().Dereference().GetValueAsUnsigned()
if bits == 8 :
retval = kern.GetValueFromAddress(data_address, 'uint8_t *').GetSBValue().Dereference().GetValueAsUnsigned()
return retval
def KDPWritePhysMEM(address, intval, bits):
""" Setup the state for WRITEPHYSMEM64 commands for saving data in kdp
params:
address : int - address where to save the data
intval : int - integer value to be stored in memory
bits : int - number of bits in the intval (8/16/32/64)
returns:
boolean: True if the write succeeded.
"""
if "kdp" != GetConnectionProtocol():
print "Target is not connected over kdp. Nothing to do here."
return False
input_address = unsigned(addressof(kern.globals.manual_pkt.input))
len_address = unsigned(addressof(kern.globals.manual_pkt.len))
data_address = unsigned(addressof(kern.globals.manual_pkt.data))
if not WriteInt32ToMemoryAddress(0, input_address):
return False
kdp_pkt_size = GetType('kdp_writephysmem64_req_t').GetByteSize() + (bits / 8)
if not WriteInt32ToMemoryAddress(kdp_pkt_size, len_address):
return False
data_addr = int(addressof(kern.globals.manual_pkt))
pkt = kern.GetValueFromAddress(data_addr, 'kdp_writephysmem64_req_t *')
header_value =GetKDPPacketHeaderInt(request=GetEnumValue('kdp_req_t::KDP_WRITEPHYSMEM64'), length=kdp_pkt_size)
if ( WriteInt64ToMemoryAddress((header_value), int(addressof(pkt.hdr))) and
WriteInt64ToMemoryAddress(address, int(addressof(pkt.address))) and
WriteInt32ToMemoryAddress((bits/8), int(addressof(pkt.nbytes))) and
WriteInt16ToMemoryAddress(xnudefines.lcpu_self, int(addressof(pkt.lcpu)))
):
if bits == 8:
if not WriteInt8ToMemoryAddress(intval, int(addressof(pkt.data))):
return False
if bits == 16:
if not WriteInt16ToMemoryAddress(intval, int(addressof(pkt.data))):
return False
if bits == 32:
if not WriteInt32ToMemoryAddress(intval, int(addressof(pkt.data))):
return False
if bits == 64:
if not WriteInt64ToMemoryAddress(intval, int(addressof(pkt.data))):
return False
if WriteInt32ToMemoryAddress(1, input_address):
return True
return False
def WritePhysInt(phys_addr, int_val, bitsize = 64):
""" Write and integer value in a physical memory data based on address.
params:
phys_addr : int - Physical address to read
int_val : int - int value to write in memory
bitsize : int - defines how many bytes to read. defaults to 64 bit
returns:
bool - True if write was successful.
"""
if "kdp" == GetConnectionProtocol():
if not KDPWritePhysMEM(phys_addr, int_val, bitsize):
print "Failed to write via KDP."
return False
return True
print "Failed: Write to physical memory is not supported for %s connection." % GetConnectionProtocol()
return False
@lldb_command('writephys')
def WritePhys(cmd_args=None):
""" writes to the specified untranslated address
The argument is interpreted as a physical address, and the 64-bit word
addressed is displayed.
usage: writephys <nbits> <address> <value>
nbits: 8,16,32,64
address: 1234 or 0x1234
value: int value to be written
ex. (lldb)writephys 16 0x12345abcd 0x25
"""
if cmd_args == None or len(cmd_args) < 3:
print "Invalid arguments.", WritePhys.__doc__
else:
nbits = ArgumentStringToInt(cmd_args[0])
phys_addr = ArgumentStringToInt(cmd_args[1])
int_value = ArgumentStringToInt(cmd_args[2])
print WritePhysInt(phys_addr, int_value, nbits)
lldb_alias('writephys8', 'writephys 8 ')
lldb_alias('writephys16', 'writephys 16 ')
lldb_alias('writephys32', 'writephys 32 ')
lldb_alias('writephys64', 'writephys 64 ')
def _PT_Step(paddr, index, verbose_level = vSCRIPT):
"""
Step to lower-level page table and print attributes
paddr: current page table entry physical address
index: current page table entry index (0..511)
verbose_level: vHUMAN: print nothing
vSCRIPT: print basic information
vDETAIL: print basic information and hex table dump
returns: (pt_paddr, pt_valid, pt_large)
pt_paddr: next level page table entry physical address
or null if invalid
pt_valid: 1 if $kgm_pt_paddr is valid, 0 if the walk
should be aborted
pt_large: 1 if kgm_pt_paddr is a page frame address
of a large page and not another page table entry
"""
entry_addr = paddr + (8 * index)
entry = ReadPhysInt(entry_addr, 64, xnudefines.lcpu_self )
out_string = ''
if verbose_level >= vDETAIL:
for pte_loop in range(0, 512):
paddr_tmp = paddr + (8 * pte_loop)
out_string += "{0: <#020x}:\t {1: <#020x}\n".format(paddr_tmp, ReadPhysInt(paddr_tmp, 64, xnudefines.lcpu_self))
paddr_mask = ~((0xfff<<52) | 0xfff)
paddr_large_mask = ~((0xfff<<52) | 0x1fffff)
pt_valid = False
pt_large = False
pt_paddr = 0
if verbose_level < vSCRIPT:
if entry & 0x1 :
pt_valid = True
pt_large = False
pt_paddr = entry & paddr_mask
if entry & (0x1 <<7):
pt_large = True
pt_paddr = entry & paddr_large_mask
else:
out_string+= "{0: <#020x}:\n\t{1:#020x}\n\t".format(entry_addr, entry)
if entry & 0x1:
out_string += " valid"
pt_paddr = entry & paddr_mask
pt_valid = True
else:
out_string += " invalid"
pt_paddr = 0
pt_valid = False
if entry & (0x1 << 62):
out_string += " compressed"
entry = 0
if entry & (0x1 << 1):
out_string += " writable"
else:
out_string += " read-only"
if entry & (0x1 << 2):
out_string += " user"
else:
out_string += " supervisor"
if entry & (0x1 << 3):
out_string += " PWT"
if entry & (0x1 << 4):
out_string += " PCD"
if entry & (0x1 << 5):
out_string += " accessed"
if entry & (0x1 << 6):
out_string += " dirty"
if entry & (0x1 << 7):
out_string += " large"
pt_large = True
else:
pt_large = False
if entry & (0x1 << 8):
out_string += " global"
if entry & (0x3 << 9):
out_string += " avail:{0:x}".format((entry >> 9) & 0x3)
if entry & (0x1 << 63):
out_string += " noexec"
print out_string
return (pt_paddr, pt_valid, pt_large)
def _PT_StepEPT(paddr, index, verbose_level = vSCRIPT):
"""
Step to lower-level page table and print attributes for EPT pmap
paddr: current page table entry physical address
index: current page table entry index (0..511)
verbose_level: vHUMAN: print nothing
vSCRIPT: print basic information
vDETAIL: print basic information and hex table dump
returns: (pt_paddr, pt_valid, pt_large)
pt_paddr: next level page table entry physical address
or null if invalid
pt_valid: 1 if $kgm_pt_paddr is valid, 0 if the walk
should be aborted
pt_large: 1 if kgm_pt_paddr is a page frame address
of a large page and not another page table entry
"""
entry_addr = paddr + (8 * index)
entry = ReadPhysInt(entry_addr, 64, xnudefines.lcpu_self )
out_string = ''
if verbose_level >= vDETAIL:
for pte_loop in range(0, 512):
paddr_tmp = paddr + (8 * pte_loop)
out_string += "{0: <#020x}:\t {1: <#020x}\n".format(paddr_tmp, ReadPhysInt(paddr_tmp, 64, xnudefines.lcpu_self))
paddr_mask = ~((0xfff<<52) | 0xfff)
paddr_large_mask = ~((0xfff<<52) | 0x1fffff)
pt_valid = False
pt_large = False
pt_paddr = 0
if verbose_level < vSCRIPT:
if entry & 0x7 :
pt_valid = True
pt_large = False
pt_paddr = entry & paddr_mask
if entry & (0x1 <<7):
pt_large = True
pt_paddr = entry & paddr_large_mask
else:
out_string+= "{0: <#020x}:\n\t{1:#020x}\n\t".format(entry_addr, entry)
if entry & 0x7:
out_string += "valid"
pt_paddr = entry & paddr_mask
pt_valid = True
else:
out_string += "invalid"
pt_paddr = 0
pt_valid = False
if entry & (0x1 << 62):
out_string += " compressed"
entry = 0
if entry & 0x1:
out_string += " readable"
else:
out_string += " no read"
if entry & (0x1 << 1):
out_string += " writable"
else:
out_string += " no write"
if entry & (0x1 << 2):
out_string += " executable"
else:
out_string += " no exec"
ctype = entry & 0x38
if ctype == 0x30:
out_string += " cache-WB"
elif ctype == 0x28:
out_string += " cache-WP"
elif ctype == 0x20:
out_string += " cache-WT"
elif ctype == 0x8:
out_string += " cache-WC"
else:
out_string += " cache-NC"
if (entry & 0x40) == 0x40:
out_string += " Ignore-PTA"
if (entry & 0x100) == 0x100:
out_string += " accessed"
if (entry & 0x200) == 0x200:
out_string += " dirty"
if entry & (0x1 << 7):
out_string += " large"
pt_large = True
else:
pt_large = False
print out_string
return (pt_paddr, pt_valid, pt_large)
def _PmapL4Walk(pmap_addr_val,vaddr, ept_pmap, verbose_level = vSCRIPT):
""" Walk the l4 pmap entry.
params: pmap_addr_val - core.value representing kernel data of type pmap_addr_t
vaddr : int - virtual address to walk
"""
is_cpu64_bit = int(kern.globals.cpu_64bit)
pt_paddr = unsigned(pmap_addr_val)
pt_valid = (unsigned(pmap_addr_val) != 0)
pt_large = 0
pframe_offset = 0
if pt_valid and is_cpu64_bit:
pt_index = (vaddr >> 39) & 0x1ff
pframe_offset = vaddr & 0x7fffffffff
if verbose_level > vHUMAN :
print "pml4 (index {0:d}):".format(pt_index)
if not(ept_pmap):
(pt_paddr, pt_valid, pt_large) = _PT_Step(pt_paddr, pt_index, verbose_level)
else:
(pt_paddr, pt_valid, pt_large) = _PT_StepEPT(pt_paddr, pt_index, verbose_level)
if pt_valid:
pt_index = (vaddr >> 30) & 0x1ff
pframe_offset = vaddr & 0x3fffffff
if verbose_level > vHUMAN:
print "pdpt (index {0:d}):".format(pt_index)
if not(ept_pmap):
(pt_paddr, pt_valid, pt_large) = _PT_Step(pt_paddr, pt_index, verbose_level)
else:
(pt_paddr, pt_valid, pt_large) = _PT_StepEPT(pt_paddr, pt_index, verbose_level)
if pt_valid and not pt_large:
pt_index = (vaddr >> 21) & 0x1ff
pframe_offset = vaddr & 0x1fffff
if verbose_level > vHUMAN:
print "pdt (index {0:d}):".format(pt_index)
if not(ept_pmap):
(pt_paddr, pt_valid, pt_large) = _PT_Step(pt_paddr, pt_index, verbose_level)
else:
(pt_paddr, pt_valid, pt_large) = _PT_StepEPT(pt_paddr, pt_index, verbose_level)
if pt_valid and not pt_large:
pt_index = (vaddr >> 12) & 0x1ff
pframe_offset = vaddr & 0xfff
if verbose_level > vHUMAN:
print "pt (index {0:d}):".format(pt_index)
if not(ept_pmap):
(pt_paddr, pt_valid, pt_large) = _PT_Step(pt_paddr, pt_index, verbose_level)
else:
(pt_paddr, pt_valid, pt_large) = _PT_StepEPT(pt_paddr, pt_index, verbose_level)
paddr = 0
paddr_isvalid = False
if pt_valid:
paddr = pt_paddr + pframe_offset
paddr_isvalid = True
if verbose_level > vHUMAN:
if paddr_isvalid:
pvalue = ReadPhysInt(paddr, 32, xnudefines.lcpu_self)
print "phys {0: <#020x}: {1: <#020x}".format(paddr, pvalue)
else:
print "no translation"
return paddr
def PmapDecodeTTEARM(tte, level, verbose_level):
""" Display the bits of an ARM translation table or page table entry
in human-readable form.
tte: integer value of the TTE/PTE
level: translation table level. Valid values are 1 or 2.
verbose_level: verbosity. vHUMAN, vSCRIPT, vDETAIL
"""
out_string = ""
if level == 1 and (tte & 0x3) == 0x2:
if verbose_level < vSCRIPT:
return
b_bit = (tte & 0x4) >> 2
c_bit = (tte & 0x8) >> 3
if (tte & 0x10) :
out_string += "no-execute"
else:
out_string += "execute"
if (tte & 0x40000) == 0x0:
out_string += " domain ({:d})".format(((tte & 0x1e0) >> 5) )
out_string += " imp({:d})".format( ((tte & 0x200) >> 9) )
access = ( (tte & 0xc00) >> 10 ) | ((tte & 0x8000) >> 13)
out_string += xnudefines.arm_level2_access_strings[access]
tex_bits = ((tte & 0x7000) >> 12)
out_string += " TEX:C:B({:d}{:d}{:d}:{:d}:{:d})".format(
1 if (tex_bits & 0x4) else 0,
1 if (tex_bits & 0x2) else 0,
1 if (tex_bits & 0x1) else 0,
c_bit,
b_bit
)
if tte & 0x10000:
out_string += " shareable"
else:
out_string += " not-shareable"
if tte & 0x20000 :
out_string += " not-global"
else:
out_string += " global"
if tte & 0x40000:
out_string += " supersection"
else:
out_string += " section"
if tte & 0x80000 :
out_string += " no-secure"
else:
out_string += " secure"
elif level == 1 and (tte & 0x3) == 0x1:
if verbose_level >= vSCRIPT:
if tte & 0x8:
out_string += ' no-secure'
else:
out_string += ' secure'
out_string += " domain({:d})".format(((tte & 0x1e0) >> 5))
out_string += " imp({:d})".format( ((tte & 0x200) >> 9))
out_string += "\n"
elif level == 2:
pte = tte
if verbose_level >= vSCRIPT:
if (pte & 0x3) == 0x0:
out_string += " invalid"
else:
if (pte & 0x3) == 0x1:
out_string += " large"
if pte & 0x8000 == 0x8000:
out_string+= " no-execute"
else:
out_string += " execute"
else:
out_string += " small"
if (pte & 0x1) == 0x01:
out_string += " no-execute"
else:
out_string += " execute"
b_bit = (pte & 0x4) >> 2
c_bit = (pte & 0x8) >> 3
access = (pte & 0x30) >> 4 | (pte & 0x200) >> 7
out_string += xnudefines.arm_level2_access_strings[access]
tex_bits = ((pte & 0x1c0) >> 6)
if (pte & 0x3) == 0x1:
tex_bits = ((pte & 0x7000) >> 12)
out_string += " TEX:C:B({:d}{:d}{:d}:{:d}:{:d})".format(
1 if (tex_bits & 0x4) else 0,
1 if (tex_bits & 0x2) else 0,
1 if (tex_bits & 0x1) else 0,
c_bit,
b_bit
)
if pte & 0x400 :
out_string += " shareable"
else:
out_string += " not-shareable"
if pte & 0x800:
out_string += " not-global"
else:
out_string += " global"
print out_string
def _PmapWalkARMLevel1Section(tte, vaddr, verbose_level = vSCRIPT):
paddr = 0
if (tte & 0x40000) == 0x40000:
paddr = ( (tte & 0xFF000000) | (vaddr & 0x00FFFFFF) )
else:
paddr = ( (tte & 0xFFF00000) | (vaddr & 0x000FFFFF) )
if verbose_level >= vSCRIPT:
print "{0: <#020x}\n\t{1: <#020x}\n\t".format(addressof(tte), tte),
PmapDecodeTTEARM(tte, 1, verbose_level)
return paddr
def _PmapWalkARMLevel2(tte, vaddr, verbose_level = vSCRIPT):
""" Pmap walk the level 2 tte.
params:
tte - value object
vaddr - int
returns: str - description of the tte + additional informaiton based on verbose_level
"""
pte_base = kern.PhysToKernelVirt(tte & 0xFFFFFC00)
pte_index = (vaddr >> 12) & 0xFF
pte_base_val = kern.GetValueFromAddress(pte_base, 'pt_entry_t *')
pte = pte_base_val[pte_index]
paddr = 0
if pte & 0x2:
paddr = (unsigned(pte) & 0xFFFFF000) | (vaddr & 0xFFF)
if verbose_level >= vSCRIPT:
print "{0: <#020x}\n\t{1: <#020x}\n\t".format(addressof(tte), tte),
PmapDecodeTTEARM(tte, 1, verbose_level)
if verbose_level >= vSCRIPT:
print "second-level table (index {:d}):".format(pte_index)
if verbose_level >= vDETAIL:
for i in range(256):
tmp = pte_base_val[i]
print "{0: <#020x}:\t{1: <#020x}".format(addressof(tmp), unsigned(tmp))
if verbose_level >= vSCRIPT:
print " {0: <#020x}\n\t{1: <#020x}\n\t".format(addressof(pte), unsigned(pte)),
PmapDecodeTTEARM(pte, 2, verbose_level)
return paddr
def PmapWalkARM(pmap, vaddr, verbose_level = vHUMAN):
""" Pmap walking for ARM kernel.
params:
pmapval: core.value - representing pmap_t in kernel
vaddr: int - integer representing virtual address to walk
"""
paddr = 0
tte_index = vaddr >> 20
tte = pmap.tte[tte_index]
if verbose_level >= vSCRIPT:
print "First-level table (index {:d}):".format(tte_index)
if verbose_level >= vDETAIL:
for i in range(0, pmap.tte_index_max):
ptr = unsigned(addressof(pmap.tte[i]))
val = unsigned(pmap.tte[i])
print "{0: <#020x}:\t {1: <#020x}".format(ptr, val)
if (tte & 0x3) == 0x1:
paddr = _PmapWalkARMLevel2(tte, vaddr, verbose_level)
elif (tte & 0x3) == 0x2 :
paddr = _PmapWalkARMLevel1Section(tte, vaddr, verbose_level)
else:
paddr = 0
if verbose_level >= vSCRIPT:
print "Invalid First-Level Translation Table Entry: {0: #020x}".format(tte)
if verbose_level >= vHUMAN:
if paddr:
print "Translation of {:#x} is {:#x}.".format(vaddr, paddr)
else:
print "(no translation)"
return paddr
def PmapWalkX86_64(pmapval, vaddr, verbose_level = vSCRIPT):
"""
params: pmapval - core.value representing pmap_t in kernel
vaddr: int - int representing virtual address to walk
"""
if pmapval.pm_cr3 != 0:
if verbose_level > vHUMAN:
print "Using normal Intel PMAP from pm_cr3\n"
return _PmapL4Walk(pmapval.pm_cr3, vaddr, 0, config['verbosity'])
else:
if verbose_level > vHUMAN:
print "Using EPT pmap from pm_eptp\n"
return _PmapL4Walk(pmapval.pm_eptp, vaddr, 1, config['verbosity'])
def assert_64bit(val):
assert(val < 2**64)
ARM64_TTE_SIZE = 8
ARM64_TTE_SHIFT = 3
ARM64_VMADDR_BITS = 48
def PmapBlockOffsetMaskARM64(level):
assert level >= 1 and level <= 3
page_size = kern.globals.arm_hardware_page_size
ttentries = (page_size / ARM64_TTE_SIZE)
return page_size * (ttentries ** (3 - level)) - 1
def PmapBlockBaseMaskARM64(level):
assert level >= 1 and level <= 3
page_size = kern.globals.arm_hardware_page_size
return ((1 << ARM64_VMADDR_BITS) - 1) & ~PmapBlockOffsetMaskARM64(level)
def PmapIndexMaskARM64(level):
assert level >= 1 and level <= 3
page_size = kern.globals.arm_hardware_page_size
ttentries = (page_size / ARM64_TTE_SIZE)
return page_size * (ttentries ** (3 - level) * (ttentries - 1))
def PmapIndexDivideARM64(level):
assert level >= 1 and level <= 3
page_size = kern.globals.arm_hardware_page_size
ttentries = (page_size / ARM64_TTE_SIZE)
return page_size * (ttentries ** (3 - level))
def PmapTTnIndexARM64(vaddr, level):
assert(type(vaddr) in (long, int))
assert_64bit(vaddr)
return (vaddr & PmapIndexMaskARM64(level)) // PmapIndexDivideARM64(level)
def PmapDecodeTTEARM64(tte, level):
""" Display the bits of an ARM64 translation table or page table entry
in human-readable form.
tte: integer value of the TTE/PTE
level: translation table level. Valid values are 1, 2, or 3.
"""
assert(type(tte) == long)
assert(type(level) == int)
assert_64bit(tte)
if tte & 0x1 == 0x1:
if (tte & 0x2 == 0x2) and (level != 0x3):
print "Type = Table pointer."
print "Table addr = {:#x}.".format(tte & 0xfffffffff000)
print "PXN = {:#x}.".format((tte >> 59) & 0x1)
print "XN = {:#x}.".format((tte >> 60) & 0x1)
print "AP = {:#x}.".format((tte >> 61) & 0x3)
print "NS = {:#x}".format(tte >> 63)
else:
print "Type = Block."
print "AttrIdx = {:#x}.".format((tte >> 2) & 0x7)
print "NS = {:#x}.".format((tte >> 5) & 0x1)
print "AP = {:#x}.".format((tte >> 6) & 0x3)
print "SH = {:#x}.".format((tte >> 8) & 0x3)
print "AF = {:#x}.".format((tte >> 10) & 0x1)
print "nG = {:#x}.".format((tte >> 11) & 0x1)
print "HINT = {:#x}.".format((tte >> 52) & 0x1)
print "PXN = {:#x}.".format((tte >> 53) & 0x1)
print "XN = {:#x}.".format((tte >> 54) & 0x1)
print "SW Use = {:#x}.".format((tte >> 55) & 0xf)
else:
print "Invalid."
return
def PmapWalkARM64(pmap, vaddr, verbose_level = vHUMAN):
assert(type(pmap) == core.cvalue.value)
assert(type(vaddr) in (long, int))
page_size = kern.globals.arm_hardware_page_size
page_offset_mask = (page_size - 1)
page_base_mask = ((1 << ARM64_VMADDR_BITS) - 1) & (~page_offset_mask)
assert_64bit(vaddr)
paddr = -1
tt0_index = 0
tt1_index = PmapTTnIndexARM64(vaddr, 1)
tt2_index = PmapTTnIndexARM64(vaddr, 2)
tt3_index = PmapTTnIndexARM64(vaddr, 3)
level = kern.globals.arm64_root_pgtable_level
assert(level <= 3)
if level == 0:
root_tt_index = tt0_index
elif level == 1:
root_tt_index = tt1_index
elif level == 2:
root_tt_index = tt2_index
elif level == 3:
root_tt_index = tt3_index
root_tt_index = root_tt_index % unsigned(kern.globals.arm64_root_pgtable_num_ttes)
tte = long(unsigned(pmap.tte[root_tt_index]))
assert(type(tte) == long)
assert_64bit(tte)
while (True):
if (level == 0):
assert(False)
elif (level == 1):
if verbose_level >= vSCRIPT:
print "L1 entry: {:#x}".format(tte)
if verbose_level >= vDETAIL:
PmapDecodeTTEARM64(tte, 1)
if tte & 0x1 == 0x1:
if tte & 0x2 == 0x0:
paddr = tte & PmapBlockBaseMaskARM64(1)
paddr = paddr | (vaddr & PmapBlockOffsetMaskARM64(1))
print "phys: {:#x}".format(paddr)
break
else:
l2_phys = (tte & page_base_mask) + (ARM64_TTE_SIZE * tt2_index)
assert(type(l2_phys) == long)
l2_virt = kern.PhysToKernelVirt(l2_phys)
assert(type(l2_virt) == long)
if verbose_level >= vDETAIL:
print "L2 physical address: {:#x}. L2 virtual address: {:#x}".format(l2_phys, l2_virt)
ttep = kern.GetValueFromAddress(l2_virt, "tt_entry_t*")
tte = long(unsigned(dereference(ttep)))
assert(type(tte) == long)
elif verbose_level >= vHUMAN:
print "L1 entry invalid: {:#x}\n".format(tte)
elif (level == 2):
if verbose_level >= vSCRIPT:
print "L2 entry: {:#0x}".format(tte)
if verbose_level >= vDETAIL:
PmapDecodeTTEARM64(tte, 2)
if tte & 0x1 == 0x1:
if tte & 0x2 == 0x0:
paddr = tte & PmapBlockBaseMaskARM64(2)
paddr = paddr | (vaddr & PmapBlockOffsetMaskARM64(2))
break
else:
l3_phys = (tte & page_base_mask) + (ARM64_TTE_SIZE * tt3_index)
assert(type(l3_phys) == long)
l3_virt = kern.PhysToKernelVirt(l3_phys)
assert(type(l3_virt) == long)
if verbose_level >= vDETAIL:
print "L3 physical address: {:#x}. L3 virtual address: {:#x}".format(l3_phys, l3_virt)
ttep = kern.GetValueFromAddress(l3_virt, "tt_entry_t*")
tte = long(unsigned(dereference(ttep)))
assert(type(tte) == long)
elif verbose_level >= vHUMAN: print "L2 entry invalid: {:#x}\n".format(tte)
elif (level == 3):
if verbose_level >= vSCRIPT:
print "L3 entry: {:#0x}".format(tte)
if verbose_level >= vDETAIL:
PmapDecodeTTEARM64(tte, 3)
if tte & 0x3 == 0x3:
paddr = tte & page_base_mask
paddr = paddr | (vaddr & page_offset_mask)
elif verbose_level >= vHUMAN:
print "L3 entry invalid: {:#x}\n".format(tte)
break
assert(level <= 3)
level = level + 1
if verbose_level >= vHUMAN:
if paddr:
print "Translation of {:#x} is {:#x}.".format(vaddr, paddr)
else:
print "(no translation)"
return paddr
def PmapWalk(pmap, vaddr, verbose_level = vHUMAN):
if kern.arch == 'x86_64':
return PmapWalkX86_64(pmap, vaddr, verbose_level)
elif kern.arch == 'arm':
return PmapWalkARM(pmap, vaddr, verbose_level)
elif kern.arch.startswith('arm64'):
return PmapWalkARM64(pmap, vaddr, verbose_level)
else:
raise NotImplementedError("PmapWalk does not support {0}".format(kern.arch))
@lldb_command('pmap_walk')
def PmapWalkHelper(cmd_args=None):
""" Perform a page-table walk in <pmap> for <virtual_address>.
Syntax: (lldb) pmap_walk <pmap> <virtual_address> [-v] [-e]
Multiple -v's can be specified for increased verbosity
"""
if cmd_args == None or len(cmd_args) < 2:
raise ArgumentError("Too few arguments to pmap_walk.")
pmap = kern.GetValueAsType(cmd_args[0], 'pmap_t')
addr = unsigned(kern.GetValueFromAddress(cmd_args[1], 'void *'))
PmapWalk(pmap, addr, config['verbosity'])
return
@lldb_command('decode_tte')
def DecodeTTE(cmd_args=None):
""" Decode the bits in the TTE/PTE value specified <tte_val> for translation level <level>
Syntax: (lldb) decode_tte <tte_val> <level>
"""
if cmd_args == None or len(cmd_args) < 2:
raise ArgumentError("Too few arguments to decode_tte.")
if kern.arch == 'arm':
PmapDecodeTTEARM(kern.GetValueFromAddress(cmd_args[0], "unsigned long"), ArgumentStringToInt(cmd_args[1]), vSCRIPT)
elif kern.arch.startswith('arm64'):
PmapDecodeTTEARM64(long(kern.GetValueFromAddress(cmd_args[0], "unsigned long")), ArgumentStringToInt(cmd_args[1]))
else:
raise NotImplementedError("decode_tte does not support {0}".format(kern.arch))
def PVWalkARM(pa):
""" Walk a physical-to-virtual reverse mapping list maintained by the arm pmap
pa: physical address (NOT page number). Does not need to be page-aligned
"""
vm_first_phys = unsigned(kern.globals.vm_first_phys)
vm_last_phys = unsigned(kern.globals.vm_last_phys)
if pa < vm_first_phys or pa >= vm_last_phys:
raise ArgumentError("PA {:#x} is outside range of managed physical addresses: [{:#x}, {:#x})".format(pa, vm_first_phys, vm_last_phys))
page_size = kern.globals.arm_hardware_page_size
pn = (pa - unsigned(kern.globals.vm_first_phys)) / page_size
pvh = unsigned(kern.globals.pv_head_table[pn])
pvh_type = pvh & 0x3
if pvh_type == 0:
print "PVH type: NULL"
return
elif pvh_type == 3:
print "PVH type: page-table descriptor ({:#x})".format(pvh & ~0x3)
return
elif pvh_type == 2:
ptep = pvh & ~0x3
print "PVH type: single PTE"
print "PTE {:#x}: {:#x}".format(ptep, dereference(kern.GetValueFromAddress(ptep, 'pt_entry_t *')))
elif pvh_type == 1:
pvep = pvh & ~0x3
print "PVH type: PTE list"
while pvep != 0:
pve = kern.GetValueFromAddress(pvep, "pv_entry_t *")
if unsigned(pve.pve_next) & 0x1:
pve_str = ' (alt acct) '
else:
pve_str = ''
current_pvep = pvep
pvep = unsigned(pve.pve_next) & ~0x1
ptep = unsigned(pve.pve_ptep) & ~0x3
print "PVE {:#x}, PTE {:#x}{:s}: {:#x}".format(current_pvep, ptep, pve_str, dereference(kern.GetValueFromAddress(ptep, 'pt_entry_t *')))
@lldb_command('pv_walk')
def PVWalk(cmd_args=None):
""" Show mappings for <physical_address> tracked in the PV list.
Syntax: (lldb) pv_walk <physical_address>
"""
if cmd_args == None or len(cmd_args) < 1:
raise ArgumentError("Too few arguments to pv_walk.")
if not kern.arch.startswith('arm'):
raise NotImplementedError("pv_walk does not support {0}".format(kern.arch))
PVWalkARM(kern.GetValueFromAddress(cmd_args[0], 'unsigned long'))
def ShowPTEARM(pte):
""" Display vital information about an ARM page table entry
pte: kernel virtual address of the PTE. Should be L3 PTE. May also work with L2 TTEs for certain devices.
"""
page_size = kern.globals.arm_hardware_page_size
pn = (pte - unsigned(kern.globals.gVirtBase) + unsigned(kern.globals.gPhysBase) - unsigned(kern.globals.vm_first_phys)) / page_size
pvh = kern.globals.pv_head_table[pn]
pvh_type = pvh & 0x3
if pvh_type != 0x3 and pvh_type != 0x0:
raise ValueError("PV head {:#x} does not correspond to a page-table descriptor".format(pvh))
ptd = kern.GetValueFromAddress(pvh & ~0x3, 'pt_desc_t *')
print "descriptor: {:#x}".format(ptd)
print "pmap: {:#x}".format(ptd.pmap)
pt_index = (pte % kern.globals.page_size) / page_size
pte_pgoff = pte % page_size
if kern.arch.startswith('arm64'):
pte_pgoff = pte_pgoff / 8
nttes = page_size / 8
else:
pte_pgoff = pte_pgoff / 4
nttes = page_size / 4
if ptd.pt_cnt[pt_index].refcnt == 0x4000:
level = 2
granule = nttes * page_size
else:
level = 3
granule = page_size
print "maps VA: {:#x}".format(long(unsigned(ptd.pt_map[pt_index].va)) + (pte_pgoff * granule))
pteval = long(unsigned(dereference(kern.GetValueFromAddress(unsigned(pte), 'pt_entry_t *'))))
print "value: {:#x}".format(pteval)
if kern.arch.startswith('arm64'):
print "level: {:d}".format(level)
PmapDecodeTTEARM64(pteval, level)
elif kern.arch == 'arm':
PmapDecodeTTEARM(pteval, 2, vSCRIPT)
@lldb_command('showpte')
def ShowPTE(cmd_args=None):
""" Display vital information about the page table entry at VA <pte>
Syntax: (lldb) showpte <pte_va>
"""
if cmd_args == None or len(cmd_args) < 1:
raise ArgumentError("Too few arguments to showpte.")
if not kern.arch.startswith('arm'):
raise NotImplementedError("showpte does not support {0}".format(kern.arch))
ShowPTEARM(kern.GetValueFromAddress(cmd_args[0], 'unsigned long'))
def FindMappingAtLevelARM(pmap, tt, nttes, level, action):
""" Perform the specified action for all valid mappings in an ARM translation table
pmap: owner of the translation table
tt: translation table or page table
nttes: number of entries in tt
level: translation table level, 1 or 2
action: callback for each valid TTE
"""
for i in range(nttes):
try:
tte = tt[i]
if level == 1:
if tte & 0x3 == 0x1:
type = 'table'
granule = 1024
paddr = tte & 0xFFFFFC00
elif tte & 0x3 == 0x2:
type = 'block'
if (tte & 0x40000) == 0x40000:
granule = 1 << 24
paddr = tte & 0xFF000000
else:
granule = 1 << 20
paddr = tte & 0xFFF00000
else:
continue
elif (tte & 0x3) == 0x1:
type = 'entry'
granule = 1 << 16
paddr = tte & 0xFFFF0000
elif (tte & 0x3) != 0:
type = 'entry'
granule = 1 << 12
paddr = tte & 0xFFFFF000
else:
continue
action(pmap, level, type, addressof(tt[i]), paddr, granule)
if level == 1 and (tte & 0x3) == 0x1:
tt_next = kern.GetValueFromAddress(kern.PhysToKernelVirt(paddr), 'tt_entry_t *')
FindMappingAtLevelARM(pmap, tt_next, granule / 4, level + 1, action)
except Exception as exc:
print "Unable to access tte {:#x}".format(unsigned(addressof(tt[i])))
def FindMappingAtLevelARM64(pmap, tt, nttes, level, action):
""" Perform the specified action for all valid mappings in an ARM64 translation table
pmap: owner of the translation table
tt: translation table or page table
nttes: number of entries in tt
level: translation table level, 1 2 or 3
action: callback for each valid TTE
"""
page_size = kern.globals.arm_hardware_page_size
page_offset_mask = (page_size - 1)
page_base_mask = ((1 << ARM64_VMADDR_BITS) - 1) & (~page_offset_mask)
for i in range(nttes):
try:
tte = tt[i]
if tte & 0x1 == 0x1:
if tte & 0x2 == 0x2:
if level < 3:
type = 'table'
else:
type = 'entry'
granule = page_size
paddr = tte & page_base_mask
elif level < 3:
type = 'block'
granule = PmapBlockOffsetMaskARM64(level) + 1
paddr = tte & PmapBlockBaseMaskARM64(level)
else:
continue
action(pmap, level, type, addressof(tt[i]), paddr, granule)
if level < 3 and (tte & 0x2 == 0x2):
tt_next = kern.GetValueFromAddress(kern.PhysToKernelVirt(paddr), 'tt_entry_t *')
FindMappingAtLevelARM64(pmap, tt_next, granule / ARM64_TTE_SIZE, level + 1, action)
except Exception as exc:
print "Unable to access tte {:#x}".format(unsigned(addressof(tt[i])))
def ScanPageTables(action, targetPmap=None):
""" Perform the specified action for all valid mappings in all page tables,
optionally restricted to a single pmap.
pmap: pmap whose page table should be scanned. If None, all pmaps on system will be scanned.
"""
print "Scanning all available translation tables. This may take a long time..."
def ScanPmap(pmap, action):
if kern.arch.startswith('arm64'):
granule = kern.globals.arm64_root_pgtable_num_ttes * 8
elif kern.arch == 'arm':
granule = pmap.tte_index_max * 4
action(pmap, 1, 'root', pmap.tte, unsigned(pmap.ttep), granule)
if kern.arch.startswith('arm64'):
FindMappingAtLevelARM64(pmap, pmap.tte, kern.globals.arm64_root_pgtable_num_ttes, kern.globals.arm64_root_pgtable_level, action)
elif kern.arch == 'arm':
FindMappingAtLevelARM(pmap, pmap.tte, pmap.tte_index_max, 1, action)
if targetPmap is not None:
ScanPmap(kern.GetValueFromAddress(targetPmap, 'pmap_t'), action)
else:
for pmap in IterateQueue(kern.globals.map_pmap_list, 'pmap_t', 'pmaps'):
ScanPmap(pmap, action)
@lldb_command('showallmappings')
def ShowAllMappings(cmd_args=None):
""" Find and display all available mappings on the system for
<physical_address>. Optionally only searches the pmap
specified by [<pmap>]
Syntax: (lldb) showallmappings <physical_address> [<pmap>]
WARNING: this macro can take a long time (up to 30min.) to complete!
"""
if cmd_args == None or len(cmd_args) < 1:
raise ArgumentError("Too few arguments to showallmappings.")
if not kern.arch.startswith('arm'):
raise NotImplementedError("showallmappings does not support {0}".format(kern.arch))
pa = kern.GetValueFromAddress(cmd_args[0], 'unsigned long')
targetPmap = None
if len(cmd_args) > 1:
targetPmap = cmd_args[1]
def printMatchedMapping(pmap, level, type, tte, paddr, granule):
if paddr <= pa < (paddr + granule):
print "pmap: {:#x}: L{:d} {:s} at {:#x}: [{:#x}, {:#x})".format(pmap, level, type, unsigned(tte), paddr, paddr + granule)
ScanPageTables(printMatchedMapping, targetPmap)
def checkPVList(pmap, level, type, tte, paddr, granule):
""" Checks an ARM physical-to-virtual mapping list for consistency error.
pmap: owner of the translation table
level: translation table level. PV lists will only be checked for L2 (arm32) or L3 (arm64) tables.
type: unused
tte: KVA of PTE to check for presence in PV list. If None, presence check will be skipped.
paddr: physical address whose PV list should be checked. Need not be page-aligned.
granule: unused
"""
vm_first_phys = unsigned(kern.globals.vm_first_phys)
vm_last_phys = unsigned(kern.globals.vm_last_phys)
page_size = kern.globals.arm_hardware_page_size
if kern.arch.startswith('arm64'):
page_offset_mask = (page_size - 1)
page_base_mask = ((1 << ARM64_VMADDR_BITS) - 1) & (~page_offset_mask)
paddr = paddr & page_base_mask
max_level = 3
elif kern.arch == 'arm':
page_base_mask = 0xFFFFF000
paddr = paddr & page_base_mask
max_level = 2
if level < max_level or paddr < vm_first_phys or paddr >= vm_last_phys:
return
pn = (paddr - vm_first_phys) / page_size
pvh = unsigned(kern.globals.pv_head_table[pn])
pvh_type = pvh & 0x3
if pmap is not None:
pmap_str = "pmap: {:#x}: ".format(pmap)
else:
pmap_str = ''
if tte is not None:
tte_str = "pte {:#x} ({:#x}): ".format(unsigned(tte), paddr)
else:
tte_str = "paddr {:#x}: ".format(paddr)
if pvh_type == 0 or pvh_type == 3:
print "{:s}{:s}unexpected PVH type {:d}".format(pmap_str, tte_str, pvh_type)
elif pvh_type == 2:
ptep = pvh & ~0x3
if tte is not None and ptep != unsigned(tte):
print "{:s}{:s}PVH mismatch ({:#x})".format(pmap_str, tte_str, ptep)
try:
pte = long(unsigned(dereference(kern.GetValueFromAddress(ptep, 'pt_entry_t *')))) & page_base_mask
if (pte != paddr):
print "{:s}{:s}PVH {:#x} maps wrong page ({:#x}) ".format(pmap_str, tte_str, ptep, pte)
except Exception as exc:
print "{:s}{:s}Unable to read PVH {:#x}".format(pmap_str, tte_str, ptep)
elif pvh_type == 1:
pvep = pvh & ~0x3
tte_match = False
while pvep != 0:
pve = kern.GetValueFromAddress(pvep, "pv_entry_t *")
pvep = unsigned(pve.pve_next) & ~0x1
ptep = unsigned(pve.pve_ptep) & ~0x3
if tte is not None and ptep == unsigned(tte):
tte_match = True
try:
pte = long(unsigned(dereference(kern.GetValueFromAddress(ptep, 'pt_entry_t *')))) & page_base_mask
if (pte != paddr):
print "{:s}{:s}PVE {:#x} maps wrong page ({:#x}) ".format(pmap_str, tte_str, ptep, pte)
except Exception as exc:
print "{:s}{:s}Unable to read PVE {:#x}".format(pmap_str, tte_str, ptep)
if tte is not None and not tte_match:
print "{:s}{:s}not found in PV list".format(pmap_str, tte_str, paddr)
@lldb_command('pv_check', 'P')
def PVCheck(cmd_args=None, cmd_options={}):
""" Check the physical-to-virtual mapping for a given PTE or physical address
Syntax: (lldb) pv_check <addr> [-p]
-P : Interpret <addr> as a physical address rather than a PTE
"""
if cmd_args == None or len(cmd_args) < 1:
raise ArgumentError("Too few arguments to showallmappings.")
if kern.arch == 'arm':
level = 2
elif kern.arch.startswith('arm64'):
level = 3
else:
raise NotImplementedError("showallmappings does not support {0}".format(kern.arch))
if "-P" in cmd_options:
pte = None
pa = long(unsigned(kern.GetValueFromAddress(cmd_args[0], "unsigned long")))
else:
pte = kern.GetValueFromAddress(cmd_args[0], 'pt_entry_t *')
pa = long(unsigned(dereference(pte)))
checkPVList(None, level, None, pte, pa, None)
@lldb_command('check_pmaps')
def CheckPmapIntegrity(cmd_args=None):
""" Performs a system-wide integrity check of all PTEs and associated PV lists.
Optionally only checks the pmap specified by [<pmap>]
Syntax: (lldb) check_pmaps [<pmap>]
WARNING: this macro can take a HUGE amount of time (several hours) if you do not
specify [pmap] to limit it to a single pmap. It will also give false positives
for kernel_pmap, as we do not create PV entries for static kernel mappings on ARM.
Use of this macro without the [<pmap>] argument is heavily discouraged.
"""
if not kern.arch.startswith('arm'):
raise NotImplementedError("showallmappings does not support {0}".format(kern.arch))
targetPmap = None
if len(cmd_args) > 0:
targetPmap = cmd_args[0]
ScanPageTables(checkPVList, targetPmap)
@lldb_command('pmapsforledger')
def PmapsForLedger(cmd_args=None):
""" Find and display all pmaps currently using <ledger>.
Syntax: (lldb) pmapsforledger <ledger>
"""
if cmd_args == None or len(cmd_args) < 1:
raise ArgumentError("Too few arguments to pmapsforledger.")
if not kern.arch.startswith('arm'):
raise NotImplementedError("pmapsforledger does not support {0}".format(kern.arch))
ledger = kern.GetValueFromAddress(cmd_args[0], 'ledger_t')
for pmap in IterateQueue(kern.globals.map_pmap_list, 'pmap_t', 'pmaps'):
if pmap.ledger == ledger:
print "pmap: {:#x}".format(pmap)