@ -1,6 +1,7 @@
from __future__ import annotations
import argparse
import signal
import string
from typing import Optional
@ -8,10 +9,82 @@ from pwnlib.util.cyclic import cyclic
from pwnlib . util . cyclic import cyclic_find
import pwndbg . aglib . arch
import pwndbg . aglib . memory
import pwndbg . aglib . proc
import pwndbg . commands
import pwndbg . lib . regs
from pwndbg . color import message
from pwndbg . commands import CommandCategory
class TimeoutException ( Exception ) :
""" Custom exception for signal-based timeouts. """
pass
def detect_register_patterns ( alphabet , length , timeout ) - > None :
if not pwndbg . aglib . proc . alive :
print ( message . error ( " Error: Process is not running. " ) )
return
ptr_size = pwndbg . aglib . arch . ptrsize
endian = pwndbg . aglib . arch . endian
found_patterns = [ ]
def alarm_handler ( signum , frame ) :
raise TimeoutException
original_handler = signal . signal ( signal . SIGALRM , alarm_handler )
current_arch_name = pwndbg . aglib . arch . name
register_set = pwndbg . lib . regs . reg_sets [ current_arch_name ]
all_register_names = register_set . all
for reg_name in all_register_names :
value = pwndbg . aglib . regs [ reg_name ]
if value is None :
continue
try :
signal . alarm ( timeout )
value_bytes = value . to_bytes ( ptr_size , endian )
offset = cyclic_find ( value_bytes , alphabet = alphabet , n = length )
if offset != - 1 :
found_patterns . append ( ( reg_name , value , offset ) )
except TimeoutException :
found_patterns . append ( ( reg_name , value , " SKIPPED (Timeout) " ) )
finally :
signal . alarm ( 0 )
if pwndbg . aglib . memory . is_readable_address ( value ) :
try :
signal . alarm ( timeout )
mem_value = pwndbg . aglib . memory . read ( value , length )
offset = cyclic_find ( mem_value , alphabet = alphabet , n = length )
if offset != - 1 :
found_patterns . append ( ( f " { reg_name } -> " , value , offset ) )
except TimeoutException :
found_patterns . append ( ( f " { reg_name } -> " , value , " <Timeout> " ) )
finally :
signal . alarm ( 0 )
# Restore the original signal handler
signal . signal ( signal . SIGALRM , original_handler )
if not found_patterns :
print ( message . notice ( " No cyclic patterns found. " ) )
return
max_reg_width = 2 + max ( max ( len ( reg ) for reg , _ , _ in found_patterns ) , 10 )
max_val_width = 2 + max ( len ( hex ( val ) ) for _ , val , _ in found_patterns )
print ( f " { ' Register ' : < { max_reg_width } } { ' Value ' : < { max_val_width } } { ' Offset ' } " )
print ( f " { ' ---------- ' : < { max_reg_width } } { ' ------------------ ' : < { max_val_width } } { ' ------ ' } " )
for reg , val , off in found_patterns :
print ( f " { reg : < { max_reg_width } } { val : <# { max_val_width } x } { off } " )
parser = argparse . ArgumentParser ( description = " Cyclic pattern creator/finder. " )
parser . add_argument (
@ -31,6 +104,14 @@ parser.add_argument(
help = " Size of the unique subsequences (defaults to the pointer size for the current arch) " ,
)
parser . add_argument (
" -t " ,
" --timeout " ,
metavar = " seconds " ,
type = int ,
default = 2 ,
help = " Timeout in seconds for --detect " ,
)
group = parser . add_mutually_exclusive_group ( required = False )
group . add_argument (
@ -44,6 +125,13 @@ group.add_argument(
help = " Do a lookup instead of printing the sequence (accepts constant values as well as expressions) " ,
)
group . add_argument (
" -d " ,
" --detect " ,
action = " store_true " ,
help = " Detect cyclic patterns in registers (Immediate values and memory pointed to by registers) " ,
)
group . add_argument (
" count " ,
type = int ,
@ -61,10 +149,16 @@ parser.add_argument(
@pwndbg.commands.Command ( parser , command_name = " cyclic " , category = CommandCategory . MISC )
def cyclic_cmd ( alphabet , length : Optional [ int ] , lookup , count = 100 , filename = " " ) - > None :
def cyclic_cmd (
alphabet , length : Optional [ int ] , lookup , detect , count = 100 , filename = " " , timeout = 2
) - > None :
if length is None :
length = pwndbg . aglib . arch . ptrsize
if detect :
detect_register_patterns ( alphabet , length , timeout )
return
if lookup :
lookup = pwndbg . commands . fix ( lookup , sloppy = True )