@ -1,6 +1,7 @@
from __future__ import annotations
import math
from typing import Dict
from typing import List
from typing import Tuple
@ -9,8 +10,8 @@ import pwndbg.aglib.vmmap_custom
import pwndbg . color . message as M
import pwndbg . lib . cache
import pwndbg . lib . memory
from pwndbg . lib . regs import BitFlags
ENTRYMASK = ~ ( ( 1 << 12 ) - 1 ) & ( ( 1 << 51 ) - 1 )
# don't return None but rather an invalid value for address markers
# this way arithmetic ops do not panic if physmap is not found
INVALID_ADDR = 1 << 64
@ -46,6 +47,9 @@ class ArchPagingInfo:
vmemmap : int
kbase : int
addr_marker_sz : int
va_bits : int
pagetable_cache : Dict [ pwndbg . dbg_mod . Value , Dict [ int , int ] ] = { }
pagetableptr_cache : Dict [ int , pwndbg . dbg_mod . Value ] = { }
@property
@pwndbg.lib.cache.cache_until ( " start " )
@ -93,8 +97,86 @@ class ArchPagingInfo:
return None
def pagewalk (
self , target , entry
) - > Tuple [ Tuple [ str , . . . ] , List [ Tuple [ int | None , int | None ] ] ] :
raise NotImplementedError ( )
def pagewalk_helper (
self , target , entry , kernel_phys_base = 0
) - > List [ Tuple [ int | None , int | None ] ] :
base = self . physmap
if entry > base :
# user inputted a physmap address as pointer to pgd
entry - = base
level = self . paging_level
result : List [ Tuple [ int | None , int | None ] ] = [ ( None , None ) ] * ( level + 1 )
page_shift = self . page_shift
ENTRYMASK = ~ ( ( 1 << page_shift ) - 1 ) & ( ( 1 << self . va_bits ) - 1 )
for i in range ( level , 0 , - 1 ) :
vaddr = ( entry & ENTRYMASK ) + base - kernel_phys_base
if self . should_stop_pagewalk ( entry ) :
break
shift = ( i - 1 ) * ( page_shift - 3 ) + page_shift
offset = target & ( ( 1 << shift ) - 1 )
idx = ( target & ( 0x1FF << shift ) ) >> shift
entry = 0
try :
# with this optimization, roughly x2 as fast on average
# especially useful when parsing a large number of pages, e.g. set kernel-vmmap monitor
if vaddr not in self . pagetableptr_cache :
self . pagetableptr_cache [ vaddr ] = pwndbg . aglib . memory . get_typed_pointer (
" unsigned long " , vaddr
)
table = self . pagetableptr_cache [ vaddr ]
if table not in self . pagetable_cache :
self . pagetable_cache [ table ] = { }
table_cache = self . pagetable_cache [ table ]
if idx not in table_cache :
table_cache [ idx ] = int ( table [ idx ] )
entry = table_cache [ idx ]
# Prior to optimization:
# table = pwndbg.aglib.memory.get_typed_pointer("unsigned long", vaddr)
# entry = int(table[idx])
except Exception as e :
print ( M . warn ( f " Exception while page walking: { e } " ) )
entry = 0
if entry == 0 :
return result
result [ i ] = ( entry , vaddr )
result [ 0 ] = ( entry , ( entry & ENTRYMASK ) + base + offset - kernel_phys_base )
return result
def pageentry_flags ( self , level ) - > BitFlags :
raise NotImplementedError ( )
def should_stop_pagewalk ( self , is_last ) :
raise NotImplementedError ( )
class x86_64PagingInfo ( ArchPagingInfo ) :
def __init__ ( self ) :
self . va_bits = 48 if self . paging_level == 4 else 51
# https://blog.zolutal.io/understanding-paging/
self . pagetable_level_names = (
(
" Page " ,
" PT " ,
" PMD " ,
" PUD " ,
" PGD " ,
)
if self . paging_level == 4
else (
" Page " ,
" PT " ,
" PMD " ,
" P4D " ,
" PUD " ,
" PGD " ,
)
)
@property
@pwndbg.lib.cache.cache_until ( " stop " )
def physmap ( self ) :
@ -222,6 +304,19 @@ class x86_64PagingInfo(ArchPagingInfo):
if pwndbg . aglib . regs [ pwndbg . aglib . regs . stack ] in page :
page . objfile = " kernel [stack] "
def pagewalk (
self , target , entry
) - > Tuple [ Tuple [ str , . . . ] , List [ Tuple [ int | None , int | None ] ] ] :
if entry is None :
entry = pwndbg . aglib . regs [ " cr3 " ]
return self . pagetable_level_names , self . pagewalk_helper ( target , entry )
def pageentry_flags ( self , is_last ) - > BitFlags :
return BitFlags ( [ ( " NX " , 63 ) , ( " PS " , 7 ) , ( " A " , 5 ) , ( " U " , 2 ) , ( " W " , 1 ) , ( " P " , 0 ) ] )
def should_stop_pagewalk ( self , entry ) :
return entry & ( 1 << 7 ) > 0
class Aarch64PagingInfo ( ArchPagingInfo ) :
def __init__ ( self ) :
@ -236,6 +331,29 @@ class Aarch64PagingInfo(ArchPagingInfo):
self . vmalloc = module_start_wo_kaslr + 0x80000000
shift = self . page_shift - self . STRUCT_PAGE_SHIFT
self . VMEMMAP_SIZE = ( module_start_wo_kaslr - ( ( - 1 << self . va_bits ) + 2 * * 64 ) ) >> shift
# correct for linux
if self . paging_level == 4 :
self . pagetable_level_names = (
" Page " ,
" L3 " ,
" L2 " ,
" L1 " ,
" L0 " ,
)
elif self . paging_level == 3 :
self . pagetable_level_names = (
" Page " ,
" L3 " ,
" L2 " ,
" L1 " ,
)
elif self . paging_level == 2 :
self . pagetable_level_names = (
" Page " ,
" L3 " ,
" L2 " ,
)
@property
@pwndbg.lib.cache.cache_until ( " stop " )
@ -336,13 +454,33 @@ class Aarch64PagingInfo(ArchPagingInfo):
@property
@pwndbg.lib.cache.cache_until ( " stop " )
def page_shift ( self ) - > int :
# TODO: this might be arm version dependent
if self . tcr_el1 [ " TG1 " ] == 1 :
if self . tcr_el1 [ " TG1 " ] == 0b01 :
return 14
elif self . tcr_el1 [ " TG1 " ] == 0:
elif self . tcr_el1 [ " TG1 " ] == 0b1 0:
return 12
el se:
el if self. tcr_el1 [ " TG1 " ] == 0b11 :
return 16
raise NotImplementedError ( )
@property
@pwndbg.lib.cache.cache_until ( " stop " )
def page_shift_user ( self ) - > int :
if self . tcr_el1 [ " TG0 " ] == 0b00 :
return 12
elif self . tcr_el1 [ " TG0 " ] == 0b01 :
return 16
elif self . tcr_el1 [ " TG0 " ] == 0b10 :
return 14
raise NotImplementedError ( )
@property
@pwndbg.lib.cache.cache_until ( " stop " )
def paging_level ( self ) :
# https://www.kernel.org/doc/html/v5.3/arm64/memory.html
if self . page_shift == 16 :
return 2
# in some cases, not all addressing bits are used
return ( self . va_bits - self . page_shift + ( self . page_shift - 4 ) ) / / ( self . page_shift - 3 )
@pwndbg.lib.cache.cache_until ( " stop " )
def markers ( self ) - > Tuple [ Tuple [ str , int ] , . . . ] :
@ -413,35 +551,41 @@ class Aarch64PagingInfo(ArchPagingInfo):
if pwndbg . aglib . regs [ pwndbg . aglib . regs . stack ] in page :
page . objfile = " kernel [stack] "
@pwndbg.aglib.proc.OnlyWithArch ( [ " x86-64 " ] )
def pagewalk ( target , entry = None ) - > List [ Tuple [ int | None , int | None ] ] :
level = pwndbg . aglib . kernel . arch_paginginfo ( ) . paging_level
base = pwndbg . aglib . kernel . arch_paginginfo ( ) . physmap
if entry is None :
entry = pwndbg . aglib . regs [ " cr3 " ]
else :
entry = int ( pwndbg . dbg . selected_frame ( ) . evaluate_expression ( entry ) )
if entry > base :
# user inputted a physmap address as pointer to pgd
entry - = base
result : List [ Tuple [ int | None , int | None ] ] = [ ( None , None ) ] * ( level + 1 )
for i in range ( level , 0 , - 1 ) :
vaddr = ( entry & ENTRYMASK ) + base
if entry & ( 1 << 7 ) > 0 :
break
shift = ( i - 1 ) * 9 + 12
offset = target & ( ( 1 << shift ) - 1 )
idx = ( target & ( 0x1FF << shift ) ) >> shift
entry = 0
@property
@pwndbg.lib.cache.cache_until ( " start " )
def kernel_phys_start ( self ) :
found_system = False
try :
table = pwndbg . aglib . memory . get_typed_pointer ( " unsigned long " , vaddr )
entry = int ( table [ idx ] )
except Exception as e :
print ( M . warn ( f " Exception while page walking: { e } " ) )
entry = 0
if entry == 0 :
return result
result [ i ] = ( entry , vaddr )
result [ 0 ] = ( entry , ( entry & ENTRYMASK ) + base + offset )
return result
for line in pwndbg . dbg . selected_inferior ( ) . send_monitor ( " info mtree -f " ) . splitlines ( ) :
line = line . strip ( )
if " Root memory region: system " in line :
found_system = True
if found_system :
split = line . split ( " - " )
if " ram " in line and len ( split ) > 1 :
return int ( split [ 0 ] , 16 )
except Exception :
pass
return 0x40000000 # default
def pagewalk (
self , target , entry
) - > Tuple [ Tuple [ str , . . . ] , List [ Tuple [ int | None , int | None ] ] ] :
if entry is None :
if pwndbg . aglib . memory . is_kernel ( target ) :
entry = pwndbg . aglib . regs . TTBR1_EL1
else :
entry = pwndbg . aglib . regs . TTBR0_EL1
self . entry = entry
return self . pagetable_level_names , self . pagewalk_helper (
target , entry , self . kernel_phys_start
)
def pageentry_flags ( self , is_last ) - > BitFlags :
if is_last :
return BitFlags ( [ ( " UNX " , 54 ) , ( " PNX " , 53 ) , ( " AP " , ( 6 , 7 ) ) ] )
return BitFlags ( [ ( " UNX " , 60 ) , ( " PNX " , 59 ) , ( " AP " , ( 6 , 7 ) ) ] )
def should_stop_pagewalk ( self , entry ) :
# self.entry is set because the call chain
return ( ( ( entry & 1 ) == 0 ) or ( ( entry & 3 ) == 1 ) ) and entry != self . entry