Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve pwnup template, gdbserver detection #1148

Merged
merged 8 commits into from
May 30, 2018
Merged

Conversation

ambiso
Copy link
Contributor

@ambiso ambiso commented May 10, 2018

I've modified the template that is used when executing pwn template. The changes should allow a smoother workflow with GDB.

Using the set sysroot command allows gdb to find the main on its own, if there are symbols.

The continue was removed as gdb will stop on entry by default, which is the next best thing without symbols.

log.info(io.recvline()) was added when running with gdb, as gdb will print a line that we want to discard:

Remote debugging from host 127.0.0.1

Please let me know what you think!

I also ran the docker tests, although I'm not sure if templates are covered by them.

$ make -C travis/docker ANDROID=no TARGET=docs/source/tubes/ssh.rst
[...]
docker run -e "ANDROID=no" -e "TARGET=docs/source/tubes/ssh.rst" --rm --init --privileged -it travis
 * Starting OpenBSD Secure Shell server sshd                                                       [ OK ] 
===========================================
  WARNING: Disabling all Android tests !!! 
===========================================
Running Sphinx v1.7.4
fatal: Not a git repository (or any of the parent directories): .git
fatal: Not a git repository (or any of the parent directories): .git
making output directory...
loading pickled environment... not yet created
loading intersphinx inventory from https://docs.python.org/2.7/objects.inv...
loading intersphinx inventory from https://paramiko-docs.readthedocs.org/en/2.1/objects.inv...
intersphinx inventory has moved: https://paramiko-docs.readthedocs.org/en/2.1/objects.inv -> https://paramiko-docs.readthedocs.io/en/2.1/objects.inv
building [mo]: targets for 0 po files that are specified
building [doctest]: 1 source files given on command line
updating environment: 70 added, 0 changed, 0 removed
reading sources... [100%] util/web                                                                        
/home/travis/pwntools/pwnlib/dynelf.py:docstring of pwnlib.dynelf.DynELF._resolve_symbol_sysv:11: WARNING: Definition list ends without a blank line; unexpected unindent.
/home/travis/pwntools/pwnlib/gdb.py:docstring of pwnlib.gdb.debug_assembly:8: WARNING: Inline strong start-string without end-string.
/home/travis/pwntools/pwnlib/gdb.py:docstring of pwnlib.gdb.debug_assembly:21: WARNING: Block quote ends without a blank line; unexpected unindent.
/home/travis/pwntools/pwnlib/gdb.py:docstring of pwnlib.gdb.debug_shellcode:4: WARNING: Inline strong start-string without end-string.
/home/travis/pwntools/pwnlib/gdb.py:docstring of pwnlib.gdb.debug_shellcode:17: WARNING: Block quote ends without a blank line; unexpected unindent.
/home/travis/pwntools/pwnlib/tubes/ssh.py:docstring of pwnlib.tubes.ssh.ssh._init_remote_platform_info:6: WARNING: Block quote ends without a blank line; unexpected unindent.
/home/travis/pwntools/pwnlib/util/packing.py:docstring of pwnlib.util.packing.flat:2: WARNING: Inline emphasis start-string without end-string.
looking for now-outdated files... none found
pickling environment... done
checking consistency... done
running tests...

Document: tubes/ssh
-------------------
1 items passed all tests:
 107 tests in default
107 tests in 1 items.
107 passed and 0 failed.
Test passed.

Doctest summary
===============
  107 tests
    0 failures in tests
    0 failures in setup code
    0 failures in cleanup code
build succeeded, 7 warnings.

Testing of doctests in the sources finished, look at the results in docs/build/doctest/output.txt.
Coverage.py warning: Module ~/.pwntools-cache/ was never imported. (module-not-imported)

@@ -161,6 +160,8 @@ continue

io = start()

if args.GDB:
log.info(io.recvline())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be added in pwnlib.gdb, not to the template, probably right after this:

pwntools/pwnlib/gdb.py

Lines 782 to 783 in 20cb049

if pid and context.native:
proc.wait_for_debugger(pid)

The issue is that doesn't actually happen all of the time -- only for certain (newer?) versions of gdbserver. We can have a .recvline(timeout=0.5) and then check to see if the line starts how we expect. If not, we'll need to .unrecv(...) the data to put it back.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll try adding it after that if statement.

Copy link
Contributor Author

@ambiso ambiso May 10, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see there's already been done some handling for this in gdb.debug:

pwntools/pwnlib/gdb.py

Lines 465 to 469 in 20cb049

