Lots of changes for automatically showing various function arguments etc.

This commit is contained in:
Zach Riggle 2015-05-11 15:30:52 -04:00
parent 8f36054413
commit 68529bcb5c
31 changed files with 1283 additions and 831 deletions

View File

@ -16,13 +16,19 @@ Best supported on Ubuntu 14.04 with default `gdb` or `gdb-multiarch` (e.g. with
### Prerequisites
As of recent versions, you need Capstone 4.0.
#### Capstone 4.0
Currently this is only available via a source build.
1. Clone the repo: `git clone https://github.com/aquynh/capstone`
2. Select the `next` branch: `git checkout -t origin/next`
3. Build and install libcapstone: `sudo make.sh install`
4. Build and install Python bindings: `cd bindings/python && python setup.py install`
#### pycparser
`pip install pycparser`
## Features
Does most things that PEDA does. Doesn't do things that PEDA does that [pwntools](https://github.com/Gallopsled/pwntools) or [binjitsu](https://binjit.su) (my fork of pwntools) do better.

View File

@ -35,7 +35,12 @@ def wrap(f):
try:
rv = []
def work(): rv.append(f(*a,**kw))
with mutex: idaapi.execute_sync(work, idaapi.MFF_WRITE)
with mutex:
flags = idaapi.MFF_WRITE
if f == idc.SetColor:
flags |= idaapi.MFF_NOWAIT
rv.append(None)
idaapi.execute_sync(work, flags)
return rv[0]
except:
import traceback

View File

@ -102,6 +102,7 @@ print(pwndbg.color.red(msg))
@pwndbg.memoize.reset_on_stop
def prompt_hook(*a):
pwndbg.commands.context.context()
with pwndbg.stdio.stdio:
pwndbg.commands.context.context()
gdb.prompt_hook = prompt_hook

View File

@ -7,36 +7,124 @@ may be passed in a combination of registers and stack values.
import gdb
import pwndbg.arch
import pwndbg.disasm
import pwndbg.functions
import pwndbg.funcparser
import pwndbg.ida
import pwndbg.memory
import pwndbg.regs
import pwndbg.typeinfo
import pwndbg.functions
import pwndbg.symbol
import pwndbg.typeinfo
def arguments():
from capstone import CS_GRP_CALL
ida_replacements = {
'__int64': 'signed long long int',
'__int32': 'signed int',
'__int16': 'signed short',
'__int8': 'signed char',
'__uint64': 'unsigned long long int',
'__uint32': 'unsigned int',
'__uint16': 'unsigned short',
'__uint8': 'unsigned char',
'_BOOL_1': 'unsigned char',
'_BOOL_2': 'unsigned short',
'_BOOL_4': 'unsigned int',
'_BYTE': 'unsigned char',
'_WORD': 'unsigned short',
'_DWORD': 'unsigned int',
'_QWORD': 'unsigned long long',
'__pure': '',
'__hidden': '',
'__return_ptr': '',
'__struct_ptr': '',
'__array_ptr': '',
'__fastcall': '',
'__cdecl': '',
'__thiscall': '',
'__userpurge': '',
}
def arguments(instruction):
"""
Returns an array containing the arguments to the current function,
if $pc is a 'call' or 'bl' type instruction.
Otherwise, returns None.
"""
pwndbg.disasm.calls
if instruction.address != pwndbg.regs.pc:
return []
if not instruction.target:
return []
if CS_GRP_CALL not in instruction.groups:
return []
sym = pwndbg.symbol.get(instruction.target)
if not sym:
return []
sym = sym.strip().lstrip('_') # _malloc
sym = sym.replace('isoc99_', '') # __isoc99_sscanf
sym = sym.replace('@plt', '') # getpwiod@plt
sym = sym.replace('_chk', '') # __printf_chk
func = pwndbg.functions.functions.get(sym, None)
result = []
args = []
# Try to grab the data out of IDA
if not func and instruction.target:
typename = pwndbg.ida.GetType(instruction.target)
if typename:
typename += ';'
# GetType() does not include the name.
typename = typename.replace('(', ' function_name(', 1)
for k,v in ida_replacements.items():
typename = typename.replace(k,v)
func = pwndbg.funcparser.ExtractFuncDeclFromSource(typename + ';')
if func:
args = func.args
else:
args = [pwndbg.functions.Argument('int',0,argname(i)) for i in range(4)]
print(repr(args))
for i,arg in enumerate(args):
result.append((arg, argument(i)))
return result
REGS = {
'x86-64': ['rdi','rsi','rdx','rcx','r8','r9'],
'arm': ['r%i' % i for i in range(0, 4)],
'aarch64': ['x%i' % i for i in range(0, 4)],
'powerpc': ['r%i' % i for i in range(3, 10+1)],
'mips': ['r%i' % i for i in range(4, 7+1)],
'sparc': ['i%i' % i for i in range(0,8)],
}
def argname(n):
regs = REGS[pwndbg.arch.current]
if n < len(regs):
return regs[n]
return 'arg[%i]' % n
def argument(n):
"""
Returns the nth argument, as if $pc were a 'call' or 'bl' type
instruction.
"""
arch = pwndbg.arch.current
regs = {
'x86-64': ['rdi','rsi','rdx','rcx','r8','r9'],
'arm': ['r%i' % i for i in range(0, 4)],
'aarch64': ['x%i' % i for i in range(0, 4)],
'powerpc': ['r%i' % i for i in range(3, 10+1)],
'mips': ['r%i' % i for i in range(4, 7+1)],
'sparc': ['i%i' % i for i in range(0,8)],
}[arch]
regs = REGS[pwndbg.arch.current]
if n < len(regs):
return getattr(pwndbg.regs, regs[n])

View File

@ -8,7 +8,7 @@ import pwndbg.vmmap
LIMIT = 5
def get(address, limit=5):
def get(address, limit=LIMIT):
"""
Recursively dereferences an address.
@ -30,32 +30,32 @@ def get(address, limit=5):
return result
def format(value):
chain = get(value, LIMIT)
def format(value, limit=LIMIT, code=True):
chain = get(value, limit)
# Enhance the last entry
# If there are no pointers (e.g. eax = 0x41414141), then enhance
# the only element there is.
if len(chain) == 1:
enhanced = pwndbg.enhance.enhance(chain[-1])
# Enhance the last entry
# If there are no pointers (e.g. eax = 0x41414141), then enhance
# the only element there is.
if len(chain) == 1:
enhanced = pwndbg.enhance.enhance(chain[-1], code=code)
# Otherwise, the last element in the chain is the non-pointer value.
# We want to enhance the last pointer value.
elif len(chain) < LIMIT:
enhanced = pwndbg.enhance.enhance(chain[-2])
# Otherwise, the last element in the chain is the non-pointer value.
# We want to enhance the last pointer value.
elif len(chain) < limit:
enhanced = pwndbg.enhance.enhance(chain[-2], code=code)
else:
enhanced = '...'
else:
enhanced = '...'
# Colorize the rest
rest = []
for link in chain[:-1]:
symbol = pwndbg.symbol.get(link) or None
if symbol:
symbol = '%#x (%s)' % (link, symbol)
rest.append(pwndbg.color.get(link, symbol))
# Colorize the rest
rest = []
for link in chain[:-1]:
symbol = pwndbg.symbol.get(link) or None
if symbol:
symbol = '%#x (%s)' % (link, symbol)
rest.append(pwndbg.color.get(link, symbol))
if len(chain) == 1:
return enhanced
if len(chain) == 1:
return enhanced
return ' --> '.join(rest) + ' <-- ' + enhanced
return ' --> '.join(rest) + ' <-- ' + enhanced

View File

@ -1,4 +1,5 @@
import gdb
import pwndbg.enhance
import pwndbg.vmmap
NORMAL = "\x1b[0m"
@ -65,3 +66,4 @@ def legend():
UNDERLINE + 'RWX' + NORMAL,
'RODATA'
))

View File

@ -1,13 +1,16 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import functools
import traceback
import gdb
import pwndbg.regs
import pwndbg.memory
import pwndbg.hexdump
import pwndbg.color
import pwndbg.chain
import pwndbg.color
import pwndbg.enhance
import pwndbg.hexdump
import pwndbg.memory
import pwndbg.regs
import pwndbg.stdio
import pwndbg.symbol
import pwndbg.ui
@ -41,7 +44,8 @@ class _Command(gdb.Command):
raise
def __call__(self, *args, **kwargs):
return self.function(*args, **kwargs)
with pwndbg.stdio.stdio:
return self.function(*args, **kwargs)
class _ParsedCommand(_Command):

View File

@ -1,4 +1,7 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import gdb
import pwndbg.arguments
import pwndbg.chain
import pwndbg.color
import pwndbg.commands
@ -12,15 +15,15 @@ import pwndbg.symbol
import pwndbg.ui
import pwndbg.vmmap
@pwndbg.events.stop
@pwndbg.commands.ParsedCommand
# @pwndbg.events.stop
@pwndbg.commands.Command
@pwndbg.commands.OnlyWhenRunning
def context(*args):
"""
Print out the current register, instruction, and stack context.
"""
if len(args) == 0:
args = ['reg','code','stack','backtrace']
args = ['reg','code','stack','backtrace','args']
args = [a[0] for a in args]
@ -29,11 +32,13 @@ def context(*args):
result.append(pwndbg.color.legend())
if 'r' in args: result.extend(context_regs())
if 'c' in args: result.extend(context_code())
if 'a' in args: result.extend(context_args())
if 's' in args: result.extend(context_stack())
if 'b' in args: result.extend(context_backtrace())
result.extend(context_signal())
print('\n'.join(map(str, result)))
for line in result:
print(line.encode('utf-8'))
def context_regs():
result = []
@ -140,6 +145,23 @@ def context_backtrace(frame_count=10, with_banner=True):
i += 1
return result
def context_args():
result = []
##################################################
# DISABLED FOR NOW, I LIKE INLINE DISPLAY BETTER
##################################################
# # For call instructions, attempt to resolve the target and
# # determine the number of arguments.
# for arg, value in pwndbg.arguments.arguments(pwndbg.disasm.one()):
# code = False if arg.type == 'char' else True
# pretty = pwndbg.chain.format(value, code=code)
# result.append('%-10s %s' % (arg.name+':', pretty))
# if not result:
# return []
# result.insert(0, pwndbg.color.blue(pwndbg.ui.banner("arguments")))
return result
last_signal = []
def save_signal(signal):

View File

@ -1,3 +1,5 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import pwndbg.commands
import pwndbg.hexdump
import pwndbg.memory
@ -26,7 +28,7 @@ def hexdump(address=None, count=64):
# if address is None:
# address =
data = pwndbg.memory.read(address, count)
data = pwndbg.memory.read(address, count, partial=True)
for line in pwndbg.hexdump.hexdump(data, address=address):
print(line)

View File

@ -1,7 +1,15 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from capstone import *
import pwndbg.arguments
import pwndbg.color
import pwndbg.disasm
import pwndbg.disasm.color
import pwndbg.functions
import pwndbg.ida
import pwndbg.regs
import pwndbg.strings
import pwndbg.symbol
import pwndbg.ui
import pwndbg.vmmap
@ -54,17 +62,38 @@ def nearpc(pc=None, lines=None, to_string=False):
symbols[i] = s.ljust(longest_sym)
# Print out each instruction
prev = None
for i,s in zip(instructions, symbols):
asm = pwndbg.disasm.color(i)
asm = pwndbg.disasm.color.instruction(i)
prefix = ' =>' if i.address == pc else ' '
pre = pwndbg.ida.Anterior(i.address)
if pre:
result.append(pwndbg.color.bold(pre))
line = ' '.join((prefix, s or hex(i.address), asm))
line = ' '.join((prefix, "%#x" % i.address, s or '', asm))
old, prev = prev, i
# Put an ellipsis between discontiguous code groups
if not old:
pass
elif old.address + old.size != i.address:
result.append('...')
# Put an empty line after fall-through basic blocks
elif any(g in old.groups for g in (CS_GRP_CALL, CS_GRP_JUMP, CS_GRP_RET)):
result.append('')
result.append(line)
# For call instructions, attempt to resolve the target and
# determine the number of arguments.
for arg, value in pwndbg.arguments.arguments(i):
code = False if arg.type == 'char' else True
pretty = pwndbg.chain.format(value, code=code)
result.append('%8s%-10s %s' % ('',arg.name+':', pretty))
if not to_string:
print('\n'.join(result))

View File

@ -82,6 +82,9 @@ def dX(size, address, count, to_string=False):
lines = []
for i, row in enumerate(rows):
if not row:
continue
line = [enhex(pwndbg.arch.ptrsize, address + i+16)]
for value in row:
line.append(enhex(size, value))

View File

@ -10,3 +10,6 @@ import sys
# Quickly determine which version is running
python2 = sys.version_info.major == 2
python3 = sys.version_info.major == 3
if python3:
globals()['basestring'] = str

View File

@ -1,172 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Functionality for disassmebling code at an address, or at an
address +/- a few instructions.
"""
import collections
import gdb
import pwndbg.arch
import pwndbg.color
import pwndbg.disasm_powerpc
import pwndbg.ida
import pwndbg.memory
import pwndbg.symbol
import pwndbg.memoize
import pwndbg.jump
from capstone import *
Instruction = collections.namedtuple('Instruction', ['address', 'length', 'asm', 'target'])
disassembler = None
last_arch = None
CapstoneArch = {
'arm': Cs(CS_ARCH_ARM, CS_MODE_ARM),
'aarch64': Cs(CS_ARCH_ARM64, CS_MODE_ARM),
'i386': Cs(CS_ARCH_X86, CS_MODE_32),
'x86-64': Cs(CS_ARCH_X86, CS_MODE_64),
'powerpc': Cs(CS_ARCH_PPC, CS_MODE_32),
'mips': Cs(CS_ARCH_MIPS, CS_MODE_32),
'sparc': Cs(CS_ARCH_SPARC, 0),
}
InstructionMaxSize = {
'arm': 4,
'aarch64': 4,
'i386': 16,
'x86-64': 16
}
def get_disassembler(pc):
arch = pwndbg.arch.current
d = CapstoneArch[arch]
if arch in ('i386', 'x86-64', 'powerpc', 'mips'):
d.mode = {4:CS_MODE_32, 8:CS_MODE_64}[pwndbg.arch.ptrsize]
if arch in ('arm', 'aarch64'):
d.mode = {0:CS_MODE_ARM,1:CS_MODE_THUMB}[pc & 1]
return d
def get_one_instruction(pc):
pass
def get(address, instructions=1):
address = int(address)
# Dont disassemble if there's no memory
if not pwndbg.memory.peek(address):
return []
raw = pwndbg.arch.disasm(address, address+0xffffffff, instructions)
retval = []
for insn in raw:
addr = int(insn['addr'])
length = insn['length']
asm = insn['asm']
target = 0
split = asm.split()
if len(split) == 2:
try:
target = split[1]
name = pwndbg.symbol.get(int(target, 0))
if name:
asm = asm + ' <%s>' % name
except ValueError:
pass
retval.append(Instruction(addr,length,asm,target))
return retval
def near(address, instructions=1):
# If we have IDA, we can just use it to find out where the various
# isntructions are.
if pwndbg.ida.available():
head = address
for i in range(instructions):
head = pwndbg.ida.PrevHead(head)
retval = []
for i in range(2*instructions + 1):
retval.append(get(head))
head = pwndbg.ida.NextHead(head)
# Find out how far back we can go without having a page fault
distance = instructions * 8
for start in range(address-distance, address):
if pwndbg.memory.peek(start):
break
# Disassemble more than we expect to need, move forward until we have
# enough instructions and we start on the correct spot
insns = []
while start < address:
insns = get(start, instructions)
if not insns:
return []
last = insns[-1]
if last.address + last.length == address:
break
start += 1
return insns[-instructions:] + get(address, instructions + 1)
calls = set([
'call', 'callq',
'bl','blx',
'jal'
])
returns = set([
'ret','retn','return',
'jr'
])
branches = calls | returns | set([
# Unconditional x86 branches
'call', 'callq',
'jmp',
# Conditional x86 branches
'ja', 'jna',
'jae', 'jnae',
'jb', 'jnb',
'jbe', 'jnbe',
'jc', 'jnc',
'je', 'jne',
'jg', 'jng',
'jge', 'jnge',
'jl', 'jnl',
'jle', 'jnle',
'jo', 'jno',
'jp', 'jnp',
'jpe', 'jpo',
'js', 'jns',
'jz', 'jnz',
# ARM branches
'b', 'bl', 'bx', 'blx', 'bxj', 'b.w',
'beq', 'beq.w', 'bne', 'bmi', 'bpl', 'blt',
'ble', 'bgt', 'bge', 'bxne',
# MIPS branches
'j', 'jal',
# SPARC
'ba', 'bne', 'be', 'bg', 'ble', 'bge', 'bl', 'bgu', 'bleu',
'jmpl'
])
branches = branches | pwndbg.disasm_powerpc.branches
def color(ins):
asm = ins.asm
mnem = asm.split()[0].strip().rstrip('+-')
if mnem in branches:
asm = pwndbg.color.bold(asm)
asm += '\n'
return asm

175
pwndbg/disasm/__init__.py Normal file
View File

@ -0,0 +1,175 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Functionality for disassmebling code at an address, or at an
address +/- a few instructions.
"""
import collections
import gdb
import pwndbg.arch
import pwndbg.disasm.mips
import pwndbg.disasm.arm
import pwndbg.disasm.ppc
import pwndbg.disasm.x86
import pwndbg.disasm.jump
import pwndbg.disasm.sparc
import pwndbg.ida
import pwndbg.memory
import pwndbg.symbol
import pwndbg.memoize
import pwndbg.jump
import capstone
from capstone import *
Instruction = collections.namedtuple('Instruction', ['address', 'length', 'asm', 'target'])
disassembler = None
last_arch = None
CapstoneArch = {
'arm': Cs(CS_ARCH_ARM, CS_MODE_ARM),
'aarch64': Cs(CS_ARCH_ARM64, CS_MODE_ARM),
'i386': Cs(CS_ARCH_X86, CS_MODE_32),
'x86-64': Cs(CS_ARCH_X86, CS_MODE_64),
'powerpc': Cs(CS_ARCH_PPC, CS_MODE_32),
'mips': Cs(CS_ARCH_MIPS, CS_MODE_32),
'sparc': Cs(CS_ARCH_SPARC, 0),
}
for cs in CapstoneArch.values():
cs.detail = True
# For variable-instruction-width architectures
# (x86 and amd64), we keep a cache of instruction
# sizes, and where the end of the instruction falls.
#
# This allows us to consistently disassemble backward.
VariableInstructionSizeMax = {
'i386': 16,
'x86-64': 16,
}
backward_cache = {}
def get_target(instruction):
"""
Make a best effort to determine what value or memory address
is important in a given instruction. For example:
- Any single-operand instruction ==> that value
- push rax ==> evaluate rax
- Jump or call ==> target address
- jmp rax ==> evaluate rax
- jmp 0xdeadbeef ==> deadbeef
- Memory load or store ==> target address
- mov [eax], ebx ==> evaluate eax
- Register move ==> source value
- mov eax, ebx ==> evaluate ebx
- Register manipulation ==> value after execution*
- lea eax, [ebx*4] ==> evaluate ebx*4
Register arguments are only evaluated for the next instruction.
Returns:
A tuple containing the resolved value (or None) and
a boolean indicating whether the value is a constant.
"""
return {
'i386': pwndbg.disasm.x86.resolve,
'x86-64': pwndbg.disasm.x86.resolve
}.get(pwndbg.arch.current, lambda *a: None)(instruction)
def get_disassembler(pc):
arch = pwndbg.arch.current
d = CapstoneArch[arch]
if arch in ('arm', 'aarch64'):
d.mode = {0:CS_MODE_ARM,1:CS_MODE_THUMB}[pc & 1]
else:
d.mode = {4:CS_MODE_32, 8:CS_MODE_64}[pwndbg.arch.ptrsize]
return d
def get_one_instruction(address):
md = get_disassembler(address)
size = VariableInstructionSizeMax.get(pwndbg.arch.current, 4)
data = pwndbg.memory.read(address, size, partial=True)
for ins in md.disasm(bytes(data), address, 1):
ins.target, ins.target_constant = get_target(ins)
return ins
def one(address=None):
if address is None:
address = pwndbg.regs.pc
for insn in get(address, 1):
return insn
def fix(i):
for op in i.operands:
if op.type == CS_OP_IMM and op.va:
i.op_str = i.op_str.replace()
return i
def get(address, instructions=1):
address = int(address)
# Dont disassemble if there's no memory
if not pwndbg.memory.peek(address):
return []
retval = []
for _ in range(instructions):
i = get_one_instruction(address)
if i is None:
break
backward_cache[address+i.size] = address
address += i.size
retval.append(i)
return retval
def near(address, instructions=1):
# # If we have IDA, we can just use it to find out where the various
# # isntructions are.
# if pwndbg.ida.available():
# head = address
# for i in range(instructions):
# head = pwndbg.ida.PrevHead(head)
# retval = []
# for i in range(2*instructions + 1):
# retval.append(get(head))
# head = pwndbg.ida.NextHead(head)
# See if we can satisfy the request based on the instruction
# length cache.
needle = address
insns = []
while len(insns) < instructions and needle in backward_cache:
needle = backward_cache[needle]
insn = one(needle)
if not insn:
return insns
insns.insert(0, insn)
current = one(address)
if not current:
return insns
target = current.target
if not pwndbg.disasm.jump.is_jump_taken(current):
target = current.address + current.size
backward_cache[target] = address
insns.append(current)
insns.extend(get(target, instructions))
return insns

49
pwndbg/disasm/color.py Normal file
View File

@ -0,0 +1,49 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import capstone
import pwndbg.chain
import pwndbg.color
import pwndbg.disasm.jump
capstone_branch_groups = [
capstone.arm.ARM_GRP_CALL,
capstone.arm.ARM_GRP_JUMP,
capstone.arm64.ARM64_GRP_JUMP,
capstone.mips.MIPS_GRP_JUMP,
capstone.ppc.PPC_GRP_JUMP,
capstone.sparc.SPARC_GRP_JUMP,
capstone.x86.X86_GRP_CALL,
capstone.x86.X86_GRP_JUMP,
]
def instruction(ins):
asm = u'%-06s %s' % (ins.mnemonic, ins.op_str)
branch = any(g in capstone_branch_groups for g in ins.groups)
taken = pwndbg.disasm.jump.is_jump_taken(ins)
if branch:
asm = pwndbg.color.bold(asm)
if ins.target is not None:
sym = pwndbg.symbol.get(ins.target)
target = pwndbg.color.get(ins.target)
const = ins.target_constant
# If it's a constant expression, color it directly in the asm.
if const:
asm = asm.replace(hex(ins.target), target)
if sym:
asm = '%-36s <%s>' % (asm, sym)
elif sym:
asm = '%-36s <%s; %s>' % (asm, target, sym)
else:
asm = '%-36s <%s>' % (asm, target)
if taken:
asm = pwndbg.color.green(u'') + asm
else:
asm = ' ' + asm
return asm

27
pwndbg/disasm/jump.py Normal file
View File

@ -0,0 +1,27 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import pwndbg.arch
import pwndbg.disasm.x86
from capstone import CS_GRP_JUMP
def is_jump_taken(instruction):
"""
Attempt to determine if a conditional instruction is executed.
Only valid for the current instruction.
Returns:
Returns True IFF the current instruction is a conditional
*or* jump instruction, and it is taken.
Returns False in all other cases.
"""
if CS_GRP_JUMP not in instruction.groups:
return False
if pwndbg.regs.pc != instruction.address:
return False
return {
'i386': pwndbg.disasm.x86.is_jump_taken,
'x86-64': pwndbg.disasm.x86.is_jump_taken,
}.get(pwndbg.arch.current, lambda *a: False)(instruction)

142
pwndbg/disasm/x86.py Normal file
View File

@ -0,0 +1,142 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import pwndbg.arch
import pwndbg.memory
import pwndbg.regs
from capstone import *
from capstone.x86 import *
groups = {v:k for k,v in globals().items() if k.startswith('X86_GRP_')}
ops = {v:k for k,v in globals().items() if k.startswith('X86_OP_')}
regs = {v:k for k,v in globals().items() if k.startswith('X86_REG_')}
access = {v:k for k,v in globals().items() if k.startswith('CS_AC_')}
def is_memory_op(op):
return op.type == X86_OP_MEM
def get_access(ac):
rv = []
for k,v in access.items():
if ac & k: rv.append(v)
return ' | '.join(rv)
def dump(instruction):
ins = instruction
rv = []
rv.append('%s %s' % (ins.mnemonic,ins.op_str))
for i, group in enumerate(ins.groups):
rv.append(' groups[%i] = %s' % (i, groups[group]))
for i, op in enumerate(ins.operands):
rv.append(' operands[%i] = %s' % (i, ops[op.type]))
rv.append(' access = %s' % (get_access(op.access)))
return '\n'.join(rv)
def resolve(instruction):
ops = list(instruction.operands)
if instruction.mnemonic == 'nop' or not ops:
return (None,None)
# 'ret', 'syscall'
if not ops:
return
# 'jmp rax', 'call 0xdeadbeef'
if len(ops) == 1:
return get_operand_target(instruction, ops[0])
# 'mov eax, ebx' ==> ebx
# 'mov [eax], ebx' ==> [eax]
# 'mov eax, 0xdeadbeef' ==> 0xdeadbeef
if len(ops) == 2:
# If there are any memory operands, prefer those
for op in filter(is_memory_op, ops):
return get_operand_target(instruction, op)
# Otherwise, prefer the 'source' operand
return get_operand_target(instruction, ops[1])
print("Weird number of operands!!!!!")
print(dump(instruction))
def get_operand_target(instruction, op):
current = (instruction.address == pwndbg.regs.pc)
# EB/E8/E9 or similar "call $+offset"
# Capstone handles the instruction + instruction size.
if op.type == X86_OP_IMM:
return (op.value.imm, True)
# jmp/call REG
if op.type == X86_OP_REG:
if not current:
return (None, False)
regname = instruction.reg_name(op.value.reg)
return (pwndbg.regs[regname], False)
# base + disp + scale * offset
assert op.type == X86_OP_MEM, "Invalid operand type %i (%s)" % (op.type, ops[op.type])
target = 0
# Don't resolve registers
constant = bool(op.mem.base == 0 and op.mem.index == 0)
if not current and not constant:
return (None, False)
if op.mem.segment != 0:
return (None, False)
if op.mem.base != 0:
regname = instruction.reg_name(op.mem.base)
target += pwndbg.regs[regname]
if op.mem.disp != 0:
target += op.value.mem.disp
if op.mem.index != 0:
scale = op.mem.scale
index = pwndbg.regs[instruction.reg_name(op.mem.index)]
target += (scale * index)
# for source operands, resolve
if op.access == CS_AC_READ:
try:
target = pwndbg.memory.u(target, op.size * 8)
except:
return (None, False)
return (target, constant)
def is_jump_taken(instruction):
efl = pwndbg.regs.eflags
cf = efl & (1<<0)
pf = efl & (1<<2)
af = efl & (1<<4)
zf = efl & (1<<6)
sf = efl & (1<<7)
of = efl & (1<<11)
return {
X86_INS_JO: of,
X86_INS_JNO: not of,
X86_INS_JS: sf,
X86_INS_JNS: not sf,
X86_INS_JE: zf,
X86_INS_JNE: not zf,
X86_INS_JB: cf,
X86_INS_JAE: not cf,
X86_INS_JBE: cf or zf,
X86_INS_JA: not (cf or zf),
X86_INS_JL: sf != of,
X86_INS_JGE: sf == of,
X86_INS_JLE: zf or (sf != of),
X86_INS_JP: pf,
X86_INS_JNP: not pf,
X86_INS_JMP: True,
}.get(instruction.id, None)

View File

@ -12,7 +12,6 @@ import string
import gdb
import pwndbg.arch
import pwndbg.color
import pwndbg.disasm
import pwndbg.memoize
import pwndbg.memory
@ -33,7 +32,7 @@ def good_instr(i):
return not any(bad in i for bad in bad_instrs)
# @pwndbg.memoize.reset_on_stop
def enhance(value):
def enhance(value, code = True):
"""
Given the last pointer in a chain, attempt to characterize
@ -43,6 +42,10 @@ def enhance(value):
'value'. For example, if it is set to RWX, we try to get information on whether
it resides on the stack, or in a RW section that *happens* to be RWX, to
determine which order to print the fields.
Arguments:
value(obj): Value to enhance
code(bool): Hint that indicates the value may be an instruction
"""
value = int(value)
@ -56,7 +59,7 @@ def enhance(value):
can_read = False
if not can_read:
retval = hex(int(value))
retval = '%#x' % int(value)
# Try to unpack the value as a string
packed = pwndbg.arch.pack(int(value))
@ -82,11 +85,9 @@ def enhance(value):
rwx = exe = False
if exe:
instr = pwndbg.disasm.get(value, 1)[0].asm
# However, if it contains bad instructions, bail
if not good_instr(instr):
instr = None
instr = pwndbg.disasm.one(value)
if instr:
instr = "%-6s %s" % (instr.mnemonic, instr.op_str)
szval = pwndbg.strings.get(value) or None
szval0 = szval
@ -98,11 +99,13 @@ def enhance(value):
if 0 <= intval < 10:
intval = str(intval)
else:
intval = hex(int(intval & pwndbg.arch.ptrmask))
intval = '%#x' % int(intval & pwndbg.arch.ptrmask)
retval = []
# print([instr,intval0,szval])
if not code:
instr = None
# If it's on the stack, don't display it as code in a chain.
if instr and 'stack' in page.objfile:

84
pwndbg/funcparser.py Normal file
View File

@ -0,0 +1,84 @@
import collections
from pycparser import c_ast, CParser
def extractTypeAndName(n, defaultName=None):
if isinstance(n, c_ast.EllipsisParam):
return ('int', 0, 'vararg')
t = n.type
d = 0
while isinstance(t, c_ast.PtrDecl) or isinstance(t, c_ast.ArrayDecl):
d += 1
children = dict(t.children())
t = children['type']
if isinstance(t, c_ast.FuncDecl):
return extractTypeAndName(t)
if isinstance(t.type, c_ast.Struct) \
or isinstance(t.type, c_ast.Union) \
or isinstance(t.type, c_ast.Enum):
typename = t.type.name
else:
typename = t.type.names[0]
if typename == 'void' and d == 0 and not t.declname:
return None
name = t.declname or defaultName or ''
return typename.lstrip('_'),d,name.lstrip('_')
Function = collections.namedtuple('Function', ('type', 'derefcnt', 'name', 'args'))
Argument = collections.namedtuple('Argument', ('type', 'derefcnt', 'name'))
def Stringify(X):
return '%s %s %s' % (X.type, X.derefcnt * '*', X.name)
def ExtractFuncDecl(node, verbose=False):
# The function name needs to be dereferenced.
ftype, fderef, fname = extractTypeAndName(node)
if not fname:
print "Skipping function without a name!"
print node.show()
return
fargs = []
for i, (argName, arg) in enumerate(node.args.children()):
defname = 'arg%i' % i
argdata = extractTypeAndName(arg, defname)
if argdata is not None:
a = Argument(*argdata)
fargs.append(a)
Func = Function(ftype, fderef, fname, fargs)
if verbose:
print Stringify(Func) + '(' + ','.join(Stringify(a) for a in Func.args) + ');'
return Func
def ExtractAllFuncDecls(ast, verbose=False):
Functions = {}
class FuncDefVisitor(c_ast.NodeVisitor):
def visit_FuncDecl(self, node, *a):
f = ExtractFuncDecl(node, verbose)
Functions[f.name] = f
FuncDefVisitor().visit(ast)
return Functions
def ExtractFuncDeclFromSource(source):
try:
p = CParser()
ast = p.parse(source + ';')
funcs = ExtractAllFuncDecls(ast)
for name, func in funcs.items():
return func
except Exception as e:
import traceback
traceback.print_exc()
# eat it

File diff suppressed because it is too large Load Diff

View File

@ -111,6 +111,13 @@ def GetFuncOffset(addr):
rv = _ida.GetFuncOffset(addr)
return rv
@withIDA
@takes_address
@pwndbg.memoize.reset_on_objfile
def GetType(addr):
rv = _ida.GetType(addr)
return rv
@withIDA
@returns_address
def here():
@ -186,20 +193,20 @@ def SetColor(pc, color):
colored_pc = None
# @pwndbg.events.stop
# @withIDA
# def Auto_Color_PC():
# global colored_pc
# colored_pc = pwndbg.regs.pc
# SetColor(colored_pc, 0x7f7fff)c
@pwndbg.events.stop
@withIDA
def Auto_Color_PC():
global colored_pc
colored_pc = pwndbg.regs.pc
SetColor(colored_pc, 0x7f7fff)
# @pwndbg.events.cont
# @withIDA
# def Auto_UnColor_PC():
# global colored_pc
# if colored_pc:
# SetColor(colored_pc, 0xffffff)
# colored_pc = None
@pwndbg.events.cont
@withIDA
def Auto_UnColor_PC():
global colored_pc
if colored_pc:
SetColor(colored_pc, 0xffffff)
colored_pc = None
@withIDA
@returns_address
@ -237,3 +244,9 @@ def GetFlags(addr):
@pwndbg.memoize.reset_on_objfile
def isASCII(flags):
return _ida.isASCII(flags)
@withIDA
@takes_address
@pwndbg.memoize.reset_on_objfile
def ArgCount(address):
pass

View File

@ -1,18 +1,18 @@
import pwndbg.arch
import pwndbg.jump.mips
import pwndbg.jump.arm
import pwndbg.jump.ppc
import pwndbg.jump.x86
import pwndbg.jump.sparc
# import pwndbg.arch
# import pwndbg.jump.mips
# import pwndbg.jump.arm
# import pwndbg.jump.ppc
# import pwndbg.jump.x86
# import pwndbg.jump.sparc
def get_target(pc):
return {
'i386': pwndbg.jump.x86.resolver,
'x86-64': pwndbg.jump.x86.resolver
}.get(pwndbg.arch.current, lambda *a: None)(pc)
# def get_target(pc):
# return {
# 'i386': pwndbg.jump.x86.resolver,
# 'x86-64': pwndbg.jump.x86.resolver
# }.get(pwndbg.arch.current, lambda *a: None)(pc)
class Foo(object):
@property
def foobar(self):
return self._foobar
# class Foo(object):
# @property
# def foobar(self):
# return self._foobar

View File

@ -1,82 +0,0 @@
import pwndbg.arch
import pwndbg.memory
import pwndbg.regs
from capstone import *
from capstone.x86 import *
md = Cs(CS_ARCH_X86, CS_MODE_32)
md.detail = True
class TargetResolver(object):
groups = {v:k for k,v in globals().items() if k.startswith('X86_GRP_')}
ops = {v:k for k,v in globals().items() if k.startswith('X86_OP_')}
regs = {v:k for k,v in globals().items() if k.startswith('X86_REG_')}
def __init__(self):
self.classes = {
X86_GRP_CALL: self.call_or_jump,
X86_GRP_JUMP: self.call_or_jump,
X86_GRP_RET: self.ret
}
def resolve(self, address):
code = bytes(pwndbg.memory.read(address, 16))
md.mode = CS_MODE_32 if pwndbg.arch.ptrsize == 4 else CS_MODE_64
instruction = next(md.disasm(code, address, 1))
for group in instruction.groups:
function = self.classes.get(group, None)
print(self.groups[group])
if function:
return function(instruction)
def get_operand_target(self, op):
# EB/E8/E9 or similar "call $+offset"
# Capstone handles the instruction + instruction size.
if op.type == X86_OP_IMM:
return op.value.imm
# jmp/call REG
if op.type == X86_OP_REG:
regname = instruction.reg_name(op.value.reg)
return pwndbg.regs[regname]
# base + disp + scale * offset
assert op.type == X86_OP_MEM, "Invalid operand type %i" % op.type
target = 0
if op.mem.base != 0:
regname = instruction.reg_name(op.value.reg)
target += pwndbg.regs[regname]
if op.mem.disp != 0:
target += op.value.mem.disp
if op.mem.index != 0:
scale = op.mem.scale
index = pwndbg.regs[instruction.reg_name(op.mem.index)]
target += (scale * index)
return target
def call_or_jump(self, instruction):
ops = instruction.operands
assert len(ops) == 1, "Too many operands (%i)" % len(ops)
return self.get_operand_target(ops[0])
def ret(self, instruction):
target = pwndbg.regs.sp
for op in instruction.operands:
assert op.type == X86_OP_IMM, "Unknown RET operand type"
target += op.value.imm
return pwndbg.memory.pvoid(target)
resolver = TargetResolver()

View File

@ -10,8 +10,15 @@ import pwndbg.typeinfo
PAGE_SIZE = 0x1000
MMAP_MIN_ADDR = 0x10000
def read(addr, count):
result = gdb.selected_inferior().read_memory(addr, count)
def read(addr, count, partial=False):
try:
result = gdb.selected_inferior().read_memory(addr, count)
except gdb.error as e:
if not partial:
raise
stop_addr = int(e.message.split()[-1], 0)
return read(addr, stop_addr-addr)
if pwndbg.compat.python3:
result = result.tobytes()
@ -42,15 +49,23 @@ def ushort(addr): return readtype(pwndbg.typeinfo.ushort, addr)
def uint(addr): return readtype(pwndbg.typeinfo.uint, addr)
def pvoid(addr): return readtype(pwndbg.typeinfo.pvoid, addr)
def u8(addr): return readtype(pwndbg.typeinfo.uint8_t, addr)
def u16(addr): return readtype(pwndbg.typeinfo.uint16_t, addr)
def u32(addr): return readtype(pwndbg.typeinfo.uint32_t, addr)
def u64(addr): return readtype(pwndbg.typeinfo.uint64_t, addr)
def u8(addr): return readtype(pwndbg.typeinfo.uint8, addr)
def u16(addr): return readtype(pwndbg.typeinfo.uint16, addr)
def u32(addr): return readtype(pwndbg.typeinfo.uint32, addr)
def u64(addr): return readtype(pwndbg.typeinfo.uint64, addr)
def s8(addr): return readtype(pwndbg.typeinfo.int8_t, addr)
def s16(addr): return readtype(pwndbg.typeinfo.int16_t, addr)
def s32(addr): return readtype(pwndbg.typeinfo.int32_t, addr)
def s64(addr): return readtype(pwndbg.typeinfo.int64_t, addr)
def u(addr, size):
return {
8: u8,
16: u16,
32: u32,
64: u64
}[size](addr)
def s8(addr): return readtype(pwndbg.typeinfo.int8, addr)
def s16(addr): return readtype(pwndbg.typeinfo.int16, addr)
def s32(addr): return readtype(pwndbg.typeinfo.int32, addr)
def s64(addr): return readtype(pwndbg.typeinfo.int64, addr)
def write(addr, data):
gdb.selected_inferior().write_memory(addr, data)

View File

@ -189,7 +189,13 @@ class module(ModuleType):
def __getattr__(self, attr):
try:
value = int(gdb.parse_and_eval('$' + attr.lstrip('$')))
value = gdb.parse_and_eval('$' + attr.lstrip('$'))
if 'eflags' not in attr:
value = value.cast(pwndbg.typeinfo.ptrdiff)
else:
# Seriously, gdb? Only accepts uint32.
value = value.cast(pwndbg.typeinfo.uint32)
value = int(value)
return value & pwndbg.arch.ptrmask
except gdb.error:
return None
@ -198,7 +204,12 @@ class module(ModuleType):
if isinstance(item, int):
return arch_to_regs[pwndbg.arch.current][item]
assert isinstance(item, str), "Unknown type %r" % item
if not isinstance(item, basestring):
print "Unknown register type: %r" % (item)
import pdb, traceback
traceback.print_stack()
pdb.set_trace()
return None
# e.g. if we're looking for register "$rax", turn it into "rax"
item = item.lstrip('$')

View File

@ -13,18 +13,28 @@ import gdb
import pwndbg.compat
def get(fd, mode):
file = io.open(fd, mode=mode, buffering=0, closefd=False)
return io.TextIOWrapper(file, write_through=True)
if pwndbg.compat.python3:
file = io.open(fd, mode=mode, buffering=0)
kw ={}
if pwndbg.compat.python3:
kw['write_through']=True
return io.TextIOWrapper(file, **kw)
else:
return open(fd, mode=mode)
class Stdio(object):
queue = []
def __enter__(self, *a, **kw):
self.queue.append((sys.stdin, sys.stdout, sys.stderr))
if pwndbg.compat.python3:
sys.stdin = get(0, 'rb')
sys.stdout = get(1, 'wb')
sys.stderr = get(2, 'wb')
if pwndbg.compat.python3 or True:
sys.stdin = get('/dev/stdin', 'rb')
sys.stdout = get('/dev/stdout', 'wb')
sys.stderr = get('/dev/stderr', 'wb')
def __exit__(self, *a, **kw):
sys.stdin, sys.stdout, sys.stderr = self.queue.pop()

2
requirements.txt Normal file
View File

@ -0,0 +1,2 @@
capstone>=4.0
pycparser