@ -1,6 +1,5 @@
"""
Helpers for finding address mappings which are used as
a stack .
Helpers for finding address mappings which are used as a stack .
Generally not needed , except under qemu - user and for when
binaries do things to remap the stack ( e . g . pwnies ' postit).
@ -16,28 +15,18 @@ import pwndbg.gdblib.events
import pwndbg . gdblib . memory
import pwndbg . lib . cache
# Dictionary of stack ranges.
# Key is the gdb thread ptid
# Value is a pwndbg.lib.memory.Page object
stacks : dict [ tuple [ int , int , int ] , pwndbg . lib . memory . Page ] = { }
# Whether the stack is protected by NX.
# This is updated automatically by is_executable.
nx = False
def find ( address : int ) :
"""
Returns a pwndbg . lib . memory . Page object which corresponds to the
currently - loaded stack .
Returns a pwndbg . lib . memory . Page object which corresponds to given address stack
or None if it does not exist
"""
if not stacks :
update ( )
for stack in stacks . values ( ) :
for stack in get ( ) . values ( ) :
if address in stack :
return stack
return None
def find_upper_stack_boundary ( stack_ptr : int , max_pages : int = 1024 ) - > int :
stack_ptr = pwndbg . lib . memory . page_align ( int ( stack_ptr ) )
@ -50,55 +39,19 @@ def find_upper_stack_boundary(stack_ptr: int, max_pages: int = 1024) -> int:
return pwndbg . gdblib . memory . find_upper_boundary ( stack_ptr , max_pages )
@pwndbg.gdblib.events.stop
@pwndbg.lib.cache.cache_until ( " stop " )
def update( ) - > None :
def get( ) - > dict [ int , pwndbg . lib . memory . Page ] :
"""
For each running thread , updates the known address range
for its stack .
For each running thread , return the known address range for its stack
Returns a dict which should never be modified ( since its cached )
"""
curr_thread = gdb . selected_thread ( )
try :
for thread in gdb . selected_inferior ( ) . threads ( ) :
thread . switch ( )
pwndbg . gdblib . regs . __getattr__ . cache . clear ( )
sp = pwndbg . gdblib . regs . sp
# Skip if sp is None or 0
# (it might be 0 if we debug a qemu kernel)
if not sp :
continue
sp_low = sp & ~ ( 0xFFF )
sp_low - = 0x1000
# If we don't already know about this thread, create
# a new Page mapping for it.
page = stacks . get ( thread . ptid , None )
if page is None :
start = sp_low
stop = find_upper_stack_boundary ( sp )
page = pwndbg . lib . memory . Page (
start , stop - start , 6 if not is_executable ( ) else 7 , 0 , " [stack] "
)
stacks [ thread . ptid ] = page
continue
elif page . objfile is None :
pid , tid , _ = thread . ptid
if pid == tid :
page . objfile = " [stack] "
else :
page . objfile = " [stack: %i ] " % tid
# If we *DO* already know about this thread, just
# update the lower boundary if it got any lower.
low = min ( page . vaddr , sp_low )
if low != page . vaddr :
page . memsz + = page . vaddr - low
page . vaddr = low
finally :
if curr_thread :
curr_thread . switch ( )
stacks = _fetch_via_vmmap ( )
# This is slow :(
if not stacks :
_fetch_via_exploration ( )
return stacks
@pwndbg.lib.cache.cache_until ( " stop " )
@ -109,22 +62,9 @@ def current():
return find ( pwndbg . gdblib . regs . sp )
@pwndbg.gdblib.events.exit
def clear ( ) - > None :
"""
Clears everything we know about any stack memory ranges .
Called when the target process exits .
"""
stacks . clear ( )
global nx
nx = False
@pwndbg.gdblib.events.stop
@pwndbg.lib.cache.cache_until ( " exit " )
def is_executable ( ) - > bool :
global nx
nx = False
PT_GNU_STACK = 0x6474E551
@ -135,3 +75,81 @@ def is_executable() -> bool:
nx = True
return not nx
def _fetch_via_vmmap ( ) - > dict [ int , pwndbg . lib . memory . Page ] :
stacks : dict [ int , pwndbg . lib . memory . Page ] = { }
pages = pwndbg . gdblib . vmmap . get ( )
curr_thread = gdb . selected_thread ( )
for thread in gdb . selected_inferior ( ) . threads ( ) :
thread . switch ( )
# Need to clear regs values cache after switching thread
# So we get proper value of the SP register
pwndbg . gdblib . regs . __getattr__ . cache . clear ( )
sp = pwndbg . gdblib . regs . sp
# Skip if sp is 0 (it might be 0 if we debug a qemu kernel)
if not sp :
continue
page = None
# Find the given SP in pages
for p in pages :
if sp in p :
page = p
break
if page :
stacks [ thread . num ] = page
continue
curr_thread . switch ( )
return stacks
def _fetch_via_exploration ( ) - > dict [ int , pwndbg . lib . memory . Page ] :
"""
TODO / FIXME : This exploration is not great since it now hits on each stop
( based on how this function is used ) . Ideally , explored stacks should be
cached globally and cleared only with new debugged target .
This way , we should also explore the stack only for a maximum of few pages
so that we won ' t take too much time finding its bounds. Then, on each stop
we can explore one more ( or a few more ) pages for the given current stack
we are currently on , ideally not taking the precious time of our users .
An alternative to this is dumping this functionality completely and this
will be decided hopefully after a next release .
"""
stacks : dict [ int , pwndbg . lib . memory . Page ] = { }
curr_thread = gdb . selected_thread ( )
for thread in gdb . selected_inferior ( ) . threads ( ) :
thread . switch ( )
pwndbg . gdblib . regs . __getattr__ . cache . clear ( )
sp = pwndbg . gdblib . regs . sp
# Skip if sp is None or 0
# (it might be 0 if we debug a qemu kernel)
if not sp :
continue
sp_low = sp & ~ ( 0xFFF )
sp_low - = 0x1000
start = sp_low
stop = find_upper_stack_boundary ( sp )
page = pwndbg . lib . memory . Page (
start , stop - start , 6 if not is_executable ( ) else 7 , 0 , f " [stack: { thread . num } ] "
)
stacks [ thread . num ] = page
continue
curr_thread . switch ( )
return stacks