# gdbserver outputs a message when a client connects
garbage = gdbserver.recvline(timeout=1)
if "Remote debugging from host" not in garbage:
gdbserver.unrecv(garbage)

Maybe gdb.debug should be used instead?

When running locally gdb.debug is used, however the fix does not trigger.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When running locally, gdb.debug spawns gdbserver and then invokes gdb.attach.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep.

I've adjusted the code in gdb.debug. It should handle gdb's message correctly now.

@@ -131,13 +131,12 @@ def start(argv=[], *a, **kw):
%endif
gdbscript = '''
%if ctx.binary:
set sysroot
Copy link
Member

@zachriggle zachriggle May 10, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC, set sysroot with no arguments is a no-op. Update: Nope, it's worse.

(gdb) show sysroot
The current system root is "target:".
(gdb) set sysroot
(gdb) show sysroot
The current system root is "".

set sysroot / (which I think you meant?) is incorrect for SSH-forwarded GDB sessions and Android devices.

set sysroot will also break for foreign-architecture binaries which are emulated under QEMU:

pwntools/pwnlib/gdb.py

Lines 424 to 430 in 20cb049

else:
qemu_port = random.randint(1024, 65535)
qemu_user = qemu.user_path()
sysroot = sysroot or qemu.ld_prefix(env)
if not qemu_user:
log.error("Cannot debug %s binaries without appropriate QEMU binaries" % context.arch)
args = [qemu_user, '-g', str(qemu_port)] + args

Finally, you don't want to set the sysroot in your GDB script. Pass the sysroot= argument into the gdb.debug() or gdb.attach() call, that's what it's there for. (Side bar, we don't actually invoke set sysroot for native-arch binaries, even if it's provided. This is a small bug.)

You might want to expose a --sysroot argument to pwn template to do this conditionally.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I don't use set sysroot I get the following error:

Temporary breakpoint 1 at 0x605: file x.c, line 2.    
Warning:                                              
Cannot insert breakpoint 1.                           
Cannot access memory at address 0x605                 
                                                      
/tmp/pwnsIcbJL.gdb:7: Error in sourced command file:  
Command aborted.                                      

The right side is with set sysroot, the left without, both include tbreak main and continue:
screenshot from 2018-05-10 19-25-24

Copy link
Member

@zachriggle zachriggle May 10, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you run your script with DEBUG to see the actual gdbscript being executed? (i.e. python foo.py LOCAL GDB DEBUG).

Can you also provide the first line of gdb --version and all of gdb --configuration?

EDIT: And gdbserver --version.

Thanks!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

➜  tmp gdb --version
GNU gdb (GDB) 8.1
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-pc-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word".
➜  tmp gdb --configuration
This GDB was configured as follows:
   configure --host=x86_64-pc-linux-gnu --target=x86_64-pc-linux-gnu
             --with-auto-load-dir=$debugdir:$datadir/auto-load
             --with-auto-load-safe-path=$debugdir:$datadir/auto-load
             --with-expat
             --with-gdb-datadir=/usr/share/gdb (relocatable)
             --with-jit-reader-dir=/usr/lib/gdb (relocatable)
             --without-libunwind-ia64
             --with-lzma
             --with-python=/usr (relocatable)
             --with-guile
             --with-separate-debug-dir=/usr/lib/debug (relocatable)
             --with-system-gdbinit=/etc/gdb/gdbinit
             --without-babeltrace

("Relocatable" means the directory can be moved with the GDB installation
tree, and GDB will still find it.)

➜  tmp gdbserver --version
GNU gdbserver (GDB) 8.1
Copyright (C) 2018 Free Software Foundation, Inc.
gdbserver is free software, covered by the GNU General Public License.
This gdbserver was configured as "x86_64-pc-linux-gnu"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's the output of ./exploit.py GDB LOCAL DEBUG

screenshot from 2018-05-10 20-59-40

break *0x{exe.symbols.main:x}
%else:
break *0x{exe.entry:x}
break main
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is probably a good plan since exe.symbols.main will be incorrect for PIE / ASLR-enabled binaries.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should also probably change this to tbreak, so that the breakpoint is removed after main is hit.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to tbreak.

%else:
break *0x{exe.entry:x}
break main
continue
Copy link
Member

@zachriggle zachriggle May 10, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We always want to continue, regardless of whether main is defined. For gdbserver, continue begins execution.

%if 'main' in ctx.binary.symbols:
break *0x{exe.symbols.main:x}
%else:
break *0x{exe.entry:x}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to remain. In the event symbols are unavailable, gdbserver will break at the entry point to the linker, not the entry point to the main binary.

In the event that the binary is PIE (and exe.entry is a small value ~close to zero), this has the same effect as setting a breakpoint on the first instruction because of a convenient bug in gdb: https://reverseengineering.stackexchange.com/q/8724

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting an invalid breakpoint in the gdbscript appears to be an error:

0x00007f9dc32aefb0 in _start () from target:/lib64/ld-linux-x86-64.so.2
Temporary breakpoint 1 at 0x520
Warning:
Cannot insert breakpoint 1.
Cannot access memory at address 0x520

/tmp/pwnrsyWx3.gdb:8: Error in sourced command file:
Command aborted.

I still end up in _start and am unable to continue the program from there:

gef➤  continue
Continuing.
Warning:
Cannot insert breakpoint 1.
Cannot access memory at address 0x520

Command aborted.

@ambiso
Copy link
Contributor Author

ambiso commented May 10, 2018

I thought I should mention it, @zachriggle, thanks a lot for your detailed feedback!

@ambiso
Copy link
Contributor Author

ambiso commented May 17, 2018

I've removed the set sysroot as we should resolve this issue separately.
@zachriggle Could you review the changes I've made to the gdb.debug function?

@zachriggle
Copy link
Member

zachriggle commented May 18, 2018 via email

@zachriggle
Copy link
Member

Cleaned up the code and made some fixes, I'll merge this once CI passes.

@zachriggle zachriggle changed the title Improve pwnup template Improve pwnup template, gdbserver detection May 30, 2018
@zachriggle zachriggle merged commit 338fbeb into Gallopsled:dev May 30, 2018
mephi42 added a commit to mephi42/pwntools that referenced this pull request Aug 4, 2024
On Ubuntu 22.04 gdb.debug() takes at least 2 seconds. This is because
it prints only "Remote debugging from host 127.0.0.1, port 33398", but
the code expects 2 lines.

It's very unclear what the second line is supposed to be; the only lead
is "* Handle extra newline printed by gdb" in commit 338fbeb ("Improve
pwnup template, gdbserver detection (Gallopsled#1148)"), but I could not trace it
back to the GDB source code, both historic and modern. Perhaps, there
is a non-upstreamed patch in some distro that introduces it.

It should still be safe to skip waiting for the second line if the
first one already starts with "Remote debugging ...", so do it.
mephi42 added a commit to mephi42/pwntools that referenced this pull request Aug 4, 2024
On Ubuntu 22.04 gdb.debug() takes at least 2 seconds. This is because
it prints only "Remote debugging from host 127.0.0.1, port 33398", but
the code expects 2 lines.

It's very unclear what the second line is supposed to be; the only lead
is "* Handle extra newline printed by gdb" in commit 338fbeb ("Improve
pwnup template, gdbserver detection (Gallopsled#1148)"), but I could not trace it
back to the GDB source code, both historic and modern. Perhaps, there
is a non-upstreamed patch in some distro that introduces it.

It should still be safe to skip waiting for the second line if the
first one already starts with "Remote debugging ...", so do it.
Arusekk pushed a commit that referenced this pull request Aug 5, 2024
On Ubuntu 22.04 gdb.debug() takes at least 2 seconds. This is because
it prints only "Remote debugging from host 127.0.0.1, port 33398", but
the code expects 2 lines.

It's very unclear what the second line is supposed to be; the only lead
is "* Handle extra newline printed by gdb" in commit 338fbeb ("Improve
pwnup template, gdbserver detection (#1148)"), but I could not trace it
back to the GDB source code, both historic and modern. Perhaps, there
is a non-upstreamed patch in some distro that introduces it.

It should still be safe to skip waiting for the second line if the
first one already starts with "Remote debugging ...", so do it.
k4lizen pushed a commit to k4lizen/pwntools that referenced this pull request Aug 6, 2024
On Ubuntu 22.04 gdb.debug() takes at least 2 seconds. This is because
it prints only "Remote debugging from host 127.0.0.1, port 33398", but
the code expects 2 lines.

It's very unclear what the second line is supposed to be; the only lead
is "* Handle extra newline printed by gdb" in commit 338fbeb ("Improve
pwnup template, gdbserver detection (Gallopsled#1148)"), but I could not trace it
back to the GDB source code, both historic and modern. Perhaps, there
is a non-upstreamed patch in some distro that introduces it.

It should still be safe to skip waiting for the second line if the
first one already starts with "Remote debugging ...", so do it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants