Skip to content

Improve function call detection#3360

Draft
OBarronCS wants to merge 1 commit into
pwndbg:devfrom
OBarronCS:improved-function-call-detection
Draft

Improve function call detection#3360
OBarronCS wants to merge 1 commit into
pwndbg:devfrom
OBarronCS:improved-function-call-detection

Conversation

@OBarronCS

@OBarronCS OBarronCS commented Oct 18, 2025

Copy link
Copy Markdown
Member

This implements the idea suggested in #3214 to show function call arguments in more cases. If an unconditional branch goes to a symbol, we infer that it is a function call.

Currently, function call arguments are shown for instructions explicitly denoted as call instructions - x86 CALL, arm bl, etc. Function calls can be invoked other branch types, however, such as in tail recursion and in PLT stubs.

image

cc @k4lizen

@OBarronCS OBarronCS changed the title Add additional check for if branch target is a symbol to determine if… Improve function call detection Oct 18, 2025
@OBarronCS

OBarronCS commented Oct 18, 2025

Copy link
Copy Markdown
Member Author

On further thought, something more is needed to rule out false-positives. When writing assembly, it's common to create labels that you jmp to (our tests do this a lot).

jmp end
nop
nop

end:
nop
nop

The label end is the symbol of the branch target. We would need to rule this case out as well.

@k4lizen

k4lizen commented Oct 18, 2025

Copy link
Copy Markdown
Contributor

1

I'm playing around with this to see if I can spot the difference:

def fallback_check_for_function_call(instruction: PwndbgInstruction) -> bool:
    target = instruction.target
    tarsym = pwndbg.aglib.symbol.resolve_addr(target)
    if tarsym is not None:
        print("raw: ", tarsym)
        sym = pwndbg.aglib.symbol.lookup_symbol(tarsym, type=SymbolLookupType.FUNCTION)
        if sym is not None:
            print("human: ", sym.value_to_human_readable())
            if sym.type is not None:
                print("type: ", sym.type.name_to_human_readable)
                print("is func", sym.type.code == pwndbg.dbg_mod.TypeCode.FUNC)
                print("target?", sym.type.target())
                if sym.type.target() is not None:
                    print("target?", sym.type.target().name_to_human_readable)
                    print("tar is func?", sym.type.target().code == pwndbg.dbg_mod.TypeCode.FUNC)

main.c:

#include <fcntl.h>

int meow() {
  return 0;
}

int main() {
  meow();
  int (*fptr)() = &meow;
  fptr();
  open("./doesntexist", 0);
  open("./doesntexist", 0);
  return 0;
}

jmpend.asm:

section .text
global _start

_start:
    jmp some_label
    nop
    nop

some_label:
    nop
    nop

    mov     rax, 60
    xor     rdi, rdi
    syscall

2

The jmp some_label looks like this regardless of debug info:

raw:  some_label
human:  0x401004 <some_label>
type:  <text variable, no debug info> *
is func False
target? <pwndbg.dbg.gdb.GDBType object at 0x7fe6c3f74ff0>
target? <text variable, no debug info>
tar is func? True

You can detect both the resolver jump and the plt jump:
image
image
image
image
using the human readable type name thing. It works with debug info, without debug info, and also for stripped binary somehow (maybe it depends on debug symbols from libc/ld?).

3

Furthermore, you can detect the fptr(); as a function call if you have debug symbols, but not if you dont (using this information that i'm printing, i'm disregarding that we already properly detect that due to the call instruction).

4

The current PR also probably has some false negatives like operator +(unsigned long) in C++, but thats absolutely fine imo.

5

Tested it, it does depend on symbols from libc/ld. Which is nice for CTFs since people usually do, but doesn't help me with android debugging ;;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants