Compare commits

...
159 Commits
Author SHA1 Message Date
Mathias Svensson 55456814fc Release 3.5.0 2017-03-26 21:13:03 +02:00
Mathias Svensson 3b2be2b2a7 Merge branch 'stable' into beta 2017-03-02 11:47:55 +01:00
Zach Riggle 0a3ac521ad Work around appdirs error for Travis CI builds (#924)
Closes Gallopsled/pwntools#921
2017-03-01 17:58:22 -06:00
Zach Riggle 326dd83321 Handle errors during ssh.checksec() (#917)
* Handle errors during ssh.checksec()

Closes Gallopsled/pwntools#916

* Add additional error handling for ulimit checks
2017-02-21 14:57:58 -05:00
Mathias Svensson 510e247034 Merge branch 'stable' into beta 2017-02-17 14:48:14 +01:00
Mathias Svensson 4d529cd543 Release 3.4.1 2017-02-17 14:29:51 +01:00
Kristoffer Søholm e021f57806 Fixed bug in generatePadding (issue #891) 2017-02-17 14:15:31 +01:00
Zach Riggle b6cfec6284 Do not fail with gdb.debug() over ssh when given an absolute path (#894)
* Do not fail with gdb.debug() over ssh when given an absolute path

Fixes Gallopsled/pwntools#885

* Prevent failing on stable since this directory doesn't exist
2017-02-17 00:02:53 -05:00
Mathias Svensson 5877f8e97e Merge branch 'stable' into beta-staging 2017-02-13 01:34:31 +01:00
Mathias Svensson 4ebcbee34b Release 3.4.0 2017-02-13 01:31:16 +01:00
Mathias Svensson d25d62c126 Merge branch 'stable' into stable-staging 2017-02-13 01:31:00 +01:00
Zach Riggle 5392611ed5 Use updated testing scripts from 3.5.0 on 3.4.0 2017-02-13 01:17:57 +01:00
Zach Riggle 7c139c7179 Shellcode syscall templates (#871)
* Add templates from generator

* Add abi.stack to all ABIs

* Enable shellcraft.arch.os.foobar.template

* Add pwnlib.registers.current()

* Add syscalls symlinks

* Add syscall generator

* Enable shellcraft.foo() shortcut for shellcraft.arch.os.syscalls.foo()

* Make templates more verbose

* Remove old templates

* Add makefile

* Delete old templates, make shellcraft --list work

This also speeds up boot time by like 0.05 seconds

* Remove unnecessary execve wrappers

* Check for common arch as well

* Don't explode on string arguments that are register names

* Catch bad argument names

* Remove accidental import

* Add aarch64 ABIs since we enabled aarch64 tests

* Fix mmap vs mmap2 syscall

* Make shellcraft command-line work for syscalls

* Remove unnecessary wrapper templates

* Set all default values to zero

* Add example of automatic determination of types

* Add missing register architectures

* Add PowerPC ABIs

* More syscall examples

* Handle mmap vs mmap2

* Add missing import, use full path

* Just use the string name

* Fix rt_sigreturn on amd64

* __NR_ --> SYS_ because of old expectations

* rstrip to remove blankline

* Fix Thumb doctests which uses unpredictable jump labels

* Import additional syscalls by updating the function source.

Support variable-argument syscalls.

Support spilling to the stack.

* Simplified constants.eval logic means no extra parens

* Simplify vararg representation; fix oepn() argument name explicitly

* Fix syscall examples

* Fix reordering of arguments, since we have to support stack spill

* Fix argument name in doctest

* Make Codacy happy

* Restore connect template accidentally removed in 9fceb12b

* Scrape all architectures for syscall names

* Enable void* and void** to be pushed

* Make Codacy happy

* Add __doc__ to sdist

* Follow shellcraft template symlinks

* Detect syscall shellcodes in shellcraft based on path

(cherry picked from commit 94462845fa)
2017-02-12 18:37:20 -05:00
Zach Riggle da9ce55dfd Fix changelog formatting 2017-02-12 18:26:11 -05:00
Zach Riggle 9d394e0df1 Add missing info to 3.4.0 changelog 2017-02-12 18:26:02 -05:00
Zach Riggle f962d2bd21 Add missing info to 3.4.0 changelog 2017-02-12 18:24:50 -05:00
Zach Riggle 667d290d9b Fix regression from 189b1fb3c 2017-02-11 15:16:14 -05:00
Zach Riggle 766c4d8a7f sh_string should return '' for empty strings (#887) 2017-02-11 15:02:46 -05:00
Zach Riggle 189b1fb3c2 Add missing registers to AArch64 sigreturn frame, and update test 2017-02-10 22:54:24 -05:00
Zach Riggle 6c1b882b40 Upgrade warnings to errors in demo 2017-02-10 22:38:04 -05:00
Zach Riggle 0c76348072 Fix AArch64 SROP register name 2017-02-10 22:37:23 -05:00
Zach Riggle 2f7d78bc64 Add sigreturn/corefile examples and sanity tests 2017-02-10 22:34:27 -05:00
Zach Riggle 646723fa8e Remove unneded arg from aarch64 sigreturn 2017-02-10 22:19:11 -05:00
Zach Riggle e77915fba6 Handle PC/SP being invalid in Corefiles 2017-02-10 22:16:13 -05:00
Zach Riggle a02e8da1ea Add AArch64 support to corefiles 2017-02-10 22:16:13 -05:00
Zach Riggle 6979cb0d2b Search for QEMU coredumps first 2017-02-10 21:57:18 -05:00
Zach Riggle bc8942006b Convert alignment error to a warning; add AArch64 2017-02-10 21:54:26 -05:00
Zach Riggle 07c7b51f25 Add ip and fp aliases to user_regs_struct on arm 2017-02-10 21:51:53 -05:00
Zach Riggle ee655cac60 Add Android and AArch64 support to SigreturnFrame 2017-02-10 21:19:48 -05:00
Zach Riggle b668815323 Fix elf.functions not containing back-references 2017-02-10 21:07:56 -05:00
Zach Riggle a26a1a1f35 Permit setting SROP arguments directly 2017-02-10 21:03:26 -05:00
Zach Riggle ab42519e26 Enable disassembling entire functions 2017-02-10 19:28:59 -05:00
Zach Riggle 59b44b4fe6 Fix typo from 0c8006ca 2017-02-10 19:28:26 -05:00
Zach Riggle f4f21f147b Squelch error messages when restarting the server 2017-02-10 12:54:01 -05:00
Zach Riggle c06822571f Do not overrite tube.flat with ADB protocol Client 2017-02-10 12:40:22 -05:00
Zach Riggle 383a4f4596 adb.push() should return the target path 2017-02-09 15:25:13 -05:00
Zach Riggle 25d63da30e Do not print connection messages when using AdbClient 2017-02-09 15:20:01 -05:00
Zach Riggle dd2ad30a51 Catch exceptions when SSHing into machines with unsupported architectures (#884)
Fixes fefd48f34
2017-02-05 17:00:09 -05:00
Zach Riggle d160f9b7c9 Make corefiles great again (#839)
Adds a large amount of functionality around Corefiles, to support smarter exploitation

Adds a ".corefile" property to both local and remote (ssh) processes.  See the
documentation for the corefile module for extensive examples and tests.

Additionally, context.delete_corefiles and context.rename_corefiles have been added,
to control the behavior of the ".corefile" property.
2017-02-05 10:49:50 -05:00
Zach Riggle 0bdbc2b8c3 Make pwnlib.rop consistent with context.bytes (#882)
Fixes Gallopsled/pwntools#879
2017-02-05 09:36:45 -05:00
Zach Riggle d0c4d8488e Disable exit-on-error at the end of bash scripts (#881) 2017-02-05 07:43:38 -05:00
Zach Riggle f3968afbbb Add aarch64 documentation, which enables doctests (#875)
* Add aarch64 documentation, which enables doctests

* Fix failing doctest

* Attempt to figure out whats going on with Travis

* Use the Xenial version of qemu-static since the Zesty version is broken for AArch64
2017-02-02 00:32:25 -05:00
Zach Riggle 2d04acb867 Enhance adb.which to support all=True 2017-01-31 15:05:26 -05:00
Zach Riggle 62842e20f0 Make command-line tools work better 2017-01-29 18:50:46 -05:00
Zach Riggle c929217947 Restore dupsh from d21af22f82
Closes Gallopsled/pwntools#873
2017-01-27 08:17:16 -05:00
Zach Riggle ff08a859f8 Docker: Install unprivileged user pwntools, use correct tags 2017-01-26 16:03:26 -05:00
Zach Riggle ba24b440c5 Convert symlink to actual file, since Docker Hub doesnt like symlinks 2017-01-26 14:40:34 -05:00
Zach Riggle b31adf27d0 Update dockerfiles and add various distros / release versions 2017-01-26 14:04:37 -05:00
Zach Riggle 20d27c651a Silence commits in the build logs 2017-01-25 18:14:06 -05:00
Zach Riggle ba7911651e Install a recent version of GDB onto Travis (#872)
These files should be cached automagically.
2017-01-25 17:21:59 -05:00
Zach Riggle cded6cda47 Make upgrades / add fields to process() (#869)
- Show the PID when a process is created
- Make process.cwd into a property, which fetches the real CWD from procs
- Record the UID/GID and SUID/SGID of the executed file
- Add a few doctests
- Make _stop_noticed a timestamp
2017-01-24 19:30:26 -05:00
Zach Riggle 25941b52bb Multiple SSH enhancements (#868)
- Break out ssh_channel into subclass ssh_process
- Make ssh.process() directly instantiate ssh_process
- Add ssh_process().ELF
- Add ssh_process().corefile
- Record the UID and GID of the executed binary
- Add the created PID to the ssh_process message
2017-01-24 19:02:57 -05:00
Zach Riggle b79a750af6 Force-install libcapstone3 since it is causing errors 2017-01-24 18:02:43 -05:00
Zach Riggle eede2e2e1d Always upgrade Pip first 2017-01-24 17:43:19 -05:00
Zach Riggle 2a59f5cf4c Fix chunked reading of ELF files (#865)
* Fix chunked reading of ELF files

This introduces a new dependency on 'intervaltree', which is used
to automatically select segments which are relevant for a given
virtual-address range.

This also introduces a new ELF.memory attribute, which is the tree
of memory ranges, which point at the segment objects that describe
the memory range.

These are recalculated when ELF.address is adjusted, and we have
tests for that.

This also optimizes some of the other routines to avoid the wrapped
file-seeking, and to just use the mmap.

* Rewrite install scripts to use more modern versions of binutils

* Load segments in file-order, chop any overlaps
2017-01-24 13:41:29 -05:00
Mathias Svensson a3e82e27bb Fix a few edge cases in weird ELF files (#867)
* Fix a few edge cases in weird ELF files

* Add a check for PT_LOAD
2017-01-24 19:14:29 +01:00
Zach Riggle 368a708663 Upgrade documentation requirements on Travis 2017-01-24 12:00:34 -05:00
Zach Riggle 4a7de306f0 Convert impossible doctest into a code-block 2017-01-23 15:47:43 -05:00
Zach Riggle 69bbd0839a Clean up pwnlib.asm documentation and doctests 2017-01-23 15:39:36 -05:00
Zach Riggle 475ffa632e Always run Android tests on staging branches and new tags (#866) 2017-01-23 14:43:32 -05:00
Zach Riggle 06db497901 Add a bit more documentation to context.rst 2017-01-23 12:16:30 -05:00
Zach Riggle 5106c4eb75 Fix test in dpkg_search_for_binutils 2017-01-23 12:11:02 -05:00
Zach Riggle 08b7b190a7 A few documentation touch-ups 2017-01-23 12:08:36 -05:00
Zach Riggle 8259d07207 Remove useless doctest lines 2017-01-23 12:01:27 -05:00
Zach Riggle ad2eb77cfa Explicitly run a few asm tests 2017-01-23 12:00:08 -05:00
Zach Riggle ec04c860c0 Remove unnecessary doctests:: blocks 2017-01-23 11:59:48 -05:00
Zach Riggle ffe4cf6807 Add doctest for print_binutils_instructions 2017-01-23 11:19:36 -05:00
Zach Riggle 673073f08d Add a test for pwnlib.asm.dpkg_search_for_binutils 2017-01-23 11:15:57 -05:00
Zach Riggle 0e45f23af8 Add pwnlib.protocols module (#860)
* Add pwnlib.protocols module

Migrate pwnlib.adb.protocol into that directory, and add compatibility shims

* Add documentation links, and run doctests

* Make doctests run

* pwnlib.protocols.adb.Client.transport should work with no device
2017-01-20 18:59:23 -05:00
Zach Riggle 31cd98affe Do not truncate the ELF file when saving to the same path 2017-01-20 15:27:46 -05:00
Zach Riggle 3e4a1bcc72 Fix errant version number printing 2017-01-20 15:22:59 -05:00
Zach Riggle 85c3074574 Allow calling ELF.save() with no arguments 2017-01-20 15:22:59 -05:00
Zach Riggle 178e64a4f7 Add symlink option for ssh.set_working_directory to simplify exploits (#862)
* Add symlink option for ssh.set_working_directory to simplify exploit code

* Add additional options and tests to symlink=xxx
2017-01-20 14:58:08 -05:00
Zach Riggle 179476604e Fix error message format string
Closes Gallopsled/pwntools#863
2017-01-20 14:38:37 -05:00
Zach Riggle fc6e00d9f0 Update changelog for 3.5.0 2017-01-19 15:18:17 -05:00
Zach Riggle 8b1f1dce84 Update changelog for 3.5.0 2017-01-19 15:11:44 -05:00
Zach Riggle 8345bc3824 Add parse_kconfig and kernel information to pwnlib.elf.ELF (#861) 2017-01-19 11:01:31 -05:00
Zach Riggle 300f8e0c1a Speed up processing of symbols slightly, by not checking for each entry 2017-01-17 16:38:01 -05:00
Zach Riggle 9ace73a977 Replace broken function-decorator context.quiet with working context.quietfunc
Fixes: ae6a0745f
2017-01-17 15:10:25 -05:00
Zach Riggle fb16e158ec Use tubes.buffer for net.wget, add more tests 2017-01-17 13:58:49 -05:00
Jakub Wilk e519874947 Fix typos (#859) 2017-01-16 11:42:38 -05:00
Zach Riggle 2e90d05156 Actually implement ssh.sftp setter, so it can be set to None 2017-01-14 15:05:15 -05:00
Zach Riggle b6e3d5d8c5 Fix register usage in execve shellcode (#857)
The previous code worked, but we emitted a register swap that hid the fact we were using the wrong registers

Fixes Gallopsled/pwntools#856
2017-01-13 14:00:28 -05:00
Zach Riggle 0726d8d339 Fix hexdump alignment for weird widths
Closes Gallopsled/pwntools#849
2017-01-12 17:57:54 -05:00
Mathias Svensson bd5a368754 Merge branch 'beta' into dev 2017-01-12 23:48:54 +01:00
Mathias Svensson 602efd2790 Release 3.4.0beta4 2017-01-12 23:42:25 +01:00
Zach Riggle 45d2c11535 Update changelog for 3.3.4 2017-01-12 17:30:30 -05:00
Zach Riggle 9cb438842c Use class- and init-level documentation for Sphinx autodocs 2017-01-12 16:09:23 -05:00
Zach Riggle 14f717b7b4 Change hexdump_iter to not rely on the length of its input file
We cannot determine the length of the file if passed e.g. a tube or a pipe or
or PTY or character device (e.g. stdin or /dev/null).
2017-01-12 15:01:42 -05:00
Zach Riggle f792407c19 Remove hanging comment 2017-01-11 19:22:48 -05:00
Zach Riggle eb0bb6ea8d Add doctest for hexdump_iter 2017-01-11 00:51:28 -05:00
Zach Riggle f46112e873 Fix hexdump_iter for actual file objects, like those used by phd 2017-01-11 00:45:39 -05:00
Zach Riggle 975844d83d Add SigreturnFrame.__flat__ 2017-01-10 21:29:36 -05:00
Zach Riggle 466c0e4179 Add elf.process, elf.fit, process.fit
Also fix the implementation of elf.flag
2017-01-10 21:27:50 -05:00
Zach Riggle 17e4f01976 Enhance output of amd64.mov shellcraft for very large values 2017-01-10 18:18:34 -05:00
Mathias Svensson fecd6f8222 Merge branch 'beta' into dev 2017-01-11 00:03:09 +01:00
Mathias Svensson 7ba3a1b32f Release 3.4.0beta3 2017-01-10 23:53:59 +01:00
Zach Riggle 8f012947e9 Add build-essential and pip upgrade to installation steps (#845)
Fixes Gallopsled/pwntools#844
2017-01-10 17:35:50 -05:00
Zach Riggle 36b05bac2b Add doctests for the hexdump utility (#842)
Related: Gallopsled/pwntools#837
Related: Gallopsled/pwntools#840
2017-01-10 17:21:54 -05:00
Mathias Svensson 65f18b6091 Merge branch 'beta' into dev 2017-01-10 18:43:46 +01:00
Mathias Svensson 48616c633d Release 3.4.0beta2 2017-01-10 18:30:16 +01:00
Mathias Svensson c928a0425e Merge branch 'beta' into dev 2017-01-10 17:00:15 +01:00
Mathias Svensson f5e16d3db6 Release 3.4.0beta1 2017-01-10 16:59:22 +01:00
Zach Riggle fefd48f341 Add ssh.checksec functionality (#835)
Looks like this:

[x] Connecting to example.pwnme on port 22
[+] Connecting to example.pwnme on port 22: Done
[*] [email protected]:
    Distro    Ubuntu 14.04
    OS:       linux
    Arch:     amd64
    Version:  3.11.0
    ASLR:     Enabled
    Note:     Susceptible to ASLR ulimit trick (CVE-2016-3672)

Fixes Gallopsled/pwntools#753
2017-01-09 13:55:38 -05:00
Zach Riggle 132f48e6ee Fix failing doctest. Not sure what changed, but it was clearly not guaranteed to be right, given global state. 2017-01-07 19:16:04 -05:00
Zach Riggle e205e17b6d Fix doctests in ELF module 2017-01-07 18:32:37 -05:00
Zach Riggle d0267f3ee7 packing.fit() should handle extremely large offsets as if they are cyclic patterns 2017-01-07 18:21:14 -05:00
Zach Riggle 0c8006ca4e Enable passing an elf.Function to flat() 2017-01-07 18:14:57 -05:00
Zach Riggle bd88c92991 Greatly enhance and update the pwnlib.elf module documentation. 2017-01-07 18:10:59 -05:00
Zach Riggle 12f1bb9126 Add signal names to negative exit codes for process.poll() 2017-01-07 16:37:57 -05:00
Zach Riggle 3fa2c659fd Merge branch 'beta' into dev 2017-01-05 12:41:28 -05:00
Zach Riggle a2c67e5075 Merge branch 'stable' into beta 2017-01-05 12:39:25 -05:00
Zach Riggle 179a0656f5 Add better example of NOPTRACE 2017-01-04 23:16:22 -05:00
Zach Riggle 58b2e36a1b Fix empty code-block 2017-01-04 23:13:50 -05:00
Zach Riggle 24ae51dae4 Greatly enhance documentation for the GDB module, and expose its members in toplevel 2017-01-04 23:06:34 -05:00
Zach Riggle 14e68ec226 Remove _raw variants from public documentation 2017-01-04 14:57:22 -05:00
Zach Riggle 485dcede2c Fix heading style for ADB docs 2017-01-04 14:45:54 -05:00
Zach Riggle 50acdbbe41 Add hyperlinks to tube class 2017-01-04 14:44:30 -05:00
Zach Riggle 4e0e9d2acb Add hyperlinks to process class 2017-01-04 14:43:51 -05:00
Zach Riggle bc7c8cd423 Add missing :const: to docstrings 2017-01-04 14:37:43 -05:00
Zach Riggle a8446bef47 Fix sh_string RST formatting 2017-01-04 14:36:21 -05:00
Zach Riggle afdcdec577 Fix sh_string docs 2017-01-04 14:23:06 -05:00
Zach Riggle c13ff2cb08 Clarify documentation and remove leading process.XXX in docs 2017-01-04 14:18:02 -05:00
Zach Riggle a12d0b6a4c Move PTY, PIPE, STDOUT to toplevel 2017-01-04 14:15:38 -05:00
Zach Riggle 3dd6045488 Fix regression caused by 0cead77 2017-01-04 14:14:23 -05:00
Zach Riggle cca9782424 Add PR_SET_PTRACER to process and ssh.process (#828)
* Add PR_SET_PTRACER to initialization, and add doctests for process.leak/corefile

* Add PR_SET_PTRACER to SSH processes

* Do not warn about YAMA if we dont need to

* Fix doctest to use raw strings

* Fix typo in doctest

* Apparently GDB is not installed on Travis by default

* Add warning message to process("foo") vs process("./foo")

* Add gdb.corefile; make run_in_new_terminal return a PID

* Intentionally break doctest because I dont believe that it worked

* Fix codacy issues

* Fix gdb.attach return value

* Add missing import

* Fix typo, use waitpid

* Refactor some code into gdb.version / gdb.binary

* Permit GDB with multipart versions

* Fix repr() on Corefile

* Silence run-in-new-terminal message for gdb.corefile

* Fix version number

* Remove Yama warning message entirely, it should no longer be necessary

* Version detection is hard.  Regular expressions to the rescue!

* Remove process.kill/stop/resume, those will be a different PR

* Fix doctests

* The GDB used by Travis must be so old!

* Apparently Travis is entirely borked.  Use a less robust test.
2017-01-04 12:59:22 -05:00
Zach Riggle 5ce5339ab5 Silence warning during doctests 2017-01-04 12:58:04 -05:00
Zach Riggle 0cead77039 Move STDOUT/PIPE to process class, so they can be referenced directly 2017-01-04 12:52:19 -05:00
Zach Riggle 1d59a90fa8 Change all imports to absolute per PEP-328, and sort imports (#832) 2017-01-03 18:04:45 -05:00
Zach Riggle ac017d97dc Enable leaking memory relatively with MemLeak (#822)
* Enable leaking memory relatively with MemLeak

Fixes Gallopsled/pwntools#758

* Add RelativeMemLeak alias for MemLeak(..., relative=True)

* Make codacy happy
2016-12-29 13:21:02 -05:00
WGH b584ca349a fixed RuntimeError in setup.py caused by pandoc on non-amd64 arches (#820)
If pandoc is not installed locally, pwntools' setup.py
tells pypandoc to download one.

Unfortunately, it fails on non-amd64 arches because
pypandocs provides precompiled binaries only for amd64.

Since pypandoc is not hard dependency for pwntools,
this error can be skipped without major harm.

In fact, pwntools' setup.py already handles ImportError
gracefully. The problem is, arch mismatch causes
a completely different exception.
2016-12-28 19:12:47 -05:00
Mathias Svensson 8b30700f92 Bump to 3.5.0dev 2016-12-24 12:14:05 +01:00
Mathias Svensson 7ed118763b Merge branch 'dev' into beta 2016-12-24 11:54:21 +01:00
Mathias Svensson e997c17397 Merge branch 'beta' into dev 2016-12-24 11:10:10 +01:00
Mathias Svensson 0acc8ee499 Merge branch 'stable' into beta 2016-12-24 11:00:32 +01:00
Zach Riggle f14e0e5652 Reorder get_buffer_size so that the value is validated
This prevents silly things like tube.recv("abcdef") which will hang
2016-12-22 16:41:51 -05:00
Zach Riggle aaa83983ad Merge branch 'beta' into dev 2016-12-22 16:27:46 -05:00
Zach Riggle 1241073391 Remove support for installation without psutil (#821)
* Remove support for installation without psutil.

OpenBSD was the edge case we cared about, it is supported for over a year (giampaolo/psutil#615)

* Bump psutil version to when they introduced OpenBSD support
2016-12-20 14:06:06 -05:00
Zach Riggle cfcd79e169 Update install strings to include libffi-dev and be consistent 2016-12-16 11:37:13 -06:00
Zach Riggle 706a784904 Directly invoke sock.close() when we hit POLLHUP 2016-12-15 20:20:07 -06:00
Zach Riggle 70c96330de Make sock.connected much more robust by using socket.poll (#817)
* Make sock.connected much more robust by using socket.poll

* Fix use of socket.socket.recv vs. ssh transport recv
2016-12-15 20:15:19 -06:00
Zach Riggle cae3cddc07 Fix incorrect error message in serialtube 2016-12-15 20:04:12 -06:00
Zach Riggle 5373dcdeed Fix dev-only regression in serialtube constructor, and use Timeout object properties 2016-12-15 20:04:12 -06:00
Zach Riggle 5d4bf3db6c Add MemLeak.u64/u32/... aliases 2016-12-15 17:15:10 -06:00
Zach Riggle fbfa54ad61 Fix failing doctestse on dev 2016-12-15 16:45:08 -06:00
Zach Riggle b323264032 Merge branch 'beta' into dev 2016-12-15 15:05:16 -06:00
Zach Riggle 2c841eaa21 Add adb.{unlink,mkdir,makedirs,isdir,exists} (#809)
* Add adb.{unlink,mkdir,makedirs,isdir,exists}

* Change unlink() to throw an exception rather than return a string

* Fix doctest and code for isdir

* Remove unused variable
2016-12-14 16:49:41 -06:00
Zach Riggle d2c6184a00 Add missing changelog entry 2016-12-14 14:58:00 -06:00
Zach Riggle e5645e7b53 Update CHANGELOG missing links 2016-12-14 14:49:54 -06:00
Zach Riggle 09dbe0a54d Update CHANGELOG links 2016-12-14 14:49:27 -06:00
Zach Riggle 0bd0de78f9 Update CHANGELOG 2016-12-14 14:48:19 -06:00
Zach Riggle 546061e577 Modify coredump_filter of spawned processes so that library contents are included 2016-12-12 18:26:46 -06:00
Zach Riggle 5ebc8b5bb7 Check the name before attempting to call startswith() 2016-12-12 18:26:46 -06:00
Zach Riggle b83a6c7db8 Fix process.leak by providing a sane default leak size 2016-12-05 15:44:04 -05:00
Owlz 5d9792fb4c Variable Size Buffer Fill (#806)
Support custom buffer-read sizes for `tube` objects via their internal `Buffer` object.  Default buffer size is 4096.  Buffer size can be set per-object at instantiation-time (`tube(buffer_fill_size=...)`), by attribute (`tube.buffer_fill_size=...`), or in a context (`context.buffer_size=...`).
2016-12-05 15:21:08 -05:00
Zach Riggle 1c54e347f2 Add 'shell' option for ssh.process to pass to "sh -c" (#800)
Add 'shell' option for ssh.process to pass to "sh -c"
2016-11-29 11:52:44 -07:00
Zach Riggle 7545dafffb Wrap context.log_console when using terminal I/O (#797)
Fixes Gallopsled/pwntools#792
2016-11-23 11:11:17 -07:00
Zach Riggle 3acec5421f Set shell=False 2016-11-12 23:16:32 -08:00
Mathias Svensson b441cf967a Start work on 3.4.0 2016-11-12 20:39:25 +01:00
3561 changed files with 46712 additions and 24864 deletions

No files matched your search

+6 -1
View File
@@ -13,6 +13,7 @@ addons:
- mksh
- zsh
- pandoc
- gdb
cache:
- pip
- directories:
@@ -22,11 +23,15 @@ cache:
python:
- "2.7"
before_install:
- echo $-
- source travis/install.sh
- source travis/ssh_setup.sh
- echo $-
install:
- pip install --upgrade pip
- pip install --upgrade appdirs # https://github.com/ActiveState/appdirs/issues/89#issuecomment-282326570
- pip install --upgrade --editable .
- pip install --requirement docs/requirements.txt
- pip install --upgrade --requirement docs/requirements.txt
before_script:
- PWNLIB_NOTERM=1 python -c 'from pwn import *; print pwnlib.term.term_mode'
- PWNLIB_NOTERM=1 python -c 'from pwn import *; print pwnlib.term.term_mode'
+130 -8
View File
@@ -9,9 +9,12 @@ The table below shows which release corresponds to each branch, and what date th
| Version | Branch | Release Date |
| ---------------- | -------- | ---------------------- |
| [3.5.0](#350) | `dev` | Mar 18, 2017 (planned)
| [3.4.0](#340) | `beta` | Feb 4, 2017 (planned)
| [3.3.4](#334) | `stable` | Jan 12, 2016
| [3.7.0](#350) | `dev` | Jun 17, 2017 (planned)
| [3.6.0](#350) | `beta` | May 6, 2017 (planned)
| [3.5.0](#350) | `stable` | Mar 26, 2017
| [3.4.1](#341) | | Feb 17, 2017
| [3.4.0](#340) | | Feb 13, 2017
| [3.3.4](#334) | | Jan 12, 2016
| [3.3.3](#333) | | Jan 10, 2016
| [3.3.2](#332) | | Jan 10, 2016
| [3.3.1](#331) | | Jan 10, 2016
@@ -27,13 +30,132 @@ The table below shows which release corresponds to each branch, and what date th
| [3.0.0](#300) | | Aug 20, 2016
| [2.2.0](#220) | | Jan 5, 2015
## 3.7.0
To be on Jun 17, 2017.
## 3.6.0
To be released on May 6, 2017.
- [#895][895] Added a Dockerfile to simplify testing setup and allow testing on OSX
- [#897][897] Fixed some incorrect AArch64 syscals
- [#893][893] Added the `pwnlib.config` module
+ Configuration options can now be set in `~/.pwn.conf`
+ This replaces the old, **undocumented** mechanism for changing logging colors. Only @br0ns and @ebeip90 were likely using this.
+ More information is available in the documentation [here](http://docs.pwntools.com/en/dev/config.html).
- [#899][899] Pwntools now uses Unicorn Engine to emulate PLT instructions to ensure correct mapping of PIE / RELRO binaries.
- [#904][904] Enhancements to the accuracy of the `pwn checksec` command.
- [#905][905] Added a `pwn debug` command-line utility which automates the process of `gdb.attach(process(...))` to spawn GDB
+ More information is available in the documentation [here](http://docs.pwntools.com/en/dev/commandline.html#pwn-debug)
- [#919][919] Added a `pwn template` command-line utility to simplify the process of bootstrapping a new exploit.
+ More information is available in the documentation [here](http://docs.pwntools.com/en/dev/commandline.html#pwn-template).
[895]: https://github.com/Gallopsled/pwntools/pull/895
[897]: https://github.com/Gallopsled/pwntools/pull/897
[893]: https://github.com/Gallopsled/pwntools/pull/893
[899]: https://github.com/Gallopsled/pwntools/pull/899
[904]: https://github.com/Gallopsled/pwntools/pull/904
[905]: https://github.com/Gallopsled/pwntools/pull/905
[919]: https://github.com/Gallopsled/pwntools/pull/919
## 3.5.0
To be released on Mar 18, 2017.
- [b584ca3][b584ca3] Fixed an issue running `setup.py` on ARM
- [#822][822] Enabled relative leaks with `MemLeak`
+ This should be useful for e.g. heap-relative leaks
- [#832][832] Changed all internal imports to use absolute imports (no functional changes)
- [a12d0b6][a12d0b6] Move `STDOUT`, `PIPE`, `PTY` constants to globals
+ `process(..., stdin=process.PTY)` --> `process(..., stdin=PTY)`
- [#828][828] Use `PR_SET_PTRACER` for all `process()` and `ssh.process()` instances
+ This simplifies debugging on systems with YAMA ptrace enabled
- Various documentation enhancements
+ In particular, the [gdb][gdb], [elf][elf], and [ssh][ssh] docs are much better
- [#833][833] Performance enhancements for `adb` module
- [d0267f3][d0267f3] `packing.fit()` now treats large offsets as cyclic patterns (e.g. `0x61616161` behaves the same as `"aaaa"`)
- [#835][835] Added `ssh.checksec`
+ Reports the kernel version and other relevant information on connection
- [#857][857] Slightly shortened `execve` shellcode
- [300f8e0][300f8e0] Slightly speed up processing of large ELF files
- [#861][861] Adds support for extracting `IKCONFIG` configs from Linux kernel images, and extends `checksec` to report on any insecure configurations discovered
- [#871][871] Moves all of the basic syscall templates to `shellcraft/common` and exposes them via symlinks. Closed [#685][685]
+ Should not have any visible effects from any documented APIs
+ `shellcraft.arch.os.syscall_function()` still works the same
+ We now have the ability to differentiate between the `connect` syscall, and a TCP `connect` helper
- [#887][887] `sh_string` now returns a quoted empty string `''` rather than just an empty string
- [#839][839] Exposes a huge amount of functionality via corefiles which was not previously availble. See the [docs][corefile_docs] for examples.
+ `process().corefile` will automatically instantiate a Corefile for the process
+ QEMU-emulated processes are supported
+ Native processes are supported, including extraction of coredumps from `apport` crash logs
+ Native processes can be dumped *while running*, in a manner similar to `GDB`'s `gcore` script
- [#875][857] Added [documentation][aarch64] (and tests) for AArch64 shellcode
- [#882][882] The `ROP` class now respects `context.bytes` instead of using the hard-coded value of `4` (fixed [#879][879])
- [#869][869] Added several fields to the `process` class (`uid`, `gid`, `suid`, `sgid`) which are recorded at execution time, based on the file permissions
- [#868][868] Changed the way that `ssh.process()` works internally, and it now returns a more specialized class, `ssh_process`.
+ Added `ssh_process.corefile` for fetching remote corefiles
+ Added `ssh_process.ELF` for getting an ELF of the remote executable
+ The `uid`, `gid`, and `suid`, and `sgid` which are recorded at execution time, based on the file permissions
- [#865][865] Fixes `ELF.read` to support contiguous memory reads across non-contiguous file-backed segments
- [#862][862] Adds a `symlink=` argument to `ssh.set_working_directory`, which will automatically symlink all of the files in the "old" working directory into the "new" working directory
[ssh]: http://docs.pwntools.com/en/dev/tubes/ssh.html
[gdb]: http://docs.pwntools.com/en/dev/gdb.html
[elf]: http://docs.pwntools.com/en/dev/elf.html
[corefile_docs]: http://docs.pwntools.com/en/dev/elf/corefile.html
[aarch64]: http://docs.pwntools.com/en/dev/shellcraft/aarch64.html
[685]: https://github.com/Gallopsled/pwntools/pull/685
[822]: https://github.com/Gallopsled/pwntools/pull/822
[828]: https://github.com/Gallopsled/pwntools/pull/828
[832]: https://github.com/Gallopsled/pwntools/pull/832
[833]: https://github.com/Gallopsled/pwntools/pull/833
[835]: https://github.com/Gallopsled/pwntools/pull/835
[839]: https://github.com/Gallopsled/pwntools/pull/839
[857]: https://github.com/Gallopsled/pwntools/pull/857
[861]: https://github.com/Gallopsled/pwntools/pull/861
[862]: https://github.com/Gallopsled/pwntools/pull/862
[865]: https://github.com/Gallopsled/pwntools/pull/865
[868]: https://github.com/Gallopsled/pwntools/pull/868
[869]: https://github.com/Gallopsled/pwntools/pull/869
[871]: https://github.com/Gallopsled/pwntools/pull/871
[875]: https://github.com/Gallopsled/pwntools/pull/857
[879]: https://github.com/Gallopsled/pwntools/issues/879
[882]: https://github.com/Gallopsled/pwntools/pull/882
[887]: https://github.com/Gallopsled/pwntools/pull/887
[b584ca3]: https://github.com/Gallopsled/pwntools/commit/b584ca3
[a12d0b6]: https://github.com/Gallopsled/pwntools/commit/a12d0b6
[d0267f3]: https://github.com/Gallopsled/pwntools/commit/d0267f3
[300f8e0]: https://github.com/Gallopsled/pwntools/commit/300f8e0
## 3.4.1
- [#894][894] Fix a bug when using `gdb.debug()` over ssh.
- [e021f57][e021f57] Fix a bug ([#891][891]) in `rop` when needing to insert padding to fix alignment
[e021f57]: https://github.com/Gallopsled/pwntools/commit/e021f57
[894]: https://github.com/Gallopsled/pwntools/pull/894
[891]: https://github.com/Gallopsled/pwntools/issues/891
## 3.4.0
To be released on Feb 4, 2017.
- [#800][800] Add `shell=` option to `ssh.process()`
- [#806][806] Add `context.buffer_size` for fine-tuning `tube` performance
+ Also adds `buffer_fill_size=` argument for all tubes
- [b83a6c7][b83a6c7] Fix undocumented `process.leak` function
- [546061e][546061e] Modify `coredump_filter` of all spawned processes, so that core dumps are more complete
- [#809][809] Add several functions to `adb` (`unlink`, `mkdir`, `makedirs`, `isdir`, `exists`)
- [#817][817] Make disconnection detection more robust
[800]: https://github.com/Gallopsled/pwntools/pull/800
[806]: https://github.com/Gallopsled/pwntools/pull/806
[809]: https://github.com/Gallopsled/pwntools/pull/809
[817]: https://github.com/Gallopsled/pwntools/pull/817
[5d9792f]: https://github.com/Gallopsled/pwntools/commit/5d9792f
[b83a6c7]: https://github.com/Gallopsled/pwntools/commit/b83a6c7
[546061e]: https://github.com/Gallopsled/pwntools/commit/546061e
## 3.3.4
@@ -154,7 +276,7 @@ Multiple bug fixes.
- [1e414af][1e414af] Added `connect()` alias for `remote()`
+ For example, `io=connect('google.com', 80)`
+ This also works with `tcp(...)` and `udp(...)` aliases
- [869ec42][869ec42] Added `ssh.read()` and `ssh.write()` aiases
- [869ec42][869ec42] Added `ssh.read()` and `ssh.write()` aliases
- [2af55c9][2af55c9] `AdbDevice` objects exposed via e.g. `adb.devices()` now offer scoped access to all `adb` module properties
+ It is now possible to e.g. `map(lambda d: d.process(['id']).recvall(), adb.devices())`
@@ -241,7 +363,7 @@ Android support via a new `adb` module, `context.device`, `context.adb_host`, an
- Added `context.log_file` setting for sending logs to a file. This can be set with the `LOG_FILE` magic command-line option.
- Added `context.noptrace` setting for disabling actions which require `ptrace` support. This is useful for turning all `gdb.debug` and `gdb.attach` options into no-ops, and can be set via the `NOPTRACE` magic command-line option.
- Added `context.proxy` which hooks all connections and sends them to a SOCKS4/SOCKS5. This can be set via the `PROXY` magic command-line option.
- Added `context.randomize` to control randommization of settings like XOR keys and register ordering (default off).
- Added `context.randomize` to control randomization of settings like XOR keys and register ordering (default off).
- Added `context.terminal` for setting how to launch commands in a new terminal.
#### DynELF and MemLeak Module
@@ -254,7 +376,7 @@ Android support via a new `adb` module, `context.device`, `context.adb_host`, an
#### Encoders Module
- Added a `pwnlib.encoders` module for assembled-shellcode encoders/decoders
- Includes position-indepentent basic XOR encoders
- Includes position-independent basic XOR encoders
- Includes position-independent delta encoders
- Includes non-position-independent alphanumeric encoders for Intel
- Includes position-independent alphanumeric encoders for ARM/Thumb
+1 -1
View File
@@ -2,6 +2,6 @@ graft examples
graft extra
include *.md *.txt *.sh *.yml MANIFEST.in
recursive-include docs *.rst *.png Makefile *.py *.txt
recursive-include pwnlib *.py *.asm *.rst *.md *.txt *.sh
recursive-include pwnlib *.py *.asm *.rst *.md *.txt *.sh __doc__
recursive-include pwn *.py *.asm *.rst *.md *.txt *.sh
recursive-exclude *.pyc
+3 -2
View File
@@ -2,7 +2,7 @@
![pwntools logo](https://github.com/Gallopsled/pwntools/blob/stable/docs/source/logo.png?raw=true)
[![Docs](https://readthedocs.org/projects/pwntools/badge/?version=stable)](https://docs.pwntools.com/)
[![PyPI](https://img.shields.io/badge/pypi-v3.3.4-green.svg?style=flat)](https://pypi.python.org/pypi/pwntools/)
[![PyPI](https://img.shields.io/badge/pypi-v3.5.0-green.svg?style=flat)](https://pypi.python.org/pypi/pwntools/)
[![Travis](https://travis-ci.org/Gallopsled/pwntools.svg)](https://travis-ci.org/Gallopsled/pwntools)
[![Coveralls](https://img.shields.io/coveralls/Gallopsled/pwntools/dev.svg)](https://coveralls.io/github/Gallopsled/pwntools?branch=dev)
[![Twitter](https://img.shields.io/badge/twitter-pwntools-4099FF.svg?style=flat)](https://twitter.com/pwntools)
@@ -38,7 +38,8 @@ Most of the functionality of pwntools is self-contained and Python-only. You sh
```sh
apt-get update
apt-get install python2.7 python-pip python-dev git libssl-dev
apt-get install python2.7 python-pip python-dev git libssl-dev libffi-dev build-essential
pip install --upgrade pip
pip install --upgrade pwntools
```
+10
View File
@@ -13,3 +13,13 @@
.. automodule:: pwnlib.asm
:members:
Internal Functions
=========================================
These are only included so that their tests are run.
You should never need these.
.. autofunction:: pwnlib.asm.dpkg_search_for_binutils
.. autofunction:: pwnlib.asm.print_binutils_instructions
+15 -3
View File
@@ -44,6 +44,10 @@ extensions = [
'sphinxcontrib.napoleon'
]
# Napoleon settings
napoleon_use_ivar = True
napoleon_use_rtype = False
doctest_global_setup = '''
import sys, os
os.environ['PWNLIB_NOTERM'] = '1'
@@ -52,6 +56,7 @@ import pwnlib
pwnlib.context.context.reset_local()
pwnlib.context.ContextType.defaults['log_level'] = 'ERROR'
pwnlib.context.ContextType.defaults['randomize'] = False
pwnlib.util.fiddling.default_style = {}
pwnlib.term.text.when = 'never'
pwnlib.log.install_default_handler()
pwnlib.log.rootlogger.setLevel(1)
@@ -67,12 +72,13 @@ class stdout(object):
pwnlib.context.ContextType.defaults['log_console'] = stdout()
'''
autodoc_member_order = 'alphabetical'
autoclass_content = 'both'
autodoc_member_order = 'groupwise' # 'alphabetical'
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
doctest_test_doctest_blocks = True
doctest_test_doctest_blocks = 'true'
# The suffix of source filenames.
source_suffix = '.rst'
@@ -231,7 +237,7 @@ latex_documents = [
]
intersphinx_mapping = {'python': ('https://docs.python.org/2.7', None),
'paramiko': ('https://paramiko-docs.readthedocs.org/en/1.15/', None)}
'paramiko': ('https://paramiko-docs.readthedocs.org/en/2.1/', None)}
# The name of an image file (relative to this directory) to place at the top of
# the title page.
@@ -347,3 +353,9 @@ if build_dash:
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
# otherwise, readthedocs.org uses their theme by default, so no need to specify it
# -- Customization to Sphinx autodoc generation --------------------------------------------
import sphinx.ext.autodoc
sphinx.ext.autodoc
+22
View File
@@ -3,10 +3,32 @@
from pwn import *
import logging
log = pwnlib.log.getLogger('pwnlib.context')
context.clear()
:mod:`pwnlib.context` --- Setting runtime variables
=====================================================
Many settings in ``pwntools`` are controlled via the global variable :data:`.context, such as the selected target operating system, architecture, and bit-width.
In general, exploits will start with something like:
.. code-block:: python
from pwn import *
context.arch = 'amd64'
Which sets up everything in the exploit for exploiting a 64-bit Intel binary.
The recommended method is to use ``context.binary`` to automagically set all of the appropriate values.
.. code-block:: python
from pwn import *
context.binary = './challenge-binary'
Module Members
=====================================================
.. autodata:: pwnlib.context.context
.. autoclass:: pwnlib.context.ContextType
+11 -3
View File
@@ -3,8 +3,16 @@
from pwnlib.elf import *
from pwnlib.util.misc import which
:mod:`pwnlib.elf` --- Working with ELF binaries
===============================================
:mod:`pwnlib.elf` --- ELF Executables and Libraries
===================================================
.. automodule:: pwnlib.elf
:members:
ELF Modules
-------------------
.. toctree::
elf/elf
elf/config
elf/corefile
+10
View File
@@ -0,0 +1,10 @@
.. testsetup:: *
from pwn import *
from pwnlib.elf.config import parse_kconfig
:mod:`pwnlib.elf.config` --- Kernel Config Parsing
===========================================================
.. automodule:: pwnlib.elf.config
:members:
+15
View File
@@ -0,0 +1,15 @@
.. testsetup:: *
from pwn import *
:mod:`pwnlib.elf.corefile` --- Core Files
===========================================================
.. automodule:: pwnlib.elf.corefile
.. autoclass:: pwnlib.elf.corefile.Corefile
:members:
:show-inheritance:
.. autoclass:: pwnlib.elf.corefile.Mapping
:members:
+27
View File
@@ -0,0 +1,27 @@
.. testsetup:: *
from pwn import *
:mod:`pwnlib.elf.elf` --- ELF Files
===========================================================
.. automodule:: pwnlib.elf.elf
.. autoclass:: pwnlib.elf.elf.ELF
:members:
:show-inheritance:
:inherited-members:
:exclude-members: address_offsets,
get_data,
get_dwarf_info,
get_section,
get_segment,
has_dwarf_info,
iter_sections,
iter_segments
.. autoclass:: pwnlib.elf.elf.Function
:members:
.. autoclass:: pwnlib.elf.elf.dotdict
:members:
+4
View File
@@ -1,3 +1,7 @@
.. testsetup:: *
from pwn import *
:mod:`pwnlib.gdb` --- Working with GDB
======================================
+6 -6
View File
@@ -13,11 +13,11 @@ Which imports a bazillion things into the global namespace to make your life eas
This is a quick list of most of the objects and routines imported, in rough order of importance and frequency of use.
- ``context``
- :obj:`.context`
- :data:`pwnlib.context.context`
- Responsible for most of the pwntools convenience settings
- Set `context.log_level = 'debug'` when troubleshooting your exploit
- Scope-aware, so you can disable logging for a subsection of code via ``pwnlib.context.ContextType.local``
- Scope-aware, so you can disable logging for a subsection of code via :meth:`.ContextType.local`
- ``remote``, ``listen``, ``ssh``, ``process``
- :mod:`pwnlib.tubes`
- Super convenient wrappers around all of the common functionality for CTF challenges
@@ -57,11 +57,11 @@ This is a quick list of most of the objects and routines imported, in rough orde
- ``gdb.debug`` and ``gdb.attach``
- :mod:`pwnlib.gdb`
- Launch a binary under GDB and pop up a new terminal to interact with it. Automates setting breakpoints and makes iteration on exploits MUCH faster.
- Alternately, attach to a running process given a PID, ``pwnlib.tubes`` object, or even just a socket that's connected to it
- Alternately, attach to a running process given a PID, :mod:`pwnlib.tubes` object, or even just a socket that's connected to it
- ``args``
- Dictionary contining all-caps command-line arguments for quick access
- Dictionary containing all-caps command-line arguments for quick access
- Run via ``python foo.py REMOTE=1`` and ``args['REMOTE'] == '1'``.
- Can also control logging verbosity and terminal fancyness
- Can also control logging verbosity and terminal fanciness
- `NOTERM`
- `SILENT`
- `DEBUG`
@@ -78,7 +78,7 @@ This is a quick list of most of the objects and routines imported, in rough orde
- It's the new ``getch``
- ``safeeval``
- :mod:`pwnlib.util.safeeval`
- Functions for safely evalutaing python code without nasty side-effects.
- Functions for safely evaluating python code without nasty side-effects.
These are all pretty self explanatory, but are useful to have in the global namespace.
+1
View File
@@ -58,6 +58,7 @@ Each of the ``pwntools`` modules is documented here.
gdb
log
memleak
protocols
replacements
rop
rop/*
+3 -1
View File
@@ -24,7 +24,9 @@ pwntools is available as a ``pip`` package.
.. code-block:: bash
$ apt-get install python2.7 python2.7-dev python-pip
$ apt-get update
$ apt-get install python2.7 python-pip python-dev git libssl-dev libffi-dev build-essential
$ pip install --upgrade pip
$ pip install --upgrade pwntools
Development
+2 -2
View File
@@ -212,8 +212,8 @@ Stop hard-coding things! Look them up at runtime with :mod:`pwnlib.elf`.
You can even patch and save the files.
>>> e = ELF('/bin/cat')
>>> e.read(e.address+1, 3)
'ELF'
>>> e.read(e.address, 4)
'\x7fELF'
>>> e.asm(e.address, 'ret')
>>> e.save('/tmp/quiet-cat')
>>> disasm(file('/tmp/quiet-cat','rb').read(1))
+15
View File
@@ -0,0 +1,15 @@
:mod:`pwnlib.protocols` --- Wire Protocols
=============================================
.. automodule:: pwnlib.protocols
Supported Protocols
-------------------
.. toctree::
:maxdepth: 3
:glob:
protocols/*
+10
View File
@@ -0,0 +1,10 @@
.. testsetup:: *
from pwn import *
from pwnlib.protocols.adb import AdbClient
:mod:`pwnlib.protocols.adb` --- ADB Protocol Implementation
===========================================================
.. automodule:: pwnlib.protocols.adb
:members:
+19
View File
@@ -0,0 +1,19 @@
.. testsetup:: *
from pwn import *
context.clear(arch='aarch64')
:mod:`pwnlib.shellcraft.aarch64` --- Shellcode for AArch64
===========================================================
:mod:`pwnlib.shellcraft.aarch64`
--------------------------------
.. automodule:: pwnlib.shellcraft.aarch64
:members:
:mod:`pwnlib.shellcraft.aarch64.linux`
--------------------------------------
.. automodule:: pwnlib.shellcraft.aarch64.linux
:members:
+1 -1
View File
@@ -27,4 +27,4 @@ Types of Tubes
.. autoclass:: pwnlib.tubes.tube.tube()
:members:
:exclude-members: recv_raw, send_raw, settimeout_raw,
can_recv_raw
can_recv_raw, shutdown_raw, connected_raw,
+2 -1
View File
@@ -1,6 +1,7 @@
.. testsetup:: *
from pwnlib.util.fiddling import *
from pwn import *
from pwnlib.util.fiddling import *
:mod:`pwnlib.util.fiddling` --- Utilities bit fiddling
+31
View File
@@ -0,0 +1,31 @@
from pwn import *
context.arch='aarch64'
frame = SigreturnFrame()
registers = ['x%i' % i for i in range(0, 31)]
registers += ['sp', 'pc']
for index, register in enumerate(registers):
setattr(frame, register, index)
assembly = '\n'.join([
shellcraft.read(constants.STDIN_FILENO, 'sp', 1024),
shellcraft.syscall(constants.SYS_rt_sigreturn)
])
binary = ELF.from_assembly(assembly)
io = binary.process()
io.flat(frame)
io.wait()
assert io.poll() == -11
corefile = io.corefile
for index, register in enumerate(registers):
value = getattr(corefile, register)
if index != value:
log.error("%s != %i (%i)" % (register, index, value))
else:
log.success("%s == %i" % (register, value))
+32
View File
@@ -0,0 +1,32 @@
from pwn import *
context.arch='amd64'
frame = SigreturnFrame()
registers = ['rax', 'rbx', 'rcx', 'rdx',
'rdi', 'rsi', 'rbp', 'rsp',
'rip',
'r8', 'r9', 'r10', 'r11', 'r12', 'r13', 'r14', 'r15']
for index, register in enumerate(registers):
setattr(frame, register, index)
assembly = '\n'.join([
shellcraft.read(constants.STDIN_FILENO, 'rsp', 1024),
shellcraft.sigreturn()
])
binary = ELF.from_assembly(assembly)
io = binary.process()
io.flat(frame)
io.wait()
assert io.poll() == -11
corefile = io.corefile
for index, register in enumerate(registers):
value = getattr(corefile, register)
if index != value:
log.error("%s != %i (%i)" % (register, index, value))
else:
log.success("%s == %i" % (register, value))
+32
View File
@@ -0,0 +1,32 @@
from pwn import *
context.arch='arm'
frame = SigreturnFrame()
registers = ['r0', 'r1', 'r2', 'r3', 'r4', 'r5', 'r6', 'r7',
'r8', 'r9', 'r10', 'fp', 'ip', 'sp', 'pc', 'lr']
for index, register in enumerate(registers):
setattr(frame, register, index)
assembly = '\n'.join([
shellcraft.read(constants.STDIN_FILENO, 'sp', 1024),
shellcraft.sigreturn()
])
binary = ELF.from_assembly(assembly)
io = binary.process()
io.flat(frame)
io.wait()
assert io.poll() == -11
corefile = io.corefile
for index, register in enumerate(registers):
value = getattr(corefile, register)
if index != value:
log.error("%s != %i (%i)" % (register, index, value))
else:
log.success("%s == %i" % (register, value))
+31
View File
@@ -0,0 +1,31 @@
from pwn import *
context.kernel='amd64'
frame = SigreturnFrame()
registers = ['eax', 'ebx', 'ecx', 'edx',
'edi', 'esi', 'ebp', 'esp',
'eip']
for index, register in enumerate(registers):
setattr(frame, register, index)
assembly = '\n'.join([
shellcraft.read(constants.STDIN_FILENO, 'esp', 1024),
shellcraft.sigreturn()
])
binary = ELF.from_assembly(assembly)
io = binary.process()
io.flat(frame)
io.wait()
assert io.poll() == -11
corefile = io.corefile
for index, register in enumerate(registers):
value = getattr(corefile, register)
if index != value:
log.error("%s != %i (%i)" % (register, index, value))
else:
log.success("%s == %i" % (register, value))
-58
View File
@@ -1,58 +0,0 @@
############################################################
# Dockerfile to build Pwntools container
# Based on Ubuntu
############################################################
FROM ubuntu:trusty
MAINTAINER Maintainer Gallopsled et al.
RUN apt-get update && apt-get install -y software-properties-common
RUN apt-add-repository --yes ppa:pwntools/binutils
RUN apt-get update
RUN apt-get install -y \
binutils \
binutils-aarch64-linux-gnu \
binutils-arm-linux-gnueabihf \
curl \
gdb \
gdbserver \
gdb-multiarch \
git \
libc6-i386 \
libc6-arm64-cross \
libc6-armhf-cross \
libc6-dev-i386 \
libffi-dev \
libglib2.0-dev \
libncurses5-dev \
libncursesw5-dev \
ltrace \
python-dev \
python-pip \
python3-pip \
python2.7 \
qemu \
qemu-user-static \
strace \
tmux \
wget
RUN pip install --upgrade ipython
RUN useradd -m zerocool
RUN usermod -U zerocool
RUN mkdir -p /etc/qemu-binfmt
RUN ln -s /usr/aarch64-linux-gnu /etc/qemu-binfmt/aarch64
RUN ln -s /usr/arm-linux-gnueabihf /etc/qemu-binfmt/arm
RUN pip install --upgrade pip pycparser psutil python-ptrace
RUN pip3 install --upgrade pip pycparser psutil python-ptrace
RUN pip install --upgrade git+https://github.com/Gallopsled/pwntools.git
RUN locale-gen en_US.UTF-8
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
ENV LC_ALL en_US.UTF-8
RUN chown -R zerocool.zerocool /home/zerocool
RUN chsh -s /bin/bash zerocool
+13
View File
@@ -0,0 +1,13 @@
subdirs=xenial stable $(wildcard */)
makefiles=$(wildcard */Makefile)
tags=stable beta dev
all: $(makefiles) $(subdirs)
$(subdirs):
docker build -t pwntools/pwntools:$(shell basename $(@)) "$@"
$(makefiles):
make -C $(shell dirname $@)
.PHONY: all $(subdirs) $(makefiles)
+6
View File
@@ -0,0 +1,6 @@
FROM pwntools/pwntools:stable
USER root
RUN pip install --upgrade git+https://github.com/Gallopsled/pwntools@beta
RUN PWNLIB_NOTERM=1 pwn update
USER pwntools
+6
View File
@@ -0,0 +1,6 @@
FROM pwntools/pwntools:stable
USER root
RUN pip install --upgrade git+https://github.com/Gallopsled/pwntools@dev
RUN PWNLIB_NOTERM=1 pwn update
USER pwntools
+26
View File
@@ -0,0 +1,26 @@
############################################################
# Dockerfile to build Pwntools container
# Based on Ubuntu
############################################################
FROM kalilinux/kali-linux-docker
MAINTAINER Maintainer Gallopsled et al.
RUN apt-get update
RUN apt-get install -y build-essential
RUN apt-get install -y git
RUN apt-get install -y libssl-dev
RUN apt-get install -y libffi-dev
RUN apt-get install -y python2.7
RUN apt-get install -y python-pip
RUN apt-get install -y python-dev
RUN pip install --upgrade pip
RUN python -m pip install --upgrade pwntools
RUN PWNLIB_NOTERM=1 pwn update
RUN apt-get install -y sudo
RUN useradd -m pwntools
RUN passwd --delete --unlock pwntools
RUN echo "pwntools ALL=(ALL:ALL) NOPASSWD: ALL" > /etc/sudoers.d/pwntools
USER pwntools
WORKDIR /home/pwntools
+8
View File
@@ -0,0 +1,8 @@
all: Dockerfile
Dockerfile: ../stable/Dockerfile FORCE
sed "s|ubuntu:xenial|kalilinux/kali-linux-docker|" < "$<" > "$@"
FORCE:
.PHONY: all
+26
View File
@@ -0,0 +1,26 @@
############################################################
# Dockerfile to build Pwntools container
# Based on Ubuntu
############################################################
FROM ubuntu:precise
MAINTAINER Maintainer Gallopsled et al.
RUN apt-get update
RUN apt-get install -y build-essential
RUN apt-get install -y git
RUN apt-get install -y libssl-dev
RUN apt-get install -y libffi-dev
RUN apt-get install -y python2.7
RUN apt-get install -y python-pip
RUN apt-get install -y python-dev
RUN pip install --upgrade pip
RUN python -m pip install --upgrade pwntools
RUN PWNLIB_NOTERM=1 pwn update
RUN apt-get install -y sudo
RUN useradd -m pwntools
RUN passwd --delete --unlock pwntools
RUN echo "pwntools ALL=(ALL:ALL) NOPASSWD: ALL" > /etc/sudoers.d/pwntools
USER pwntools
WORKDIR /home/pwntools
+8
View File
@@ -0,0 +1,8 @@
all: Dockerfile
Dockerfile: ../xenial/Dockerfile FORCE
sed "s|ubuntu:xenial|ubuntu:precise|" < "$<" > "$@"
FORCE:
.PHONY: all
+26
View File
@@ -0,0 +1,26 @@
############################################################
# Dockerfile to build Pwntools container
# Based on Ubuntu
############################################################
FROM ubuntu:xenial
MAINTAINER Maintainer Gallopsled et al.
RUN apt-get update
RUN apt-get install -y build-essential
RUN apt-get install -y git
RUN apt-get install -y libssl-dev
RUN apt-get install -y libffi-dev
RUN apt-get install -y python2.7
RUN apt-get install -y python-pip
RUN apt-get install -y python-dev
RUN pip install --upgrade pip
RUN python -m pip install --upgrade pwntools
RUN PWNLIB_NOTERM=1 pwn update
RUN apt-get install -y sudo
RUN useradd -m pwntools
RUN passwd --delete --unlock pwntools
RUN echo "pwntools ALL=(ALL:ALL) NOPASSWD: ALL" > /etc/sudoers.d/pwntools
USER pwntools
WORKDIR /home/pwntools
+8
View File
@@ -0,0 +1,8 @@
all: Dockerfile
Dockerfile: ../xenial/Dockerfile FORCE
cp "$<" "$@"
FORCE:
.PHONY: all
+26
View File
@@ -0,0 +1,26 @@
############################################################
# Dockerfile to build Pwntools container
# Based on Ubuntu
############################################################
FROM ubuntu:trusty
MAINTAINER Maintainer Gallopsled et al.
RUN apt-get update
RUN apt-get install -y build-essential
RUN apt-get install -y git
RUN apt-get install -y libssl-dev
RUN apt-get install -y libffi-dev
RUN apt-get install -y python2.7
RUN apt-get install -y python-pip
RUN apt-get install -y python-dev
RUN pip install --upgrade pip
RUN python -m pip install --upgrade pwntools
RUN PWNLIB_NOTERM=1 pwn update
RUN apt-get install -y sudo
RUN useradd -m pwntools
RUN passwd --delete --unlock pwntools
RUN echo "pwntools ALL=(ALL:ALL) NOPASSWD: ALL" > /etc/sudoers.d/pwntools
USER pwntools
WORKDIR /home/pwntools
+8
View File
@@ -0,0 +1,8 @@
all: Dockerfile
Dockerfile: ../xenial/Dockerfile FORCE
sed "s|ubuntu:xenial|ubuntu:trusty|" < "$<" > "$@"
FORCE:
.PHONY: all
+26
View File
@@ -0,0 +1,26 @@
############################################################
# Dockerfile to build Pwntools container
# Based on Ubuntu
############################################################
FROM ubuntu:xenial
MAINTAINER Maintainer Gallopsled et al.
RUN apt-get update
RUN apt-get install -y build-essential
RUN apt-get install -y git
RUN apt-get install -y libssl-dev
RUN apt-get install -y libffi-dev
RUN apt-get install -y python2.7
RUN apt-get install -y python-pip
RUN apt-get install -y python-dev
RUN pip install --upgrade pip
RUN python -m pip install --upgrade pwntools
RUN PWNLIB_NOTERM=1 pwn update
RUN apt-get install -y sudo
RUN useradd -m pwntools
RUN passwd --delete --unlock pwntools
RUN echo "pwntools ALL=(ALL:ALL) NOPASSWD: ALL" > /etc/sudoers.d/pwntools
USER pwntools
WORKDIR /home/pwntools
+3 -1
View File
@@ -1,5 +1,7 @@
# Promote useful stuff to toplevel
from .toplevel import *
from __future__ import absolute_import
from pwn.toplevel import *
pwnlib.args.initialize()
pwnlib.log.install_default_handler()
+5 -5
View File
@@ -24,15 +24,15 @@ from pwnlib.context import Thread
from pwnlib.context import context
from pwnlib.dynelf import DynELF
from pwnlib.encoders import *
from pwnlib.elf import Core
from pwnlib.elf import ELF
from pwnlib.elf import load
from pwnlib.elf.corefile import Core, Corefile, Coredump
from pwnlib.elf.elf import ELF, load
from pwnlib.encoders import *
from pwnlib.exception import PwnlibException
from pwnlib.gdb import attach, debug, debug_assembly, debug_shellcode
from pwnlib.flag import *
from pwnlib.fmtstr import FmtStr, fmtstr_payload
from pwnlib.log import getLogger
from pwnlib.memleak import MemLeak
from pwnlib.memleak import MemLeak, RelativeMemLeak
from pwnlib.regsort import *
from pwnlib.replacements import *
from pwnlib.rop import ROP
@@ -40,7 +40,7 @@ from pwnlib.rop.srop import SigreturnFrame
from pwnlib.runner import *
from pwnlib.timeout import Timeout
from pwnlib.tubes.listen import listen
from pwnlib.tubes.process import process
from pwnlib.tubes.process import process, PTY, PIPE, STDOUT
from pwnlib.tubes.remote import remote, tcp, udp, connect
from pwnlib.tubes.serialtube import serialtube
from pwnlib.tubes.ssh import ssh
+3 -1
View File
@@ -1,6 +1,8 @@
from __future__ import absolute_import
import importlib
from .version import __version__
from pwnlib.version import __version__
version = __version__
+49 -24
View File
@@ -1,6 +1,8 @@
# -*- coding: utf-8 -*-
from .context import LocalContext
from .context import context
from __future__ import absolute_import
from pwnlib.context import LocalContext
from pwnlib.context import context
class ABI(object):
@@ -24,7 +26,8 @@ class ABI(object):
#: Indicates that this ABI returns to the next address on the slot
returns = True
def __init__(self, regs, align, minimum):
def __init__(self, stack, regs, align, minimum):
self.stack = stack
self.register_arguments = regs
self.arg_alignment = align
self.stack_minimum = minimum
@@ -32,35 +35,52 @@ class ABI(object):
@staticmethod
@LocalContext
def default():
if context.os == 'android':
context.os = 'linux'
return {
(32, 'i386', 'linux'): linux_i386,
(64, 'aarch64', 'linux'): linux_aarch64,
(64, 'amd64', 'linux'): linux_amd64,
(32, 'arm', 'linux'): linux_arm,
(32, 'thumb', 'linux'): linux_arm,
(32, 'mips', 'linux'): linux_mips,
(32, 'i386', 'windows'): windows_i386,
(64, 'amd64', 'windows'): windows_amd64,
(32, 'powerpc', 'linux'): linux_ppc,
(64, 'powerpc', 'linux'): linux_ppc64,
}[(context.bits, context.arch, context.os)]
@staticmethod
@LocalContext
def syscall():
if context.os == 'android':
context.os = 'linux'
return {
(32, 'i386', 'linux'): linux_i386_syscall,
(64, 'amd64', 'linux'): linux_amd64_syscall,
(64, 'aarch64', 'linux'): linux_aarch64_syscall,
(32, 'arm', 'linux'): linux_arm_syscall,
(32, 'thumb', 'linux'): linux_arm_syscall,
(32, 'mips', 'linux'): linux_mips_syscall,
(64, 'aarch64', 'linux'): linux_aarch64_syscall,
(32, 'powerpc', 'linux'): linux_ppc_syscall,
(64, 'powerpc', 'linux'): linux_ppc64_syscall,
}[(context.bits, context.arch, context.os)]
@staticmethod
@LocalContext
def sigreturn():
if context.os == 'android':
context.os = 'linux'
return {
(32, 'i386', 'linux'): linux_i386_sigreturn,
(64, 'amd64', 'linux'): linux_amd64_sigreturn,
(32, 'arm', 'linux'): linux_arm_sigreturn,
(32, 'thumb', 'linux'): linux_arm_sigreturn,
(64, 'aarch64', 'linux'): linux_aarch64_sigreturn,
}[(context.bits, context.arch, context.os)]
class SyscallABI(ABI):
@@ -68,9 +88,9 @@ class SyscallABI(ABI):
The syscall ABI treats the syscall number as the zeroth argument,
which must be loaded into the specified register.
"""
def __init__(self, register_arguments, *a, **kw):
super(SyscallABI, self).__init__(register_arguments, *a, **kw)
self.syscall_register = register_arguments[0]
def __init__(self, *a, **kw):
super(SyscallABI, self).__init__(*a, **kw)
self.syscall_register = self.register_arguments[0]
class SigreturnABI(SyscallABI):
"""
@@ -81,26 +101,31 @@ class SigreturnABI(SyscallABI):
returns = False
linux_i386 = ABI([], 4, 0)
linux_amd64 = ABI(['rdi','rsi','rdx','rcx','r8','r9'], 8, 0)
linux_arm = ABI(['r0', 'r1', 'r2', 'r3'], 8, 0)
linux_aarch64 = ABI(['x0', 'x1', 'x2', 'x3'], 16, 0)
linux_mips = ABI(['$a0','$a1','$a2','$a3'], 4, 0)
linux_i386 = ABI('esp', [], 4, 0)
linux_amd64 = ABI('rsp', ['rdi','rsi','rdx','rcx','r8','r9'], 8, 0)
linux_arm = ABI('sp', ['r0', 'r1', 'r2', 'r3'], 8, 0)
linux_aarch64 = ABI('sp', ['x0', 'x1', 'x2', 'x3'], 16, 0)
linux_mips = ABI('$sp', ['$a0','$a1','$a2','$a3'], 4, 0)
linux_ppc = ABI('sp', ['r3', 'r4', 'r5', 'r6', 'r7', 'r8', 'r9', 'r10'], 4, 0)
linux_ppc64 = ABI('sp', ['r3', 'r4', 'r5', 'r6', 'r7', 'r8', 'r9', 'r10'], 8, 0)
linux_i386_syscall = SyscallABI(['eax', 'ebx', 'ecx', 'edx', 'esi', 'edi', 'ebp'], 4, 0)
linux_amd64_syscall = SyscallABI(['rax','rdi', 'rsi', 'rdx', 'r10', 'r8', 'r9'], 8, 0)
linux_arm_syscall = SyscallABI(['r7', 'r0', 'r1', 'r2', 'r3', 'r4', 'r5', 'r6'], 4, 0)
linux_aarch64_syscall = SyscallABI(['x8', 'x0', 'x1', 'x2', 'x3', 'x4', 'x5', 'x6'], 16, 0)
linux_mips_syscall = ABI(['$v0', '$a0','$a1','$a2','$a3'], 4, 0)
linux_i386_syscall = SyscallABI('esp', ['eax', 'ebx', 'ecx', 'edx', 'esi', 'edi', 'ebp'], 4, 0)
linux_amd64_syscall = SyscallABI('rsp', ['rax', 'rdi', 'rsi', 'rdx', 'r10', 'r8', 'r9'], 8, 0)
linux_arm_syscall = SyscallABI('sp', ['r7', 'r0', 'r1', 'r2', 'r3', 'r4', 'r5', 'r6'], 4, 0)
linux_aarch64_syscall = SyscallABI('sp', ['x8', 'x0', 'x1', 'x2', 'x3', 'x4', 'x5', 'x6'], 16, 0)
linux_mips_syscall = SyscallABI('$sp', ['$v0','$a0','$a1','$a2','$a3'], 4, 0)
linux_ppc_syscall = ABI('sp', ['r0', 'r3', 'r4', 'r5', 'r6', 'r7', 'r8', 'r9'], 4, 0)
linux_ppc64_syscall = ABI('sp', ['r0', 'r3', 'r4', 'r5', 'r6', 'r7', 'r8', 'r9'], 8, 0)
linux_i386_sigreturn = SigreturnABI(['eax'], 4, 0)
linux_amd64_sigreturn = SigreturnABI(['rax'], 4, 0)
linux_arm_sigreturn = SigreturnABI(['r7'], 4, 0)
linux_i386_sigreturn = SigreturnABI('esp', ['eax'], 4, 0)
linux_amd64_sigreturn = SigreturnABI('rsp', ['rax'], 4, 0)
linux_arm_sigreturn = SigreturnABI('sp', ['r7'], 4, 0)
linux_aarch64_sigreturn = SigreturnABI('sp', ['x8'], 16, 0)
windows_i386 = ABI([], 4, 0)
windows_amd64 = ABI(['rcx','rdx','r8','r9'], 32, 32)
windows_i386 = ABI('esp', [], 4, 0)
windows_amd64 = ABI('rsp', ['rcx','rdx','r8','r9'], 32, 32)
# Fake ABIs used by SROP
linux_i386_srop = ABI(['eax'], 4, 0)
linux_amd64_srop = ABI(['rax'], 4, 0)
linux_arm_srop = ABI(['r7'], 4, 0)
linux_i386_srop = ABI('esp', ['eax'], 4, 0)
linux_amd64_srop = ABI('rsp', ['rax'], 4, 0)
linux_arm_srop = ABI('sp', ['r7'], 4, 0)
+4 -2
View File
@@ -1,2 +1,4 @@
from .adb import *
from .protocol import Client
from __future__ import absolute_import
from pwnlib.adb.adb import *
from pwnlib.adb.protocol import Client
+253 -68
View File
@@ -1,6 +1,7 @@
"""Provides utilities for interacting with Android devices via the Android Debug Bridge.
# Using Android Devices with Pwntools
Using Android Devices with Pwntools
-----------------------------------
Pwntools tries to be as easy as possible to use with Android devices.
@@ -29,7 +30,7 @@ instance.
error("Could not find any shamus!")
Once a device is selected, you can operate on it with any of the functions in
the ``pwnlib.adb`` module.
the :mod:`pwnlib.adb` module.
.. code-block:: python
@@ -44,6 +45,8 @@ the ``pwnlib.adb`` module.
adb.write('/data/local/tmp/foo', 'my data')
"""
from __future__ import absolute_import
import functools
import glob
import logging
@@ -57,14 +60,14 @@ import time
import dateutil.parser
from .. import atexit
from .. import tubes
from ..context import LocalContext
from ..context import context
from ..device import Device
from ..log import getLogger
from ..util import misc
from .protocol import Client
from pwnlib import atexit
from pwnlib import tubes
from pwnlib.context import LocalContext
from pwnlib.context import context
from pwnlib.device import Device
from pwnlib.log import getLogger
from pwnlib.protocols.adb import AdbClient
from pwnlib.util import misc
log = getLogger(__name__)
@@ -85,10 +88,10 @@ def adb(argv, *a, **kw):
return tubes.process.process(context.adb + argv, *a, **kw).recvall()
@context.quiet
@context.quietfunc
def devices(serial=None):
"""Returns a list of ``Device`` objects corresponding to the connected devices."""
with Client() as c:
with AdbClient() as c:
lines = c.devices(long=True)
result = []
@@ -143,7 +146,7 @@ def root():
log.info("Enabling root on %s" % context.device)
with context.quiet:
with Client() as c:
with AdbClient() as c:
reply = c.root()
if 'already running as root' in reply:
@@ -172,7 +175,7 @@ def reboot(wait=True):
"""
log.info('Rebooting device %s' % context.device)
with Client() as c:
with AdbClient() as c:
c.reboot()
if wait:
@@ -185,7 +188,7 @@ def reboot_bootloader():
"""
log.info('Rebooting %s to bootloader' % context.device)
with Client() as c:
with AdbClient() as c:
c.reboot_bootloader()
@with_device
@@ -376,7 +379,7 @@ def wait_for_device(kick=False):
>>> device = adb.wait_for_device()
"""
with log.waitfor("Waiting for device to come online") as w:
with Client() as c:
with AdbClient() as c:
if kick:
try:
c.reconnect()
@@ -387,7 +390,7 @@ def wait_for_device(kick=False):
if context.device:
serial = str(context.device)
with Client() as c:
with AdbClient() as c:
c.wait_for_device(serial)
for device in devices():
@@ -415,7 +418,7 @@ def disable_verity():
with log.waitfor("Disabling dm-verity on %s" % context.device) as w:
root()
with Client() as c:
with AdbClient() as c:
reply = c.disable_verity()
if 'Verity already disabled' in reply:
@@ -434,7 +437,7 @@ def remount():
disable_verity()
root()
with Client() as c:
with AdbClient() as c:
reply = c.remount()
if 'remount succeeded' not in reply:
@@ -445,7 +448,7 @@ def unroot():
"""Restarts adbd as AID_SHELL."""
log.info("Unrooting %s" % context.device)
with context.quiet:
with Client() as c:
with AdbClient() as c:
reply = c.unroot()
if '0006closed' == reply:
@@ -508,10 +511,14 @@ def push(local_path, remote_path):
local_path(str): Path to the local file to push.
remote_path(str): Path or directory to store the file on the device.
Returns:
Remote path of the file.
Example:
>>> write('./filename', 'contents')
>>> _=adb.push('./filename', '/data/local/tmp')
>>> adb.push('./filename', '/data/local/tmp')
'/data/local/tmp/filename'
>>> adb.read('/data/local/tmp/filename')
'contents'
>>> adb.push('./filename', '/does/not/exist')
@@ -526,7 +533,7 @@ def push(local_path, remote_path):
msg += ' (%s)' % context.device
with log.waitfor(msg) as w:
with Client() as c:
with AdbClient() as c:
# We need to discover whether remote_path is a directory or not.
# If we cannot stat the full path, assume it's a path-plus-filename,
@@ -546,10 +553,13 @@ def push(local_path, remote_path):
if stat.S_ISDIR(mode):
remote_path = os.path.join(remote_path, remote_filename)
return c.write(remote_path,
misc.read(local_path),
callback=_create_adb_push_pull_callback(w))
@context.quiet
c.write(remote_path,
misc.read(local_path),
callback=_create_adb_push_pull_callback(w))
return remote_path
@context.quietfunc
@with_device
def read(path, target=None, callback=None):
"""Download a file from the device, and extract its contents.
@@ -559,7 +569,7 @@ def read(path, target=None, callback=None):
target(str): Optional, location to store the file.
Uses a temporary file by default.
callback(callable): See the documentation for
``adb.protocol.Client.read``.
``adb.protocol.AdbClient.read``.
Examples:
@@ -570,7 +580,7 @@ def read(path, target=None, callback=None):
...
PwnlibException: Could not stat '/does/not/exist'
"""
with Client() as c:
with AdbClient() as c:
stat = c.stat(path)
if not stat:
log.error('Could not stat %r' % path)
@@ -581,7 +591,7 @@ def read(path, target=None, callback=None):
return data
@context.quiet
@context.quietfunc
@with_device
def write(path, data=''):
"""Create a file on the device with the provided contents.
@@ -599,14 +609,158 @@ def write(path, data=''):
misc.write(temp.name, data)
push(temp.name, path)
@context.quietfunc
@with_device
def mkdir(path):
"""Create a directory on the target device.
Note:
Silently succeeds if the directory already exists.
Arguments:
path(str): Directory to create.
Examples:
>>> adb.mkdir('/')
>>> path = '/data/local/tmp/mkdir_test'
>>> adb.exists(path)
False
>>> adb.mkdir(path)
>>> adb.exists(path)
True
>>> adb.mkdir('/init')
Traceback (most recent call last):
...
PwnlibException: mkdir failed for /init, File exists
"""
if not path.startswith('/'):
log.error("Must provide an absolute path: %r" % path)
with AdbClient() as c:
st = c.stat(path)
# Don't re-create existing directories
if st and stat.S_ISDIR(st['mode']):
return
result = process(['mkdir', path]).recvall()
# Any output at all is an error
if result:
log.error(result)
@context.quietfunc
@with_device
def makedirs(path):
"""Create a directory and all parent directories on the target device.
Note:
Silently succeeds if the directory already exists.
Examples:
>>> adb.makedirs('/data/local/tmp/this/is/a/directory/heirarchy')
>>> adb.listdir('/data/local/tmp/this/is/a/directory')
['heirarchy']
"""
if path != '/':
makedirs(os.path.dirname(path))
mkdir(path)
@context.quietfunc
@with_device
def exists(path):
"""Return :const:`True` if ``path`` exists on the target device.
Examples:
>>> adb.exists('/')
True
>>> adb.exists('/init')
True
>>> adb.exists('/does/not/exist')
False
"""
with AdbClient() as c:
return bool(c.stat(path))
@context.quietfunc
@with_device
def isdir(path):
"""Return :const:`True` if ``path`` is a on the target device.
Examples:
>>> adb.isdir('/')
True
>>> adb.isdir('/init')
False
>>> adb.isdir('/does/not/exist')
False
"""
with AdbClient() as c:
st = c.stat(path)
return bool(st and stat.S_ISDIR(st['mode']))
@context.quietfunc
@with_device
def unlink(path, recursive=False):
"""Unlinks a file or directory on the target device.
Examples:
>>> adb.unlink("/does/not/exist")
Traceback (most recent call last):
...
PwnlibException: Could not unlink '/does/not/exist': Does not exist
>>> filename = '/data/local/tmp/unlink-test'
>>> adb.write(filename, 'hello')
>>> adb.exists(filename)
True
>>> adb.unlink(filename)
>>> adb.exists(filename)
False
>>> adb.mkdir(filename)
>>> adb.write(filename + '/contents', 'hello')
>>> adb.unlink(filename)
Traceback (most recent call last):
...
PwnlibException: Cannot delete non-empty directory '/data/local/tmp/unlink-test' without recursive=True
>>> adb.unlink(filename, recursive=True)
>>> adb.exists(filename)
False
"""
with AdbClient() as c:
st = c.stat(path)
if not st:
log.error("Could not unlink %r: Does not exist" % path)
# If the directory is not empty, do not delete it
if isdir(path) and c.list(path) and not recursive:
log.error("Cannot delete non-empty directory %r without recursive=True" % path)
flags = '-rf' if recursive else '-r'
output = c.execute(['rm', flags, path]).recvall()
if output:
log.error(output)
@with_device
def process(argv, *a, **kw):
"""Execute a process on the device.
See ``pwnlib.tubes.process.process`` documentation for more info.
See :class:`pwnlib.tubes.process.process` documentation for more info.
Returns:
A ``process`` tube.
A :class:`pwnlib.tubes.process.process` tube.
Examples:
@@ -623,7 +777,7 @@ def process(argv, *a, **kw):
if argv != [argv[0]]: message += ' argv=%r ' % argv
with log.progress(message) as p:
return Client().execute(argv)
return AdbClient().execute(argv)
@with_device
def interactive(**kw):
@@ -636,27 +790,59 @@ def shell(**kw):
return process(['sh', '-i'], **kw)
@with_device
def which(name):
"""Retrieves the full path to a binary in ``PATH`` on the device
def which(name, all = False, *a, **kw):
"""Retrieves the full path to a binary in ``$PATH`` on the device
>>> adb.which('sh')
'/system/bin/sh'
Arguments:
name(str): Binary name
all(bool): Whether to return all paths, or just the first
*a: Additional arguments for :func:`.adb.process`
**kw: Additional arguments for :func:`.adb.process`
Returns:
Either a path, or list of paths
Example:
>>> adb.which('sh')
'/system/bin/sh'
>>> adb.which('sh', all=True)
['/system/bin/sh']
>>> adb.which('foobar') is None
True
>>> adb.which('foobar', all=True)
[]
"""
# Unfortunately, there is no native 'which' on many phones.
which_cmd = '''
IFS=:
BINARY=%s
P=($PATH)
for path in "${P[@]}"; do \
if [ -e "$path/$BINARY" ]; then \
echo "$path/$BINARY";
break
fi
echo $PATH | while read -d: directory; do
[ -x "$directory/{name}" ] || continue;
echo -n "$directory/{name}\\x00";
done
''' % name
'''.format(name=name)
which_cmd = which_cmd.strip()
return process(['sh','-c',which_cmd]).recvall().strip()
data = process(['sh','-c', which_cmd], *a, **kw).recvall()
result = []
for path in data.split('\x00'):
# Skip empty entries
if not path:
continue
# Return the first entry if all=False
if not all:
return path
# Accumulate all entries if all=True
result.append(path)
if all:
return result
return None
@with_device
def whoami():
@@ -669,7 +855,7 @@ def forward(port):
start_forwarding = adb(['forward', tcp_port, tcp_port])
atexit.register(lambda: adb(['forward', '--remove', tcp_port]))
@context.quiet
@context.quietfunc
@with_device
def logcat(stream=False):
"""Reads the system log file.
@@ -677,12 +863,12 @@ def logcat(stream=False):
By default, causes logcat to exit after reading the file.
Arguments:
stream(bool): If ``True``, the contents are streamed rather than
read in a one-shot manner. Default is ``False``.
stream(bool): If :const:`True`, the contents are streamed rather than
read in a one-shot manner. Default is :const:`False`.
Returns:
If ``stream`` is ``False``, returns a string containing the log data.
Otherwise, it returns a ``tube`` connected to the log output.
If ``stream`` is :const:`False`, returns a string containing the log data.
Otherwise, it returns a :class:`pwnlib.tubes.tube.tube` connected to the log output.
"""
if stream:
@@ -758,18 +944,17 @@ def listdir(directory='/'):
Otherwise, less files may be returned due to restrictive SELinux
policies on adbd.
"""
return list(sorted(Client().list(directory)))
return list(sorted(AdbClient().list(directory)))
@with_device
def fastboot(args, *a, **kw):
"""Executes a fastboot command.
Returns:
The command output.
"""
serial = context.device
if not serial:
log.error("Unknown device")
return tubes.process.process(['fastboot', '-s', serial] + list(args), **kw).recvall()
argv = ['fastboot', '-s', str(context.device)] + list(args)
return tubes.process.process(argv, *a, **kw).recvall()
@with_device
def fingerprint():
@@ -794,7 +979,7 @@ def unlock_bootloader():
Note:
This requires physical interaction with the device.
"""
Client().reboot_bootloader()
AdbClient().reboot_bootloader()
fastboot(['oem', 'unlock'])
fastboot(['continue'])
@@ -806,7 +991,7 @@ class Kernel(object):
return self.symbols['_text']
@property
@context.quiet
@context.quietfunc
def symbols(self):
"""Returns a dictionary of kernel symbols"""
result = {}
@@ -818,7 +1003,7 @@ class Kernel(object):
return result
@property
@context.quiet
@context.quietfunc
def kallsyms(self):
"""Returns the raw output of kallsyms"""
if not self._kallsyms:
@@ -829,20 +1014,20 @@ class Kernel(object):
return self._kallsyms
@property
@context.quiet
@context.quietfunc
def version(self):
"""Returns the kernel version of the device."""
root()
return read('/proc/version').strip()
@property
@context.quiet
@context.quietfunc
def cmdline(self):
root()
return read('/proc/cmdline').strip()
@property
@context.quiet
@context.quietfunc
def lastmsg(self):
root()
if 'last_kmsg' in listdir('/proc'):
@@ -1056,7 +1241,7 @@ class Partition(object):
def walk(top, topdown=True):
join = os.path.join
isdir = lambda x: stat.S_ISDIR(x['mode'])
client = Client()
client = AdbClient()
names = client.list(top)
dirs, nondirs = [], []
@@ -1094,15 +1279,15 @@ def readlink(path):
class Partitions(object):
@property
@context.quiet
@context.quietfunc
def by_name_dir(self):
return next(find('/dev/block/platform','by-name'))
@context.quiet
@context.quietfunc
def __dir__(self):
return list(self)
@context.quiet
@context.quietfunc
@with_device
def __iter__(self):
root()
@@ -1111,7 +1296,7 @@ class Partitions(object):
for name in listdir(self.by_name_dir):
yield name
@context.quiet
@context.quietfunc
@with_device
def __getattr__(self, attr):
for name in self:
@@ -1181,7 +1366,7 @@ def uninstall(package, *arguments):
with context.quiet:
return process(['pm','uninstall',package] + list(arguments)).recvall()
@context.quiet
@context.quietfunc
def packages():
"""Returns a list of packages installed on the system"""
packages = process(['pm', 'list', 'packages']).recvall()
+3 -551
View File
@@ -1,553 +1,5 @@
"""
Implementation of the Android Debug Bridge (ADB) protocol, as far as Binjitsu needs it.
Documentation is available here:
https://android.googlesource.com/platform/system/core/+/master/adb/protocol.txt
This file exists only for backward compatibility
"""
import functools
import stat
import time
from ..context import context
from ..log import Logger
from ..log import getLogger
from ..tubes.listen import listen
from ..tubes.process import process
from ..tubes.remote import remote
from ..util.lists import group
from ..util.misc import size
from ..util.packing import p32
from ..util.proc import pidof
from ..util.sh_string import sh_string
log = getLogger(__name__)
def pack(val):
return '%04x' % val
def unpack(val):
return int(val, 16)
OKAY = "OKAY"
FAIL = "FAIL"
class Message(object):
"""An ADB hex-length-prefixed message"""
def __init__(self, string):
self.string = string
def __str__(self):
return ('%04x' % len(self.string)) + self.string
def __flat__(self):
return str(self)
class Connection(remote):
"""Connection to the ADB server"""
def __init__(self, host, port, level=None, *a, **kw):
super(Connection, self).__init__(host, port, level=level, *a, **kw)
self._executable = None
self._argv = None
self._pid = None
self._cwd = None
self._env = None
def close(self):
with context.quiet:
super(Connection, self).close()
def adb_send(self, message):
self.send(str(Message(message)))
return self.recvn(4)
def adb_unpack(self):
return unpack(self.recvn(4))
def flat(self, *a, **kw):
kw.setdefault('word_size', 32)
return super(Connection, self).flat(*a, **kw)
class Process(Connection):
"""Duck-typed ``tubes.remote`` object to add properties of a ``tubes.process``"""
class Client(Logger):
"""ADB Client"""
def __init__(self, level=None):
super(Client, self).__init__()
if level is not None:
self.setLevel(level)
self.host = context.adb_host
self.port = context.adb_port
self._c = None
@property
def c(self):
"""Client's connection to the ADB server"""
if not self._c:
try:
self._c = Connection(self.host, self.port, level=self.level)
except Exception:
# If the connection fails, try starting a server on that port
# as long as it's the *default* port.
if self.host == context.defaults['adb_host'] \
and self.port == context.defaults['adb_port']:
log.warn("Could not connect to ADB server, trying to start it")
process(context.adb + ['start-server']).recvall()
else:
log.exception('Could not connect to ADB server')
# Final attempt...
if not self._c:
self._c = Connection(self.host, self.port, level=self.level)
return self._c
def _autoclose(fn):
"""Decorator which automatically closes the connection to the ADB server
after calling the decorated function."""
@functools.wraps(fn)
def wrapper(self, *a, **kw):
rv = fn(self, *a, **kw)
if self._c:
self._c.close()
self._c = None
return rv
return wrapper
def _with_transport(fn):
"""Decorator which automatically selects a device transport before calling
the decorated function, and closes the connection afterward."""
@functools.wraps(fn)
def wrapper(self, *a, **kw):
self.transport()
rv = fn(self, *a, **kw)
if self._c:
self._c.close()
self._c = None
return rv
return wrapper
def send(self, *a, **kw):
"""Sends data to the ADB server"""
return self.c.adb_send(*a, **kw)
def unpack(self, *a, **kw):
"""Receives a hex-ascii packed integer from the ADB server"""
return self.c.adb_unpack(*a, **kw)
def recvl(self):
"""Receives a length-prefixed data buffer from the ADB server"""
length = self.c.adb_unpack()
return self.c.recvn(length)
@_autoclose
def kill(self):
"""Kills the remote ADB server"
>>> c=adb.protocol.Client()
>>> c.kill()
The server is automatically re-started on the next request,
if the default host/port are used.
>>> c.version() > (4,0)
True
"""
try:
self.send('host:kill')
except EOFError:
pass
@_autoclose
def version(self):
"""
Returns:
Tuple containing the ``(major, minor)`` version from the ADB server
Example:
>>> adb.protocol.Client().version() # doctest: +SKIP
(4, 36)
"""
response = self.send('host:version')
if response == OKAY:
return (self.c.adb_unpack(), self.c.adb_unpack())
self.error("Could not fetch version")
@_autoclose
def devices(self, long=False):
"""
Arguments:
long(bool): If ``True``, fetch the long-format listing.
Returns:
String representation of all available devices.
"""
msg = 'host:devices'
if long:
msg += '-l'
response = self.send(msg)
if response == 'OKAY':
return self.recvl()
self.error("Could not enumerate devices")
@_autoclose
def track_devices(self):
"""
Returns:
Generator which returns a short-format listing of available
devices each time a device state changes.
"""
self.send('host:track-devices')
while True:
yield self.recvl()
def transport(self, serial=None):
"""Sets the Transport on the rmeote device.
Examples:
>>> adb.protocol.Client().transport()
"""
msg = 'host:transport:%s' % (serial or context.device)
if self.send(msg) == FAIL:
self.error("Could not set transport to %r" % serial)
@_autoclose
@_with_transport
def execute(self, argv):
r"""Executes a program on the device.
Returns:
A ``tube`` which is connected to the process.
Examples:
>>> adb.protocol.Client().execute(['echo','hello']).recvall()
'hello\n'
"""
self.transport(context.device)
if isinstance(argv, str):
argv = [argv]
argv = list(map(sh_string, argv))
cmd = 'exec:%s' % (' '.join(argv))
if OKAY == self.send(cmd):
rv = self._c
self._c = None
return rv
def _basic_wrapper(string):
@_autoclose
@_with_transport
def wrapper(self):
self.send(string)
return self.c.recvall()
return wrapper
@_autoclose
@_with_transport
def remount(self):
self.send('remount:')
return self.c.recvall()
@_autoclose
@_with_transport
def root(self):
self.send('root:')
return self.c.recvall()
@_autoclose
@_with_transport
def unroot(self):
self.send('unroot:')
return self.c.recvall()
@_autoclose
@_with_transport
def disable_verity(self):
self.send('disable-verity:')
return self.c.recvall()
@_autoclose
@_with_transport
def enable_verity(self):
self.send('enable-verity:')
return self.c.recvall()
@_autoclose
@_with_transport
def reconnect(self):
self.send('reconnect:')
return self.c.recvall()
@_autoclose
@_with_transport
def reboot(self):
self.send('reboot:')
return self.c.recvall()
@_autoclose
@_with_transport
def reboot_bootloader(self):
self.send('reboot:bootloader')
return self.c.recvall()
@_autoclose
def wait_for_device(self, serial=''):
response = self.send('host-serial:%s:wait-for-any-device' % serial)
# The first OKAY is that the command was understood
if response != 'OKAY':
self.error("An error occurred while waiting for device with serial %r" % serial)
# The second OKAY is that the device is available
response = self.c.recvn(4)
if response != 'OKAY':
self.error("An error occurred while waiting for device with serial %r" % serial)
def _sync(fn):
"""Decorator which enters 'sync:' mode to the selected transport,
then invokes the decorated funciton."""
@functools.wraps(fn)
def wrapper(self, *a, **kw):
rv = None
if FAIL != self.send('sync:'):
rv = fn(self, *a, **kw)
return rv
return wrapper
def list(self, path):
"""Execute the ``LIST`` command of the ``SYNC`` API.
Arguments:
path(str): Path of the directory to list.
Return:
A dictionary, where the keys are relative filenames,
and the values are a dictionary containing the same
values as ``stat()`` supplies.
Note:
In recent releases of Android (e.g. 7.0), the domain that
adbd executes from does not have access to everything that
the shell user does.
Because of this, while the shell user can get listings of
e.g. the root directory ('/'), adbd cannot.
The SYNC APIs are executed within the adbd context, not the
shell user context.
This issue is not a problem if the phone is rooted via
'adb root', since adbd then runs in the ``su`` domain.
Examples:
>>> pprint(adb.Client().list('/data/user'))
{'0': {'mode': 41471, 'size': 11, 'time': ...}}
>>> adb.Client().list('/does/not/exist')
Traceback (most recent call last):
...
PwnlibException: Cannot list directory '/does/not/exist': Does not exist
"""
st = self.stat(path)
if not st:
log.error("Cannot list directory %r: Does not exist" % path)
if not stat.S_ISDIR(st['mode']):
log.error("Cannot list directory %r: Path is not a directory" % path)
return self._list(path)
@_with_transport
@_sync
def _list(self, path):
self.c.flat('LIST', len(path), path)
files = {}
while True:
response = self.c.recvn(4)
if response == 'DONE':
break
if response != 'DENT':
self.error('Unexpected response: %r' % response)
mode = self.c.u32()
size = self.c.u32()
time = self.c.u32()
name = self.c.recvn(self.c.u32())
# Ignore the current directory and parent
if name in ('', '.', '..'):
continue
files[name] = {'mode': mode,
'size': size,
'time': time}
return files
@_with_transport
@_sync
def stat(self, path):
"""Execute the ``STAT`` command of the ``SYNC`` API.
Arguments:
path(str): Path to the file to stat.
Return:
On success, a dictionary mapping the values returned.
If the file cannot be ``stat()``ed, None is returned.
Example:
>>> expected = {'mode': 16749, 'size': 0, 'time': 0}
>>> adb.protocol.Client().stat('/proc') == expected
True
>>> adb.protocol.Client().stat('/does/not/exist') == None
True
"""
self.c.flat('STAT', len(path), path)
if self.c.recvn(4) != 'STAT':
self.error("An error occured while attempting to STAT a file.")
mode = self.c.u32()
size = self.c.u32()
time = self.c.u32()
if (mode,size,time) == (0,0,0):
return None
return {'mode': mode, 'size': size, 'time': time}
def write(self, path, data, mode=0o755, timestamp=None, callback=None):
"""Execute the ``WRITE`` command of the ``SYNC`` API.
Arguments:
path(str): Path to the file to write
data(str): Data to write to the file
mode(int): File mode to set (e.g. ``0o755``)
timestamp(int): Unix timestamp to set the file date to
callback(callable): Callback function invoked as data
is written. Arguments provided are:
- File path
- All data
- Expected size of all data
- Current chunk
- Expected size of chunk
"""
# We must ensure that 'path' is not a directory
# Writing to a directory is supported, but creates a temporary file
st = self.stat(path)
if st and stat.S_ISDIR(st['mode']):
log.error("Cannot write to %r: Path is a directory" % path)
return self._write(path, data, mode=0o755, timestamp=None, callback=None)
@_with_transport
@_sync
def _write(self, path, data, mode=0o755, timestamp=None, callback=None):
path += ',' + str(mode)
self.c.flat('SEND', len(path), path)
sent = 0
# Data needs to be broken up into chunks!
for chunk in group(0x10000, data):
self.c.flat('DATA', len(chunk), chunk)
if callback:
callback(path, data[:sent], len(data), chunk, len(chunk))
sent += len(chunk)
# Send completion notification and timestamp
if timestamp is None:
timestamp = int(time.time())
self.c.flat('DONE', timestamp)
result = self.c.recvn(4)
if result != 'OKAY':
log.error("Sync write failed: %r (expected OKAY)" % result)
return
@_with_transport
@_sync
def read(self, path, filesize=0, callback=lambda *a: True):
"""Execute the ``READ`` command of the ``SYNC`` API.
Arguments:
path(str): Path to the file to read
filesize(int): Size of the file, in bytes. Optional.
callback(callable): Callback function invoked as data
becomes available. Arguments provided are:
- File path
- All data
- Expected size of all data
- Current chunk
- Expected size of chunk
Return:
The data received as a string.
"""
self.c.send('RECV' + p32(len(path)) + path)
# Accumulate all data here
all_data = ''
while True:
magic = self.c.recvn(4)
# adbd says there is no more data to send
if magic == 'DONE':
break
if magic == 'FAIL':
self.error('Could not read file %r: Got FAIL.' % path)
# did we expect to be done?
if magic != 'DATA':
self.error('Error after file read: %r (expected DATA)' % magic)
# receive all of the data in the chunk
chunk_size = self.c.u32()
chunk_data = ''
while len(chunk_data) != chunk_size:
chunk_data += self.c.recv(chunk_size - len(chunk_data))
if callback:
callback(path,
all_data,
filesize,
chunk_data,
chunk_size)
# add the chunk onto what we have
all_data += chunk_data
zero = self.c.u32()
if zero != 0:
self.error('Error after file read: %r (expected ZERO)' % zero)
return all_data
@_with_transport
def forward(self, device, host_proto, host_port, device_proto, device_port):
self.send('host:forward:%s:%s;%s:%s' % (host_proto, host_port, device_proto, device_port))
self.c.recvall()
def __enter__(self, *a, **kw):
return self
@_autoclose
def __exit__(self, *a, **kw): pass
def proxy(port=9999):
"""Starts an ADB proxy on the specified port, for debugging purposes."""
l = listen(port)
l.wait_for_connection()
r = remote(context.adb_host, context.adb_port, level='debug')
l <> r
from pwnlib.protocols.adb import AdbClient
Client = AdbClient
+19 -5
View File
@@ -1,3 +1,4 @@
#!/usr/bin/env python2
"""
Pwntools exposes several magic command-line arguments and environment
@@ -16,8 +17,8 @@ The easiest example is to enable more verbose debugging. Just set ``DEBUG``.
$ python exploit.py DEBUG
These arguments are automatically extracted, regardless of their name, and
exposed via ``pwnlib.args.args``, which is exposed as the global variable
``args``. Arguments which ``pwntools`` reserves internally are not exposed
exposed via :mod:`pwnlib.args.args`, which is exposed as the global variable
:data:`args`. Arguments which ``pwntools`` reserves internally are not exposed
this way.
.. code-block:: bash
@@ -39,18 +40,31 @@ not specified evaluate to an empty string.
The full list of supported "magic arguments" and their effects are listed
below.
"""
from __future__ import absolute_import
import collections
import logging
import os
import string
import sys
from . import term
from .context import context
from pwnlib import term
from pwnlib.context import context
term_mode = True
args = collections.defaultdict(str)
env_prefix = 'PWNLIB_'
free_form = True
# Check to see if we were invoked as one of the 'pwn xxx' scripts.
# If so, we don't want to remove e.g. "SYS_" from the end of the command
# line, as this breaks things like constgrep.
import pwnlib.commandline
basename = os.path.basename(sys.argv[0])
if basename == 'pwn' or basename in pwnlib.commandline.__all__:
free_form = False
def isident(s):
"""
@@ -172,7 +186,7 @@ def initialize():
sys.argv.remove(orig)
hooks[arg](value)
elif isident(arg):
elif free_form and isident(arg):
sys.argv.remove(orig)
args[arg] = value
+149 -84
View File
@@ -39,6 +39,8 @@ Disassembly
' 0: b8 0b 00 00 00 mov eax,0xb'
"""
from __future__ import absolute_import
import errno
import os
import platform
@@ -53,11 +55,11 @@ from glob import glob
from os import environ
from os import path
from . import atexit
from . import shellcraft
from .context import LocalContext
from .context import context
from .log import getLogger
from pwnlib import atexit
from pwnlib import shellcraft
from pwnlib.context import LocalContext
from pwnlib.context import context
from pwnlib.log import getLogger
log = getLogger(__name__)
@@ -71,6 +73,11 @@ def dpkg_search_for_binutils(arch, util):
Returns:
A list of candidate package names.
::
>>> pwnlib.asm.dpkg_search_for_binutils('aarch64', 'as')
['binutils-aarch64-linux-gnu']
"""
# Example output:
@@ -97,6 +104,16 @@ def dpkg_search_for_binutils(arch, util):
def print_binutils_instructions(util, context):
"""On failure to find a binutils utility, inform the user of a way
they can get it easily.
Doctest:
>>> context.clear(arch = 'amd64')
>>> pwnlib.asm.print_binutils_instructions('as', context)
Traceback (most recent call last):
...
PwnlibException: Could not find 'as' installed for ContextType(arch = 'amd64', bits = 64, endian = 'little')
Try installing binutils for this architecture:
$ sudo apt-get install binutils
"""
# This links to our instructions on how to manually install binutils
# for several architectures.
@@ -173,10 +190,12 @@ def which_binutils(util):
for arch in arches:
for gutil in utils:
# e.g. objdump
if arch is None: pattern = gutil
if arch is None:
pattern = gutil
# e.g. aarch64-linux-gnu-objdump
else: pattern = '%s*linux*-%s' % (arch,gutil)
else:
pattern = '%s*linux*-%s' % (arch,gutil)
for dir in environ['PATH'].split(':'):
res = sorted(glob(path.join(dir, pattern)))
@@ -380,16 +399,14 @@ def cpp(shellcode):
Examples:
.. doctest::
>>> cpp("mov al, SYS_setresuid", arch = "i386", os = "linux")
'mov al, 164\n'
>>> cpp("weee SYS_setresuid", arch = "arm", os = "linux")
'weee (0+164)\n'
>>> cpp("SYS_setresuid", arch = "thumb", os = "linux")
'(0+164)\n'
>>> cpp("SYS_setresuid", os = "freebsd")
'311\n'
>>> cpp("mov al, SYS_setresuid", arch = "i386", os = "linux")
'mov al, 164\n'
>>> cpp("weee SYS_setresuid", arch = "arm", os = "linux")
'weee (0+164)\n'
>>> cpp("SYS_setresuid", arch = "thumb", os = "linux")
'(0+164)\n'
>>> cpp("SYS_setresuid", os = "freebsd")
'311\n'
"""
arch = context.arch
os = context.os
@@ -406,17 +423,39 @@ def cpp(shellcode):
return _run(cmd, code).strip('\n').rstrip() + '\n'
@LocalContext
def make_elf_from_assembly(assembly, vma = None, extract=False, shared = False):
r"""
Builds an ELF file with the specified assembly as its
executable code.
def make_elf_from_assembly(assembly,
vma=None,
extract=False,
shared=False,
strip=False,
**kwargs):
r"""make_elf_from_assembly(assembly, vma=None, extract=None, shared=False, strip=False, **kwargs) -> str
Builds an ELF file with the specified assembly as its executable code.
This differs from :func:`.make_elf` in that all ELF symbols are preserved,
such as labels and local variables. Use :func:`.make_elf` if size matters.
Additionally, the default value for ``extract`` in :func:`.make_elf` is
different.
Note:
This is effectively a wrapper around :func:`.asm`. with setting
``extract=False``, ``vma=0x10000000``, and marking the resulting
file as executable (``chmod +x``).
Note:
ELF files created with `arch=thumb` will prepend an ARM stub
which switches to Thumb mode.
Arguments:
assembly(str): Assembly
assembly(str): Assembly code to build into an ELF
vma(int): Load address of the binary
extract(bool): Whether to return the data extracted from the file created,
or the path to it.
(Default: ``0x10000000``, or ``0`` if ``shared=True``)
extract(bool): Extract the full ELF data from the file.
(Default: ``False``)
shared(bool): Create a shared library
(Default: ``False``)
kwargs(dict): Arguments to pass to :func:`.asm`.
Returns:
@@ -425,6 +464,9 @@ def make_elf_from_assembly(assembly, vma = None, extract=False, shared = False):
Example:
This example shows how to create a shared library, and load it via
``LD_PRELOAD``.
>>> context.clear()
>>> context.arch = 'amd64'
>>> sc = 'push rbp; mov rbp, rsp;'
@@ -433,6 +475,18 @@ def make_elf_from_assembly(assembly, vma = None, extract=False, shared = False):
>>> solib = make_elf_from_assembly(sc, shared=1)
>>> subprocess.check_output(['echo', 'World'], env={'LD_PRELOAD': solib})
'Hello\nWorld\n'
The same thing can be done with :func:`.make_elf`, though the sizes
are different. They both
>>> file_a = make_elf(asm('nop'), extract=True)
>>> file_b = make_elf_from_assembly('nop', extract=True)
>>> file_a[:4] == file_b[:4]
True
>>> len(file_a) < 0x200
True
>>> len(file_b) > 0x1000
True
"""
if shared and vma:
log.error("Cannot specify a VMA for a shared library.")
@@ -449,38 +503,44 @@ def make_elf_from_assembly(assembly, vma = None, extract=False, shared = False):
if not assembly.startswith(to_thumb):
assembly = to_thumb + assembly
path = asm(assembly, vma = vma, extract = extract, shared = shared)
result = asm(assembly, vma = vma, shared = shared, extract = False, **kwargs)
if not extract:
os.chmod(path, 0755)
os.chmod(result, 0755)
else:
with open(result, 'rb') as io:
result = io.read()
return path
return result
@LocalContext
def make_elf(data, vma = None, strip=True, extract=True, shared=False):
r"""
Builds an ELF file with the specified binary data as its
executable code.
def make_elf(data,
vma=None,
strip=True,
extract=True,
shared=False):
r"""make_elf(data, vma=None, strip=True, extract=True, shared=False, **kwargs) -> str
Builds an ELF file with the specified binary data as its executable code.
Arguments:
data(str): Assembled code
vma(int): Load address for the ELF file
strip(bool): Strip the resulting ELF file. Only matters if ``extract=False``.
(Default: ``True``)
extract(bool): Extract the assembly from the ELF file.
If ``False``, the path of the ELF file is returned.
(Default: ``True``)
shared(bool): Create a Dynamic Shared Object (DSO, i.e. a ``.so``)
which can be loaded via ``dlopen`` or ``LD_PRELOAD``.
Examples:
This example creates an i386 ELF that just does
execve('/bin/sh',...).
>>> context.clear()
>>> context.arch = 'i386'
>>> context.bits = 32
>>> filename = tempfile.mktemp()
>>> context.clear(arch='i386')
>>> bin_sh = '6a68682f2f2f73682f62696e89e331c96a0b5899cd80'.decode('hex')
>>> data = make_elf(bin_sh)
>>> with open(filename,'wb+') as f:
... f.write(data)
... f.flush()
>>> os.chmod(filename,0777)
>>> filename = make_elf(bin_sh, extract=False)
>>> p = process(filename)
>>> p.sendline('echo Hello; exit')
>>> p.recvline()
@@ -548,38 +608,37 @@ def make_elf(data, vma = None, strip=True, extract=True, shared=False):
@LocalContext
def asm(shellcode, vma = 0, extract = True, shared = False):
r"""asm(code, vma = 0, extract = True, ...) -> str
r"""asm(code, vma = 0, extract = True, shared = False, ...) -> str
Runs :func:`cpp` over a given shellcode and then assembles it into bytes.
To see which architectures or operating systems are supported,
look in :mod:`pwnlib.contex`.
To support all these architecture, we bundle the GNU assembler
and objcopy with pwntools.
Assembling shellcode requires that the GNU assembler is installed
for the target architecture.
See :doc:`Installing Binutils </install/binutils>` for more information.
Arguments:
shellcode(str): Assembler code to assemble.
vma(int): Virtual memory address of the beginning of assembly
extract(bool): Extract the raw assembly bytes from the assembled
file. If ``False``, returns the path to an ELF file
with the assembly embedded.
Kwargs:
Any arguments/properties that can be set on ``context``
shellcode(str): Assembler code to assemble.
vma(int): Virtual memory address of the beginning of assembly
extract(bool): Extract the raw assembly bytes from the assembled
file. If :const:`False`, returns the path to an ELF file
with the assembly embedded.
shared(bool): Create a shared object.
kwargs(dict): Any attributes on :data:`.context` can be set, e.g.set
``arch='arm'``.
Examples:
.. doctest::
>>> asm("mov eax, SYS_select", arch = 'i386', os = 'freebsd')
'\xb8]\x00\x00\x00'
>>> asm("mov eax, SYS_select", arch = 'amd64', os = 'linux')
'\xb8\x17\x00\x00\x00'
>>> asm("mov rax, SYS_select", arch = 'amd64', os = 'linux')
'H\xc7\xc0\x17\x00\x00\x00'
>>> asm("mov r0, #SYS_select", arch = 'arm', os = 'linux', bits=32)
'R\x00\xa0\xe3'
>>> asm("mov eax, SYS_select", arch = 'i386', os = 'freebsd')
'\xb8]\x00\x00\x00'
>>> asm("mov eax, SYS_select", arch = 'amd64', os = 'linux')
'\xb8\x17\x00\x00\x00'
>>> asm("mov rax, SYS_select", arch = 'amd64', os = 'linux')
'H\xc7\xc0\x17\x00\x00\x00'
>>> asm("mov r0, #SYS_select", arch = 'arm', os = 'linux', bits=32)
'R\x00\xa0\xe3'
"""
result = ''
@@ -611,11 +670,20 @@ def asm(shellcode, vma = 0, extract = True, shared = False):
ldflags = ['-z', 'execstack', '-o', step3, step2]
if vma:
ldflags += ['--section-start=.shellcode=%#x' % vma,
'--entry=%#x' % vma,
'-z', 'max-page-size=4096',
'-z', 'common-page-size=4096']
'--entry=%#x' % vma]
elif shared:
ldflags += ['-shared', '-init=_start']
# In order to ensure that we generate ELF files with 4k pages,
# and not e.g. 65KB pages (AArch64), force the page size.
# This is a result of GNU Gold being silly.
#
# Introduced in commit dd58f409 without supporting evidence,
# this shouldn't do anything except keep consistent page granularity
# across architectures.
ldflags += ['-z', 'max-page-size=4096',
'-z', 'common-page-size=4096']
_run(linker + ldflags)
elif file(step2,'rb').read(4) == '\x7fELF':
@@ -667,24 +735,21 @@ def disasm(data, vma = 0, byte = True, offset = True, instructions = True):
Examples:
.. doctest::
>>> print disasm('b85d000000'.decode('hex'), arch = 'i386')
0: b8 5d 00 00 00 mov eax,0x5d
>>> print disasm('b85d000000'.decode('hex'), arch = 'i386', byte = 0)
0: mov eax,0x5d
>>> print disasm('b85d000000'.decode('hex'), arch = 'i386', byte = 0, offset = 0)
mov eax,0x5d
>>> print disasm('b817000000'.decode('hex'), arch = 'amd64')
0: b8 17 00 00 00 mov eax,0x17
>>> print disasm('48c7c017000000'.decode('hex'), arch = 'amd64')
0: 48 c7 c0 17 00 00 00 mov rax,0x17
>>> print disasm('04001fe552009000'.decode('hex'), arch = 'arm')
0: e51f0004 ldr r0, [pc, #-4] ; 0x4
4: 00900052 addseq r0, r0, r2, asr r0
>>> print disasm('4ff00500'.decode('hex'), arch = 'thumb', bits=32)
0: f04f 0005 mov.w r0, #5
>>>
>>> print disasm('b85d000000'.decode('hex'), arch = 'i386')
0: b8 5d 00 00 00 mov eax,0x5d
>>> print disasm('b85d000000'.decode('hex'), arch = 'i386', byte = 0)
0: mov eax,0x5d
>>> print disasm('b85d000000'.decode('hex'), arch = 'i386', byte = 0, offset = 0)
mov eax,0x5d
>>> print disasm('b817000000'.decode('hex'), arch = 'amd64')
0: b8 17 00 00 00 mov eax,0x17
>>> print disasm('48c7c017000000'.decode('hex'), arch = 'amd64')
0: 48 c7 c0 17 00 00 00 mov rax,0x17
>>> print disasm('04001fe552009000'.decode('hex'), arch = 'arm')
0: e51f0004 ldr r0, [pc, #-4] ; 0x4
4: 00900052 addseq r0, r0, r2, asr r0
>>> print disasm('4ff00500'.decode('hex'), arch = 'thumb', bits=32)
0: f04f 0005 mov.w r0, #5
"""
result = ''
+2 -1
View File
@@ -2,12 +2,13 @@
Analogous to atexit, this module allows the programmer to register functions to
be run if an unhandled exception occurs.
"""
from __future__ import absolute_import
import sys
import threading
import traceback
from .context import context
from pwnlib.context import context
__all__ = ['register', 'unregister']
+2 -1
View File
@@ -7,12 +7,13 @@ this replacement module also defines :func:`unregister`.
This module also fixes a the issue that exceptions raised by an exit handler is
printed twice when the standard :mod:`atexit` is used.
"""
from __future__ import absolute_import
import sys
import threading
import traceback
from .context import context
from pwnlib.context import context
__all__ = ['register', 'unregister']
+19
View File
@@ -1 +1,20 @@
#!/usr/bin/env python2
__all__ = [
'asm',
'checksec',
'common',
'constgrep',
'cyclic',
'disasm',
'elfdiff',
'elfpatch',
'errno',
'hex',
'main',
'phd',
'pwnstrip',
'scramble',
'shellcraft',
'unhex',
'update'
]
+3 -2
View File
@@ -1,10 +1,11 @@
#!/usr/bin/env python2
from __future__ import absolute_import
import argparse
import sys
from pwn import *
from . import common
from pwnlib.commandline import common
parser = common.parser_commands.add_parser(
'asm',
+3 -2
View File
@@ -1,10 +1,11 @@
#!/usr/bin/env python2
from __future__ import absolute_import
import argparse
import sys
from pwn import *
from . import common
from pwnlib.commandline import common
parser = common.parser_commands.add_parser(
'checksec',
+6 -2
View File
@@ -1,12 +1,15 @@
#!/usr/bin/env python2
from __future__ import absolute_import
import argparse
import os
import re
from pwn import *
import pwnlib
pwnlib.args.free_form = False
from . import common
from pwn import *
from pwnlib.commandline import common
p = common.parser_commands.add_parser(
'constgrep',
@@ -75,6 +78,7 @@ def main(args):
else:
matcher = re.compile(args.regex)
# Evaluate the given constant
if args.constant:
try:
+6 -2
View File
@@ -1,11 +1,15 @@
#!/usr/bin/env python2
from __future__ import absolute_import
import argparse
import string
import sys
from pwn import *
import pwnlib
pwnlib.args.free_form = False
from . import common
from pwn import *
from pwnlib.commandline import common
parser = common.parser_commands.add_parser(
'cyclic',
+6 -2
View File
@@ -1,11 +1,15 @@
#!/usr/bin/env python2
from __future__ import absolute_import
import argparse
import string
import sys
from pwn import *
import pwnlib
pwnlib.args.free_form = False
from . import common
from pwn import *
from pwnlib.commandline import common
parser = common.parser_commands.add_parser(
'disasm',
+6 -2
View File
@@ -1,13 +1,17 @@
#!/usr/bin/env python2
from __future__ import absolute_import
import shutil
from argparse import ArgumentParser
from subprocess import CalledProcessError
from subprocess import check_output
from tempfile import NamedTemporaryFile
from pwn import *
import pwnlib
pwnlib.args.free_form = False
from . import common
from pwn import *
from pwnlib.commandline import common
def dump(objdump, path):
+6 -2
View File
@@ -1,10 +1,14 @@
#!/usr/bin/env python2
from __future__ import absolute_import
import argparse
import sys
from pwn import *
import pwnlib
pwnlib.args.free_form = False
from . import common
from pwn import *
from pwnlib.commandline import common
p = common.parser_commands.add_parser(
'elfpatch',
+3 -1
View File
@@ -1,7 +1,9 @@
from __future__ import absolute_import
import argparse
import os
from . import common
from pwnlib.commandline import common
parser = common.parser_commands.add_parser(
'errno',
+3 -1
View File
@@ -1,8 +1,10 @@
#!/usr/bin/env python2
from __future__ import absolute_import
import argparse
import sys
from . import common
from pwnlib.commandline import common
parser = common.parser_commands.add_parser(
'hex',
+20 -18
View File
@@ -1,23 +1,25 @@
from __future__ import absolute_import
import sys
from . import asm
from . import checksec
from . import common
from . import constgrep
from . import cyclic
from . import disasm
from . import elfdiff
from . import elfpatch
from . import errno
from . import hex
from . import phd
from . import pwnstrip
from . import scramble
from . import shellcraft
from . import unhex
from . import update
from .common import parser
from ..context import context
from pwnlib.commandline import asm
from pwnlib.commandline import checksec
from pwnlib.commandline import common
from pwnlib.commandline import constgrep
from pwnlib.commandline import cyclic
from pwnlib.commandline import disasm
from pwnlib.commandline import elfdiff
from pwnlib.commandline import elfpatch
from pwnlib.commandline import errno
from pwnlib.commandline import hex
from pwnlib.commandline import phd
from pwnlib.commandline import pwnstrip
from pwnlib.commandline import scramble
from pwnlib.commandline import shellcraft
from pwnlib.commandline import unhex
from pwnlib.commandline import update
from pwnlib.commandline.common import parser
from pwnlib.context import context
commands = {
'asm': asm.main,
+6 -2
View File
@@ -1,11 +1,15 @@
#!/usr/bin/env python2
from __future__ import absolute_import
import argparse
import os
import sys
from pwn import *
import pwnlib
pwnlib.args.free_form = False
from . import common
from pwn import *
from pwnlib.commandline import common
parser = common.parser_commands.add_parser(
'phd',
+6 -2
View File
@@ -1,8 +1,12 @@
from __future__ import absolute_import
import argparse
from pwn import *
import pwnlib
pwnlib.args.free_form = False
from . import common
from pwn import *
from pwnlib.commandline import common
p = common.parser_commands.add_parser(
'pwnstrip',
+6 -2
View File
@@ -1,9 +1,13 @@
from __future__ import absolute_import
import argparse
import sys
from pwn import *
import pwnlib
pwnlib.args.free_form = False
from . import common
from pwn import *
from pwnlib.commandline import common
parser = common.parser_commands.add_parser(
'scramble',
+7 -3
View File
@@ -1,12 +1,16 @@
#!/usr/bin/env python2
from __future__ import absolute_import
import argparse
import os
import sys
import types
from pwn import *
import pwnlib
pwnlib.args.free_form = False
from . import common
from pwn import *
from pwnlib.commandline import common
# ____ _ _ _ __ _
@@ -168,7 +172,7 @@ def get_template(name):
def is_not_a_syscall_template(name):
template_src = shellcraft._get_source(name)
return 'man 2' not in read(template_src)
return '/syscalls' not in template_src
def main(args):
if args.list:
+3 -1
View File
@@ -1,9 +1,11 @@
#!/usr/bin/env python2
from __future__ import absolute_import
import argparse
import sys
from string import whitespace
from . import common
from pwnlib.commandline import common
parser = common.parser_commands.add_parser(
'unhex',
+7 -3
View File
@@ -1,9 +1,13 @@
#!/usr/bin/env python2
from __future__ import absolute_import
import subprocess
from pwn import *
import pwnlib
pwnlib.args.free_form = False
from . import common
from pwn import *
from pwnlib.commandline import common
p = common.parser_commands.add_parser(
'update',
@@ -21,7 +25,7 @@ p.add_argument('--pre', action='store_true', help='''
def main(a):
result = pwnlib.update.perform_check(prerelease=a.pre)
if a.install:
subprocess.check_call(result, shell=True)
subprocess.check_call(result, shell=False)
if __name__ == '__main__':
pwnlib.common.main(__file__)
+10 -3
View File
@@ -40,13 +40,15 @@ Example:
4
"""
from __future__ import absolute_import
import importlib
import sys
from types import ModuleType
from ..context import context
from ..util import safeeval
from .constant import Constant
from pwnlib.constants.constant import Constant
from pwnlib.context import context
from pwnlib.util import safeeval
class ConstantsModule(ModuleType):
@@ -137,6 +139,11 @@ class ConstantsModule(ModuleType):
if not isinstance(string, str):
return string
simple = getattr(self, string, None)
if simple is not None:
return simple
key = context.os, context.arch
if key not in self._env_store:
self._env_store[key] = {key: getattr(self, key) for key in dir(self) if not key.endswith('__')}
+60 -16
View File
@@ -4,6 +4,8 @@
Implements context management so that nested/scoped contexts and threaded
contexts work properly and as expected.
"""
from __future__ import absolute_import
import collections
import functools
import logging
@@ -19,8 +21,8 @@ import time
import socks
from ..device import Device
from ..timeout import Timeout
from pwnlib.device import Device
from pwnlib.timeout import Timeout
_original_socket = socket.socket
@@ -261,7 +263,7 @@ class ContextType(object):
r"""
Class for specifying information about the target machine.
Intended for use as a pseudo-singleton through the global
variable ``pwnlib.context.context``, available via
variable :data:`.context`, available via
``from pwn import *`` as ``context``.
The context is usually specified at the top of the Python file for clarity. ::
@@ -330,6 +332,8 @@ class ContextType(object):
'aslr': True,
'binary': None,
'bits': 32,
'buffer_size': 4096,
'delete_corefiles': False,
'device': os.getenv('ANDROID_SERIAL', None) or None,
'endian': 'little',
'kernel': None,
@@ -337,6 +341,7 @@ class ContextType(object):
'log_file': _devnull(),
'log_console': sys.stdout,
'randomize': False,
'rename_corefiles': True,
'newline': '\n',
'noptrace': False,
'os': 'linux',
@@ -527,12 +532,13 @@ class ContextType(object):
def quiet(self, function=None):
"""Disables all non-error logging within the enclosed scope,
*unless* the debugging level is set to 'debug' or lower."""
if not function:
level = 'error'
if context.log_level <= logging.DEBUG:
level = None
return self.local(function, log_level=level)
level = 'error'
if context.log_level <= logging.DEBUG:
level = None
return self.local(function, log_level=level)
def quietfunc(self, function):
"""Similar to :attr:`quiet`, but wraps a whole function."""
@functools.wraps(function)
def wrapper(*a, **kw):
level = 'error'
@@ -562,6 +568,7 @@ class ContextType(object):
Examples:
>>> # Default value
>>> context.clear()
>>> context.arch == 'i386'
True
>>> context.arch = 'arm'
@@ -688,7 +695,7 @@ class ContextType(object):
"""
ASLR settings for new processes.
If ``False``, attempt to disable ASLR in all processes which are
If :const:`False`, attempt to disable ASLR in all processes which are
created via ``personality`` (``setarch -R``) and ``setrlimit``
(``ulimit -s unlimited``).
@@ -755,7 +762,7 @@ class ContextType(object):
"""
# Cyclic imports... sorry Idolf.
from ..elf import ELF
from pwnlib.elf import ELF
if not isinstance(binary, ELF):
binary = ELF(binary)
@@ -984,7 +991,7 @@ class ContextType(object):
Can be set to any non-string truthy value, or the specific string
values ``'signed'`` or ``'unsigned'`` which are converted into
``True`` and ``False`` correspondingly.
:const:`True` and :const:`False` correspondingly.
Examples:
@@ -1146,12 +1153,21 @@ class ContextType(object):
return command
@_validator
def buffer_size(self, size):
"""Internal buffer size to use for :class:`pwnlib.tubes.tube.tube` objects.
This is not the maximum size of the buffer, but this is the amount of data
which is passed to each raw ``read`` syscall (or equivalent).
"""
return int(size)
@property
def cache_dir(self):
"""Directory used for caching data.
Note:
May be either a path string, or ``None``.
May be either a path string, or :const:`None`.
Example:
@@ -1185,6 +1201,31 @@ class ContextType(object):
return cache
@_validator
def delete_corefiles(self, v):
"""Whether pwntools automatically deletes corefiles after exiting.
This only affects corefiles accessed via :attr:`.process.corefile`.
Default value is ``False``.
"""
return bool(v)
@_validator
def rename_corefiles(self, v):
"""Whether pwntools automatically renames corefiles.
This is useful for two things:
- Prevent corefiles from being overwritten, if ``kernel.core_pattern``
is something simple like ``"core"``.
- Ensure corefiles are generated, if ``kernel.core_pattern`` uses ``apport``,
which refuses to overwrite any existing files.
This only affects corefiles accessed via :attr:`.process.corefile`.
Default value is ``True``.
"""
return bool(v)
#*************************************************************************
# ALIASES
@@ -1260,11 +1301,14 @@ class ContextType(object):
Thread = Thread
#: Global ``context`` object, used to store commonly-used pwntools settings.
#: Global :class:`.ContextType` object, used to store commonly-used pwntools settings.
#:
#: In most cases, the context is used to infer default variables values.
#: For example, :meth:`pwnlib.asm.asm` can take an ``os`` parameter as a
#: keyword argument. If it is not supplied, the ``os`` specified by
#: ``context`` is used instead.
#: For example, :func:`.asm` can take an ``arch`` parameter as a
#: keyword argument.
#:
#: If it is not supplied, the ``arch`` specified by ``context`` is used instead.
#:
#: Consider it a shorthand to passing ``os=`` and ``arch=`` to every single
#: function call.
context = ContextType()
+10
View File
@@ -0,0 +1,10 @@
ROOT=$(shell git rev-parse --show-toplevel)
TEMPLATE_DIR="$ROOT/pwnlib/shellcraft/templates/common/linux/syscalls"
all: generate.py functions.py
python generate.py "$(ROOT)/pwnlib/shellcraft/templates/common/linux/syscalls"
functions.py:
wget https://raw.githubusercontent.com/zachriggle/functions/master/functions.py
.phony: all
+283
View File
@@ -0,0 +1,283 @@
#!/usr/bin/env python2
import argparse
import os
from pwnlib import constants
from pwnlib.context import context
# github.com/zachriggle/functions
from functions import functions, Function, Argument
ARCHITECTURES = ['i386', 'amd64', 'arm', 'aarch64', 'mips']
HEADER = '''
<%
import collections
import pwnlib.abi
import pwnlib.constants
import pwnlib.shellcraft
%>
'''
DOCSTRING = '''
<%docstring>{name}({arguments_comma_separated}) -> str
Invokes the syscall {name}.
See 'man 2 {name}' for more information.
Arguments:
{arg_docs}
Returns:
{return_type}
</%docstring>
'''
ARGUMENTS = """
<%page args="{arguments_default_values}"/>
"""
CALL = """
<%
abi = pwnlib.abi.ABI.syscall()
stack = abi.stack
regs = abi.register_arguments[1:]
allregs = pwnlib.shellcraft.registers.current()
can_pushstr = {string_arguments!r}
can_pushstr_array = {array_arguments!r}
argument_names = {argument_names!r}
argument_values = [{arguments_comma_separated!s}]
# Load all of the arguments into their destination registers / stack slots.
register_arguments = dict()
stack_arguments = collections.OrderedDict()
string_arguments = dict()
dict_arguments = dict()
array_arguments = dict()
syscall_repr = []
for name, arg in zip(argument_names, argument_values):
if arg is not None:
syscall_repr.append('%s=%r' % (name, arg))
# If the argument itself (input) is a register...
if arg in allregs:
index = argument_names.index(name)
if index < len(regs):
target = regs[index]
register_arguments[target] = arg
elif arg is not None:
stack_arguments[index] = arg
# The argument is not a register. It is a string value, and we
# are expecting a string value
elif name in can_pushstr and isinstance(arg, str):
string_arguments[name] = arg
# The argument is not a register. It is a dictionary, and we are
# expecting K:V paris.
elif name in can_pushstr_array and isinstance(arg, dict):
array_arguments[name] = ['%s=%s' % (k,v) for (k,v) in arg.items()]
# The arguent is not a register. It is a list, and we are expecting
# a list of arguments.
elif name in can_pushstr_array and isinstance(arg, (list, tuple)):
array_arguments[name] = arg
# The argument is not a register, string, dict, or list.
# It could be a constant string ('O_RDONLY') for an integer argument,
# an actual integer value, or a constant.
else:
index = argument_names.index(name)
if index < len(regs):
target = regs[index]
register_arguments[target] = arg
elif arg is not None:
stack_arguments[target] = arg
# Some syscalls have different names on various architectures.
# Determine which syscall number to use for the current architecture.
for syscall in {syscalls!r}:
if hasattr(pwnlib.constants, syscall):
break
else:
raise Exception("Could not locate any syscalls: %r" % syscalls)
%>
/* {name}(${{', '.join(syscall_repr)}}) */
%for name, arg in string_arguments.items():
${{pwnlib.shellcraft.pushstr(arg, append_null=('\\x00' not in arg))}}
${{pwnlib.shellcraft.mov(regs[argument_names.index(name)], abi.stack)}}
%endfor
%for name, arg in array_arguments.items():
${{pwnlib.shellcraft.pushstr_array(regs[argument_names.index(name)], arg)}}
%endfor
%for name, arg in stack_arguments.items():
${{pwnlib.shellcraft.push(arg)}}
%endfor
${{pwnlib.shellcraft.setregs(register_arguments)}}
${{pwnlib.shellcraft.syscall(syscall)}}
"""
def can_be_constant(arg):
if arg.derefcnt == 0:
return True
def can_be_string(arg):
if arg.type == 'char' and arg.derefcnt == 1:
return True
if arg.type == 'void' and arg.derefcnt == 1:
return True
def can_be_array(arg):
if arg.type == 'char' and arg.derefcnt == 2:
return True
if arg.type == 'void' and arg.derefcnt == 2:
return True
def fix_bad_arg_names(func, arg):
if arg.name == 'str':
return 'str_'
if arg.name == 'len':
return 'length'
if arg.name == 'repr':
return 'repr_'
if func.name == 'open' and arg.name == 'vararg':
return 'mode'
return arg.name
def get_arg_default(arg):
return 0
def fix_rt_syscall_name(name):
if name.startswith('rt_'):
return name[3:]
return name
def fix_syscall_names(name):
# Do not use old_mmap
if name == 'SYS_mmap':
return ['SYS_mmap2', name]
# Some arches don't have vanilla sigreturn
if name.endswith('_sigreturn'):
return ['SYS_sigreturn', 'SYS_rt_sigreturn']
return [name]
def main(target):
for arch in ARCHITECTURES:
with context.local(arch=arch):
generate_one(target)
def generate_one(target):
SYSCALL_NAMES = [c for c in dir(constants) if c.startswith('SYS_')]
for syscall in SYSCALL_NAMES:
name = syscall[4:]
# Skip anything with uppercase
if name.lower() != name:
print 'Skipping %s' % name
continue
# Skip anything that starts with 'unused' or 'sys' after stripping
if name.startswith('unused'):
print 'Skipping %s' % name
continue
function = functions.get(name, None)
if name.startswith('rt_'):
name = name[3:]
# If we can't find a function, just stub it out with something
# that has a vararg argument.
if function is None:
print 'Stubbing out %s' % name
args = [Argument('int', 0, 'vararg')]
function = Function('long', 0, name, args)
# Some syscalls have different names on different architectures,
# or are superceded. We try to do the "best" thing at runtime.
syscalls = fix_syscall_names(syscall)
# Set up the argument string for Mako
argument_names = []
argument_defaults = []
#
for arg in function.args:
argname = fix_bad_arg_names(function, arg)
default = get_arg_default(arg)
# Mako is unable to use *vararg and *kwarg, so we just stub in
# a whole bunch of additional arguments.
if argname == 'vararg':
for j in range(5):
argname = 'vararg_%i' % j
argument_names.append(argname)
argument_defaults.append('%s=%s' % (argname, None))
break
argument_names.append(argname)
argument_defaults.append('%s=%s' % (argname, default))
arguments_default_values = ', '.join(argument_defaults)
arguments_comma_separated = ', '.join(argument_names)
string_arguments = []
array_arguments = []
arg_docs = []
for arg in function.args:
if can_be_array(arg):
array_arguments.append(arg.name)
if can_be_string(arg):
string_arguments.append(arg.name)
argname = arg.name
argtype = str(arg.type) + ('*' * arg.derefcnt)
arg_docs.append(
' {argname}({argtype}): {argname}'.format(argname=argname,
argtype=argtype))
return_type = str(function.type) + ('*' * function.derefcnt)
arg_docs = '\n'.join(arg_docs)
template_variables = {
'name': name,
'arg_docs': arg_docs,
'syscalls': syscalls,
'arguments_default_values': arguments_default_values,
'arguments_comma_separated': arguments_comma_separated,
'return_type': return_type,
'string_arguments': string_arguments,
'array_arguments': array_arguments,
'argument_names': argument_names,
}
lines = [
HEADER,
DOCSTRING.format(**template_variables),
ARGUMENTS.format(**template_variables),
CALL.format(**template_variables)
]
with open(os.path.join(target, name + '.asm'), 'wt+') as f:
f.write('\n'.join(map(str.strip, lines)))
if __name__ == '__main__':
p = argparse.ArgumentParser()
p.add_argument('target_directory')
args = p.parse_args()
main(args.target_directory)
@@ -7,6 +7,7 @@ import re
import urllib
from bs4 import BeautifulSoup
from pwn import *
uas = set()
+13 -11
View File
@@ -47,20 +47,22 @@ Example
DynELF
"""
from __future__ import absolute_import
import ctypes
from elftools.elf.enums import ENUM_D_TAG
from . import elf
from . import libcdb
from .context import context
from .elf import ELF
from .elf import constants
from .log import getLogger
from .memleak import MemLeak
from .util.fiddling import enhex
from .util.packing import unpack
from .util.web import wget
from pwnlib import elf
from pwnlib import libcdb
from pwnlib.context import context
from pwnlib.elf import ELF
from pwnlib.elf import constants
from pwnlib.log import getLogger
from pwnlib.memleak import MemLeak
from pwnlib.util.fiddling import enhex
from pwnlib.util.packing import unpack
from pwnlib.util.web import wget
log = getLogger(__name__)
sizeof = ctypes.sizeof
@@ -512,7 +514,7 @@ class DynELF(object):
If set to ``'libc'``, ``'libc.so'`` is assumed.
Returns:
Address of the named symbol, or ``None``.
Address of the named symbol, or :const:`None`.
"""
result = None
+12 -4
View File
@@ -1,6 +1,14 @@
from .corefile import Core
from .datatypes import *
from .elf import ELF
from .elf import load
"""
Most exploitable CTF challenges are provided in the Executable and Linkable
Format (``ELF``). Generally, it is very useful to be able to interact with
these files to extract data such as function addresses, ROP gadgets, and
writable page addresses.
"""
from __future__ import absolute_import
from pwnlib.elf.corefile import Core
from pwnlib.elf.datatypes import *
from pwnlib.elf.elf import ELF
from pwnlib.elf.elf import load
__all__ = ['load', 'ELF', 'Core'] + sorted(filter(lambda x: not x.startswith('_'), datatypes.__dict__.keys()))
+258
View File
@@ -0,0 +1,258 @@
"""
Kernel-specific ELF functionality
"""
class KernelConfig(object):
def __init__(self, name, title, requires=[], excludes=[], minver=0, maxver=99):
#: Name of the configuration option
self.name = name
#: Section to which the configuration point belongs
self.title = title
#: List of configuration items, one of which must be present,
#: for this checker to be used.
self.requires = set(requires)
#: List of configuration items, NONE of which must be present,
#: for this checker to be used.
self.excludes = set(excludes)
#: Kernel version that this check should be enforced on
self.minver = map(int, str(minver).split('.'))
self.maxver = map(int, str(maxver).split('.'))
def relevant(self, config):
# If any of the excluded options are ENABLED,
# this config is not relevant.
if self.excludes:
for value in self.excludes & set(config):
if config.get(value, False):
return False
# If any of the required options are PRESENT but DISABLED,
# this config is not relevant.
if self.requires:
for value in self.requires & set(config):
if config.get(value, False):
return True
# We did not find a required value
return False
# If we are not in the correct version range, bail
if 'version' in config:
version = config['version']
if not (self.minver <= version < self.maxver):
return False
return True
def check(self, value):
raise NotImplementedError()
def __call__(self, config):
"""__call__(config) -> str
Check whether the configuration point is set correctly.
Arguments:
config(dict): Dictionary of all configuration points
Returns:
A tuple (result, message) where result is whether the
option is correctly configured, and message is an optional
message describing the error.
"""
if not self.relevant(config):
return (True, '')
return self.check(config.get(self.name, None))
class Enabled(KernelConfig):
def __init__(self, name, **kw):
super(Enabled, self).__init__(name, 'Not Enabled', **kw)
def check(self, config):
return (bool(config), '')
class EnabledIfPresent(Enabled):
def check(self, config):
if config is None:
return (True, '')
return super(EnabledIfPresent, self).check(config)
class Disabled(KernelConfig):
def __init__(self, name, **kw):
super(Disabled, self).__init__(name, 'Not Disabled', **kw)
def check(self, config):
return (not bool(config), '')
class Minimum(KernelConfig):
def __init__(self, name, minimum, **kw):
super(Minimum, self).__init__(name, 'Misconfigured', **kw)
self.minimum = minimum
def check(self, value):
if value is None or value < self.minimum:
return (False, "Should be at least {}".format(self.minimum))
return (True, '')
kernel_configuration = [
# Enabled
Enabled('STRICT_DEVMEM', requires=['DEVMEM']),
Enabled('IO_STRICT_DEVMEM', requires=['DEVMEM']),
Enabled('SYN_COOKIES'),
Enabled('DEBUG_CREDENTIALS'),
Enabled('DEBUG_NOTIFIERS'),
Enabled('DEBUG_LIST'),
Enabled('SECCOMP'),
Enabled('SECCOMP_FILTER', minver=3.5, requires=['HAVE_ARCH_SECCOMP_FILTER']),
Enabled('SECURITY'),
# Enabled('SECURITY_YAMA'),
Enabled('HARDENED_USERCOPY', minver=4.8),
Enabled('SLAB_FREELIST_RANDOM', minver=4.7),
# Enabled('SLUB_DEBUG'),
# Enabled('PAGE_POISONING'),
# Enabled('PAGE_POISONING_NO_SANITY'),
# Enabled('PAGE_POISONING_ZERO'),
Enabled('CC_STACKPROTECTOR', excludes=['CC_STACKPROTECTOR_STRONG']),
Enabled('CC_STACKPROTECTOR_STRONG', minver=3.14),
Enabled('PANIC_ON_OOPS', minver=3.5),
Enabled('DEBUG_SET_MODULE_RONX', requires=['MODULES']),
Enabled('DEBUG_RODATA'),
Enabled('SECURITY_KPTR_RESTRICT'),
Enabled('SECURITY_PERF_EVENTS_RESTRICT'),
Enabled('SECURITY_DMESG_RESTRICT'),
Enabled('SECURITY_NETWORK'),
Enabled('SECURITY_SELINUX'),
EnabledIfPresent('MSM_KERNEL_PROTECT'),
# Not really security relevant, but come on guys
Enabled('COREDUMP'),
# Disabled
Disabled('ACPI_CUSTOM_METHOD'),
Disabled('COMPAT_BRK'),
Disabled('DEVKMEM'),
Disabled('DEVMEM'),
Disabled('COMPAT_VDSO'),
Disabled('KEXEC'),
Disabled('BINFMT_MISC'),
Disabled('LEGACY_PTYS'),
Disabled('MODULES'),
Disabled('KEXEC'),
Disabled('SECURITY_SELINUX_DISABLE'),
Disabled('MISC_FILESYSTEMS'),
Disabled('SCSI_TGT'),
Disabled('SCSI_CONSTANTS'),
Disabled('SCSI_LOGGING'),
Disabled('SCSI_SCAN_ASYNC'),
Disabled('CONFIG_MEDIA_RADIO_SUPPORT'),
Disabled('CONFIG_PFT'),
Disabled('CONFIG_SYSVIPC'),
# Permits reloading the kernel from disk
Disabled('HIBERNATION'),
# Prior to v4.1, assists heap memory attacks; best to keep interface disabled
Disabled('INET_DIAG', maxver=4.1),
# x86-specific
Enabled('HIGHMEM64G', requires=['X86_32']),
Enabled('X86_PAE', requires=['X86_32']),
Disabled('LEGACY_VSYSCALL_NONE', requires=['X86_32', 'X86_64'], minver=4.4),
Disabled('CONFIG_IA32_EMULATION'),
Disabled('X86_X32'),
Disabled('MODIFY_LDT_SYSCALL'),
Minimum('DEFAULT_MMAP_MIN_ADDR', 65536, requires=['X86_32', 'X86_64']),
Enabled('RANDOMIZE_BASE', excludes=['ARM']),
Enabled('RANDOMIZE_MEMORY', requires=['X86_64']),
# ARM specific
Enabled('VMSPLIT_3G', requires=['ARM']),
Disabled('STRICT_MEMORY_RWX', requires=['ARM', 'ARM64']),
Minimum('DEFAULT_MMAP_MIN_ADDR', 32768, requires=['ARM', 'ARM64']),
Minimum('ARCH_MMAP_RND_BITS', 16, requires=['ARM']),
Minimum('ARCH_MMAP_RND_BITS', 24, requires=['ARM64']),
Minimum('ARCH_MMAP_RND_COMPAT_BITS', 16, requires=['ARM64']),
Enabled('CPU_SW_DOMAIN_PAN', requires=['ARM'], minver=4.3),
Enabled('CONFIG_ARM64_PAN', requires=['ARM64'], minver=4.3),
Disabled('OABI_COMPAT'),
# Only relevant of MODULES are enabled
Enabled('MODULE_SIG', requires=['MODULES']),
Enabled('MODULE_SIG_FORCE', requires=['MODULES']),
Enabled('MODULE_SIG_ALL', requires=['MODULES']),
Enabled('MODULE_SIG_SHA512', requires=['MODULES']),
Enabled('MODULE_SIG_KEY', requires=['MODULES']),
]
def parse_kconfig(data):
"""Parses configuration data from a kernel .config.
Arguments:
data(str): Configuration contents.
Returns:
A :class:`dict` mapping configuration options.
"Not set" is converted into ``None``, ``y`` and ``n`` are converted
into :class:`bool`. Numbers are converted into :class:`int`.
All other values are as-is.
Each key has ``CONFIG_`` stripped from the beginning.
Examples:
>>> parse_kconfig('FOO=3')
{'FOO': 3}
>>> parse_kconfig('FOO=y')
{'FOO': True}
>>> parse_kconfig('FOO=n')
{'FOO': False}
>>> parse_kconfig('FOO=bar')
{'FOO': 'bar'}
>>> parse_kconfig('# FOO is not set')
{'FOO': None}
"""
config = {}
NOT_SET=' is not set'
if not data:
return
for line in data.splitlines():
# Not set? Then set to None.
if NOT_SET in line:
line = line.split(NOT_SET, 1)[0]
name = line.strip('# ')
config[name] = None
# Set to a value? Extract it
if '=' in line:
k, v = line.split('=', 1)
# Boolean conversions
if v == 'y':
v = True
elif v == 'n':
v = False
else:
# Integer conversions
try:
v = int(v, 0)
except ValueError:
pass
config[k] = v
# Strip off all of the CONFIG_ prefixes
config = ({k.replace('CONFIG_', ''): v for k,v in config.items()})
return config
+991 -64
View File
File diff suppressed because it is too large. Load diff
+82
View File
@@ -533,6 +533,44 @@ class user_regs_struct_amd64(ctypes.Structure):
assert ctypes.sizeof(user_regs_struct_amd64) == 0xd8
class user_regs_struct_arm(ctypes.Structure):
_fields_ = [('r%i' % i, ctypes.c_uint32) for i in range(18)]
@property
def cpsr(self):
return self.r16
@property
def pc(self):
return self.r15
@property
def lr(self):
return self.r14
@property
def sp(self):
return self.r13
@property
def ip(self):
return self.r12
@property
def fp(self):
return self.r11
class user_regs_struct_aarch64(ctypes.Structure):
_fields_ = [('x%i' % i, ctypes.c_uint64) for i in range(31)] \
+ [('sp', ctypes.c_uint64),
('pc', ctypes.c_uint64),
('pstate', ctypes.c_uint64)]
@property
def lr(self):
return self.x30
def __getattr__(self, name):
if name.startswith('r'):
name = 'x' + name[1:]
return getattr(name) & 0xffffffff
class elf_prstatus_i386(ctypes.Structure):
_fields_ = generate_prstatus_common(32, user_regs_struct_i386)
@@ -544,9 +582,53 @@ class elf_prstatus_amd64(ctypes.Structure):
assert ctypes.sizeof(elf_prstatus_amd64) == 0x150
class elf_prstatus_arm(ctypes.Structure):
_fields_ = generate_prstatus_common(32, user_regs_struct_arm)
class elf_prstatus_aarch64(ctypes.Structure):
_fields_ = generate_prstatus_common(64, user_regs_struct_aarch64)
class Elf32_auxv_t(ctypes.Structure):
_fields_ = [('a_type', ctypes.c_uint32),
('a_val', ctypes.c_uint32),]
class Elf64_auxv_t(ctypes.Structure):
_fields_ = [('a_type', ctypes.c_uint64),
('a_val', ctypes.c_uint64),]
def generate_prspinfo(long):
return [
('pr_state', byte),
('pr_sname', char),
('pr_zomb', byte),
('pr_nice', byte),
('pr_flag', long),
('pr_uid', ctypes.c_ushort),
('pr_gid', ctypes.c_ushort),
('pr_pid', ctypes.c_int),
('pr_ppid', ctypes.c_int),
('pr_pgrp', ctypes.c_int),
('pr_sid', ctypes.c_int),
('pr_fname', char * 16),
('pr_psargs', char * 80)
]
class elf_prspinfo_32(ctypes.Structure):
_fields_ = generate_prspinfo(Elf32_Addr)
class elf_prspinfo_64(ctypes.Structure):
_fields_ = generate_prspinfo(Elf64_Addr)
def generate_siginfo(int_t, long_t):
class siginfo_t(ctypes.Structure):
_fields_ = [('si_signo', int_t),
('si_errno', int_t),
('si_code', int_t),
('sigfault_addr', long_t),
('sigfault_trapno', int_t)]
return siginfo_t
class elf_siginfo_32(generate_siginfo(ctypes.c_uint32, ctypes.c_uint32)):
pass
class elf_siginfo_64(generate_siginfo(ctypes.c_uint32, ctypes.c_uint64)):
pass
+685 -181
View File
File diff suppressed because it is too large. Load diff
+13 -11
View File
@@ -2,14 +2,16 @@
"""
Encode shellcode to avoid input filtering and impress your friends!
"""
from . import amd64
from . import arm
from . import i386
from . import mips
from .encoder import Encoder
from .encoder import alphanumeric
from .encoder import encode
from .encoder import line
from .encoder import null
from .encoder import printable
from .encoder import scramble
from __future__ import absolute_import
from pwnlib.encoders import amd64
from pwnlib.encoders import arm
from pwnlib.encoders import i386
from pwnlib.encoders import mips
from pwnlib.encoders.encoder import Encoder
from pwnlib.encoders.encoder import alphanumeric
from pwnlib.encoders.encoder import encode
from pwnlib.encoders.encoder import line
from pwnlib.encoders.encoder import null
from pwnlib.encoders.encoder import printable
from pwnlib.encoders.encoder import scramble
+3 -1
View File
@@ -1 +1,3 @@
from . import delta
from __future__ import absolute_import
from pwnlib.encoders.amd64 import delta
+3 -1
View File
@@ -1,4 +1,6 @@
from ..i386.delta import i386DeltaEncoder
from __future__ import absolute_import
from pwnlib.encoders.i386.delta import i386DeltaEncoder
class amd64DeltaEncoder(i386DeltaEncoder):
+4 -2
View File
@@ -1,2 +1,4 @@
from . import alphanumeric
from . import xor
from __future__ import absolute_import
from pwnlib.encoders.arm import alphanumeric
from pwnlib.encoders.arm import xor
+5 -4
View File
@@ -1,12 +1,13 @@
from __future__ import absolute_import
import binascii
import random
import string
import sys
import builder
from ....context import context
from ...encoder import Encoder
from pwnlib.context import context
from pwnlib.encoders.arm.alphanumeric import builder
from pwnlib.encoders.encoder import Encoder
class ArmEncoder(Encoder):
+9 -7
View File
@@ -1,10 +1,12 @@
from ... import shellcraft
from ...asm import asm
from ...context import context
from ...util.fiddling import xor_key
from ...util.lists import group
from ...util.packing import u8
from ..encoder import Encoder
from __future__ import absolute_import
from pwnlib import shellcraft
from pwnlib.asm import asm
from pwnlib.context import context
from pwnlib.encoders.encoder import Encoder
from pwnlib.util.fiddling import xor_key
from pwnlib.util.lists import group
from pwnlib.util.packing import u8
class ArmXorEncoder(Encoder):
+6 -4
View File
@@ -1,13 +1,15 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import
import collections
import random
import re
import string
from ..context import LocalContext
from ..context import context
from ..log import getLogger
from ..util.fiddling import hexdump
from pwnlib.context import LocalContext
from pwnlib.context import context
from pwnlib.log import getLogger
from pwnlib.util.fiddling import hexdump
log = getLogger(__name__)
+4 -2
View File
@@ -1,2 +1,4 @@
from . import delta
from . import xor
from __future__ import absolute_import
from pwnlib.encoders.i386 import delta
from pwnlib.encoders.i386 import xor
+7 -5
View File
@@ -1,12 +1,14 @@
from __future__ import absolute_import
import collections
from random import choice
from random import randint
from ...asm import asm
from ...asm import disasm
from ...context import context
from ...util.fiddling import hexdump
from ..encoder import Encoder
from pwnlib.asm import asm
from pwnlib.asm import disasm
from pwnlib.context import context
from pwnlib.encoders.encoder import Encoder
from pwnlib.util.fiddling import hexdump
'''
+7 -6
View File
@@ -10,13 +10,14 @@
#; This program is distributed in the hope that it will be useful,
#; but WITHOUT ANY WARRANTY; without even the implied warranty of
#; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
from __future__ import absolute_import
from ... import shellcraft
from ...asm import asm
from ...context import context
from ...util.fiddling import xor_pair
from ...util.lists import group
from ..encoder import Encoder
from pwnlib import shellcraft
from pwnlib.asm import asm
from pwnlib.context import context
from pwnlib.encoders.encoder import Encoder
from pwnlib.util.fiddling import xor_pair
from pwnlib.util.lists import group
# Note shellcode assumes it's based at ecx
+3 -1
View File
@@ -1 +1,3 @@
from . import xor
from __future__ import absolute_import
from pwnlib.encoders.mips import xor
+6 -5
View File
@@ -23,12 +23,13 @@
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from __future__ import absolute_import
from ... import shellcraft
from ...asm import asm
from ...context import context
from ...util.fiddling import xor_key
from ..encoder import Encoder
from pwnlib import asm
from pwnlib import shellcraft
from pwnlib.context import context
from pwnlib.encoders.encoder import Encoder
from pwnlib.util.fiddling import xor_key
decoders = {
'little': ''.join([
+3 -1
View File
@@ -1,4 +1,6 @@
from .flag import submit_flag
from __future__ import absolute_import
from pwnlib.flag.flag import submit_flag
__all__ = [
'submit_flag',
+5 -3
View File
@@ -1,10 +1,12 @@
"""Describes a way to submit a key to a key server.
"""
from __future__ import absolute_import
import os
from ..args import args
from ..log import getLogger
from ..tubes.remote import remote
from pwnlib.args import args
from pwnlib.log import getLogger
from pwnlib.tubes.remote import remote
env_server = args.get('FLAG_HOST', 'flag-submission-server').strip()
env_port = args.get('FLAG_PORT', '31337').strip()
+1 -1
View File
@@ -40,7 +40,7 @@ Examples:
...
>>> autofmt = FmtStr(exec_fmt)
>>> offset = autofmt.offset
>>> p = process(program, stderr=subprocess.PIPE)
>>> p = process(program, stderr=PIPE)
>>> addr = unpack(p.recv(4))
>>> payload = fmtstr_payload(offset, {addr: 0x1337babe})
>>> p.sendline(payload)
+434 -113
View File
@@ -1,33 +1,136 @@
# -*- coding: utf-8 -*-
"""
During exploit development, it is frequently useful to debug the
target binary under GDB.
Pwntools makes this easy-to-do with a handful of helper routines, designed
to make your exploit-debug-update cycles much faster.
Useful Functions
----------------
- :func:`attach` - Attach to an existing process
- :func:`debug` - Start a new process under a debugger, stopped at the first instruction
- :func:`debug_shellcode` - Build a binary with the provided shellcode, and start it under a debugger
Debugging Tips
--------------
The :func:`attach` and :func:`debug` functions will likely be your bread and
butter for debugging.
Both allow you to provide a script to pass to GDB when it is started, so that
it can automatically set your breakpoints.
Attaching to Processes
~~~~~~~~~~~~~~~~~~~~~~
To attach to an existing process, just use :func:`attach`. It is surprisingly
versatile, and can attach to a :class:`.process` for simple
binaries, or will automatically find the correct process to attach to for a
forking server, if given a :class:`.remote` object.
Spawning New Processes
~~~~~~~~~~~~~~~~~~~~~~
Attaching to processes with :func:`attach` is useful, but the state the process
is in may vary. If you need to attach to a process very early, and debug it from
the very first instruction (or even the start of ``main``), you instead should use
:func:`debug`.
When you use :func:`debug`, the return value is a :class:`.tube` object
that you interact with exactly like normal.
Tips and Troubleshooting
------------------------
``NOPTRACE`` magic argument
~~~~~~~~~~~~~~~~~~~~~~~~~~~
It's quite cumbersom to comment and un-comment lines containing `attach`.
You can cause these lines to be a no-op by running your script with the
``NOPTRACE`` argument appended, or with ``PWNLIB_NOPTRACE=1`` in the environment.
::
$ python exploit.py NOPTRACE
[+] Starting local process '/bin/bash': Done
[!] Skipping debug attach since context.noptrace==True
...
Kernel Yama ptrace_scope
~~~~~~~~~~~~~~~~~~~~~~~~
The Linux kernel v3.4 introduced a security mechanism called ``ptrace_scope``,
which is intended to prevent processes from debugging eachother unless there is
a direct parent-child relationship.
This causes some issues with the normal Pwntools workflow, since the process
heirarchy looks like this:
::
python ---> target
`--> gdb
Note that ``python`` is the parent of ``target``, not ``gdb``.
In order to avoid this being a problem, Pwntools uses the function
``prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY)``. This disables Yama
for any processes launched by Pwntools via :class:`.process` or via
:meth:`.ssh.process`.
Older versions of Pwntools did not perform the ``prctl`` step, and
required that the Yama security feature was disabled systemwide, which
requires ``root`` access.
Member Documentation
===============================
"""
from __future__ import absolute_import
import os
import random
import re
import shlex
import tempfile
import time
from . import adb
from . import atexit
from . import elf
from . import tubes
from .asm import _bfdname
from .asm import make_elf
from .asm import make_elf_from_assembly
from .context import LocalContext
from .context import context
from .log import getLogger
from .qemu import get_qemu_user
from .util import misc
from .util import proc
from pwnlib import adb
from pwnlib import atexit
from pwnlib import elf
from pwnlib import tubes
from pwnlib.asm import _bfdname
from pwnlib.asm import make_elf
from pwnlib.asm import make_elf_from_assembly
from pwnlib.context import LocalContext
from pwnlib.context import context
from pwnlib.log import getLogger
from pwnlib.qemu import get_qemu_user
from pwnlib.util import misc
from pwnlib.util import proc
log = getLogger(__name__)
@LocalContext
def debug_assembly(asm, execute=None, vma=None):
"""
Creates an ELF file, and launches it with GDB.
def debug_assembly(asm, gdbscript=None, vma=None):
"""debug_assembly(asm, gdbscript=None, vma=None) -> tube
Creates an ELF file, and launches it under a debugger.
This is identical to debug_shellcode, except that
any defined symbols are available in GDB, and it
saves you the explicit call to asm().
Arguments:
asm(str): Assembly code to debug
gdbscript(str): Script to run in GDB
vma(int): Base address to load the shellcode at
**kwargs: Override any :obj:`.context` values.
Returns:
:class:`.process`
"""
tmp_elf = make_elf_from_assembly(asm, vma=vma, extract=False)
os.chmod(tmp_elf, 0777)
@@ -39,19 +142,21 @@ def debug_assembly(asm, execute=None, vma=None):
adb.push(tmp_elf, android_path)
tmp_elf = android_path
return debug(tmp_elf, execute=execute, arch=context.arch)
return debug(tmp_elf, gdbscript=gdbscript, arch=context.arch)
@LocalContext
def debug_shellcode(data, execute=None, vma=None):
def debug_shellcode(data, gdbscript=None, vma=None):
"""
Creates an ELF file, and launches it with GDB.
Creates an ELF file, and launches it under a debugger.
Arguments:
data(str): Assembled shellcode bytes
kwargs(dict): Arguments passed to context (e.g. arch='arm')
gdbscript(str): Script to run in GDB
vma(int): Base address to load the shellcode at
**kwargs: Override any :obj:`.context` values.
Returns:
A ``process`` tube connected to the shellcode on stdin/stdout/stderr.
:class:`.process`
"""
if isinstance(data, unicode):
log.error("Shellcode is cannot be unicode. Did you mean debug_assembly?")
@@ -65,7 +170,7 @@ def debug_shellcode(data, execute=None, vma=None):
adb.push(tmp_elf, android_path)
tmp_elf = android_path
return debug(tmp_elf, execute=execute, arch=context.arch)
return debug(tmp_elf, gdbscript=gdbscript, arch=context.arch)
def _gdbserver_args(pid=None, path=None, args=None, which=None):
"""_gdbserver_args(pid=None, path=None) -> list
@@ -163,24 +268,90 @@ def _get_runner(ssh=None):
else: return tubes.process.process
@LocalContext
def debug(args, execute=None, exe=None, ssh=None, env=None, **kwargs):
def debug(args, gdbscript=None, exe=None, ssh=None, env=None, **kwargs):
"""debug(args) -> tube
Launch a GDB server with the specified command line,
and launches GDB to attach to it.
Arguments:
args: Same args as passed to pwnlib.tubes.process
ssh: Remote ssh session to use to launch the process.
Automatically sets up port forwarding so that gdb runs locally.
args(list): Arguments to the process, similar to :class:`.process`.
gdbscript(str): GDB script to run.
exe(str): Path to the executable on disk
env(dict): Environment to start the binary in
ssh(:class:`.ssh`): Remote ssh session to use to launch the process.
Returns:
A tube connected to the target process
"""
if context.noptrace:
log.warn_once("Skipping debugger since context.noptrace==True")
return tubes.process.process(args, executable=exe, env=env)
:class:`.process` or :class:`.ssh_channel`: A tube connected to the target process
Notes:
The debugger is attached automatically, and you can debug everything
from the very beginning. This requires that both ``gdb`` and ``gdbserver``
are installed on your machine.
.. code-block:: python
# Create a new process, and stop it at 'main'
io = gdb.debug('bash', '''
break main
continue
''')
When GDB opens via :func:`debug`, it will initially be stopped on the very first
instruction of the dynamic linker (``ld.so``) for dynamically-linked binaries.
Only the target binary and the linker will be loaded in memory, so you cannot
set breakpoints on shared library routines like ``malloc`` since ``libc.so``
has not even been loaded yet.
There are several ways to handle this:
1. Set a breakpoint on the executable's entry point (generally, ``_start``)
- This is only invoked after all of the required shared libraries
are loaded.
- You can generally get the address via the GDB command ``info file``.
2. Use pending breakpoints via ``set breakpoint pending on``
- This has the side-effect of setting breakpoints for **every** function
which matches the name. For ``malloc``, this will generally set a
breakpoint in the executable's PLT, in the linker's internal ``malloc``,
and eventaully in ``libc``'s malloc.
3. Wait for libraries to be loaded with ``set stop-on-solib-event 1``
- There is no way to stop on any specific library being loaded, and sometimes
multiple libraries are loaded and only a single breakpoint is issued.
- Generally, you just add a few ``continue`` commands until things are set up
the way you want it to be.
.. code-block:: python
# Create a new process, and stop it at 'main'
io = gdb.debug('bash', '''
# Wait until we hit the main executable's entry point
break _start
continue
# Now set breakpoint on shared library routines
break malloc
break free
continue
''')
You can use :func:`debug` to spawn new processes on remote machines as well,
by using the ``ssh=`` keyword to pass in your :class:`.ssh` instance.
.. code-block:: python
# Connect to the SSH server
shell = ssh('passcode', 'pwnable.kr', 2222, password='guest')
# Start a process on the server
bash = gdb.debug(['bash'],
ssh=shell,
gdbscript='''
break main
continue
''')
"""
if isinstance(args, (int, tubes.process.process, tubes.ssh.ssh_channel)):
log.error("Use gdb.attach() to debug a running process")
@@ -195,6 +366,10 @@ def debug(args, execute=None, exe=None, ssh=None, env=None, **kwargs):
runner = _get_runner(ssh)
which = _get_which(ssh)
if context.noptrace:
log.warn_once("Skipping debugger since context.noptrace==True")
return runner(args, executable=exe, env=env)
if ssh or context.native or (context.os == 'android'):
args = _gdbserver_args(args=args, which=which)
else:
@@ -226,7 +401,7 @@ def debug(args, execute=None, exe=None, ssh=None, env=None, **kwargs):
if not ssh and context.os == 'android':
host = context.adb_host
attach((host, port), exe=exe, execute=execute, need_ptrace_scope = False)
attach((host, port), exe=exe, gdbscript=gdbscript, need_ptrace_scope = False, ssh=ssh)
# gdbserver outputs a message when a client connects
garbage = gdbserver.recvline(timeout=1)
@@ -245,75 +420,146 @@ def get_gdb_arch():
'thumb': 'arm'
}.get(context.arch, context.arch)
@LocalContext
def attach(target, execute = None, exe = None, need_ptrace_scope = True):
"""attach(target, execute = None, exe = None, arch = None) -> None
Start GDB in a new terminal and attach to `target`.
:func:`pwnlib.util.proc.pidof` is used to find the PID of `target` except
when `target` is a ``(host, port)``-pair. In that case `target` is assumed
to be a GDB server.
If it is running locally and `exe` is not given we will try to find the path
of the target binary from parsing the command line of the program running
the GDB server (e.g. qemu or gdbserver). Notice that if the PID is known
(when `target` is not a GDB server) `exe` will be read from
``/proc/<pid>/exe``.
If `gdb-multiarch` is installed we use that or 'gdb' otherwise.
Arguments:
target: The target to attach to.
execute (str or file): GDB script to run after attaching.
exe (str): The path of the target binary.
arch (str): Architechture of the target binary. If `exe` known GDB will
detect the architechture automatically (if it is supported).
def binary():
"""binary() -> str
Returns:
:const:`None`
"""
str: Path to the appropriate ``gdb`` binary to use.
"""
gdb = misc.which('gdb')
if not context.native:
multiarch = misc.which('gdb-multiarch')
if multiarch:
return multiarch
log.warn_once('Cross-architecture debugging usually requires gdb-multiarch\n' \
'$ apt-get install gdb-multiarch')
if not gdb:
log.error('GDB is not installed\n'
'$ apt-get install gdb')
return gdb
@LocalContext
def attach(target, gdbscript = None, exe = None, need_ptrace_scope = True, gdb_args = None, ssh = None):
"""attach(target, gdbscript = None, exe = None, arch = None, ssh = None) -> None
Start GDB in a new terminal and attach to `target`.
Arguments:
target: The target to attach to.
gdbscript(:obj:`str` or :obj:`file`): GDB script to run after attaching.
exe(str): The path of the target binary.
arch(str): Architechture of the target binary. If `exe` known GDB will
detect the architechture automatically (if it is supported).
gdb_args(list): List of additional arguments to pass to GDB.
Returns:
PID of the GDB process (or the window which it is running in).
Notes:
The ``target`` argument is very robust, and can be any of the following:
:obj:`int`
PID of a process
:obj:`str`
Process name. The youngest process is selected.
:obj:`tuple`
Host, port pair of a listening ``gdbserver``
:class:`.process`
Process to connect to
:class:`.sock`
Connected socket. The executable on the other end of the connection is attached to.
Can be any socket type, including :class:`.listen` or :class:`.remote`.
:class:`.ssh_channel`
Remote process spawned via :meth:`.ssh.process`.
This will use the GDB installed on the remote machine.
If a password is required to connect, the ``sshpass`` program must be installed.
.. code-block:: python
# Attach directly to pid 1234
gdb.attach(1234)
.. code-block:: python
# Attach to the youngest "bash" process
gdb.attach('bash')
.. code-block:: python
# Start a process
bash = process('bash')
# Attach the debugger
gdb.attach(bash, '''
set follow-fork-mode child
break execve
continue
''')
# Interact with the process
bash.sendline('whoami')
.. code-block:: python
# Start a forking server
server = process(['socat', 'tcp-listen:1234,fork,reuseaddr', 'exec:/bin/sh'])
# Connect to the server
io = remote('localhost', 1234)
# Connect the debugger to the server-spawned process
gdb.attach(io, '''
break exit
continue
''')
# Talk to the spawned 'sh'
io.sendline('exit')
.. code-block:: python
# Connect to the SSH server
shell = ssh('bandit0', 'bandit.labs.overthewire.org', password='bandit0')
# Start a process on the server
cat = shell.process(['cat'])
# Attach a debugger to it
gdb.attach(cat, '''
break exit
continue
''')
# Cause `cat` to exit
cat.close()
"""
if context.noptrace:
log.warn_once("Skipping debug attach since context.noptrace==True")
return
# if execute is a file object, then read it; we probably need to run some
# if gdbscript is a file object, then read it; we probably need to run some
# more gdb script anyway
if execute:
if isinstance(execute, file):
fd = execute
execute = fd.read()
fd.close()
if isinstance(gdbscript, file):
with gdbscript:
gdbscript = gdbscript.read()
# enable gdb.attach(p, 'continue')
if execute and not execute.endswith('\n'):
execute += '\n'
if gdbscript and not gdbscript.endswith('\n'):
gdbscript += '\n'
# gdb script to run before `execute`
# gdb script to run before `gdbscript`
pre = ''
if not context.native:
if not misc.which('gdb-multiarch'):
log.warn_once('Cross-architecture debugging usually requires gdb-multiarch\n' \
'$ apt-get install gdb-multiarch')
pre += 'set endian %s\n' % context.endian
pre += 'set architecture %s\n' % get_gdb_arch()
if context.os == 'android':
pre += 'set gnutarget ' + _bfdname() + '\n'
else:
# If ptrace_scope is set and we're not root, we cannot attach to a
# running process.
# We assume that we do not need this to be set if we are debugging on
# a different architecture (e.g. under qemu-user).
try:
ptrace_scope = open('/proc/sys/kernel/yama/ptrace_scope').read().strip()
if need_ptrace_scope and ptrace_scope != '0' and os.geteuid() != 0:
msg = 'Disable ptrace_scope to attach to running processes.\n'
msg += 'More info: https://askubuntu.com/q/41629'
log.warning(msg)
return
except IOError:
pass
# let's see if we can find a pid to attach to
pid = None
@@ -340,11 +586,13 @@ def attach(target, execute = None, exe = None, need_ptrace_scope = True):
shell = target.parent
tmpfile = shell.mktemp()
execute = 'shell rm %s\n%s' % (tmpfile, execute)
shell.upload_data(execute or '', tmpfile)
gdbscript = 'shell rm %s\n%s' % (tmpfile, gdbscript)
shell.upload_data(gdbscript or '', tmpfile)
cmd = ['ssh', '-C', '-t', '-p', str(shell.port), '-l', shell.user, shell.host]
if shell.password:
if not misc.which('sshpass'):
log.error("sshpass must be installed to debug ssh processes")
cmd = ['sshpass', '-p', shell.password] + cmd
if shell.keyfile:
cmd += ['-i', shell.keyfile]
@@ -377,6 +625,8 @@ def attach(target, execute = None, exe = None, need_ptrace_scope = True):
return os.path.join(proc.cwd(spid), exe)
exe = exe or findexe()
elif isinstance(target, elf.corefile.Corefile):
pre += 'target core %s\n' % target.path
else:
log.error("don't know how to attach to target: %r" % target)
@@ -390,19 +640,20 @@ def attach(target, execute = None, exe = None, need_ptrace_scope = True):
if not pid and not exe:
log.error('could not find target process')
cmd = None
for p in ('gdb-multiarch', 'gdb'):
if misc.which(p):
cmd = p
break
else:
log.error('no gdb installed')
cmd = binary()
if gdb_args:
cmd += ' '
cmd += ' '.join(gdb_args)
cmd += ' -q '
if exe and context.native:
if ssh:
ssh.download_file(exe)
exe = os.path.basename(exe)
if not os.path.isfile(exe):
log.error('no such file: %s' % exe)
log.error('No such file: %s' % exe)
cmd += ' "%s"' % exe
if pid and not context.os == 'android':
@@ -417,38 +668,40 @@ def attach(target, execute = None, exe = None, need_ptrace_scope = True):
host = context.adb_host
pre += 'target remote %s:%i' % (context.adb_host, port)
execute = pre + (execute or '')
gdbscript = pre + (gdbscript or '')
if execute:
if gdbscript:
tmp = tempfile.NamedTemporaryFile(prefix = 'pwn', suffix = '.gdb',
delete = False)
log.debug('Wrote gdb script to %r\n%s' % (tmp.name, execute))
execute = 'shell rm %s\n%s' % (tmp.name, execute)
log.debug('Wrote gdb script to %r\n%s' % (tmp.name, gdbscript))
gdbscript = 'shell rm %s\n%s' % (tmp.name, gdbscript)
tmp.write(execute)
tmp.write(gdbscript)
tmp.close()
cmd += ' -x "%s"' % (tmp.name)
log.info('running in new terminal: %s' % cmd)
misc.run_in_new_terminal(cmd)
gdb_pid = misc.run_in_new_terminal(cmd)
if pid and context.native:
proc.wait_for_debugger(pid)
return pid
def ssh_gdb(ssh, process, execute = None, arch = None, **kwargs):
if isinstance(process, (list, tuple)):
exe = process[0]
process = ["gdbserver", "127.0.0.1:0"] + process
else:
exe = process
process = "gdbserver 127.0.0.1:0 " + process
return gdb_pid
def ssh_gdb(ssh, argv, gdbscript = None, arch = None, **kwargs):
if not isinstance(argv, (list, tuple)):
argv = [argv]
exe = argv[0]
argv = ["gdbserver", "127.0.0.1:0"] + argv
# Download the executable
local_exe = os.path.basename(exe)
ssh.download_file(exe, local_exe)
ssh.download_file(ssh.which(exe), local_exe)
# Run the process
c = ssh.run(process, **kwargs)
c = ssh.process(argv, **kwargs)
# Find the port for the gdb server
c.recvuntil('port ')
@@ -460,7 +713,7 @@ def ssh_gdb(ssh, process, execute = None, arch = None, **kwargs):
l = tubes.listen.listen(0)
forwardport = l.lport
attach(('127.0.0.1', forwardport), execute, local_exe, arch)
attach(('127.0.0.1', forwardport), gdbscript, local_exe, arch, ssh=ssh)
l.wait_for_connection() <> ssh.connect_remote('127.0.0.1', gdbport)
return c
@@ -501,8 +754,8 @@ def find_module_addresses(binary, ssh=None, ulimit=False):
Arguments:
binary(str): Path to the binary on the remote server
ssh(pwnlib.tubes.tube): SSH connection through which to load the libraries.
If left as ``None``, will use a ``pwnlib.tubes.process.process``.
ulimit(bool): Set to ``True`` to run "ulimit -s unlimited" before GDB.
If left as :const:`None`, will use a :class:`pwnlib.tubes.process.process`.
ulimit(bool): Set to :const:`True` to run "ulimit -s unlimited" before GDB.
Returns:
A list of pwnlib.elf.ELF objects, with correct base addresses.
@@ -589,3 +842,71 @@ def find_module_addresses(binary, ssh=None, ulimit=False):
rv.append(lib)
return rv
def corefile(process):
r"""Drops a core file for the process.
Arguments:
process: Process to dump
Returns:
:class:`.Core`: The generated core file
"""
if context.noptrace:
log.warn_once("Skipping corefile since context.noptrace==True")
return
corefile_path = './core.%s.%i' % (os.path.basename(process.executable),
process.pid)
# Due to https://sourceware.org/bugzilla/show_bug.cgi?id=16092
# will disregard coredump_filter, and will not dump private mappings.
if version() < (7,11):
log.warn_once('The installed GDB (%s) does not emit core-dumps which '
'contain all of the data in the process.\n'
'Upgrade to GDB >= 7.11 for better core-dumps.' % binary())
# This is effectively the same as what the 'gcore' binary does
gdb_args = ['-batch',
'-q',
'--nx',
'-ex', '"set pagination off"',
'-ex', '"set height 0"',
'-ex', '"set width 0"',
'-ex', '"set use-coredump-filter on"',
'-ex', '"generate-core-file %s"' % corefile_path,
'-ex', 'detach']
with context.local(terminal = ['sh', '-c']):
with context.quiet:
pid = attach(process, gdb_args=gdb_args)
os.waitpid(pid, 0)
return elf.corefile.Core(corefile_path)
def version(program='gdb'):
"""Gets the current GDB version.
Note:
Requires that GDB version meets the following format:
``GNU gdb (GDB) 7.12``
Returns:
tuple: A tuple containing the version numbers
Example:
>>> (7,0) <= gdb.version() <= (8,0)
True
"""
program = misc.which(program)
expr = r'([0-9]+\.?)+'
with tubes.process.process([program, '--version'], level='error') as gdb:
version = gdb.recvline()
versions = re.search(expr, version).group()
return tuple(map(int, versions.split('.')))
+12 -10
View File
@@ -1,21 +1,23 @@
"""
Fetch a LIBC binary based on some heuristics.
"""
from __future__ import absolute_import
import codecs
import json
import os
import tempfile
import urlparse
from .context import context
from .elf import ELF
from .log import getLogger
from .util.fiddling import b64d
from .util.fiddling import hexdump
from .util.misc import read
from .util.misc import write
from .util.safeeval import const
from .util.web import wget
from pwnlib.context import context
from pwnlib.elf import ELF
from pwnlib.log import getLogger
from pwnlib.util.fiddling import b64d
from pwnlib.util.fiddling import hexdump
from pwnlib.util.misc import read
from pwnlib.util.misc import write
from pwnlib.util.safeeval import const
from pwnlib.util.web import wget
log = getLogger(__name__)
@@ -33,7 +35,7 @@ def search_by_build_id(hex_encoded_id):
Hex-encoded Build ID (e.g. 'ABCDEF...') of the library
Returns:
Path to the downloaded library on disk, or ``None``.
Path to the downloaded library on disk, or :const:`None`.
"""
cache = cache_dir + '-libc.so.' + hex_encoded_id
+14 -11
View File
@@ -40,7 +40,7 @@ Pwnlib Developers
-----------------
A module-specific logger can be imported into the module via::
from .log import getLogger
from pwnlib.log import getLogger
log = getLogger(__name__)
This provides an easy way to filter logging programmatically
@@ -91,10 +91,7 @@ unless :data:`context.log_level` is also set to ``'DEBUG'``. Other handlers
will however see the extra log records generated by the ``'pwnlib.tubes.ssh'``
logger.
"""
__all__ = [
'getLogger', 'install_default_handler', 'rootlogger'
]
from __future__ import absolute_import
import ConfigParser
import logging
@@ -105,12 +102,18 @@ import sys
import threading
import time
from . import term
from .context import Thread
from .context import context
from .exception import PwnlibException
from .term import spinners
from .term import text
from pwnlib import term
from pwnlib.context import Thread
from pwnlib.context import context
from pwnlib.exception import PwnlibException
from pwnlib.term import spinners
from pwnlib.term import text
__all__ = [
'getLogger', 'install_default_handler', 'rootlogger'
]
# list of prefixes to use for the different message types. note that the `text`
# module won't add any escape codes if `pwnlib.context.log_console.isatty()` is `False`
+65 -41
View File
@@ -1,11 +1,13 @@
from __future__ import absolute_import
import ctypes
import functools
import string
from .context import context
from .log import getLogger
from .util.packing import pack
from .util.packing import unpack
from pwnlib.context import context
from pwnlib.log import getLogger
from pwnlib.util.packing import pack
from pwnlib.util.packing import unpack
log = getLogger(__name__)
@@ -31,46 +33,52 @@ class MemLeak(object):
Example:
.. doctest:: leaker
>>> import pwnlib
>>> binsh = pwnlib.util.misc.read('/bin/sh')
>>> @pwnlib.memleak.MemLeak
... def leaker(addr):
... print "leaking 0x%x" % addr
... return binsh[addr:addr+4]
>>> leaker.s(0)[:4]
leaking 0x0
leaking 0x4
'\\x7fELF'
>>> leaker[:4]
'\\x7fELF'
>>> hex(leaker.d(0))
'0x464c457f'
>>> hex(leaker.clearb(1))
'0x45'
>>> hex(leaker.d(0))
leaking 0x1
'0x464c457f'
>>> @pwnlib.memleak.MemLeak
... def leaker_nonulls(addr):
... print "leaking 0x%x" % addr
... if addr & 0xff == 0:
... return None
... return binsh[addr:addr+4]
>>> leaker_nonulls.d(0) == None
leaking 0x0
True
>>> leaker_nonulls[0x100:0x104] == binsh[0x100:0x104]
leaking 0x100
leaking 0xff
leaking 0x103
True
>>> import pwnlib
>>> binsh = pwnlib.util.misc.read('/bin/sh')
>>> @pwnlib.memleak.MemLeak
... def leaker(addr):
... print "leaking 0x%x" % addr
... return binsh[addr:addr+4]
>>> leaker.s(0)[:4]
leaking 0x0
leaking 0x4
'\\x7fELF'
>>> leaker[:4]
'\\x7fELF'
>>> hex(leaker.d(0))
'0x464c457f'
>>> hex(leaker.clearb(1))
'0x45'
>>> hex(leaker.d(0))
leaking 0x1
'0x464c457f'
>>> @pwnlib.memleak.MemLeak
... def leaker_nonulls(addr):
... print "leaking 0x%x" % addr
... if addr & 0xff == 0:
... return None
... return binsh[addr:addr+4]
>>> leaker_nonulls.d(0) == None
leaking 0x0
True
>>> leaker_nonulls[0x100:0x104] == binsh[0x100:0x104]
leaking 0x100
leaking 0xff
leaking 0x103
True
>>> memory = {-4+i: c for i,c in enumerate('wxyzABCDE')}
>>> def relative_leak(index):
... return memory.get(index, None)
>>> leak = pwnlib.memleak.MemLeak(relative_leak, relative = True)
>>> leak[-1:2]
'zAB'
"""
def __init__(self, f, search_range = 20, reraise = True):
def __init__(self, f, search_range = 20, reraise = True, relative = False):
self.leak = f
self.search_range = search_range
self.reraise = reraise
self.relative = relative
# Map of address: byte for all bytes received
self.cache = {}
@@ -164,9 +172,9 @@ class MemLeak(object):
Leak ``n`` consecutive bytes starting at ``addr``.
Returns:
A string of length ``n``, or ``None``.
A string of length ``n``, or :const:`None`.
"""
if addr < 0:
if not self.relative and addr < 0:
return None
addresses = [addr+i for i in xrange(n)]
@@ -606,3 +614,19 @@ class MemLeak(object):
return result
return MemLeak(string_wrapper)
# Aliases for convenience
u64 = q
u32 = d
u16 = w
u8 = b
p64 = setq
p32 = setd
p16 = setw
p8 = setb
class RelativeMemLeak(MemLeak):
def __init__(self, *a, **kw):
kw.setdefault('relative', True)
super(RelativeMemLeak, self).__init__(*a, **kw)
View File
Whitespace-only changes.
+578
View File
@@ -0,0 +1,578 @@
"""
Implementation of the Android Debug Bridge (ADB) protocol.
Documentation is available here_.
.. _here: https://android.googlesource.com/platform/system/core/+/master/adb/protocol.txt
"""
from __future__ import absolute_import
import logging
import functools
import stat
import time
from pwnlib.context import context
from pwnlib.log import Logger
from pwnlib.log import getLogger
from pwnlib.tubes.listen import listen
from pwnlib.tubes.process import process
from pwnlib.tubes.remote import remote
from pwnlib.util.lists import group
from pwnlib.util.misc import size
from pwnlib.util.packing import p32
from pwnlib.util.proc import pidof
from pwnlib.util.sh_string import sh_string
log = getLogger(__name__)
def pack(val):
return '%04x' % val
def unpack(val):
return int(val, 16)
OKAY = "OKAY"
FAIL = "FAIL"
class Message(object):
"""An ADB hex-length-prefixed message"""
def __init__(self, string):
self.string = string
def __str__(self):
return ('%04x' % len(self.string)) + self.string
def __flat__(self):
return str(self)
class Connection(remote):
"""Connection to the ADB server"""
def __init__(self, host, port, level=None, *a, **kw):
super(Connection, self).__init__(host, port, level=level, *a, **kw)
self._executable = None
self._argv = None
self._pid = None
self._cwd = None
self._env = None
def close(self):
with context.quiet:
super(Connection, self).close()
def adb_send(self, message):
self.send(str(Message(message)))
return self.recvn(4)
def adb_unpack(self):
return unpack(self.recvn(4))
def flat32(self, *a, **kw):
kw.setdefault('word_size', 32)
return super(Connection, self).flat(*a, **kw)
class Process(Connection):
"""Duck-typed ``tubes.remote`` object to add properties of a ``tubes.process``"""
class AdbClient(Logger):
"""ADB Client"""
def __init__(self, level=None):
super(AdbClient, self).__init__()
if level is not None:
self.setLevel(level)
self.host = context.adb_host
self.port = context.adb_port
self._c = None
@property
def c(self):
"""AdbClient's connection to the ADB server"""
if not self._c:
try:
# Squelch the first '[ERROR] Could not connect to localhost on port 5037'
level = self.level
with context.quiet:
if not self.isEnabledFor(logging.INFO):
level = logging.FATAL
self._c = Connection(self.host, self.port, level=level)
except Exception:
# If the connection fails, try starting a server on that port
# as long as it's the *default* port.
if self.host == context.defaults['adb_host'] \
and self.port == context.defaults['adb_port']:
log.warn("Could not connect to ADB server, trying to start it")
process(context.adb + ['start-server']).recvall()
else:
log.exception('Could not connect to ADB server (%s:%s)' % \
(self.host, self.port))
# Final attempt...
if not self._c:
self._c = Connection(self.host, self.port, level=self.level)
return self._c
def _autoclose(fn):
"""Decorator which automatically closes the connection to the ADB server
after calling the decorated function."""
@functools.wraps(fn)
def wrapper(self, *a, **kw):
rv = fn(self, *a, **kw)
if self._c:
self._c.close()
self._c = None
return rv
return wrapper
def _with_transport(fn):
"""Decorator which automatically selects a device transport before calling
the decorated function, and closes the connection afterward."""
@functools.wraps(fn)
def wrapper(self, *a, **kw):
self.transport()
rv = fn(self, *a, **kw)
if self._c:
self._c.close()
self._c = None
return rv
return wrapper
def send(self, *a, **kw):
"""Sends data to the ADB server"""
return self.c.adb_send(*a, **kw)
def unpack(self, *a, **kw):
"""Receives a hex-ascii packed integer from the ADB server"""
return self.c.adb_unpack(*a, **kw)
def recvl(self):
"""Receives a length-prefixed data buffer from the ADB server"""
length = self.c.adb_unpack()
return self.c.recvn(length)
@_autoclose
def kill(self):
"""Kills the remote ADB server"
>>> c=pwnlib.protocols.adb.AdbClient()
>>> c.kill()
The server is automatically re-started on the next request,
if the default host/port are used.
>>> c.version() > (4,0)
True
"""
try:
self.send('host:kill')
except EOFError:
pass
@_autoclose
def version(self):
"""
Returns:
Tuple containing the ``(major, minor)`` version from the ADB server
Example:
>>> pwnlib.protocols.adb.AdbClient().version() # doctest: +SKIP
(4, 36)
"""
response = self.send('host:version')
if response == OKAY:
return (self.c.adb_unpack(), self.c.adb_unpack())
self.error("Could not fetch version")
@_autoclose
def devices(self, long=False):
"""
Arguments:
long(bool): If :const:`True`, fetch the long-format listing.
Returns:
String representation of all available devices.
"""
msg = 'host:devices'
if long:
msg += '-l'
response = self.send(msg)
if response == 'OKAY':
return self.recvl()
self.error("Could not enumerate devices")
@_autoclose
def track_devices(self):
"""
Returns:
Generator which returns a short-format listing of available
devices each time a device state changes.
"""
self.send('host:track-devices')
while True:
yield self.recvl()
def transport(self, serial=None):
"""Sets the Transport on the rmeote device.
Examples:
>>> pwnlib.protocols.adb.AdbClient().transport()
"""
# If no serial was explicitly provided, try the current device
if not serial and context.device:
serial = context.device
# Select the appropriate (or any) device
if serial:
# Extract the serial, str(Device) --> serial
serial = str(serial)
msg = 'host:transport:%s' % serial
else:
msg = 'host:transport-any'
if self.send(msg) == FAIL:
if serial:
self.error("Could not set transport to %r" % serial)
else:
self.error("Could not set transport 'any'")
@_autoclose
@_with_transport
def execute(self, argv):
r"""Executes a program on the device.
Returns:
A :class:`pwnlib.tubes.tube.tube` which is connected to the process.
Examples:
>>> pwnlib.protocols.adb.AdbClient().execute(['echo','hello']).recvall()
'hello\n'
"""
self.transport(context.device)
if isinstance(argv, str):
argv = [argv]
argv = list(map(sh_string, argv))
cmd = 'exec:%s' % (' '.join(argv))
if OKAY == self.send(cmd):
rv = self._c
self._c = None
return rv
def _basic_wrapper(string):
@_autoclose
@_with_transport
def wrapper(self):
self.send(string)
return self.c.recvall()
return wrapper
@_autoclose
@_with_transport
def remount(self):
self.send('remount:')
return self.c.recvall()
@_autoclose
@_with_transport
def root(self):
self.send('root:')
return self.c.recvall()
@_autoclose
@_with_transport
def unroot(self):
self.send('unroot:')
return self.c.recvall()
@_autoclose
@_with_transport
def disable_verity(self):
self.send('disable-verity:')
return self.c.recvall()
@_autoclose
@_with_transport
def enable_verity(self):
self.send('enable-verity:')
return self.c.recvall()
@_autoclose
@_with_transport
def reconnect(self):
self.send('reconnect:')
return self.c.recvall()
@_autoclose
@_with_transport
def reboot(self):
self.send('reboot:')
return self.c.recvall()
@_autoclose
@_with_transport
def reboot_bootloader(self):
self.send('reboot:bootloader')
return self.c.recvall()
@_autoclose
def wait_for_device(self, serial=''):
response = self.send('host-serial:%s:wait-for-any-device' % serial)
# The first OKAY is that the command was understood
if response != 'OKAY':
self.error("An error occurred while waiting for device with serial %r" % serial)
# The second OKAY is that the device is available
response = self.c.recvn(4)
if response != 'OKAY':
self.error("An error occurred while waiting for device with serial %r" % serial)
def _sync(fn):
"""Decorator which enters 'sync:' mode to the selected transport,
then invokes the decorated funciton."""
@functools.wraps(fn)
def wrapper(self, *a, **kw):
rv = None
if FAIL != self.send('sync:'):
rv = fn(self, *a, **kw)
return rv
return wrapper
def list(self, path):
"""Execute the ``LIST`` command of the ``SYNC`` API.
Arguments:
path(str): Path of the directory to list.
Return:
A dictionary, where the keys are relative filenames,
and the values are a dictionary containing the same
values as ``stat()`` supplies.
Note:
In recent releases of Android (e.g. 7.0), the domain that
adbd executes from does not have access to everything that
the shell user does.
Because of this, while the shell user can get listings of
e.g. the root directory ('/'), adbd cannot.
The SYNC APIs are executed within the adbd context, not the
shell user context.
This issue is not a problem if the phone is rooted via
'adb root', since adbd then runs in the ``su`` domain.
Examples:
>>> pprint(AdbClient().list('/data/user'))
{'0': {'mode': 41471, 'size': 11, 'time': ...}}
>>> AdbClient().list('/does/not/exist')
Traceback (most recent call last):
...
PwnlibException: Cannot list directory '/does/not/exist': Does not exist
"""
st = self.stat(path)
if not st:
log.error("Cannot list directory %r: Does not exist" % path)
if not stat.S_ISDIR(st['mode']):
log.error("Cannot list directory %r: Path is not a directory" % path)
return self._list(path)
@_with_transport
@_sync
def _list(self, path):
self.c.flat32('LIST', len(path), path)
files = {}
while True:
response = self.c.recvn(4)
if response == 'DONE':
break
if response != 'DENT':
self.error('Unexpected response: %r' % response)
mode = self.c.u32()
size = self.c.u32()
time = self.c.u32()
name = self.c.recvn(self.c.u32())
# Ignore the current directory and parent
if name in ('', '.', '..'):
continue
files[name] = {'mode': mode,
'size': size,
'time': time}
return files
@_with_transport
@_sync
def stat(self, path):
"""Execute the ``STAT`` command of the ``SYNC`` API.
Arguments:
path(str): Path to the file to stat.
Return:
On success, a dictionary mapping the values returned.
If the file cannot be ``stat()``ed, None is returned.
Example:
>>> expected = {'mode': 16749, 'size': 0, 'time': 0}
>>> pwnlib.protocols.adb.AdbClient().stat('/proc') == expected
True
>>> pwnlib.protocols.adb.AdbClient().stat('/does/not/exist') == None
True
"""
self.c.flat32('STAT', len(path), path)
if self.c.recvn(4) != 'STAT':
self.error("An error occured while attempting to STAT a file.")
mode = self.c.u32()
size = self.c.u32()
time = self.c.u32()
if (mode,size,time) == (0,0,0):
return None
return {'mode': mode, 'size': size, 'time': time}
def write(self, path, data, mode=0o755, timestamp=None, callback=None):
"""Execute the ``WRITE`` command of the ``SYNC`` API.
Arguments:
path(str): Path to the file to write
data(str): Data to write to the file
mode(int): File mode to set (e.g. ``0o755``)
timestamp(int): Unix timestamp to set the file date to
callback(callable): Callback function invoked as data
is written. Arguments provided are:
- File path
- All data
- Expected size of all data
- Current chunk
- Expected size of chunk
"""
# We must ensure that 'path' is not a directory
# Writing to a directory is supported, but creates a temporary file
st = self.stat(path)
if st and stat.S_ISDIR(st['mode']):
log.error("Cannot write to %r: Path is a directory" % path)
return self._write(path, data, mode=0o755, timestamp=None, callback=None)
@_with_transport
@_sync
def _write(self, path, data, mode=0o755, timestamp=None, callback=None):
path += ',' + str(mode)
self.c.flat32('SEND', len(path), path)
sent = 0
# Data needs to be broken up into chunks!
for chunk in group(0x10000, data):
self.c.flat32('DATA', len(chunk), chunk)
if callback:
callback(path, data[:sent], len(data), chunk, len(chunk))
sent += len(chunk)
# Send completion notification and timestamp
if timestamp is None:
timestamp = int(time.time())
self.c.flat32('DONE', timestamp)
result = self.c.recvn(4)
if result != 'OKAY':
log.error("Sync write failed: %r (expected OKAY)" % result)
return
@_with_transport
@_sync
def read(self, path, filesize=0, callback=lambda *a: True):
"""Execute the ``READ`` command of the ``SYNC`` API.
Arguments:
path(str): Path to the file to read
filesize(int): Size of the file, in bytes. Optional.
callback(callable): Callback function invoked as data
becomes available. Arguments provided are:
- File path
- All data
- Expected size of all data
- Current chunk
- Expected size of chunk
Return:
The data received as a string.
"""
self.c.send('RECV' + p32(len(path)) + path)
# Accumulate all data here
all_data = ''
while True:
magic = self.c.recvn(4)
# adbd says there is no more data to send
if magic == 'DONE':
break
if magic == 'FAIL':
self.error('Could not read file %r: Got FAIL.' % path)
# did we expect to be done?
if magic != 'DATA':
self.error('Error after file read: %r (expected DATA)' % magic)
# receive all of the data in the chunk
chunk_size = self.c.u32()
chunk_data = ''
while len(chunk_data) != chunk_size:
chunk_data += self.c.recv(chunk_size - len(chunk_data))
if callback:
callback(path,
all_data,
filesize,
chunk_data,
chunk_size)
# add the chunk onto what we have
all_data += chunk_data
zero = self.c.u32()
if zero != 0:
self.error('Error after file read: %r (expected ZERO)' % zero)
return all_data
@_with_transport
def forward(self, device, host_proto, host_port, device_proto, device_port):
self.send('host:forward:%s:%s;%s:%s' % (host_proto, host_port, device_proto, device_port))
self.c.recvall()
def __enter__(self, *a, **kw):
return self
@_autoclose
def __exit__(self, *a, **kw): pass
def proxy(port=9999):
"""Starts an ADB proxy on the specified port, for debugging purposes."""
l = listen(port)
l.wait_for_connection()
r = remote(context.adb_host, context.adb_port, level='debug')
l <> r
+6 -4
View File
@@ -1,7 +1,9 @@
from .context import LocalContext
from .context import context
from .log import getLogger
from .util import misc
from __future__ import absolute_import
from pwnlib.context import LocalContext
from pwnlib.context import context
from pwnlib.log import getLogger
from pwnlib.util import misc
log = getLogger(__name__)
+7 -5
View File
@@ -2,13 +2,15 @@
"""
Topographical sort
"""
from __future__ import absolute_import
from collections import OrderedDict
from collections import defaultdict
from random import randint
from random import shuffle
from .context import context
from .log import getLogger
from pwnlib.context import context
from pwnlib.log import getLogger
log = getLogger(__name__)
@@ -121,7 +123,7 @@ def regsort(in_out, all_regs, tmp = None, xchg = True, randomize = None):
If a dependency cycle is encountered, one of the following will
occur:
- If ``xchg`` is ``True``, it is assumed that dependency cyles can
- If ``xchg`` is :const:`True`, it is assumed that dependency cyles can
be broken by swapping the contents of two register (a la the
``xchg`` instruction on i386).
- If ``xchg`` is not set, but not all destination registers in
@@ -165,8 +167,8 @@ def regsort(in_out, all_regs, tmp = None, xchg = True, randomize = None):
as a temporary register, and later overwritten with its final
value.
If ``xchg`` is ``True`` and it is used to break a dependency cycle,
then ``reg_name`` will be ``None`` and ``value`` will be a tuple
If ``xchg`` is :const:`True` and it is used to break a dependency cycle,
then ``reg_name`` will be :const:`None` and ``value`` will be a tuple
of the instructions to swap.
Example:
Loaded 100 of 3561 files, more files were not shown because too many files have changed in this diff. Show more